From fac454e54925c3411775b5c580aadb4a67ca6a70 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 28 Oct 2026 12:34:21 +0100 Subject: [PATCH 01/10] [jsroot] Provide builtin_jsroot option JSROOT will be installed from github When git clone is used, many files can be deleted which are not necessary - like demos or examples. Let use $JSROOTSYS as custom source for JSROOT Add special dependency that git cloning happens before additional files copied to js/files/ directory --- .../root-ci-config/buildconfig/global.txt | 1 + CMakeLists.txt | 5 +- builtins/jsroot/CMakeLists.txt | 25 + cmake/modules/RootBuildOptions.cmake | 2 + cmake/modules/SearchInstalledSoftware.cmake | 17 + js/LICENSE | 21 - js/build/jsroot.js | 189904 --------------- js/changes.md | 1835 - js/files/draw.htm | 21 - js/files/online.htm | 20 - js/img/RootIcon.ico | Bin 32038 -> 0 bytes js/index.htm | 65 - js/modules/base/BasePainter.mjs | 1004 - js/modules/base/FontHandler.mjs | 308 - js/modules/base/ObjectPainter.mjs | 1831 - js/modules/base/RObjectPainter.mjs | 398 - js/modules/base/TAttFillHandler.mjs | 587 - js/modules/base/TAttLineHandler.mjs | 187 - js/modules/base/TAttMarkerHandler.mjs | 327 - js/modules/base/TAttTextHandler.mjs | 123 - js/modules/base/base3d.mjs | 1755 - js/modules/base/colors.mjs | 528 - js/modules/base/func.mjs | 175 - js/modules/base/jspdf.mjs | 32164 --- js/modules/base/latex.mjs | 1595 - js/modules/base/latex3d.mjs | 277 - js/modules/base/lzma.mjs | 614 - js/modules/base/makepdf.mjs | 180 - js/modules/base/math.mjs | 2157 - js/modules/base/sha256.mjs | 265 - js/modules/base/svg2pdf.mjs | 5953 - js/modules/base/zstd.mjs | 17 - js/modules/core.mjs | 2208 - js/modules/d3.mjs | 6879 - js/modules/draw.mjs | 804 - js/modules/draw/RNTuple.mjs | 65 - js/modules/draw/RTreeMapPainter.mjs | 387 - js/modules/draw/RTreeMapTooltip.mjs | 87 - js/modules/draw/TASImagePainter.mjs | 456 - js/modules/draw/TAnnotation3DPainter.mjs | 72 - js/modules/draw/TArrowPainter.mjs | 108 - js/modules/draw/TBoxPainter.mjs | 164 - js/modules/draw/TGaxisPainter.mjs | 234 - js/modules/draw/TGraphPolarPainter.mjs | 756 - js/modules/draw/TLinePainter.mjs | 144 - js/modules/draw/TPiePainter.mjs | 525 - js/modules/draw/TPolyLinePainter.mjs | 108 - js/modules/draw/TPolyMarker3D.mjs | 96 - js/modules/draw/TRatioPlotPainter.mjs | 244 - js/modules/draw/TSplinePainter.mjs | 337 - js/modules/draw/TTextPainter.mjs | 138 - js/modules/draw/TTree.mjs | 515 - js/modules/draw/TWebPaintingPainter.mjs | 227 - js/modules/draw/draw3d.mjs | 109 - js/modules/draw/more.mjs | 245 - js/modules/draw/v7more.mjs | 356 - js/modules/geom/TGeoPainter.mjs | 6372 - js/modules/geom/bundle.mjs | 5 - js/modules/geom/csg.mjs | 854 - js/modules/geom/geobase.mjs | 4353 - js/modules/geom/geoworker.mjs | 15 - js/modules/geom/nodeworker.mjs | 6 - js/modules/gpad/RAxisPainter.mjs | 1179 - js/modules/gpad/RCanvasPainter.mjs | 1022 - js/modules/gpad/RFramePainter.mjs | 1302 - js/modules/gpad/RPadPainter.mjs | 1890 - js/modules/gpad/TAxisPainter.mjs | 1578 - js/modules/gpad/TCanvasPainter.mjs | 1064 - js/modules/gpad/TFramePainter.mjs | 3331 - js/modules/gpad/TPadPainter.mjs | 2949 - js/modules/gui.mjs | 344 - js/modules/gui/HierarchyPainter.mjs | 4373 - js/modules/gui/display.mjs | 1904 - js/modules/gui/lil-gui.mjs | 2393 - js/modules/gui/menu.mjs | 1803 - js/modules/gui/utils.mjs | 717 - js/modules/hist/RPavePainter.mjs | 288 - js/modules/hist/TEfficiencyPainter.mjs | 251 - js/modules/hist/TF1Painter.mjs | 385 - js/modules/hist/TF2Painter.mjs | 344 - js/modules/hist/TF3Painter.mjs | 307 - js/modules/hist/TGraph2DPainter.mjs | 1694 - js/modules/hist/TGraphPainter.mjs | 63 - js/modules/hist/TGraphTimePainter.mjs | 171 - js/modules/hist/TH1Painter.mjs | 85 - js/modules/hist/TH2Painter.mjs | 334 - js/modules/hist/TH3Painter.mjs | 839 - js/modules/hist/THStackPainter.mjs | 26 - js/modules/hist/TMultiGraphPainter.mjs | 32 - js/modules/hist/TPavePainter.mjs | 1977 - js/modules/hist/bundle.mjs | 8 - js/modules/hist/hist3d.mjs | 2403 - js/modules/hist2d/TGraphPainter.mjs | 1835 - js/modules/hist2d/TH1Painter.mjs | 1441 - js/modules/hist2d/TH2Painter.mjs | 3876 - js/modules/hist2d/THStackPainter.mjs | 571 - js/modules/hist2d/THistPainter.mjs | 2991 - js/modules/hist2d/TMultiGraphPainter.mjs | 379 - js/modules/hist2d/TScatterPainter.mjs | 259 - js/modules/hist2d/bundle.mjs | 7 - js/modules/io.mjs | 4222 - js/modules/jspdf.mjs | 410 - js/modules/main.mjs | 48 - js/modules/rntuple.mjs | 1690 - js/modules/svg2pdf.mjs | 27 - js/modules/three.mjs | 72295 ------ js/modules/three_addons.mjs | 6263 - js/modules/tree.mjs | 3152 - js/modules/webwindow.mjs | 1083 - js/scripts/JSRoot.core.js | 345 - 110 files changed, 49 insertions(+), 405131 deletions(-) create mode 100644 builtins/jsroot/CMakeLists.txt delete mode 100644 js/LICENSE delete mode 100644 js/build/jsroot.js delete mode 100644 js/changes.md delete mode 100644 js/files/draw.htm delete mode 100644 js/files/online.htm delete mode 100644 js/img/RootIcon.ico delete mode 100644 js/index.htm delete mode 100644 js/modules/base/BasePainter.mjs delete mode 100644 js/modules/base/FontHandler.mjs delete mode 100644 js/modules/base/ObjectPainter.mjs delete mode 100644 js/modules/base/RObjectPainter.mjs delete mode 100644 js/modules/base/TAttFillHandler.mjs delete mode 100644 js/modules/base/TAttLineHandler.mjs delete mode 100644 js/modules/base/TAttMarkerHandler.mjs delete mode 100644 js/modules/base/TAttTextHandler.mjs delete mode 100644 js/modules/base/base3d.mjs delete mode 100644 js/modules/base/colors.mjs delete mode 100644 js/modules/base/func.mjs delete mode 100644 js/modules/base/jspdf.mjs delete mode 100644 js/modules/base/latex.mjs delete mode 100644 js/modules/base/latex3d.mjs delete mode 100644 js/modules/base/lzma.mjs delete mode 100644 js/modules/base/makepdf.mjs delete mode 100644 js/modules/base/math.mjs delete mode 100644 js/modules/base/sha256.mjs delete mode 100644 js/modules/base/svg2pdf.mjs delete mode 100644 js/modules/base/zstd.mjs delete mode 100644 js/modules/core.mjs delete mode 100644 js/modules/d3.mjs delete mode 100644 js/modules/draw.mjs delete mode 100644 js/modules/draw/RNTuple.mjs delete mode 100644 js/modules/draw/RTreeMapPainter.mjs delete mode 100644 js/modules/draw/RTreeMapTooltip.mjs delete mode 100644 js/modules/draw/TASImagePainter.mjs delete mode 100644 js/modules/draw/TAnnotation3DPainter.mjs delete mode 100644 js/modules/draw/TArrowPainter.mjs delete mode 100644 js/modules/draw/TBoxPainter.mjs delete mode 100644 js/modules/draw/TGaxisPainter.mjs delete mode 100644 js/modules/draw/TGraphPolarPainter.mjs delete mode 100644 js/modules/draw/TLinePainter.mjs delete mode 100644 js/modules/draw/TPiePainter.mjs delete mode 100644 js/modules/draw/TPolyLinePainter.mjs delete mode 100644 js/modules/draw/TPolyMarker3D.mjs delete mode 100644 js/modules/draw/TRatioPlotPainter.mjs delete mode 100644 js/modules/draw/TSplinePainter.mjs delete mode 100644 js/modules/draw/TTextPainter.mjs delete mode 100644 js/modules/draw/TTree.mjs delete mode 100644 js/modules/draw/TWebPaintingPainter.mjs delete mode 100644 js/modules/draw/draw3d.mjs delete mode 100644 js/modules/draw/more.mjs delete mode 100644 js/modules/draw/v7more.mjs delete mode 100644 js/modules/geom/TGeoPainter.mjs delete mode 100644 js/modules/geom/bundle.mjs delete mode 100644 js/modules/geom/csg.mjs delete mode 100644 js/modules/geom/geobase.mjs delete mode 100644 js/modules/geom/geoworker.mjs delete mode 100644 js/modules/geom/nodeworker.mjs delete mode 100644 js/modules/gpad/RAxisPainter.mjs delete mode 100644 js/modules/gpad/RCanvasPainter.mjs delete mode 100644 js/modules/gpad/RFramePainter.mjs delete mode 100644 js/modules/gpad/RPadPainter.mjs delete mode 100644 js/modules/gpad/TAxisPainter.mjs delete mode 100644 js/modules/gpad/TCanvasPainter.mjs delete mode 100644 js/modules/gpad/TFramePainter.mjs delete mode 100644 js/modules/gpad/TPadPainter.mjs delete mode 100644 js/modules/gui.mjs delete mode 100644 js/modules/gui/HierarchyPainter.mjs delete mode 100644 js/modules/gui/display.mjs delete mode 100644 js/modules/gui/lil-gui.mjs delete mode 100644 js/modules/gui/menu.mjs delete mode 100644 js/modules/gui/utils.mjs delete mode 100644 js/modules/hist/RPavePainter.mjs delete mode 100644 js/modules/hist/TEfficiencyPainter.mjs delete mode 100644 js/modules/hist/TF1Painter.mjs delete mode 100644 js/modules/hist/TF2Painter.mjs delete mode 100644 js/modules/hist/TF3Painter.mjs delete mode 100644 js/modules/hist/TGraph2DPainter.mjs delete mode 100644 js/modules/hist/TGraphPainter.mjs delete mode 100644 js/modules/hist/TGraphTimePainter.mjs delete mode 100644 js/modules/hist/TH1Painter.mjs delete mode 100644 js/modules/hist/TH2Painter.mjs delete mode 100644 js/modules/hist/TH3Painter.mjs delete mode 100644 js/modules/hist/THStackPainter.mjs delete mode 100644 js/modules/hist/TMultiGraphPainter.mjs delete mode 100644 js/modules/hist/TPavePainter.mjs delete mode 100644 js/modules/hist/bundle.mjs delete mode 100644 js/modules/hist/hist3d.mjs delete mode 100644 js/modules/hist2d/TGraphPainter.mjs delete mode 100644 js/modules/hist2d/TH1Painter.mjs delete mode 100644 js/modules/hist2d/TH2Painter.mjs delete mode 100644 js/modules/hist2d/THStackPainter.mjs delete mode 100644 js/modules/hist2d/THistPainter.mjs delete mode 100644 js/modules/hist2d/TMultiGraphPainter.mjs delete mode 100644 js/modules/hist2d/TScatterPainter.mjs delete mode 100644 js/modules/hist2d/bundle.mjs delete mode 100644 js/modules/io.mjs delete mode 100644 js/modules/jspdf.mjs delete mode 100644 js/modules/main.mjs delete mode 100644 js/modules/rntuple.mjs delete mode 100644 js/modules/svg2pdf.mjs delete mode 100644 js/modules/three.mjs delete mode 100644 js/modules/three_addons.mjs delete mode 100644 js/modules/tree.mjs delete mode 100644 js/modules/webwindow.mjs delete mode 100644 js/scripts/JSRoot.core.js diff --git a/.github/workflows/root-ci-config/buildconfig/global.txt b/.github/workflows/root-ci-config/buildconfig/global.txt index f290a5a06c11e..b6eedd626835b 100644 --- a/.github/workflows/root-ci-config/buildconfig/global.txt +++ b/.github/workflows/root-ci-config/buildconfig/global.txt @@ -21,6 +21,7 @@ builtin_lzma=OFF builtin_mathtext=ON builtin_nlohmannjson=OFF builtin_openssl=OFF +builtin_jsroot=ON builtin_mathjax=ON builtin_openui5=ON builtin_pcre=OFF diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fe8fc8a0b335..54eccd9ab1b1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -339,6 +339,9 @@ add_custom_command(OUTPUT ${stamp_file} set(directories_to_copy test icons fonts macros tutorials) if(http) list(APPEND directories_to_copy js) + if(builtin_jsroot) + list(APPEND directories_deps BUILTIN_JSROOT) + endif() endif() if(webgui) list(APPEND directories_to_copy ui5) @@ -356,7 +359,7 @@ foreach(dir_to_copy ${directories_to_copy}) add_custom_command(OUTPUT ${artifacts_out} COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/${dir_to_copy} ${CMAKE_BINARY_DIR}/${dir_to_copy} COMMENT "Copy ${dir_to_copy}" - DEPENDS ${artifacts_in}) + DEPENDS ${artifacts_in} ${directories_deps}) endforeach() #---Copy etc/* files individually to the build area at build time--------------------------------- diff --git a/builtins/jsroot/CMakeLists.txt b/builtins/jsroot/CMakeLists.txt new file mode 100644 index 0000000000000..28976e742e729 --- /dev/null +++ b/builtins/jsroot/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +ExternalProject_Add(BUILTIN_JSROOT + GIT_REPOSITORY https://github.com/root-project/jsroot.git + GIT_TAG 7.12.0 + GIT_SHALLOW TRUE # only works with tags/branches, comment out when commit hashes used + SOURCE_DIR ${CMAKE_BINARY_DIR}/js/ + UPDATE_COMMAND "" + PATCH_COMMAND ${CMAKE_COMMAND} -E rm -rf /demo + /fonts + /docs + /libs + /server + /.git + /.github + /api.htm + /examples.htm + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" +) diff --git a/cmake/modules/RootBuildOptions.cmake b/cmake/modules/RootBuildOptions.cmake index 951464c75809a..89b3c6f675074 100644 --- a/cmake/modules/RootBuildOptions.cmake +++ b/cmake/modules/RootBuildOptions.cmake @@ -104,6 +104,7 @@ ROOT_BUILD_OPTION(builtin_lzma OFF "Build lzma from an automatically downloaded ROOT_BUILD_OPTION(builtin_mathtext ON "Build mathtext from an in-built source tarball (does not require network)") ROOT_BUILD_OPTION(builtin_nlohmannjson OFF "Build nlohmann/json from an automatically downloaded source tarball (requires network)") ROOT_BUILD_OPTION(builtin_openssl OFF "Build OpenSSL from an automatically downloaded source tarball (requires network)") +ROOT_BUILD_OPTION(builtin_jsroot OFF "Install JSROOT to run webgui widgets (requires network)") ROOT_BUILD_OPTION(builtin_mathjax OFF "Install mathjax package for mathtext rendering in JSROOT (requires network)") ROOT_BUILD_OPTION(builtin_openui5 OFF "Install openui5 package to run webgui widgets (requires network)") ROOT_BUILD_OPTION(builtin_pcre OFF "Build PCRE2 from an automatically downloaded source tarball (requires network)") @@ -274,6 +275,7 @@ if(builtin_all) if(APPLE) set(builtin_openssl_defvalue ON) endif() + set(builtin_jsroot_defvalue ON) set(builtin_mathjax_defvalue ON) set(builtin_openui5_defvalue ON) set(builtin_pcre_defvalue ON) diff --git a/cmake/modules/SearchInstalledSoftware.cmake b/cmake/modules/SearchInstalledSoftware.cmake index 74dc7fe9ff9d6..6e0fe71082ff7 100644 --- a/cmake/modules/SearchInstalledSoftware.cmake +++ b/cmake/modules/SearchInstalledSoftware.cmake @@ -1102,6 +1102,23 @@ if (testing OR testsupport) endforeach() endif() +#------------------------------------------------------------------------------------ + +if(builtin_jsroot) + ROOT_CHECK_CONNECTION("builtin_jsroot=OFF") + if (NO_CONNECTION) + message(SEND_ERROR "builtin_jsroot=ON requires internet connection, check it or disable feature") + list(APPEND HOTFIX_BUILD_FLAGS -Dbuiltin_jsroot=OFF) + endif() + add_subdirectory(builtins/jsroot) +elseif(NOT "$ENV{JSROOTSYS}" STREQUAL "") + # use preconfigured JSROOT to run web widgets +elseif(http OR webgui) + message(SEND_ERROR "Without builtin_jsroot option http and webgui is not working, either disable http/webgui or enable builtin_jsroot") + list(APPEND HOTFIX_BUILD_FLAGS -Dhttp=OFF -Dwebgui=OFF) +endif() + + #------------------------------------------------------------------------------------ if(http) if(builtin_mathjax) diff --git a/js/LICENSE b/js/LICENSE deleted file mode 100644 index a6d9f24717fe9..0000000000000 --- a/js/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright � 2013-2026 JavaScript ROOT authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/js/build/jsroot.js b/js/build/jsroot.js deleted file mode 100644 index be08825c3524e..0000000000000 --- a/js/build/jsroot.js +++ /dev/null @@ -1,189904 +0,0 @@ -// https://root.cern/js/ v7.12.0 -(function (global, factory) { -typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : -typeof define === 'function' && define.amd ? define(['exports'], factory) : -(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.JSROOT = global.JSROOT || {})); -})(this, (function (exports) { 'use strict'; - -var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; -/* eslint-disable @stylistic/js/indent */ - -/** @summary version id - * @desc For the JSROOT release the string in format 'major.minor.patch' like '7.0.0' */ -const version_id = '7.12.0', - -/** @summary version date - * @desc Release date in format day/month/year like '14/04/2022' */ -version_date = '28/10/2026', - -/** @summary version id and date - * @desc Produced by concatenation of {@link version_id} and {@link version_date} - * Like '7.0.0 14/04/2022' */ -version = version_id + ' ' + version_date, - -/** @summary Is node.js flag - * @private */ -nodejs = Boolean((typeof process === 'object') && process.versions?.node && process.versions.v8), - -/** @summary internal data - * @private */ -internals = { - /** @summary unique id counter, starts from 1 */ - id_counter: 1 -}, - -_src = (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('jsroot.js', document.baseURI).href)), - -_src_dir = '$jsrootsys'; - - -/** @summary Check if argument is a not-null Object - * @private */ -function isObject(arg) { return arg && typeof arg === 'object'; } - -/** @summary Check if argument is a Function - * @private */ -function isFunc(arg) { return typeof arg === 'function'; } - -/** @summary Check if argument is a String - * @private */ -function isStr(arg) { return typeof arg === 'string'; } - -/** @summary Check if object is a Promise - * @private */ -function isPromise(obj) { return isObject(obj) && isFunc(obj.then); } - -/** @summary Postpone func execution and return result in promise - * @private */ -function postponePromise(func, timeout) { - return new Promise(resolveFunc => { - setTimeout(() => { - const res = isFunc(func) ? func() : func; - resolveFunc(res); - }, timeout); - }); -} - -/** @summary Provide promise in any case - * @private */ -function getPromise(obj) { return isPromise(obj) ? obj : Promise.resolve(obj); } - - -/** @summary Location of JSROOT modules - * @desc Automatically detected and used to dynamically load other modules - * @private */ -exports.source_dir = ''; - -if (_src_dir[0] !== '$') - exports.source_dir = _src_dir; -else if (_src && isStr(_src)) { - let pos = _src.indexOf('modules/core.mjs'); - if (pos < 0) - pos = _src.indexOf('build/jsroot.js'); - if (pos < 0) - pos = _src.indexOf('build/jsroot.min.js'); - if (pos >= 0) - exports.source_dir = _src.slice(0, pos); - else - internals.ignore_v6 = true; -} - -if (!nodejs) { - if (exports.source_dir) - console.log(`Set jsroot source_dir to ${exports.source_dir}, ${version}`); - else - console.log(`jsroot bundle, ${version}`); -} - -/** @summary Is batch mode flag - * @private */ -let batch_mode = nodejs; - -/** @summary Indicates if running in batch mode */ -function isBatchMode() { return batch_mode; } - -/** @summary Set batch mode - * @private */ -function setBatchMode(on) { batch_mode = Boolean(on); } - -/** @summary Indicates if running inside Node.js */ -function isNodeJs() { return nodejs; } - -/** @summary atob function in all environments - * @private */ -const atob_func = isNodeJs() ? str => Buffer.from(str, 'base64').toString('latin1') : globalThis?.atob, - -/** @summary btoa function in all environments - * @private */ -btoa_func = isNodeJs() ? str => Buffer.from(str, 'latin1').toString('base64') : globalThis?.btoa, - -/** @summary browser detection flags - * @private */ -browser = { isFirefox: true, isSafari: false, isChrome: false, isWin: false, touches: false, screenWidth: 1200 }; - -if ((typeof document !== 'undefined') && (typeof window !== 'undefined') && (typeof navigator !== 'undefined')) { - navigator.userAgentData?.brands?.forEach(item => { - if (item.brand === 'HeadlessChrome') { - browser.isChromeHeadless = true; - browser.chromeVersion = parseInt(item.version); - } else if (item.brand === 'Chromium') { - browser.isChrome = true; - browser.chromeVersion = parseInt(item.version); - } - }); - - if (browser.chromeVersion) { - browser.isFirefox = false; - browser.isWin = navigator.userAgentData.platform === 'Windows'; - } else { - browser.isFirefox = navigator.userAgent.indexOf('Firefox') >= 0; - browser.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; - browser.isChrome = Boolean(window.chrome); - browser.isChromeHeadless = navigator.userAgent.indexOf('HeadlessChrome') >= 0; - browser.chromeVersion = (browser.isChrome || browser.isChromeHeadless) ? (navigator.userAgent.indexOf('Chrom') > 0 ? parseInt(navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)[1]) : 134) : 0; - browser.isWin = navigator.userAgent.indexOf('Windows') >= 0; - } - browser.android = /android/i.test(navigator.userAgent); - browser.touches = ('ontouchend' in document); // identify if touch events are supported - browser.screenWidth = window.screen?.width ?? 1200; -} - -/** @summary Check if prototype string match to array (typed on untyped) - * @return {number} 0 - not array, 1 - regular array, 2 - typed array - * @private */ -function isArrayProto(proto) { - if ((proto.length < 14) || proto.indexOf('[object ')) - return 0; - const p = proto.indexOf('Array]'); - if ((p < 0) || (p !== proto.length - 6)) - return 0; - // plain array has only '[object Array]', typed array type name inside - return proto.length === 14 ? 1 : 2; -} - -/** @desc Specialized JSROOT constants, used in {@link settings} - * @namespace */ -const constants$1 = { - /** @summary Kind of 3D rendering, used for {@link settings.Render3D} - * @namespace */ - Render3D: { - /** @summary Default 3D rendering, normally WebGL, if not supported - SVG */ - Default: 0, - /** @summary Use normal WebGL rendering and place as interactive Canvas element on HTML page */ - WebGL: 1, - /** @summary Use WebGL rendering, but convert into svg image, not interactive */ - WebGLImage: 2, - /** @summary Use SVG rendering, slow, imprecise and not interactive, not recommended */ - SVG: 3, - /** @summary Disable renderer, used for three.js model creation, only for internal use recommended */ - None: 4, - fromString(s) { - if ((s === 'webgl') || (s === 'gl')) - return this.WebGL; - if (s === 'img') - return this.WebGLImage; - if (s === 'svg') - return this.SVG; - if (s === 'none') - return this.None; - return this.Default; - } - }, - /** @summary Way to embed 3D into SVG, used for {@link settings.Embed3D} - * @namespace */ - Embed3D: { - /** @summary Do not embed 3D drawing, use complete space */ - NoEmbed: -1, - /** @summary Default embedding mode - on Firefox and latest Chrome is real ```Embed```, on all other ```Overlay``` */ - Default: 0, - /** @summary WebGL canvas not inserted into SVG, but just overlay. The only way how old Chrome browser can be used */ - Overlay: 1, - /** @summary Really embed WebGL Canvas into SVG */ - Embed: 2, - /** @summary Embedding, but when SVG rendering or SVG image conversion is used */ - EmbedSVG: 3, - /** @summary Convert string values into number */ - fromString(s) { - if (s === 'embed') - return this.Embed; - if (s === 'overlay') - return this.Overlay; - return this.Default; - } - }, - /** @summary How to use latex in text drawing, used for {@link settings.Latex} - * @namespace */ - Latex: { - /** @summary do not use Latex at all for text drawing */ - Off: 0, - /** @summary convert only known latex symbols */ - Symbols: 1, - /** @summary normal latex processing with svg */ - Normal: 2, - /** @summary use MathJax for complex cases, otherwise simple SVG text */ - MathJax: 3, - /** @summary always use MathJax for text rendering */ - AlwaysMathJax: 4, - /** @summary Convert string values into number */ - fromString(s) { - if (!s || !isStr(s)) - return this.Normal; - switch (s) { - case 'off': return this.Off; - case 'symbols': return this.Symbols; - case 'normal': - case 'latex': - case 'exp': - case 'experimental': return this.Normal; - case 'MathJax': - case 'mathjax': - case 'math': return this.MathJax; - case 'AlwaysMathJax': - case 'alwaysmath': - case 'alwaysmathjax': return this.AlwaysMathJax; - } - const code = parseInt(s); - return (Number.isInteger(code) && (code >= this.Off) && (code <= this.AlwaysMathJax)) ? code : this.Normal; - } - } -}, - -/** @desc Global JSROOT settings - * @namespace */ -settings = { - /** @summary Render of 3D drawing methods, see {@link constants.Render3D} for possible values */ - Render3D: constants$1.Render3D.Default, - /** @summary 3D drawing methods in batch mode, see {@link constants.Render3D} for possible values */ - Render3DBatch: constants$1.Render3D.Default, - /** @summary Way to embed 3D drawing in SVG, see {@link constants.Embed3D} for possible values */ - Embed3D: constants$1.Embed3D.Default, - /** @summary Use `resvg-js` backend for converting SVGs in node.js */ - UseResvgJs: true, - /** @summary Default canvas width */ - CanvasWidth: 1200, - /** @summary Default canvas height */ - CanvasHeight: 800, - /** @summary Canvas pixel ratio between viewport and display, default 1 */ - CanvasScale: 1, - /** @summary Enable or disable tooltips, default on */ - Tooltip: !nodejs, - /** @summary Time in msec for appearance of tooltips, 0 - no animation */ - TooltipAnimation: 500, - /** @summary Enables context menu usage */ - ContextMenu: !nodejs, - /** @summary Global zooming flag, enable/disable any kind of interactive zooming */ - Zooming: !nodejs, - /** @summary Zooming with the mouse events */ - ZoomMouse: !nodejs, - /** @summary Zooming with mouse wheel */ - ZoomWheel: !nodejs, - /** @summary Zooming on touch devices */ - ZoomTouch: !nodejs, - /** @summary Enables move and resize of elements like statistic box, title, pave, colz */ - MoveResize: !browser.touches && !nodejs, - /** @summary Configures keyboard key press handling - * @desc Can be disabled to prevent keys handling in complex HTML layouts - * @default true */ - HandleKeys: !nodejs, - /** @summary enables drag and drop functionality */ - DragAndDrop: !nodejs, - /** @summary Interactive dragging of TGraph points */ - DragGraphs: true, - /** @summary Value of user-select style in interactive drawings */ - UserSelect: 'none', - /** @summary Show progress box, can be false, true or 'modal' */ - ProgressBox: !nodejs, - /** @summary Show additional tool buttons on the canvas, false - disabled, true - enabled, 'popup' - only toggle button */ - ToolBar: nodejs ? false : 'popup', - /** @summary Position of toolbar 'left' left-bottom corner on canvas, 'right' - right-bottom corner on canvas, opposite on sub-pads */ - ToolBarSide: 'left', - /** @summary display tool bar vertical (default false) */ - ToolBarVert: false, - /** @summary if drawing inside particular div can be enlarged on full window */ - CanEnlarge: true, - /** @summary if frame position can be adjusted to let show axis or colz labels */ - CanAdjustFrame: false, - /** @summary calculation of text size consumes time and can be skipped to improve performance (but with side effects on text adjustments) */ - ApproxTextSize: false, - /** @summary Load symbol.ttf font to display greek labels. By default font file not loaded and unicode is used */ - LoadSymbolTtf: false, - /** @summary Histogram drawing optimization: 0 - disabled, 1 - only for large (>5000 1d bins, >50 2d bins) histograms, 2 - always */ - OptimizeDraw: 1, - /** @summary Automatically create stats box, default on */ - AutoStat: true, - /** @summary Default frame position in NFC - * @deprecated Use gStyle.fPad[Left/Right/Top/Bottom]Margin values instead, to be removed in v8 */ - FrameNDC: {}, - /** @summary size of pad, where many features will be deactivated like text draw or zooming */ - SmallPad: { width: 150, height: 100 }, - /** @summary Default color palette id */ - Palette: 57, - /** @summary Configures Latex usage, see {@link constants.Latex} for possible values */ - Latex: constants$1.Latex.Normal, - /** @summary Grads per segment in TGeo spherical shapes like tube */ - GeoGradPerSegm: 6, - /** @summary Enables faces compression after creation of composite shape */ - GeoCompressComp: true, - /** @summary if true, ignore all kind of URL options in the browser URL */ - IgnoreUrlOptions: false, - /** @summary how many items shown on one level of hierarchy */ - HierarchyLimit: 250, - /** @summary default display kind for the hierarchy painter */ - DisplayKind: 'simple', - /** @summary default left area width in browser layout */ - BrowserWidth: 250, - /** @summary custom format for all X values, when not specified {@link gStyle.fStatFormat} is used */ - XValuesFormat: undefined, - /** @summary custom format for all Y values, when not specified {@link gStyle.fStatFormat} is used */ - YValuesFormat: undefined, - /** @summary custom format for all Z values, when not specified {@link gStyle.fStatFormat} is used */ - ZValuesFormat: undefined, - /** @summary custom format for all float values like TH1F content */ - FloatFormat: '8.6g', - /** @summary custom format for all double values like TH1D content */ - DoubleFormat: '10.8g', - /** @summary Let detect and solve problem when server returns wrong Content-Length header - * @desc See [jsroot#189]{@link https://github.com/root-project/jsroot/issues/189} for more info - * Can be enabled by adding 'wrong_http_response' parameter to URL when using JSROOT UI - * @default false */ - HandleWrongHttpResponse: false, - /** @summary Tweak browser caching with stamp URL parameter - * @desc When specified, extra URL parameter like ```?stamp=unique_value``` append to each files loaded - * In such case browser will be forced to load file content disregards of browser or server cache settings - * Can be disabled by providing &usestamp=false in URL or via Settings/Files sub-menu - * Disabled by default on node.js, enabled in the web browsers */ - UseStamp: !nodejs, - /** @summary Maximal number of bytes ranges in http 'Range' header - * @desc Some http server has limitations for number of bytes ranges therefore let change maximal number via setting - * @default 200 */ - MaxRanges: 200, - /** @summary Number of bytes requested once by TTree::Draw processing - * @desc TTree can be very large and data from baskets read by portion specified by this variable - * @default 1e6 */ - TreeReadBunchSize: 1e6, - /** @summary File read timeout in ms - * @desc Configures timeout for each http operation for reading ROOT files - * @default 0 */ - FilesTimeout: 0, - /** @summary Default remap object for files loading - * @desc Allows to retry files reading if original URL fails - * @private */ - FilesRemap: { 'https://root.cern/': 'https://root-eos.web.cern.ch/' }, - /** @summary THttpServer read timeout in ms - * @desc Configures timeout for requests to THttpServer - * @default 0 */ - ServerTimeout: 0, - /** @summary Configure xhr.withCredentials = true when submitting http requests from JSROOT */ - WithCredentials: false, - /** @summary Skip streamer infos from the GUI */ - SkipStreamerInfos: false, - /** @summary Show only last cycle for objects in TFile */ - OnlyLastCycle: false, - /** @summary Configures dark mode for the GUI */ - DarkMode: false, - /** @summary Prefer to use saved points in TF1/TF2, avoids eval() and Function() when possible */ - PreferSavedPoints: false, - /** @summary Angle in degree for axis labels tilt when available space is not enough */ - AxisTiltAngle: 25, - /** @summary Strip axis labels trailing 0 or replace 10^0 by 1 */ - StripAxisLabels: true, - /** @summary If true exclude (cut off) axis labels which may exceed graphical range, also axis name can be specified */ - CutAxisLabels: false, - /** @summary Draw TF1 by default as curve or line */ - FuncAsCurve: false, - /** @summary Time zone used for date/time display, local by default, can be 'UTC' or 'Europe/Berlin' or any other valid value */ - TimeZone: '', - /** @summary Page URL which will be used to show item in new tab, jsroot main dir used by default */ - NewTabUrl: '', - /** @summary Extra parameters which will be append to the url when item shown in new tab */ - NewTabUrlPars: '', - /** @summary Export different settings in output URL */ - NewTabUrlExportSettings: false, - /** @summary Enable more debug output, also via 'WebGui.Debug: yes' on ROOT side */ - Debug: false -}, - -/** @namespace - * @summary Instance of TStyle object like in ROOT - * @desc Includes default draw styles, can be changed after loading of core.mjs - * or can be load from the file providing style=itemname in the URL - * See [TStyle docu]{@link https://root.cern/doc/master/classTStyle.html} 'Private attributes' section for more detailed info about each value */ -gStyle = { - fName: 'Modern', - /** @summary Default log x scale */ - fOptLogx: 0, - /** @summary Default log y scale */ - fOptLogy: 0, - /** @summary Default log z scale */ - fOptLogz: 0, - /** @summary Show date on canvas */ - fOptDate: 0, - /** @summary Show file name on canvas */ - fOptFile: 0, - /** @summary X position of date on canvas */ - fDateX: 0.01, - /** @summary Y position of date on canvas */ - fDateY: 0.01, - /** @summary Draw histogram title */ - fOptTitle: 1, - /** @summary Canvas fill color */ - fCanvasColor: 0, - /** @summary Pad fill color */ - fPadColor: 0, - /** @summary Pad bottom margin */ - fPadBottomMargin: 0.1, - /** @summary Pad top margin */ - fPadTopMargin: 0.1, - /** @summary Pad left margin */ - fPadLeftMargin: 0.1, - /** @summary Pad right margin */ - fPadRightMargin: 0.1, - /** @summary TPad.fGridx default value */ - fPadGridX: false, - /** @summary TPad.fGridy default value */ - fPadGridY: false, - /** @summary TPad.fTickx default value */ - fPadTickX: 0, - /** @summary TPad.fTicky default value */ - fPadTickY: 0, - /** @summary Pad border size */ - fPadBorderSize: 2, - /** @summary Pad border mode */ - fPadBorderMode: 0, - /** @summary Canvas border size */ - fCanvasBorderSize: 2, - /** @summary Canvas border mode */ - fCanvasBorderMode: 0, - /** @summary fill color for stat box */ - fStatColor: 0, - /** @summary fill style for stat box */ - fStatStyle: 1000, - /** @summary text color in stat box */ - fStatTextColor: 1, - /** @summary text size in stat box */ - fStatFontSize: 0, - /** @summary stat text font */ - fStatFont: 42, - /** @summary Stat border size */ - fStatBorderSize: 1, - /** @summary Printing format for stats */ - fStatFormat: '6.4g', - /** @summary Stat box X position - top right corner */ - fStatX: 0.98, - /** @summary Stat box Y position - top right corner */ - fStatY: 0.935, - /** @summary Stat box width */ - fStatW: 0.2, - /** @summary Stat box height */ - fStatH: 0.16, - /** @summary Title text align */ - fTitleAlign: 23, - /** @summary Title fill color */ - fTitleColor: 0, - /** @summary Title text color */ - fTitleTextColor: 1, - /** @summary Title border size */ - fTitleBorderSize: 0, - /** @summary Title text font */ - fTitleFont: 42, - /** @summary Title font size */ - fTitleFontSize: 0.05, - /** @summary Title fill style */ - fTitleStyle: 0, - /** @summary X position of top left corner of title box */ - fTitleX: 0.5, - /** @summary Y position of top left corner of title box */ - fTitleY: 0.995, - /** @summary Width of title box */ - fTitleW: 0, - /** @summary Height of title box */ - fTitleH: 0, - /** @summary Printing format for fit parameters */ - fFitFormat: '5.4g', - /** @summary Default optstat value */ - fOptStat: 1111, - /** @summary Default optfit value */ - fOptFit: 0, - /** @summary Default number of colors in contour */ - fNumberContours: 20, - /** @summary Grids color */ - fGridColor: 0, - /** @summary Grids line style */ - fGridStyle: 3, - /** @summary Grids line width */ - fGridWidth: 1, - /** @summary Frame fill color */ - fFrameFillColor: 0, - /** @summary Frame fill style */ - fFrameFillStyle: 1001, - /** @summary Frame line color */ - fFrameLineColor: 1, - /** @summary Frame line width */ - fFrameLineWidth: 1, - /** @summary Frame line style */ - fFrameLineStyle: 1, - /** @summary Frame border size */ - fFrameBorderSize: 1, - /** @summary Frame border mode */ - fFrameBorderMode: 0, - /** @summary size in pixels of end error for E1 draw options */ - fEndErrorSize: 2, - /** @summary X size of the error marks for the histogram drawings */ - fErrorX: 0.5, - /** @summary when true, BAR and LEGO drawing using base = 0 */ - fHistMinimumZero: false, - /** @summary Margin between histogram's top and pad's top */ - fHistTopMargin: 0.05, - /** @summary Histogram fill color */ - fHistFillColor: 0, - /** @summary Histogram fill style */ - fHistFillStyle: 1001, - /** @summary Histogram line color */ - fHistLineColor: 602, - /** @summary Histogram line style */ - fHistLineStyle: 1, - /** @summary Histogram line width */ - fHistLineWidth: 1, - /** @summary format for bin content */ - fPaintTextFormat: 'g', - /** @summary default time offset, UTC time at 01/01/95 */ - fTimeOffset: 788918400, - /** @summary Legend border size */ - fLegendBorderSize: 1, - /** @summary Legend text font */ - fLegendFont: 42, - /** @summary Legend font size */ - fLegendTextSize: 0, - /** @summary Legend fill color */ - fLegendFillColor: 0, - /** @summary Legend fill style */ - fLegendFillStyle: 1001, - /** @summary Hatches line width in fill styles */ - fHatchesLineWidth: 1, - fHatchesSpacing: 1, - fCandleWhiskerRange: 1.0, - fCandleBoxRange: 0.5, - fCandleScaled: false, - fViolinScaled: true, - fCandleCircleLineWidth: 1, - fCandleCrossLineWidth: 1, - fOrthoCamera: false, - fXAxisExpXOffset: 0, - fXAxisExpYOffset: 0, - fYAxisExpXOffset: 0, - fYAxisExpYOffset: 0, - fAxisMaxDigits: 5, - fStripDecimals: true, - /** @summary Width of bar for graphs */ - fBarWidth: 1, - /** @summary Offset of bar for graphs */ - fBarOffset: 0 -}; - -/** @summary Method returns current document in use - * @private */ -function getDocument() { - if (nodejs) - return internals.nodejs_document; - if (typeof document !== 'undefined') - return document; - if (typeof window === 'object') - return window.document; - return undefined; -} - -/** @summary Ensure global JSROOT and v6 support methods - * @private */ -exports._ensureJSROOT = null; - - -/** @summary Generate mask for given bit - * @param {number} n bit number - * @return {number} produced mask - * @private */ -function BIT(n) { return 1 << n; } - -/** @summary Make deep clone of the object, including all sub-objects - * @return {object} cloned object - * @private */ -function clone(src, map, nofunc) { - if (!src) - return null; - - if (!map) - map = { obj: [], clones: [], nofunc }; - else { - const i = map.obj.indexOf(src); - if (i >= 0) - return map.clones[i]; - } - - const arr_kind = isArrayProto(Object.prototype.toString.apply(src)); - - // process normal array - if (arr_kind === 1) { - const tgt = []; - map.obj.push(src); - map.clones.push(tgt); - for (let i = 0; i < src.length; ++i) - tgt.push(isObject(src[i]) ? clone(src[i], map) : src[i]); - return tgt; - } - - // process typed array - if (arr_kind === 2) { - const tgt = []; - map.obj.push(src); - map.clones.push(tgt); - for (let i = 0; i < src.length; ++i) - tgt.push(src[i]); - - return tgt; - } - - const tgt = {}; - map.obj.push(src); - map.clones.push(tgt); - - for (const k in src) { - if (isObject(src[k])) - tgt[k] = clone(src[k], map); - else if (!map.nofunc || !isFunc(src[k])) - tgt[k] = src[k]; - } - - return tgt; -} - -// used very often - keep shortcut -const extend$1 = Object.assign; - -/** @summary Adds specific methods to the object. - * @desc JSROOT implements some basic methods for different ROOT classes. - * @function - * @param {object} obj - object where methods are assigned - * @param {string} [typename] - optional typename, if not specified, obj._typename will be used - * @private */ -exports.addMethods = null; - -/** @summary Should be used to parse JSON string produced with TBufferJSON class - * @desc Replace all references inside object like { "$ref": "1" } - * @param {object|string} json object where references will be replaced - * @return {object} parsed object */ -function parse$1(json) { - if (!json) - return null; - - const obj = isStr(json) ? JSON.parse(json) : json, map = []; - let newfmt; - - const unref_value = value => { - if ((value === null) || (value === undefined)) - return; - - if (isStr(value)) { - if (newfmt || (value.length < 6) || value.indexOf('$ref:')) - return; - const ref = parseInt(value.slice(5)); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) - return; - newfmt = false; - return map[ref]; - } - - if (typeof value !== 'object') - return; - - const proto = Object.prototype.toString.apply(value); - - // scan array - it can contain other objects - if (isArrayProto(proto) > 0) { - for (let i = 0; i < value.length; ++i) { - const res = unref_value(value[i]); - if (res !== undefined) - value[i] = res; - } - return; - } - - const ks = Object.keys(value), len = ks.length; - - if ((newfmt !== false) && (len === 1) && (ks[0] === '$ref')) { - const ref = parseInt(value.$ref); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) - return; - newfmt = true; - return map[ref]; - } - - if ((newfmt !== false) && (len > 1) && (ks[0] === '$arr') && (ks[1] === 'len')) { - // this is ROOT-coded array - let arr; - switch (value.$arr) { - case 'Int8': arr = new Int8Array(value.len); break; - case 'Uint8': arr = new Uint8Array(value.len); break; - case 'Int16': arr = new Int16Array(value.len); break; - case 'Uint16': arr = new Uint16Array(value.len); break; - case 'Int32': arr = new Int32Array(value.len); break; - case 'Uint32': arr = new Uint32Array(value.len); break; - case 'Float32': arr = new Float32Array(value.len); break; - case 'Int64': - case 'Uint64': - case 'Float64': arr = new Float64Array(value.len); break; - default: arr = new Array(value.len); - } - - arr.fill((value.$arr === 'Bool') ? false : 0); - - if (value.b !== undefined) { - // base64 coding - const buf = atob_func(value.b); - if (arr.buffer) { - const dv = new DataView(arr.buffer, value.o || 0), - blen = Math.min(buf.length, dv.byteLength); - for (let k = 0; k < blen; ++k) - dv.setUint8(k, buf.charCodeAt(k)); - } else - throw new Error('base64 coding supported only for native arrays with binary data'); - } else { - // compressed coding - let nkey = 2, p = 0; - while (nkey < len) { - if (ks[nkey][0] === 'p') - p = value[ks[nkey++]]; // position - if (ks[nkey][0] !== 'v') - throw new Error(`Unexpected member ${ks[nkey]} in array decoding`); - const v = value[ks[nkey++]]; // value - if (typeof v === 'object') { - for (let k = 0; k < v.length; ++k) - arr[p++] = v[k]; - } else { - arr[p++] = v; - if ((nkey < len) && (ks[nkey][0] === 'n')) { - let cnt = value[ks[nkey++]]; // counter - while (--cnt) - arr[p++] = v; - } - } - } - } - - return arr; - } - - if ((newfmt !== false) && (len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { - newfmt = true; - const f1 = unref_value(value.first), - s1 = unref_value(value.second); - if (f1 !== undefined) - value.first = f1; - if (s1 !== undefined) - value.second = s1; - value._typename = value.$pair; - delete value.$pair; - return; // pair object is not counted in the objects map - } - - // prevent endless loop - if (map.indexOf(value) >= 0) - return; - - // add object to object map - map.push(value); - - // add methods to all objects, where _typename is specified - if (value._typename) - exports.addMethods(value); - - for (let k = 0; k < len; ++k) { - const i = ks[k], res = unref_value(value[i]); - if (res !== undefined) - value[i] = res; - } - }; - - unref_value(obj); - - return obj; -} - -/** @summary Parse response from multi.json request - * @desc Method should be used to parse JSON code, produced by multi.json request of THttpServer - * @param {string} json string to parse - * @return {Array} array of parsed elements */ -function parseMulti(json) { - if (!json) - return null; - const arr = JSON.parse(json); - if (arr?.length) { - for (let i = 0; i < arr.length; ++i) - arr[i] = parse$1(arr[i]); - } - return arr; -} - -/** @summary Method converts JavaScript object into ROOT-like JSON - * @desc When performed properly, JSON can be used in [TBufferJSON::fromJSON()]{@link https://root.cern/doc/master/classTBufferJSON.html} - * method to read data back with C++. Or one can again parse json with {@link parse} function - * @param {object} obj - JavaScript object to convert - * @param {number} [spacing] - optional line spacing in JSON - * @return {string} produced JSON code - * @example - * import { openFile, draw, toJSON } from 'https://root.cern/js/latest/modules/main.mjs'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let obj = await file.readObject('hpxpy;1'); - * obj.fTitle = 'New histogram title'; - * let json = toJSON(obj); */ -function toJSON(obj, spacing) { - if (!isObject(obj)) - return ''; - - const map = [], // map of stored objects - copy_value = value => { - if (isFunc(value)) - return undefined; - - if ((value === undefined) || (value === null) || !isObject(value)) - return value; - - // typed array need to be converted into normal array, otherwise looks strange - if (isArrayProto(Object.prototype.toString.apply(value)) > 0) { - const arr = new Array(value.length); - for (let i = 0; i < value.length; ++i) - arr[i] = copy_value(value[i]); - return arr; - } - - // this is how reference is code - const refid = map.indexOf(value); - if (refid >= 0) - return { $ref: refid }; - - const ks = Object.keys(value), len = ks.length, tgt = {}; - - if ((len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { - // special handling of pair objects which does not included into objects map - tgt.$pair = value.$pair; - tgt.first = copy_value(value.first); - tgt.second = copy_value(value.second); - return tgt; - } - - map.push(value); - - for (let k = 0; k < len; ++k) { - const name = ks[k]; - if (name && (name[0] !== '$')) - tgt[name] = copy_value(value[name]); - } - - return tgt; - }, - tgt = copy_value(obj); - - return JSON.stringify(tgt, null, spacing); -} - -/** @summary decodes URL options after '?' mark - * @desc Following options supported ?opt1&opt2=3 - * @param {string} [url] URL string with options, document.URL will be used when not specified - * @return {object} with ```.has(opt)``` and ```.get(opt,dflt)``` methods - * @example - * import { decodeUrl } from 'https://root.cern/js/latest/modules/core.mjs'; - * let d = decodeUrl('any?opt1&op2=3'); - * console.log(`Has opt1 ${d.has('opt1')}`); // true - * console.log(`Get opt1 ${d.get('opt1')}`); // '' - * console.log(`Get opt2 ${d.get('opt2')}`); // '3' - * console.log(`Get opt3 ${d.get('opt3','-')}`); // '-' */ -function decodeUrl(url) { - const res = { - opts: {}, - has(opt) { return this.opts[opt] !== undefined; }, - get(opt, dflt) { return this.opts[opt] ?? dflt; } - }; - - if (!url || !isStr(url)) { - if (settings.IgnoreUrlOptions || (typeof document === 'undefined')) - return res; - url = document.URL; - } - res.url = url; - - const p1 = url.indexOf('?'); - if (p1 < 0) - return res; - url = decodeURI(url.slice(p1 + 1)); - - while (url) { - // try to correctly handle quotes in the URL - let pos = 0, nq = 0, eq = -1, firstq = -1; - while ((pos < url.length) && (nq || ((url[pos] !== '&') && (url[pos] !== '#')))) { - switch (url[pos]) { - case '\'': - if (nq >= 0) - nq = (nq + 1) % 2; - if (firstq < 0) - firstq = pos; - break; - case '"': - if (nq <= 0) - nq = (nq - 1) % 2; - if (firstq < 0) - firstq = pos; - break; - case '=': - if ((firstq < 0) && (eq < 0)) - eq = pos; - break; - } - pos++; - } - - if ((eq < 0) && (firstq < 0)) - res.opts[url.slice(0, pos)] = ''; - else if (eq > 0) { - let val = url.slice(eq + 1, pos); - if (((val[0] === '\'') || (val[0] === '"')) && (val.at(0) === val.at(-1))) - val = val.slice(1, val.length - 1); - res.opts[url.slice(0, eq)] = val; - } - - if ((pos >= url.length) || (url[pos] === '#')) - break; - - url = url.slice(pos + 1); - } - - return res; -} - -/** @summary Find function with given name - * @private */ -function findFunction(name) { - if (isFunc(name)) - return name; - if (!isStr(name)) - return null; - const names = name.split('.'); - let elem = globalThis; - - for (let n = 0; elem && (n < names.length); ++n) - elem = elem[names[n]]; - - return isFunc(elem) ? elem : null; -} - -/** @summary Method to create http request, without promise can be used only in browser environment - * @private */ -function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise, tmout) { - function handle_error(xhr, message, code, abort_reason) { - if (!xhr.did_abort) { - xhr.did_abort = abort_reason || true; - xhr.abort(); - } - if (!xhr.did_error || abort_reason) - console.warn(message); - if (!xhr.did_error) { - xhr.did_error = true; - xhr.error_callback(Error(message), code); - } - } - function configureXhr(xhr) { - xhr.http_callback = isFunc(user_accept_callback) ? user_accept_callback.bind(xhr) : () => {}; - xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function() { this.http_callback(null); }; - - if (!kind) - kind = 'buf'; - - let method = 'GET', is_async = true; - const p = kind.indexOf(';sync'); - if (p > 0) { - kind = kind.slice(0, p); - is_async = false; - } - switch (kind) { - case 'head': - method = 'HEAD'; - break; - case 'posttext': - method = 'POST'; - kind = 'text'; - break; - case 'postbuf': - method = 'POST'; - kind = 'buf'; - break; - case 'post': - case 'multi': - method = 'POST'; - break; - } - - xhr.kind = kind; - - if (settings.WithCredentials) - xhr.withCredentials = true; - - if (settings.HandleWrongHttpResponse && (method === 'GET') && isFunc(xhr.addEventListener)) { - xhr.addEventListener('progress', function(oEvent) { - if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size)) - handle_error(this, `Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`, 598); - }.bind(xhr)); - } - - xhr.onreadystatechange = function() { - if (this.did_abort) - return; - - if ((this.readyState === 2) && this.expected_size) { - const len = parseInt(this.getResponseHeader('Content-Length')); - if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse) - return handle_error(this, `Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`, 599, 'large'); - } - - if (this.readyState !== 4) - return; - - if ((this.status !== 200) && (this.status !== 206) && !browser.qt6 && - // in these special cases browsers not always set status - !((this.status === 0) && ((url.indexOf('file://') === 0) || (url.indexOf('blob:') === 0)))) - return handle_error(this, `Fail to load url ${url}`, this.status); - - if (this.nodejs_checkzip && (this.getResponseHeader('content-encoding') === 'gzip')) { - // special handling of gzip JSON objects in Node.js - return import('zlib').then(handle => { - const res = handle.unzipSync(Buffer.from(this.response)), - obj = JSON.parse(res); // zlib returns Buffer, use JSON to parse it - return this.http_callback(parse$1(obj)); - }); - } - - switch (this.kind) { - case 'xml': return this.http_callback(this.responseXML); - case 'text': return this.http_callback(this.responseText); - case 'object': return this.http_callback(parse$1(this.responseText)); - case 'multi': return this.http_callback(parseMulti(this.responseText)); - case 'head': return this.http_callback(this); - } - - // if no response type is supported, return as text (most probably, will fail) - if (this.responseType === undefined) - return this.http_callback(this.responseText); - - if ((this.kind === 'bin') && ('byteLength' in this.response)) { - // if string representation in requested - provide it - const u8Arr = new Uint8Array(this.response); - let filecontent = ''; - for (let i = 0; i < u8Arr.length; ++i) - filecontent += String.fromCharCode(u8Arr[i]); - return this.http_callback(filecontent); - } - - this.http_callback(this.response); - }; - - xhr.open(method, url, is_async); - - if ((kind === 'bin') || (kind === 'buf')) - xhr.responseType = 'arraybuffer'; - - if (nodejs && (method === 'GET') && (kind === 'object') && (url.indexOf('.json.gz') > 0)) { - xhr.nodejs_checkzip = true; - xhr.responseType = 'arraybuffer'; - } - - if (tmout && Number.isFinite(tmout)) { - xhr.timeout = tmout; - xhr.ontimeout = function() { handle_error(this, `Request ${url} timeout set ${tmout} ms`, 600, 'timeout'); }; - } - - return xhr; - } - - if (isNodeJs()) { - if (!use_promise) - throw Error('Not allowed to create http requests in node.js without promise'); - return import('xhr2').then(h => configureXhr(new h.default())); - } - - const xhr = configureXhr(new XMLHttpRequest()); - return use_promise ? Promise.resolve(xhr) : xhr; -} - -/** @summary Submit asynchronous http request - * @desc Following requests kind can be specified: - * - 'bin' - abstract binary data, result as string - * - 'buf' - abstract binary data, result as ArrayBuffer (default) - * - 'text' - returns req.responseText - * - 'object' - returns parse(req.responseText) - * - 'multi' - returns correctly parsed multi.json request - * - 'xml' - returns req.responseXML - * - 'head' - returns request itself, uses 'HEAD' request method - * - 'post' - creates post request, submits req.send(post_data) - * - 'postbuf' - creates post request, expects binary data as response - * @param {string} url - URL for the request - * @param {string} kind - kind of requested data - * @param {string} [post_data] - data submitted with post kind of request - * @return {Promise} Promise for requested data, result type depends from the kind - * @example - * import { httpRequest } from 'https://root.cern/js/latest/modules/core.mjs'; - * httpRequest('https://root.cern/js/files/thstack.json.gz', 'object') - * .then(obj => console.log(`Get object of type ${obj._typename}`)) - * .catch(err => console.error(err.message)); */ -async function httpRequest(url, kind, post_data) { - return new Promise((resolve, reject) => { - createHttpRequest(url, kind, resolve, reject, true).then(xhr => xhr.send(post_data || null)); - }); -} - -/** @summary Inject javascript code - * @desc Replacement for eval - * @return {Promise} when code is injected - * @private */ -async function injectCode(code) { - if (nodejs) { - let name, fs; - return import('tmp').then(tmp => { - name = tmp.tmpNameSync() + '.js'; - return import('fs'); - }).then(_fs => { - fs = _fs; - fs.writeFileSync(name, code); - return import(/* webpackIgnore: true */ /* @vite-ignore */ 'file://' + name); - }).finally(() => fs.unlinkSync(name)); - } - - if (typeof document !== 'undefined') { - // check if code already loaded - to avoid duplication - const scripts = document.getElementsByTagName('script'); - for (let n = 0; n < scripts.length; ++n) { - if (scripts[n].innerText === code) - return true; - } - - // try to detect if code includes import and must be treated as module - const is_v6 = code.indexOf('JSROOT.require') >= 0, - is_mjs = !is_v6 && (code.indexOf('import {') > 0) && (code.indexOf('} from \'') > 0), - is_batch = !is_v6 && !is_mjs && (code.indexOf('JSROOT.ObjectPainter') >= 0), - promise = (is_v6 ? exports._ensureJSROOT() : Promise.resolve(true)); - - if (is_batch && !globalThis.JSROOT) - globalThis.JSROOT = internals.jsroot; - - return promise.then(() => { - const element = document.createElement('script'); - element.setAttribute('type', is_mjs ? 'module' : 'text/javascript'); - element.innerText = code; - document.head.appendChild(element); - // while onload event not fired, just postpone resolve - return isBatchMode() ? true : postponePromise(true, 10); - }); - } - - return false; -} - -/** @summary Load ES6 modules - * @param {string} arg - single URL or array of URLs - * @return {Promise} */ -async function loadModules(arg) { - if (isStr(arg)) - arg = arg.split(';'); - if (!arg.length) - return true; - return import(/* webpackIgnore: true */ /* @vite-ignore */ arg.shift()).then(() => loadModules(arg)); -} - -/** @summary Load script or CSS file into the browser - * @param {string} url - script or css file URL (or array, in this case they all loaded sequentially) - * @return {Promise} */ -async function loadScript(url) { - if (!url) - return true; - - if (isStr(url) && (url.indexOf(';') >= 0)) - url = url.split(';'); - - if (!isStr(url)) { - const scripts = url, loadNext = () => { - return !scripts.length ? true : loadScript(scripts.shift()).then(loadNext, loadNext); - }; - return loadNext(); - } - - if (url.indexOf('$$$') === 0) { - url = url.slice(3); - if ((url.indexOf('style/') === 0) && (url.indexOf('.css') < 0)) - url += '.css'; - url = exports.source_dir + url; - } - - const isstyle = url.indexOf('.css') > 0; - - if (nodejs) { - if (isstyle) - return null; - if ((url.indexOf('http:') === 0) || (url.indexOf('https:') === 0)) - return httpRequest(url, 'text').then(code => injectCode(code)); - - // local files, read and use it - if (url.indexOf('./') === 0) - return import('fs').then(fs => injectCode(fs.readFileSync(url))); - - return import(/* webpackIgnore: true */ /* @vite-ignore */ url); - } - - const match_url = src => { - if (src === url) - return true; - const indx = src.indexOf(url); - return (indx > 0) && (indx + url.length === src.length) && (src[indx - 1] === '/'); - }; - - if (isstyle) { - const styles = document.getElementsByTagName('link'); - for (let n = 0; n < styles.length; ++n) { - if (!styles[n].href || (styles[n].type !== 'text/css') || (styles[n].rel !== 'stylesheet')) - continue; - if (match_url(styles[n].href)) - return true; - } - } else { - const scripts = document.getElementsByTagName('script'); - for (let n = 0; n < scripts.length; ++n) { - if (match_url(scripts[n].src)) - return true; - } - } - - let element; - if (isstyle) { - element = document.createElement('link'); - element.setAttribute('rel', 'stylesheet'); - element.setAttribute('type', 'text/css'); - element.setAttribute('href', url); - } else { - element = document.createElement('script'); - element.setAttribute('type', 'text/javascript'); - element.setAttribute('src', url); - } - - return new Promise((resolveFunc, rejectFunc) => { - element.onload = () => resolveFunc(true); - element.onerror = () => { - element.remove(); - rejectFunc(Error(`Fail to load ${url}`)); - }; - document.head.appendChild(element); - }); -} - -exports._ensureJSROOT = async function() { - const pr = globalThis.JSROOT ? Promise.resolve(true) : loadScript(exports.source_dir + 'scripts/JSRoot.core.js'); - - return pr.then(() => { - if (globalThis.JSROOT?._complete_loading) - return globalThis.JSROOT._complete_loading(); - }).then(() => globalThis.JSROOT); -}; - - -const prROOT = 'ROOT.', clTObject = 'TObject', clTNamed = 'TNamed', clTString = 'TString', clTObjString = 'TObjString', - clTKey = 'TKey', clTFile = 'TFile', clTTree = 'TTree', - clTList = 'TList', clTHashList = 'THashList', clTMap = 'TMap', clTObjArray = 'TObjArray', clTClonesArray = 'TClonesArray', - clTAttLine = 'TAttLine', clTAttFill = 'TAttFill', clTAttMarker = 'TAttMarker', clTAttText = 'TAttText', - clTHStack = 'THStack', clTGraph = 'TGraph', clTMultiGraph = 'TMultiGraph', clTCutG = 'TCutG', - clTGraph2DErrors = 'TGraph2DErrors', clTGraph2DAsymmErrors = 'TGraph2DAsymmErrors', - clTGraphPolar = 'TGraphPolar', clTGraphPolargram = 'TGraphPolargram', clTGraphTime = 'TGraphTime', - clTPave = 'TPave', clTPaveText = 'TPaveText', clTPaveStats = 'TPaveStats', clTPavesText = 'TPavesText', - clTPaveLabel = 'TPaveLabel', clTPaveClass = 'TPaveClass', clTDiamond = 'TDiamond', - clTLegend = 'TLegend', clTLegendEntry = 'TLegendEntry', - clTPaletteAxis = 'TPaletteAxis', clTImagePalette = 'TImagePalette', - clTText = 'TText', clTLink = 'TLink', clTLatex = 'TLatex', clTMathText = 'TMathText', clTAnnotation = 'TAnnotation', - clTColor = 'TColor', clTLine = 'TLine', clTMarker = 'TMarker', clTBox = 'TBox', clTPolyLine = 'TPolyLine', - clTPolyLine3D = 'TPolyLine3D', clTPolyMarker3D = 'TPolyMarker3D', - clTAttPad = 'TAttPad', clTPad = 'TPad', clTCanvas = 'TCanvas', clTFrame = 'TFrame', clTAttCanvas = 'TAttCanvas', - clTGaxis = 'TGaxis', clTAttAxis = 'TAttAxis', clTAxis = 'TAxis', clTStyle = 'TStyle', - clTH1 = 'TH1', clTH1I = 'TH1I', clTH1F = 'TH1F', clTH1D = 'TH1D', - clTH2 = 'TH2', clTH2I = 'TH2I', clTH2F = 'TH2F', clTH2D = 'TH2D', - clTH3 = 'TH3', clTH3I = 'TH3I', clTH3F = 'TH3F', clTH3D = 'TH3D', - clTProfile = 'TProfile', clTProfile2D = 'TProfile2D', clTProfile3D = 'TProfile3D', - clTF1 = 'TF1', clTF12 = 'TF12', clTF2 = 'TF2', clTF3 = 'TF3', - clTGeoVolume = 'TGeoVolume', clTGeoNode = 'TGeoNode', clTGeoNodeMatrix = 'TGeoNodeMatrix', - nsROOT = 'ROOT::', nsREX = nsROOT + 'Experimental::', nsSVG = 'http://www.w3.org/2000/svg', - kNoZoom = -1111, kNoStats = BIT(9), kInspect = 'inspect', kTitle = 'title', - urlClassPrefix = 'https://root.cern/doc/master/class'; - - -/** @summary Create some ROOT classes - * @desc Supported classes: `TObject`, `TNamed`, `TList`, `TAxis`, `TLine`, `TText`, `TLatex`, `TPad`, `TCanvas` - * @param {string} typename - ROOT class name - * @example - * import { create } from 'https://root.cern/js/latest/modules/core.mjs'; - * let obj = create('TNamed'); - * obj.fName = 'name'; - * obj.fTitle = 'title'; */ -function create$1(typename, target) { - const obj = target || {}; - - switch (typename) { - case clTObject: - extend$1(obj, { fUniqueID: 0, fBits: 0 }); - break; - case clTNamed: - extend$1(obj, { fUniqueID: 0, fBits: 0, fName: '', fTitle: '' }); - break; - case clTList: - case clTHashList: - extend$1(obj, { name: typename, arr: [], opt: [] }); - break; - case clTObjArray: - extend$1(obj, { name: typename, arr: [] }); - break; - case clTAttAxis: - extend$1(obj, { fNdivisions: 510, fAxisColor: 1, - fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, fTickLength: 0.03, - fTitleOffset: 1, fTitleSize: 0.035, fTitleColor: 1, fTitleFont: 42 }); - break; - case clTAxis: - create$1(clTNamed, obj); - create$1(clTAttAxis, obj); - extend$1(obj, { fNbins: 1, fXmin: 0, fXmax: 1, fXbins: [], fFirst: 0, fLast: 0, - fBits2: 0, fTimeDisplay: false, fTimeFormat: '', fLabels: null, fModLabs: null }); - break; - case clTAttLine: - extend$1(obj, { fLineColor: 1, fLineStyle: 1, fLineWidth: 1 }); - break; - case clTAttFill: - extend$1(obj, { fFillColor: 0, fFillStyle: 0 }); - break; - case clTAttMarker: - extend$1(obj, { fMarkerColor: 1, fMarkerStyle: 1, fMarkerSize: 1 }); - break; - case clTLine: - create$1(clTObject, obj); - create$1(clTAttLine, obj); - extend$1(obj, { fX1: 0, fX2: 1, fY1: 0, fY2: 1 }); - break; - case clTMarker: - create$1(clTObject, obj); - create$1(clTAttMarker, obj); - extend$1(obj, { fX: 0, fY: 0 }); - break; - case clTBox: - create$1(clTObject, obj); - create$1(clTAttLine, obj); - create$1(clTAttFill, obj); - extend$1(obj, { fX1: 0, fX2: 1, fY1: 0, fY2: 1 }); - break; - case clTPave: - create$1(clTBox, obj); - extend$1(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 0, fY2NDC: 0, - fBorderSize: 0, fInit: 1, fShadowColor: 1, - fCornerRadius: 0, fOption: 'brNDC', fName: '' }); - break; - case clTAttText: - extend$1(obj, { fTextAngle: 0, fTextSize: 0, fTextAlign: 22, fTextColor: 1, fTextFont: 42 }); - break; - case clTPaveText: - create$1(clTPave, obj); - create$1(clTAttText, obj); - extend$1(obj, { fLabel: '', fLongest: 27, fMargin: 0.05, fLines: create$1(clTList) }); - break; - case clTPaveStats: - create$1(clTPaveText, obj); - extend$1(obj, { fFillColor: gStyle.fStatColor, fFillStyle: gStyle.fStatStyle, - fTextFont: gStyle.fStatFont, fTextSize: gStyle.fStatFontSize, fTextColor: gStyle.fStatTextColor, - fBorderSize: gStyle.fStatBorderSize, - fOptFit: gStyle.fOptFit, fOptStat: gStyle.fOptStat, fFitFormat: gStyle.fFitFormat, fStatFormat: gStyle.fStatFormat, fParent: null }); - break; - case clTLegend: - create$1(clTPave, obj); - create$1(clTAttText, obj); - extend$1(obj, { fColumnSeparation: 0, fEntrySeparation: 0.1, fMargin: 0.25, fNColumns: 1, fPrimitives: create$1(clTList), fName: clTPave, - fBorderSize: gStyle.fLegendBorderSize, fTextFont: gStyle.fLegendFont, fTextSize: gStyle.fLegendTextSize, - fFillColor: gStyle.fLegendFillColor, fFillStyle: gStyle.fLegendFillStyle }); - break; - case clTPaletteAxis: - create$1(clTPave, obj); - extend$1(obj, { fAxis: create$1(clTGaxis), fH: null, fName: clTPave }); - break; - case clTLegendEntry: - create$1(clTObject, obj); - create$1(clTAttText, obj); - create$1(clTAttLine, obj); - create$1(clTAttFill, obj); - create$1(clTAttMarker, obj); - extend$1(obj, { fLabel: '', fObject: null, fOption: '', fTextAlign: 0, fTextColor: 0, fTextFont: 0 }); - break; - case clTText: - create$1(clTNamed, obj); - create$1(clTAttText, obj); - extend$1(obj, { fX: 0, fY: 0 }); - break; - case clTLatex: - create$1(clTText, obj); - create$1(clTAttLine, obj); - extend$1(obj, { fLimitFactorSize: 3, fOriginSize: 0.04 }); - break; - case clTObjString: - create$1(clTObject, obj); - extend$1(obj, { fString: '' }); - break; - case clTH1: - create$1(clTNamed, obj); - create$1(clTAttLine, obj); - create$1(clTAttFill, obj); - create$1(clTAttMarker, obj); - extend$1(obj, { fBits: 8, fNcells: 0, - fXaxis: create$1(clTAxis), fYaxis: create$1(clTAxis), fZaxis: create$1(clTAxis), - fFillColor: gStyle.fHistFillColor, fFillStyle: gStyle.fHistFillStyle, - fLineColor: gStyle.fHistLineColor, fLineStyle: gStyle.fHistLineStyle, fLineWidth: gStyle.fHistLineWidth, - fBarOffset: 0, fBarWidth: 1000, fEntries: 0, - fTsumw: 0, fTsumw2: 0, fTsumwx: 0, fTsumwx2: 0, - fMaximum: kNoZoom, fMinimum: kNoZoom, fNormFactor: 0, fContour: [], - fSumw2: [], fOption: '', fFunctions: create$1(clTList), - fBufferSize: 0, fBuffer: [], fBinStatErrOpt: 0, fStatOverflows: 2 }); - break; - case clTH1I: - case clTH1D: - case 'TH1L64': - case clTH1F: - case 'TH1S': - case 'TH1C': - create$1(clTH1, obj); - obj.fArray = []; - break; - case clTH2: - create$1(clTH1, obj); - extend$1(obj, { fScalefactor: 1, fTsumwy: 0, fTsumwy2: 0, fTsumwxy: 0 }); - break; - case clTH2I: - case 'TH2L64': - case clTH2F: - case clTH2D: - case 'TH2S': - case 'TH2C': - create$1(clTH2, obj); - obj.fArray = []; - break; - case clTH3: - create$1(clTH1, obj); - extend$1(obj, { fTsumwy: 0, fTsumwy2: 0, fTsumwz: 0, fTsumwz2: 0, fTsumwxy: 0, fTsumwxz: 0, fTsumwyz: 0 }); - break; - case clTH3I: - case 'TH3L64': - case clTH3F: - case clTH3D: - case 'TH3S': - case 'TH3C': - create$1(clTH3, obj); - obj.fArray = []; - break; - case clTHStack: - create$1(clTNamed, obj); - extend$1(obj, { fHists: create$1(clTList), fHistogram: null, fMaximum: kNoZoom, fMinimum: kNoZoom }); - break; - case clTGraph: - create$1(clTNamed, obj); - create$1(clTAttLine, obj); - create$1(clTAttFill, obj); - create$1(clTAttMarker, obj); - extend$1(obj, { fFunctions: create$1(clTList), fHistogram: null, - fMaxSize: 0, fMaximum: kNoZoom, fMinimum: kNoZoom, fNpoints: 0, fX: [], fY: [] }); - break; - case 'TGraphAsymmErrors': - create$1(clTGraph, obj); - extend$1(obj, { fEXlow: [], fEXhigh: [], fEYlow: [], fEYhigh: [] }); - break; - case clTMultiGraph: - create$1(clTNamed, obj); - extend$1(obj, { fFunctions: create$1(clTList), fGraphs: create$1(clTList), - fHistogram: null, fMaximum: kNoZoom, fMinimum: kNoZoom }); - break; - case clTGraphPolargram: - create$1(clTNamed, obj); - create$1(clTAttText, obj); - create$1(clTAttLine, obj); - extend$1(obj, { fRadian: false, fDegree: false, fGrad: false, fPolarLabelColor: 1, fRadialLabelColor: 1, - fAxisAngle: 0, fPolarOffset: 0.04, fPolarTextSize: 0.04, fRadialOffset: 0.025, fRadialTextSize: 0.035, - fRwrmin: 0, fRwrmax: 1, fRwtmin: 0, fRwtmax: 2 * Math.PI, fTickpolarSize: 0.02, - fPolarLabelFont: 62, fRadialLabelFont: 62, fCutRadial: 0, fNdivRad: 508, fNdivPol: 508 }); - break; - case clTPolyLine: - create$1(clTObject, obj); - create$1(clTAttLine, obj); - create$1(clTAttFill, obj); - extend$1(obj, { fLastPoint: -1, fN: 0, fOption: '', fX: null, fY: null }); - break; - case clTGaxis: - create$1(clTLine, obj); - create$1(clTAttText, obj); - extend$1(obj, { fChopt: '', fFunctionName: '', fGridLength: 0, - fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, - fName: '', fNdiv: 12, fTickSize: 0.02, fTimeFormat: '', - fTitle: '', fTitleOffset: 1, fTitleSize: 0.035, - fWmax: 100, fWmin: 0 }); - break; - case clTAttPad: - extend$1(obj, { fLeftMargin: gStyle.fPadLeftMargin, - fRightMargin: gStyle.fPadRightMargin, - fBottomMargin: gStyle.fPadBottomMargin, - fTopMargin: gStyle.fPadTopMargin, - fXfile: 2, fYfile: 2, fAfile: 1, fXstat: 0.99, fYstat: 0.99, fAstat: 2, - fFrameFillColor: gStyle.fFrameFillColor, - fFrameFillStyle: gStyle.fFrameFillStyle, - fFrameLineColor: gStyle.fFrameLineColor, - fFrameLineWidth: gStyle.fFrameLineWidth, - fFrameLineStyle: gStyle.fFrameLineStyle, - fFrameBorderSize: gStyle.fFrameBorderSize, - fFrameBorderMode: gStyle.fFrameBorderMode }); - break; - case clTPad: - create$1(clTObject, obj); - create$1(clTAttLine, obj); - create$1(clTAttFill, obj); - create$1(clTAttPad, obj); - extend$1(obj, { fFillColor: gStyle.fPadColor, fFillStyle: 1001, - fX1: 0, fY1: 0, fX2: 1, fY2: 1, fXtoAbsPixelk: 1, fXtoPixelk: 1, - fXtoPixel: 1, fYtoAbsPixelk: 1, fYtoPixelk: 1, fYtoPixel: 1, - fUtoAbsPixelk: 1, fUtoPixelk: 1, fUtoPixel: 1, fVtoAbsPixelk: 1, - fVtoPixelk: 1, fVtoPixel: 1, fAbsPixeltoXk: 1, fPixeltoXk: 1, - fPixeltoX: 1, fAbsPixeltoYk: 1, fPixeltoYk: 1, fPixeltoY: 1, - fXlowNDC: 0, fYlowNDC: 0, fXUpNDC: 0, fYUpNDC: 0, fWNDC: 1, fHNDC: 1, - fAbsXlowNDC: 0, fAbsYlowNDC: 0, fAbsWNDC: 1, fAbsHNDC: 1, - fUxmin: 0, fUymin: 0, fUxmax: 0, fUymax: 0, fTheta: 30, fPhi: 30, fAspectRatio: 0, - fNumber: 0, fLogx: gStyle.fOptLogx, fLogy: gStyle.fOptLogy, fLogz: gStyle.fOptLogz, - fTickx: gStyle.fPadTickX, fTicky: gStyle.fPadTickY, - fPadPaint: 0, fCrosshair: 0, fCrosshairPos: 0, fBorderSize: gStyle.fPadBorderSize, - fBorderMode: gStyle.fPadBorderMode, fModified: false, - fGridx: gStyle.fPadGridX, fGridy: gStyle.fPadGridY, - fAbsCoord: false, fEditable: true, fFixedAspectRatio: false, - fPrimitives: create$1(clTList), fExecs: null, - fName: 'pad', fTitle: 'canvas' }); - break; - case clTAttCanvas: - extend$1(obj, { fXBetween: 2, fYBetween: 2, fTitleFromTop: 1.2, - fXdate: 0.2, fYdate: 0.3, fAdate: 1 }); - break; - case clTCanvas: - create$1(clTPad, obj); - extend$1(obj, { fFillColor: gStyle.fCanvasColor, fFillStyle: 1001, - fNumPaletteColor: 0, fNextPaletteColor: 0, fDISPLAY: '$DISPLAY', - fDoubleBuffer: 0, fRetained: true, fXsizeUser: 0, - fYsizeUser: 0, fXsizeReal: 20, fYsizeReal: 10, - fWindowTopX: 0, fWindowTopY: 0, fWindowWidth: 0, fWindowHeight: 0, - fBorderSize: gStyle.fCanvasBorderSize, fBorderMode: gStyle.fCanvasBorderMode, - fCw: settings.CanvasWidth, fCh: settings.CanvasHeight, fCatt: create$1(clTAttCanvas), - kMoveOpaque: true, kResizeOpaque: true, fHighLightColor: 5, - fBatch: true, kShowEventStatus: false, kAutoExec: true, kMenuBar: true }); - break; - case clTGeoVolume: - create$1(clTNamed, obj); - create$1(clTAttLine, obj); - create$1(clTAttFill, obj); - extend$1(obj, { fGeoAtt: 0, fFinder: null, fMedium: null, fNodes: null, fNtotal: 0, fNumber: 0, fRefCount: 0, fShape: null, fVoxels: null }); - break; - case clTGeoNode: - create$1(clTNamed, obj); - extend$1(obj, { fGeoAtt: 0, fMother: null, fNovlp: 0, fNumber: 0, fOverlaps: null, fVolume: null }); - break; - case clTGeoNodeMatrix: - create$1(clTGeoNode, obj); - extend$1(obj, { fMatrix: null }); - break; - case 'TGeoTrack': - create$1(clTObject, obj); - create$1(clTAttLine, obj); - create$1(clTAttMarker, obj); - extend$1(obj, { fGeoAtt: 0, fNpoints: 0, fPoints: [] }); - break; - case clTPolyLine3D: - create$1(clTObject, obj); - create$1(clTAttLine, obj); - extend$1(obj, { fLastPoint: -1, fN: 0, fOption: '', fP: [] }); - break; - case clTPolyMarker3D: - create$1(clTObject, obj); - create$1(clTAttMarker, obj); - extend$1(obj, { fLastPoint: -1, fN: 0, fName: '', fOption: '', fP: [] }); - break; - } - - obj._typename = typename; - exports.addMethods(obj, typename); - return obj; -} - -/** @summary Create histogram object of specified type - * @param {string} typename - histogram typename like 'TH1I' or 'TH2F' - * @param {number} nbinsx - number of bins on X-axis - * @param {number} [nbinsy] - number of bins on Y-axis (for 2D/3D histograms) - * @param {number} [nbinsz] - number of bins on Z-axis (for 3D histograms) - * @return {object} created histogram object - * @example - * import { createHistogram } from 'https://root.cern/js/latest/modules/core.mjs'; - * let h1 = createHistogram('TH1I', 20); - * h1.fName = 'Hist1'; - * h1.fTitle = 'Histogram title'; - * h1.fXaxis.fTitle = 'xaxis'; - * h1.fYaxis.fTitle = 'yaxis'; - * h1.fXaxis.fLabelSize = 0.02; */ -function createHistogram(typename, nbinsx, nbinsy, nbinsz) { - const histo = create$1(typename); - if (!histo.fXaxis || !histo.fYaxis || !histo.fZaxis) - return null; - histo.fName = 'hist'; - histo.fTitle = 'title'; - if (nbinsx) - extend$1(histo.fXaxis, { fNbins: nbinsx, fXmin: 0, fXmax: nbinsx }); - if (nbinsy) - extend$1(histo.fYaxis, { fNbins: nbinsy, fXmin: 0, fXmax: nbinsy }); - if (nbinsz) - extend$1(histo.fZaxis, { fNbins: nbinsz, fXmin: 0, fXmax: nbinsz }); - switch (parseInt(typename[2])) { - case 1: - if (nbinsx) - histo.fNcells = nbinsx + 2; - break; - case 2: - if (nbinsx && nbinsy) - histo.fNcells = (nbinsx + 2) * (nbinsy + 2); - break; - case 3: - if (nbinsx && nbinsy && nbinsz) - histo.fNcells = (nbinsx + 2) * (nbinsy + 2) * (nbinsz + 2); - break; - } - if (histo.fNcells > 0) { - switch (typename[3]) { - case 'C': - histo.fArray = new Int8Array(histo.fNcells); - break; - case 'S': - histo.fArray = new Int16Array(histo.fNcells); - break; - case 'I': - histo.fArray = new Int32Array(histo.fNcells); - break; - case 'F': - histo.fArray = new Float32Array(histo.fNcells); - break; - case 'L': - case 'D': - histo.fArray = new Float64Array(histo.fNcells); - break; - default: - histo.fArray = new Array(histo.fNcells); - } - histo.fArray.fill(0); - } - return histo; -} - -/** @summary Set histogram title - * @desc Title may include axes titles, provided with ';' symbol like "Title;x;y;z" */ - -function setHistogramTitle(histo, title) { - if (!histo || !isStr(title)) - return; - if (title.indexOf(';') < 0) - histo.fTitle = title; - else { - const arr = title.split(';'); - histo.fTitle = arr[0]; - if (arr.length > 1) - histo.fXaxis.fTitle = arr[1]; - if (arr.length > 2) - histo.fYaxis.fTitle = arr[2]; - if (arr.length > 3) - histo.fZaxis.fTitle = arr[3]; - } -} - - -/** @summary Creates TPolyLine object - * @param {number} npoints - number of points - * @param {boolean} [use_int32] - use Int32Array type for points, default is Float32Array */ -function createTPolyLine(npoints, use_int32) { - const poly = create$1(clTPolyLine); - if (npoints) { - poly.fN = npoints; - if (use_int32) { - poly.fX = new Int32Array(npoints); - poly.fY = new Int32Array(npoints); - } else { - poly.fX = new Float32Array(npoints); - poly.fY = new Float32Array(npoints); - } - } - return poly; -} - -/** @summary Creates TGraph object - * @param {number} npoints - number of points in TGraph - * @param {Array} [xpts] - array with X coordinates - * @param {Array} [ypts] - array with Y coordinates */ -function createTGraph(npoints, xpts, ypts) { - const graph = extend$1(create$1(clTGraph), { fBits: 0x408, fName: 'graph', fTitle: 'title' }); - - if (npoints > 0) { - graph.fMaxSize = graph.fNpoints = npoints; - - const usex = isObject(xpts) && (xpts.length === npoints), - usey = isObject(ypts) && (ypts.length === npoints); - - for (let i = 0; i < npoints; ++i) { - graph.fX.push(usex ? xpts[i] : i / npoints); - graph.fY.push(usey ? ypts[i] : i / npoints); - } - } - - return graph; -} - -/** @summary Creates THStack object - * @desc As arguments one could specify any number of histograms objects - * @example - * import { createHistogram, createTHStack } from 'https://root.cern/js/latest/modules/core.mjs'; - * let nbinsx = 20; - * let h1 = createHistogram('TH1F', nbinsx); - * let h2 = createHistogram('TH1F', nbinsx); - * let h3 = createHistogram('TH1F', nbinsx); - * let stack = createTHStack(h1, h2, h3); */ -function createTHStack(...args) { - const stack = create$1(clTHStack); - for (let i = 0; i < args.length; ++i) - stack.fHists.Add(args[i], ''); - return stack; -} - -/** @summary Creates TMultiGraph object - * @desc As arguments one could specify any number of TGraph objects - * @example - * import { createTGraph, createTMultiGraph } from 'https://root.cern/js/latest/modules/core.mjs'; - * let gr1 = createTGraph(100); - * let gr2 = createTGraph(100); - * let gr3 = createTGraph(100); - * let mgr = createTMultiGraph(gr1, gr2, gr3); */ -function createTMultiGraph(...args) { - const mgraph = create$1(clTMultiGraph); - for (let i = 0; i < args.length; ++i) - mgraph.fGraphs.Add(args[i], ''); - return mgraph; -} - -/** @summary variable used to keep methods for known classes - * @private */ -const methodsCache = {}; - -/** @summary Returns methods for given typename - * @private */ -function getMethods(typename, obj) { - let m = methodsCache[typename]; - const has_methods = (m !== undefined); - if (!has_methods) - m = {}; - - // Due to binary I/O such TObject methods may not be set for derived classes - // Therefore when methods requested for given object, check also that basic methods are there - if ((typename === clTObject) || (typename === clTNamed) || (obj?.fBits !== undefined)) { - if (typeof m.TestBit === 'undefined') { - m.TestBit = function(f) { return Boolean(this.fBits & f); }; - m.InvertBit = function(f) { this.fBits ^= f & 0xffffff; }; - m.SetBit = function(f, on = true) { this.fBits = on ? this.fBits | f : this.fBits & ~f; }; - } - } - - if (has_methods) - return m; - - if ((typename === clTList) || (typename === clTHashList)) { - m.Clear = function() { - this.arr = []; - this.opt = []; - }; - m.Add = function(elem, opt) { - this.arr.push(elem); - this.opt.push(isStr(opt) ? opt : ''); - }; - m.AddFirst = function(elem, opt) { - this.arr.unshift(elem); - this.opt.unshift(isStr(opt) ? opt : ''); - }; - m.RemoveAt = function(indx) { - this.arr.splice(indx, 1); - this.opt.splice(indx, 1); - }; - } - - if ((typename === clTPaveText) || (typename === clTPaveStats)) { - m.AddText = function(txt) { - const line = create$1(clTLatex); - line.fTitle = txt; - line.fTextAlign = 0; - line.fTextColor = 0; - line.fTextFont = 0; - line.fTextSize = 0; - this.fLines.Add(line); - }; - m.Clear = function() { - this.fLines.Clear(); - }; - } - - if ((typename.indexOf(clTF1) === 0) || (typename === clTF12) || (typename === clTF2) || (typename === clTF3)) { - m.addFormula = function(formula) { - if (formula) { - if (this.formulas === undefined) - this.formulas = []; - this.formulas.push(formula); - } - }; - m.GetParName = function(n) { - if (this.fParams?.fParNames) - return this.fParams.fParNames[n]; - if (this.fFormula?.fParams) { - for (let k = 0, arr = this.fFormula.fParams; k < arr.length; ++k) { - if (arr[k].second === n) - return arr[k].first; - } - } - return (this.fNames && this.fNames[n]) ? this.fNames[n] : `p${n}`; - }; - m.GetParValue = function(n) { - if (this.fParams?.fParameters) - return this.fParams.fParameters[n]; - if (this.fFormula?.fClingParameters) - return this.fFormula.fClingParameters[n]; - return this.fParams ? this.fParams[n] : undefined; - }; - m.GetParError = function(n) { - return this.fParErrors ? this.fParErrors[n] : undefined; - }; - m.GetNumPars = function() { - return this.fNpar; - }; - } - - if (((typename.indexOf(clTGraph) === 0) || (typename === clTCutG)) && (typename !== clTGraphPolargram) && (typename !== clTGraphTime)) { - // check if point inside figure specified by the TGraph - m.IsInside = function(xp, yp) { - const x = this.fX, y = this.fY; - let i = 0, j = this.fNpoints - 1, oddNodes = false; - - for (; i < this.fNpoints; ++i) { - if ((y[i] < yp && y[j] >= yp) || (y[j] < yp && y[i] >= yp)) { - if (x[i] + (yp - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < xp) - oddNodes = !oddNodes; - } - j = i; - } - - return oddNodes; - }; - } - - if (typename.indexOf(clTH1) === 0 || typename.indexOf(clTH2) === 0 || typename.indexOf(clTH3) === 0) { - m.getBinError = function(bin) { - // -*-*-*-*-*Return value of error associated to bin number bin*-*-*-*-* - // if the sum of squares of weights has been defined (via Sumw2), - // this function returns the sqrt(sum of w2). - // otherwise it returns the sqrt(contents) for this bin. - if (bin >= this.fNcells) - bin = this.fNcells - 1; - if (bin < 0) - bin = 0; - if (bin < this.fSumw2.length) - return Math.sqrt(this.fSumw2[bin]); - return Math.sqrt(Math.abs(this.fArray[bin])); - }; - m.setBinContent = function(bin, content) { - // Set bin content - only trivial case, without expansion - this.fEntries++; - this.fTsumw = 0; - if ((bin >= 0) && (bin < this.fArray.length)) - this.fArray[bin] = content; - }; - } - - if (typename.indexOf(clTH1) === 0) { - m.getBin = function(x) { return x; }; - m.getBinContent = function(bin) { return this.fArray[bin]; }; - m.Fill = function(x, weight) { - const a = this.fXaxis, - bin = Math.max(0, 1 + Math.min(a.fNbins, Math.floor((x - a.fXmin) / (a.fXmax - a.fXmin) * a.fNbins))); - this.fArray[bin] += weight ?? 1; - this.fEntries++; - }; - } - - if (typename.indexOf(clTH2) === 0) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins + 2) * y); }; - m.getBinContent = function(x, y) { return this.fArray[this.getBin(x, y)]; }; - m.Fill = function(x, y, weight) { - const a1 = this.fXaxis, a2 = this.fYaxis, - bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), - bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2) * bin2] += weight ?? 1; - this.fEntries++; - }; - } - - if (typename.indexOf(clTH3) === 0) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); }; - m.getBinContent = function(x, y, z) { return this.fArray[this.getBin(x, y, z)]; }; - m.Fill = function(x, y, z, weight) { - const a1 = this.fXaxis, a2 = this.fYaxis, a3 = this.fZaxis, - bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), - bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))), - bin3 = Math.max(0, 1 + Math.min(a3.fNbins, Math.floor((z - a3.fXmin) / (a3.fXmax - a3.fXmin) * a3.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2) * (bin2 + (a2.fNbins + 2) * bin3)] += weight ?? 1; - this.fEntries++; - }; - } - - if (typename === clTPad || typename === clTCanvas) { - m.Divide = function(nx, ny, xmargin = 0.01, ymargin = 0.01, color = 0) { - if (!ny) { - const ndiv = nx; - if (ndiv < 2) - return this; - nx = ny = Math.round(Math.sqrt(ndiv)); - if (nx * ny < ndiv) - nx += 1; - } - if (nx * ny < 2) - return 0; - this.fPrimitives.Clear(); - const dy = 1 / ny, dx = 1 / nx; - let n = 0; - for (let iy = 0; iy < ny; iy++) { - const y2 = 1 - iy * dy - ymargin, - y1 = Math.max(0, y2 - dy + 2 * ymargin); - if (y1 > y2) - continue; - for (let ix = 0; ix < nx; ix++) { - const x1 = ix * dx + xmargin, - x2 = x1 + dx - 2 * xmargin; - if (x1 > x2) - continue; - n++; - const pad = create$1(clTPad); - pad.fName = pad.fTitle = `${this.fName}_${n}`; - pad.fNumber = n; - if (this._typename !== clTCanvas) { - pad.fAbsWNDC = (x2 - x1) * this.fAbsWNDC; - pad.fAbsHNDC = (y2 - y1) * this.fAbsHNDC; - pad.fAbsXlowNDC = this.fAbsXlowNDC + x1 * this.fAbsWNDC; - pad.fAbsYlowNDC = this.fAbsYlowNDC + y1 * this.fAbsHNDC; - } else { - pad.fAbsWNDC = x2 - x1; - pad.fAbsHNDC = y2 - y1; - pad.fAbsXlowNDC = x1; - pad.fAbsYlowNDC = y1; - } - - pad.fFillColor = color || this.fFillColor; - - this.fPrimitives.Add(pad); - } - } - return nx * ny; - }; - m.GetPad = function(number) { - return this.fPrimitives.arr.find(elem => { return elem._typename === clTPad && elem.fNumber === number; }); - }; - } - - if (typename.indexOf(clTProfile) === 0) { - if (typename === clTProfile3D) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); }; - m.getBinContent = function(x, y, z) { - const bin = this.getBin(x, y, z); - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) - return 0; - return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; - }; - m.getBinEntries = function(x, y, z) { - const bin = this.getBin(x, y, z); - return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; - }; - } else if (typename === clTProfile2D) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins + 2) * y); }; - m.getBinContent = function(x, y) { - const bin = this.getBin(x, y); - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) - return 0; - return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; - }; - m.getBinEntries = function(x, y) { - const bin = this.getBin(x, y); - return (bin < 0 || bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; - }; - } else { - m.getBin = function(x) { return x; }; - m.getBinContent = function(bin) { - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) - return 0; - return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; - }; - m.getBinEntries = function(bin) { - return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; - }; - } - m.getBinEffectiveEntries = function(bin) { - if (bin < 0 || bin >= this.fNcells) - return 0; - const sumOfWeights = this.fBinEntries[bin]; - if (!this.fBinSumw2 || this.fBinSumw2.length !== this.fNcells) - // this can happen when reading an old file - return sumOfWeights; - const sumOfWeightsSquare = this.fBinSumw2[bin]; - return (sumOfWeightsSquare > 0) ? sumOfWeights * sumOfWeights / sumOfWeightsSquare : 0; - }; - m.getBinError = function(bin) { - if (bin < 0 || bin >= this.fNcells) - return 0; - const cont = this.fArray[bin], // sum of bin w *y - sum = this.fBinEntries[bin], // sum of bin weights - err2 = this.fSumw2[bin], // sum of bin w * y^2 - neff = this.getBinEffectiveEntries(bin); // (sum of w)^2 / (sum of w^2) - if (sum < 1e-300) - return 0; // for empty bins - const EErrorType = { kERRORSPREAD: 1, kERRORSPREADI: 2, kERRORSPREADG: 3 }; - // case the values y are gaussian distributed y +/- sigma and w = 1/sigma^2 - if (this.fErrorMode === EErrorType.kERRORSPREADG) - return 1.0 / Math.sqrt(sum); - // compute variance in y (eprim2) and standard deviation in y (eprim) - const contsum = cont / sum, eprim = Math.sqrt(Math.abs(err2 / sum - contsum ** 2)); - if (this.fErrorMode === EErrorType.kERRORSPREADI) { - if (eprim) - return eprim / Math.sqrt(neff); - // in case content y is an integer (so each my has an error +/- 1/sqrt(12) - // when the std(y) is zero - return 1.0 / Math.sqrt(12 * neff); - } - // if approximate compute the sums (of w, wy and wy2) using all the bins - // when the variance in y is zero - // case option 'S' return standard deviation in y - if (this.fErrorMode === EErrorType.kERRORSPREAD) - return eprim; - // default case : fErrorMode = kERRORMEAN - // return standard error on the mean of y - return eprim / Math.sqrt(neff); - }; - } - - if (typename === clTAxis) { - m.GetBinLowEdge = function(bin) { - if (this.fNbins <= 0) - return 0; - if (this.fXbins.length) { - if ((bin > 0) && (bin <= this.fNbins + 1)) - return this.fXbins[bin - 1]; - if (bin === 0) // underflow - return 2 * this.fXbins[0] - this.fXbins[1]; - if (bin === this.fNbins + 2) // right border of overflow bin - return 2 * this.fXbins[bin - 2] - this.fXbins[bin - 3]; - return 0; - } - return this.fXmin + (bin - 1) * (this.fXmax - this.fXmin) / this.fNbins; - }; - m.GetBinCenter = function(bin) { - if (this.fNbins <= 0) - return 0; - if (this.fXbins.length) { - if ((bin > 0) && (bin <= this.fNbins)) - return (this.fXbins[bin - 1] + this.fXbins[bin]) / 2; - if (bin === 0) // underflow - return 1.5 * this.fXbins[0] - 0.5 * this.fXbins[1]; - if (bin === this.fNbins + 1) // overflow - return 1.5 * this.fXbins[bin - 1] - 0.5 * this.fXbins[bin - 2]; - return 0; - } - return this.fXmin + (bin - 0.5) * (this.fXmax - this.fXmin) / this.fNbins; - }; - } - - if (typename.indexOf(nsROOT + 'Math::LorentzVector') === 0) { - m.Px = m.X = function() { return this.fCoordinates.Px(); }; - m.Py = m.Y = function() { return this.fCoordinates.Py(); }; - m.Pz = m.Z = function() { return this.fCoordinates.Pz(); }; - m.E = m.T = function() { return this.fCoordinates.E(); }; - m.M2 = function() { return this.fCoordinates.M2(); }; - m.M = function() { return this.fCoordinates.M(); }; - m.R = m.P = function() { return this.fCoordinates.R(); }; - m.P2 = function() { return this.P() * this.P(); }; - m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; - m.Phi = m.phi = function() { return Math.atan2(this.fCoordinates.Py(), this.fCoordinates.Px()); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz() / this.P()); }; - } - - if (typename.indexOf(nsROOT + 'Math::PxPyPzE4D') === 0) { - m.Px = m.X = function() { return this.fX; }; - m.Py = m.Y = function() { return this.fY; }; - m.Pz = m.Z = function() { return this.fZ; }; - m.E = m.T = function() { return this.fT; }; - m.P2 = function() { return this.fX ** 2 + this.fY ** 2 + this.fZ ** 2; }; - m.R = m.P = function() { return Math.sqrt(this.P2()); }; - m.Mag2 = m.M2 = function() { return this.fT ** 2 - this.fX ** 2 - this.fY ** 2 - this.fZ ** 2; }; - m.Mag = m.M = function() { return (this.M2() >= 0) ? Math.sqrt(this.M2()) : -Math.sqrt(-this.M2()); }; - m.Perp2 = m.Pt2 = function() { return this.fX ** 2 + this.fY ** 2; }; - m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; - m.Phi = m.phi = function() { return Math.atan2(this.fY, this.fX); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz / this.P()); }; - } - - methodsCache[typename] = m; - return m; -} - -exports.addMethods = function(obj, typename) { - extend$1(obj, getMethods(typename || obj._typename, obj)); -}; - -gStyle.fXaxis = create$1(clTAttAxis); -gStyle.fYaxis = create$1(clTAttAxis); -gStyle.fZaxis = create$1(clTAttAxis); - -/** @summary Add methods for specified type. - * @desc Will be automatically applied when decoding JSON string - * @private */ -function registerMethods(typename, m) { - methodsCache[typename] = m; -} - -/** @summary Returns true if object represents basic ROOT collections - * @desc Checks if type is TList or TObjArray or TClonesArray or TMap or THashList - * @param {object} lst - object to check - * @param {string} [typename] - or just typename to check - * @private */ -function isRootCollection(lst, typename) { - if (isObject(lst)) { - if ((lst.$kind === clTList) || (lst.$kind === clTObjArray)) - return true; - if (!typename) - typename = lst._typename; - } - return (typename === clTList) || (typename === clTHashList) || (typename === clTMap) || - (typename === clTObjArray) || (typename === clTClonesArray); -} - -/** @summary Return kind string for type - * @desc Used when internal objects without clear ROOT type are used - * For such objects abstract 'kind' is defined. Used in hpainter and draw handles - * @private */ -function getKindForType(typ) { - return (!isStr(typ) || (typ.indexOf(nsROOT) !== 0)) ? prROOT + typ : typ; -} - -/** @summary Return type name from kind string - * @private */ -function getTypeForKind(kind) { - if (!isStr(kind)) - return null; - if (kind.indexOf(prROOT) === 0) - return kind.slice(prROOT.length); - if (kind.indexOf(nsROOT) === 0) - return kind; - return null; -} - -/** @summary Internal collection of functions potentially used by batch scripts - * @private */ -internals.jsroot = { version, source_dir: exports.source_dir, settings, gStyle, parse: parse$1, isBatchMode }; - -var core = /*#__PURE__*/Object.freeze({ -__proto__: null, -BIT: BIT, -get _ensureJSROOT () { return exports._ensureJSROOT; }, -get addMethods () { return exports.addMethods; }, -atob_func: atob_func, -browser: browser, -btoa_func: btoa_func, -clTAnnotation: clTAnnotation, -clTAttCanvas: clTAttCanvas, -clTAttFill: clTAttFill, -clTAttLine: clTAttLine, -clTAttMarker: clTAttMarker, -clTAttText: clTAttText, -clTAxis: clTAxis, -clTBox: clTBox, -clTCanvas: clTCanvas, -clTClonesArray: clTClonesArray, -clTColor: clTColor, -clTCutG: clTCutG, -clTDiamond: clTDiamond, -clTF1: clTF1, -clTF12: clTF12, -clTF2: clTF2, -clTF3: clTF3, -clTFile: clTFile, -clTFrame: clTFrame, -clTGaxis: clTGaxis, -clTGeoNode: clTGeoNode, -clTGeoNodeMatrix: clTGeoNodeMatrix, -clTGeoVolume: clTGeoVolume, -clTGraph: clTGraph, -clTGraph2DAsymmErrors: clTGraph2DAsymmErrors, -clTGraph2DErrors: clTGraph2DErrors, -clTGraphPolar: clTGraphPolar, -clTGraphPolargram: clTGraphPolargram, -clTGraphTime: clTGraphTime, -clTH1: clTH1, -clTH1D: clTH1D, -clTH1F: clTH1F, -clTH1I: clTH1I, -clTH2: clTH2, -clTH2D: clTH2D, -clTH2F: clTH2F, -clTH2I: clTH2I, -clTH3: clTH3, -clTH3D: clTH3D, -clTH3F: clTH3F, -clTH3I: clTH3I, -clTHStack: clTHStack, -clTHashList: clTHashList, -clTImagePalette: clTImagePalette, -clTKey: clTKey, -clTLatex: clTLatex, -clTLegend: clTLegend, -clTLegendEntry: clTLegendEntry, -clTLine: clTLine, -clTLink: clTLink, -clTList: clTList, -clTMap: clTMap, -clTMarker: clTMarker, -clTMathText: clTMathText, -clTMultiGraph: clTMultiGraph, -clTNamed: clTNamed, -clTObjArray: clTObjArray, -clTObjString: clTObjString, -clTObject: clTObject, -clTPad: clTPad, -clTPaletteAxis: clTPaletteAxis, -clTPave: clTPave, -clTPaveClass: clTPaveClass, -clTPaveLabel: clTPaveLabel, -clTPaveStats: clTPaveStats, -clTPaveText: clTPaveText, -clTPavesText: clTPavesText, -clTPolyLine: clTPolyLine, -clTPolyLine3D: clTPolyLine3D, -clTPolyMarker3D: clTPolyMarker3D, -clTProfile: clTProfile, -clTProfile2D: clTProfile2D, -clTProfile3D: clTProfile3D, -clTString: clTString, -clTStyle: clTStyle, -clTText: clTText, -clTTree: clTTree, -clone: clone, -constants: constants$1, -create: create$1, -createHistogram: createHistogram, -createHttpRequest: createHttpRequest, -createTGraph: createTGraph, -createTHStack: createTHStack, -createTMultiGraph: createTMultiGraph, -createTPolyLine: createTPolyLine, -decodeUrl: decodeUrl, -findFunction: findFunction, -gStyle: gStyle, -getDocument: getDocument, -getKindForType: getKindForType, -getMethods: getMethods, -getPromise: getPromise, -getTypeForKind: getTypeForKind, -httpRequest: httpRequest, -injectCode: injectCode, -internals: internals, -isArrayProto: isArrayProto, -isBatchMode: isBatchMode, -isFunc: isFunc, -isNodeJs: isNodeJs, -isObject: isObject, -isPromise: isPromise, -isRootCollection: isRootCollection, -isStr: isStr, -kInspect: kInspect, -kNoStats: kNoStats, -kNoZoom: kNoZoom, -kTitle: kTitle, -loadModules: loadModules, -loadScript: loadScript, -nsREX: nsREX, -nsROOT: nsROOT, -nsSVG: nsSVG, -parse: parse$1, -parseMulti: parseMulti, -postponePromise: postponePromise, -prROOT: prROOT, -registerMethods: registerMethods, -setBatchMode: setBatchMode, -setHistogramTitle: setHistogramTitle, -settings: settings, -get source_dir () { return exports.source_dir; }, -toJSON: toJSON, -urlClassPrefix: urlClassPrefix, -version: version, -version_date: version_date, -version_id: version_id -}); - -// https://d3js.org v7.9.0 Copyright 2010-2021 Mike Bostock - -function define(constructor, factory, prototype) { - constructor.prototype = factory.prototype = prototype; - prototype.constructor = constructor; -} - -function extend(parent, definition) { - var prototype = Object.create(parent.prototype); - for (var key in definition) prototype[key] = definition[key]; - return prototype; -} - -function Color$1() {} - -var darker = 0.7; -var brighter = 1 / darker; - -var reI = "\\s*([+-]?\\d+)\\s*", - reN = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*", - reP = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*", - reHex = /^#([0-9a-f]{3,8})$/, - reRgbInteger = new RegExp(`^rgb\\(${reI},${reI},${reI}\\)$`), - reRgbPercent = new RegExp(`^rgb\\(${reP},${reP},${reP}\\)$`), - reRgbaInteger = new RegExp(`^rgba\\(${reI},${reI},${reI},${reN}\\)$`), - reRgbaPercent = new RegExp(`^rgba\\(${reP},${reP},${reP},${reN}\\)$`), - reHslPercent = new RegExp(`^hsl\\(${reN},${reP},${reP}\\)$`), - reHslaPercent = new RegExp(`^hsla\\(${reN},${reP},${reP},${reN}\\)$`); - -var named = { - aliceblue: 0xf0f8ff, - antiquewhite: 0xfaebd7, - aqua: 0x00ffff, - aquamarine: 0x7fffd4, - azure: 0xf0ffff, - beige: 0xf5f5dc, - bisque: 0xffe4c4, - black: 0x000000, - blanchedalmond: 0xffebcd, - blue: 0x0000ff, - blueviolet: 0x8a2be2, - brown: 0xa52a2a, - burlywood: 0xdeb887, - cadetblue: 0x5f9ea0, - chartreuse: 0x7fff00, - chocolate: 0xd2691e, - coral: 0xff7f50, - cornflowerblue: 0x6495ed, - cornsilk: 0xfff8dc, - crimson: 0xdc143c, - cyan: 0x00ffff, - darkblue: 0x00008b, - darkcyan: 0x008b8b, - darkgoldenrod: 0xb8860b, - darkgray: 0xa9a9a9, - darkgreen: 0x006400, - darkgrey: 0xa9a9a9, - darkkhaki: 0xbdb76b, - darkmagenta: 0x8b008b, - darkolivegreen: 0x556b2f, - darkorange: 0xff8c00, - darkorchid: 0x9932cc, - darkred: 0x8b0000, - darksalmon: 0xe9967a, - darkseagreen: 0x8fbc8f, - darkslateblue: 0x483d8b, - darkslategray: 0x2f4f4f, - darkslategrey: 0x2f4f4f, - darkturquoise: 0x00ced1, - darkviolet: 0x9400d3, - deeppink: 0xff1493, - deepskyblue: 0x00bfff, - dimgray: 0x696969, - dimgrey: 0x696969, - dodgerblue: 0x1e90ff, - firebrick: 0xb22222, - floralwhite: 0xfffaf0, - forestgreen: 0x228b22, - fuchsia: 0xff00ff, - gainsboro: 0xdcdcdc, - ghostwhite: 0xf8f8ff, - gold: 0xffd700, - goldenrod: 0xdaa520, - gray: 0x808080, - green: 0x008000, - greenyellow: 0xadff2f, - grey: 0x808080, - honeydew: 0xf0fff0, - hotpink: 0xff69b4, - indianred: 0xcd5c5c, - indigo: 0x4b0082, - ivory: 0xfffff0, - khaki: 0xf0e68c, - lavender: 0xe6e6fa, - lavenderblush: 0xfff0f5, - lawngreen: 0x7cfc00, - lemonchiffon: 0xfffacd, - lightblue: 0xadd8e6, - lightcoral: 0xf08080, - lightcyan: 0xe0ffff, - lightgoldenrodyellow: 0xfafad2, - lightgray: 0xd3d3d3, - lightgreen: 0x90ee90, - lightgrey: 0xd3d3d3, - lightpink: 0xffb6c1, - lightsalmon: 0xffa07a, - lightseagreen: 0x20b2aa, - lightskyblue: 0x87cefa, - lightslategray: 0x778899, - lightslategrey: 0x778899, - lightsteelblue: 0xb0c4de, - lightyellow: 0xffffe0, - lime: 0x00ff00, - limegreen: 0x32cd32, - linen: 0xfaf0e6, - magenta: 0xff00ff, - maroon: 0x800000, - mediumaquamarine: 0x66cdaa, - mediumblue: 0x0000cd, - mediumorchid: 0xba55d3, - mediumpurple: 0x9370db, - mediumseagreen: 0x3cb371, - mediumslateblue: 0x7b68ee, - mediumspringgreen: 0x00fa9a, - mediumturquoise: 0x48d1cc, - mediumvioletred: 0xc71585, - midnightblue: 0x191970, - mintcream: 0xf5fffa, - mistyrose: 0xffe4e1, - moccasin: 0xffe4b5, - navajowhite: 0xffdead, - navy: 0x000080, - oldlace: 0xfdf5e6, - olive: 0x808000, - olivedrab: 0x6b8e23, - orange: 0xffa500, - orangered: 0xff4500, - orchid: 0xda70d6, - palegoldenrod: 0xeee8aa, - palegreen: 0x98fb98, - paleturquoise: 0xafeeee, - palevioletred: 0xdb7093, - papayawhip: 0xffefd5, - peachpuff: 0xffdab9, - peru: 0xcd853f, - pink: 0xffc0cb, - plum: 0xdda0dd, - powderblue: 0xb0e0e6, - purple: 0x800080, - rebeccapurple: 0x663399, - red: 0xff0000, - rosybrown: 0xbc8f8f, - royalblue: 0x4169e1, - saddlebrown: 0x8b4513, - salmon: 0xfa8072, - sandybrown: 0xf4a460, - seagreen: 0x2e8b57, - seashell: 0xfff5ee, - sienna: 0xa0522d, - silver: 0xc0c0c0, - skyblue: 0x87ceeb, - slateblue: 0x6a5acd, - slategray: 0x708090, - slategrey: 0x708090, - snow: 0xfffafa, - springgreen: 0x00ff7f, - steelblue: 0x4682b4, - tan: 0xd2b48c, - teal: 0x008080, - thistle: 0xd8bfd8, - tomato: 0xff6347, - turquoise: 0x40e0d0, - violet: 0xee82ee, - wheat: 0xf5deb3, - white: 0xffffff, - whitesmoke: 0xf5f5f5, - yellow: 0xffff00, - yellowgreen: 0x9acd32 -}; - -define(Color$1, color, { - copy(channels) { - return Object.assign(new this.constructor, this, channels); - }, - displayable() { - return this.rgb().displayable(); - }, - hex: color_formatHex, // Deprecated! Use color.formatHex. - formatHex: color_formatHex, - formatHex8: color_formatHex8, - formatHsl: color_formatHsl, - formatRgb: color_formatRgb, - toString: color_formatRgb -}); - -function color_formatHex() { - return this.rgb().formatHex(); -} - -function color_formatHex8() { - return this.rgb().formatHex8(); -} - -function color_formatHsl() { - return hslConvert(this).formatHsl(); -} - -function color_formatRgb() { - return this.rgb().formatRgb(); -} - -function color(format) { - var m, l; - format = (format + "").trim().toLowerCase(); - return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000 - : l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00 - : l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000 - : l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000 - : null) // invalid hex - : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0) - : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%) - : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1) - : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1) - : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%) - : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1) - : named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins - : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0) - : null; -} - -function rgbn(n) { - return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1); -} - -function rgba(r, g, b, a) { - if (a <= 0) r = g = b = NaN; - return new Rgb(r, g, b, a); -} - -function rgbConvert(o) { - if (!(o instanceof Color$1)) o = color(o); - if (!o) return new Rgb; - o = o.rgb(); - return new Rgb(o.r, o.g, o.b, o.opacity); -} - -function rgb(r, g, b, opacity) { - return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity); -} - -function Rgb(r, g, b, opacity) { - this.r = +r; - this.g = +g; - this.b = +b; - this.opacity = +opacity; -} - -define(Rgb, rgb, extend(Color$1, { - brighter(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - darker(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - rgb() { - return this; - }, - clamp() { - return new Rgb(clampi(this.r), clampi(this.g), clampi(this.b), clampa(this.opacity)); - }, - displayable() { - return (-0.5 <= this.r && this.r < 255.5) - && (-0.5 <= this.g && this.g < 255.5) - && (-0.5 <= this.b && this.b < 255.5) - && (0 <= this.opacity && this.opacity <= 1); - }, - hex: rgb_formatHex, // Deprecated! Use color.formatHex. - formatHex: rgb_formatHex, - formatHex8: rgb_formatHex8, - formatRgb: rgb_formatRgb, - toString: rgb_formatRgb -})); - -function rgb_formatHex() { - return `#${hex$1(this.r)}${hex$1(this.g)}${hex$1(this.b)}`; -} - -function rgb_formatHex8() { - return `#${hex$1(this.r)}${hex$1(this.g)}${hex$1(this.b)}${hex$1((isNaN(this.opacity) ? 1 : this.opacity) * 255)}`; -} - -function rgb_formatRgb() { - const a = clampa(this.opacity); - return `${a === 1 ? "rgb(" : "rgba("}${clampi(this.r)}, ${clampi(this.g)}, ${clampi(this.b)}${a === 1 ? ")" : `, ${a})`}`; -} - -function clampa(opacity) { - return isNaN(opacity) ? 1 : Math.max(0, Math.min(1, opacity)); -} - -function clampi(value) { - return Math.max(0, Math.min(255, Math.round(value) || 0)); -} - -function hex$1(value) { - value = clampi(value); - return (value < 16 ? "0" : "") + value.toString(16); -} - -function hsla(h, s, l, a) { - if (a <= 0) h = s = l = NaN; - else if (l <= 0 || l >= 1) h = s = NaN; - else if (s <= 0) h = NaN; - return new Hsl(h, s, l, a); -} - -function hslConvert(o) { - if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity); - if (!(o instanceof Color$1)) o = color(o); - if (!o) return new Hsl; - if (o instanceof Hsl) return o; - o = o.rgb(); - var r = o.r / 255, - g = o.g / 255, - b = o.b / 255, - min = Math.min(r, g, b), - max = Math.max(r, g, b), - h = NaN, - s = max - min, - l = (max + min) / 2; - if (s) { - if (r === max) h = (g - b) / s + (g < b) * 6; - else if (g === max) h = (b - r) / s + 2; - else h = (r - g) / s + 4; - s /= l < 0.5 ? max + min : 2 - max - min; - h *= 60; - } else { - s = l > 0 && l < 1 ? 0 : h; - } - return new Hsl(h, s, l, o.opacity); -} - -function hsl(h, s, l, opacity) { - return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity); -} - -function Hsl(h, s, l, opacity) { - this.h = +h; - this.s = +s; - this.l = +l; - this.opacity = +opacity; -} - -define(Hsl, hsl, extend(Color$1, { - brighter(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - darker(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - rgb() { - var h = this.h % 360 + (this.h < 0) * 360, - s = isNaN(h) || isNaN(this.s) ? 0 : this.s, - l = this.l, - m2 = l + (l < 0.5 ? l : 1 - l) * s, - m1 = 2 * l - m2; - return new Rgb( - hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), - hsl2rgb(h, m1, m2), - hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), - this.opacity - ); - }, - clamp() { - return new Hsl(clamph(this.h), clampt(this.s), clampt(this.l), clampa(this.opacity)); - }, - displayable() { - return (0 <= this.s && this.s <= 1 || isNaN(this.s)) - && (0 <= this.l && this.l <= 1) - && (0 <= this.opacity && this.opacity <= 1); - }, - formatHsl() { - const a = clampa(this.opacity); - return `${a === 1 ? "hsl(" : "hsla("}${clamph(this.h)}, ${clampt(this.s) * 100}%, ${clampt(this.l) * 100}%${a === 1 ? ")" : `, ${a})`}`; - } -})); - -function clamph(value) { - value = (value || 0) % 360; - return value < 0 ? value + 360 : value; -} - -function clampt(value) { - return Math.max(0, Math.min(1, value || 0)); -} - -/* From FvD 13.37, CSS Color Module Level 3 */ -function hsl2rgb(h, m1, m2) { - return (h < 60 ? m1 + (m2 - m1) * h / 60 - : h < 180 ? m2 - : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60 - : m1) * 255; -} - -const radians = Math.PI / 180; -const degrees$1 = 180 / Math.PI; - -// https://observablehq.com/@mbostock/lab-and-rgb -const K = 18, - Xn = 0.96422, - Yn = 1, - Zn = 0.82521, - t0$1 = 4 / 29, - t1$1 = 6 / 29, - t2 = 3 * t1$1 * t1$1, - t3 = t1$1 * t1$1 * t1$1; - -function labConvert(o) { - if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity); - if (o instanceof Hcl) return hcl2lab(o); - if (!(o instanceof Rgb)) o = rgbConvert(o); - var r = rgb2lrgb(o.r), - g = rgb2lrgb(o.g), - b = rgb2lrgb(o.b), - y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn), x, z; - if (r === g && g === b) x = z = y; else { - x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn); - z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn); - } - return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity); -} - -function lab(l, a, b, opacity) { - return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity); -} - -function Lab(l, a, b, opacity) { - this.l = +l; - this.a = +a; - this.b = +b; - this.opacity = +opacity; -} - -define(Lab, lab, extend(Color$1, { - brighter(k) { - return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity); - }, - darker(k) { - return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity); - }, - rgb() { - var y = (this.l + 16) / 116, - x = isNaN(this.a) ? y : y + this.a / 500, - z = isNaN(this.b) ? y : y - this.b / 200; - x = Xn * lab2xyz(x); - y = Yn * lab2xyz(y); - z = Zn * lab2xyz(z); - return new Rgb( - lrgb2rgb( 3.1338561 * x - 1.6168667 * y - 0.4906146 * z), - lrgb2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z), - lrgb2rgb( 0.0719453 * x - 0.2289914 * y + 1.4052427 * z), - this.opacity - ); - } -})); - -function xyz2lab(t) { - return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0$1; -} - -function lab2xyz(t) { - return t > t1$1 ? t * t * t : t2 * (t - t0$1); -} - -function lrgb2rgb(x) { - return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055); -} - -function rgb2lrgb(x) { - return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); -} - -function hclConvert(o) { - if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity); - if (!(o instanceof Lab)) o = labConvert(o); - if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0 < o.l && o.l < 100 ? 0 : NaN, o.l, o.opacity); - var h = Math.atan2(o.b, o.a) * degrees$1; - return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity); -} - -function hcl(h, c, l, opacity) { - return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity); -} - -function Hcl(h, c, l, opacity) { - this.h = +h; - this.c = +c; - this.l = +l; - this.opacity = +opacity; -} - -function hcl2lab(o) { - if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity); - var h = o.h * radians; - return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity); -} - -define(Hcl, hcl, extend(Color$1, { - brighter(k) { - return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity); - }, - darker(k) { - return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity); - }, - rgb() { - return hcl2lab(this).rgb(); - } -})); - -var A = -0.14861, - B = 1.78277, - C = -0.29227, - D = -0.90649, - E = 1.97294, - ED = E * D, - EB = E * B, - BC_DA = B * C - D * A; - -function cubehelixConvert(o) { - if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity); - if (!(o instanceof Rgb)) o = rgbConvert(o); - var r = o.r / 255, - g = o.g / 255, - b = o.b / 255, - l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB), - bl = b - l, - k = (E * (g - l) - C * bl) / D, - s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), // NaN if l=0 or l=1 - h = s ? Math.atan2(k, bl) * degrees$1 - 120 : NaN; - return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity); -} - -function cubehelix(h, s, l, opacity) { - return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity); -} - -function Cubehelix(h, s, l, opacity) { - this.h = +h; - this.s = +s; - this.l = +l; - this.opacity = +opacity; -} - -define(Cubehelix, cubehelix, extend(Color$1, { - brighter(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Cubehelix(this.h, this.s, this.l * k, this.opacity); - }, - darker(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Cubehelix(this.h, this.s, this.l * k, this.opacity); - }, - rgb() { - var h = isNaN(this.h) ? 0 : (this.h + 120) * radians, - l = +this.l, - a = isNaN(this.s) ? 0 : this.s * l * (1 - l), - cosh = Math.cos(h), - sinh = Math.sin(h); - return new Rgb( - 255 * (l + a * (A * cosh + B * sinh)), - 255 * (l + a * (C * cosh + D * sinh)), - 255 * (l + a * (E * cosh)), - this.opacity - ); - } -})); - -var abs$1 = Math.abs; -var cos$1 = Math.cos; -var sin$1 = Math.sin; -var pi$2 = Math.PI; -var halfPi$1 = pi$2 / 2; -var tau$2 = pi$2 * 2; -var max$2 = Math.max; -var epsilon$2 = 1e-12; - -function range$1(i, j) { - return Array.from({length: j - i}, (_, k) => i + k); -} - -function compareValue(compare) { - return function(a, b) { - return compare( - a.source.value + a.target.value, - b.source.value + b.target.value - ); - }; -} - -function chord_default() { - return chord(false); -} - -function chord(directed, transpose) { - var padAngle = 0, - sortGroups = null, - sortSubgroups = null, - sortChords = null; - - function chord(matrix) { - var n = matrix.length, - groupSums = new Array(n), - groupIndex = range$1(0, n), - chords = new Array(n * n), - groups = new Array(n), - k = 0, dx; - - matrix = Float64Array.from({length: n * n}, (_, i) => matrix[i / n | 0][i % n]); - - // Compute the scaling factor from value to angle in [0, 2pi]. - for (let i = 0; i < n; ++i) { - let x = 0; - for (let j = 0; j < n; ++j) x += matrix[i * n + j] + directed * matrix[j * n + i]; - k += groupSums[i] = x; - } - k = max$2(0, tau$2 - padAngle * n) / k; - dx = k ? padAngle : tau$2 / n; - - // Compute the angles for each group and constituent chord. - { - let x = 0; - if (sortGroups) groupIndex.sort((a, b) => sortGroups(groupSums[a], groupSums[b])); - for (const i of groupIndex) { - const x0 = x; - { - const subgroupIndex = range$1(0, n).filter(j => matrix[i * n + j] || matrix[j * n + i]); - if (sortSubgroups) subgroupIndex.sort((a, b) => sortSubgroups(matrix[i * n + a], matrix[i * n + b])); - for (const j of subgroupIndex) { - let chord; - if (i < j) { - chord = chords[i * n + j] || (chords[i * n + j] = {source: null, target: null}); - chord.source = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; - } else { - chord = chords[j * n + i] || (chords[j * n + i] = {source: null, target: null}); - chord.target = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; - if (i === j) chord.source = chord.target; - } - if (chord.source && chord.target && chord.source.value < chord.target.value) { - const source = chord.source; - chord.source = chord.target; - chord.target = source; - } - } - groups[i] = {index: i, startAngle: x0, endAngle: x, value: groupSums[i]}; - } - x += dx; - } - } - - // Remove empty chords. - chords = Object.values(chords); - chords.groups = groups; - return sortChords ? chords.sort(sortChords) : chords; - } - - chord.padAngle = function(_) { - return arguments.length ? (padAngle = max$2(0, _), chord) : padAngle; - }; - - chord.sortGroups = function(_) { - return arguments.length ? (sortGroups = _, chord) : sortGroups; - }; - - chord.sortSubgroups = function(_) { - return arguments.length ? (sortSubgroups = _, chord) : sortSubgroups; - }; - - chord.sortChords = function(_) { - return arguments.length ? (_ == null ? sortChords = null : (sortChords = compareValue(_))._ = _, chord) : sortChords && sortChords._; - }; - - return chord; -} - -const pi$1 = Math.PI, - tau$1 = 2 * pi$1, - epsilon$1 = 1e-6, - tauEpsilon = tau$1 - epsilon$1; - -function append(strings) { - this._ += strings[0]; - for (let i = 1, n = strings.length; i < n; ++i) { - this._ += arguments[i] + strings[i]; - } -} - -function appendRound(digits) { - let d = Math.floor(digits); - if (!(d >= 0)) throw new Error(`invalid digits: ${digits}`); - if (d > 15) return append; - const k = 10 ** d; - return function(strings) { - this._ += strings[0]; - for (let i = 1, n = strings.length; i < n; ++i) { - this._ += Math.round(arguments[i] * k) / k + strings[i]; - } - }; -} - -let Path$2 = class Path { - constructor(digits) { - this._x0 = this._y0 = // start of current subpath - this._x1 = this._y1 = null; // end of current subpath - this._ = ""; - this._append = digits == null ? append : appendRound(digits); - } - moveTo(x, y) { - this._append`M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}`; - } - closePath() { - if (this._x1 !== null) { - this._x1 = this._x0, this._y1 = this._y0; - this._append`Z`; - } - } - lineTo(x, y) { - this._append`L${this._x1 = +x},${this._y1 = +y}`; - } - quadraticCurveTo(x1, y1, x, y) { - this._append`Q${+x1},${+y1},${this._x1 = +x},${this._y1 = +y}`; - } - bezierCurveTo(x1, y1, x2, y2, x, y) { - this._append`C${+x1},${+y1},${+x2},${+y2},${this._x1 = +x},${this._y1 = +y}`; - } - arcTo(x1, y1, x2, y2, r) { - x1 = +x1, y1 = +y1, x2 = +x2, y2 = +y2, r = +r; - - // Is the radius negative? Error. - if (r < 0) throw new Error(`negative radius: ${r}`); - - let x0 = this._x1, - y0 = this._y1, - x21 = x2 - x1, - y21 = y2 - y1, - x01 = x0 - x1, - y01 = y0 - y1, - l01_2 = x01 * x01 + y01 * y01; - - // Is this path empty? Move to (x1,y1). - if (this._x1 === null) { - this._append`M${this._x1 = x1},${this._y1 = y1}`; - } - - // Or, is (x1,y1) coincident with (x0,y0)? Do nothing. - else if (!(l01_2 > epsilon$1)); - - // Or, are (x0,y0), (x1,y1) and (x2,y2) collinear? - // Equivalently, is (x1,y1) coincident with (x2,y2)? - // Or, is the radius zero? Line to (x1,y1). - else if (!(Math.abs(y01 * x21 - y21 * x01) > epsilon$1) || !r) { - this._append`L${this._x1 = x1},${this._y1 = y1}`; - } - - // Otherwise, draw an arc! - else { - let x20 = x2 - x0, - y20 = y2 - y0, - l21_2 = x21 * x21 + y21 * y21, - l20_2 = x20 * x20 + y20 * y20, - l21 = Math.sqrt(l21_2), - l01 = Math.sqrt(l01_2), - l = r * Math.tan((pi$1 - Math.acos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2), - t01 = l / l01, - t21 = l / l21; - - // If the start tangent is not coincident with (x0,y0), line to. - if (Math.abs(t01 - 1) > epsilon$1) { - this._append`L${x1 + t01 * x01},${y1 + t01 * y01}`; - } - - this._append`A${r},${r},0,0,${+(y01 * x20 > x01 * y20)},${this._x1 = x1 + t21 * x21},${this._y1 = y1 + t21 * y21}`; - } - } - arc(x, y, r, a0, a1, ccw) { - x = +x, y = +y, r = +r, ccw = !!ccw; - - // Is the radius negative? Error. - if (r < 0) throw new Error(`negative radius: ${r}`); - - let dx = r * Math.cos(a0), - dy = r * Math.sin(a0), - x0 = x + dx, - y0 = y + dy, - cw = 1 ^ ccw, - da = ccw ? a0 - a1 : a1 - a0; - - // Is this path empty? Move to (x0,y0). - if (this._x1 === null) { - this._append`M${x0},${y0}`; - } - - // Or, is (x0,y0) not coincident with the previous point? Line to (x0,y0). - else if (Math.abs(this._x1 - x0) > epsilon$1 || Math.abs(this._y1 - y0) > epsilon$1) { - this._append`L${x0},${y0}`; - } - - // Is this arc empty? We’re done. - if (!r) return; - - // Does the angle go the wrong way? Flip the direction. - if (da < 0) da = da % tau$1 + tau$1; - - // Is this a complete circle? Draw two arcs to complete the circle. - if (da > tauEpsilon) { - this._append`A${r},${r},0,1,${cw},${x - dx},${y - dy}A${r},${r},0,1,${cw},${this._x1 = x0},${this._y1 = y0}`; - } - - // Is this arc non-empty? Draw an arc! - else if (da > epsilon$1) { - this._append`A${r},${r},0,${+(da >= pi$1)},${cw},${this._x1 = x + r * Math.cos(a1)},${this._y1 = y + r * Math.sin(a1)}`; - } - } - rect(x, y, w, h) { - this._append`M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}h${w = +w}v${+h}h${-w}Z`; - } - toString() { - return this._; - } -}; - -function path() { - return new Path$2; -} - -// Allow instanceof d3.path -path.prototype = Path$2.prototype; - -var slice = Array.prototype.slice; - -function constant$4(x) { - return function() { - return x; - }; -} - -function defaultSource(d) { - return d.source; -} - -function defaultTarget(d) { - return d.target; -} - -function defaultRadius(d) { - return d.radius; -} - -function defaultStartAngle(d) { - return d.startAngle; -} - -function defaultEndAngle(d) { - return d.endAngle; -} - -function defaultPadAngle() { - return 0; -} - -function ribbon(headRadius) { - var source = defaultSource, - target = defaultTarget, - sourceRadius = defaultRadius, - targetRadius = defaultRadius, - startAngle = defaultStartAngle, - endAngle = defaultEndAngle, - padAngle = defaultPadAngle, - context = null; - - function ribbon() { - var buffer, - s = source.apply(this, arguments), - t = target.apply(this, arguments), - ap = padAngle.apply(this, arguments) / 2, - argv = slice.call(arguments), - sr = +sourceRadius.apply(this, (argv[0] = s, argv)), - sa0 = startAngle.apply(this, argv) - halfPi$1, - sa1 = endAngle.apply(this, argv) - halfPi$1, - tr = +targetRadius.apply(this, (argv[0] = t, argv)), - ta0 = startAngle.apply(this, argv) - halfPi$1, - ta1 = endAngle.apply(this, argv) - halfPi$1; - - if (!context) context = buffer = path(); - - if (ap > epsilon$2) { - if (abs$1(sa1 - sa0) > ap * 2 + epsilon$2) sa1 > sa0 ? (sa0 += ap, sa1 -= ap) : (sa0 -= ap, sa1 += ap); - else sa0 = sa1 = (sa0 + sa1) / 2; - if (abs$1(ta1 - ta0) > ap * 2 + epsilon$2) ta1 > ta0 ? (ta0 += ap, ta1 -= ap) : (ta0 -= ap, ta1 += ap); - else ta0 = ta1 = (ta0 + ta1) / 2; - } - - context.moveTo(sr * cos$1(sa0), sr * sin$1(sa0)); - context.arc(0, 0, sr, sa0, sa1); - if (sa0 !== ta0 || sa1 !== ta1) { - { - context.quadraticCurveTo(0, 0, tr * cos$1(ta0), tr * sin$1(ta0)); - context.arc(0, 0, tr, ta0, ta1); - } - } - context.quadraticCurveTo(0, 0, sr * cos$1(sa0), sr * sin$1(sa0)); - context.closePath(); - - if (buffer) return context = null, buffer + "" || null; - } - - ribbon.radius = function(_) { - return arguments.length ? (sourceRadius = targetRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : sourceRadius; - }; - - ribbon.sourceRadius = function(_) { - return arguments.length ? (sourceRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : sourceRadius; - }; - - ribbon.targetRadius = function(_) { - return arguments.length ? (targetRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : targetRadius; - }; - - ribbon.startAngle = function(_) { - return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$4(+_), ribbon) : startAngle; - }; - - ribbon.endAngle = function(_) { - return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$4(+_), ribbon) : endAngle; - }; - - ribbon.padAngle = function(_) { - return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant$4(+_), ribbon) : padAngle; - }; - - ribbon.source = function(_) { - return arguments.length ? (source = _, ribbon) : source; - }; - - ribbon.target = function(_) { - return arguments.length ? (target = _, ribbon) : target; - }; - - ribbon.context = function(_) { - return arguments.length ? ((context = _ == null ? null : _), ribbon) : context; - }; - - return ribbon; -} - -function ribbon_default() { - return ribbon(); -} - -var noop = {value: () => {}}; - -function dispatch() { - for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) { - if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t); - _[t] = []; - } - return new Dispatch(_); -} - -function Dispatch(_) { - this._ = _; -} - -function parseTypenames$1(typenames, types) { - return typenames.trim().split(/^|\s+/).map(function(t) { - var name = "", i = t.indexOf("."); - if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); - if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t); - return {type: t, name: name}; - }); -} - -Dispatch.prototype = dispatch.prototype = { - constructor: Dispatch, - on: function(typename, callback) { - var _ = this._, - T = parseTypenames$1(typename + "", _), - t, - i = -1, - n = T.length; - - // If no callback was specified, return the callback of the given type and name. - if (arguments.length < 2) { - while (++i < n) if ((t = (typename = T[i]).type) && (t = get$1(_[t], typename.name))) return t; - return; - } - - // If a type was specified, set the callback for the given type and name. - // Otherwise, if a null callback was specified, remove callbacks of the given name. - if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback); - while (++i < n) { - if (t = (typename = T[i]).type) _[t] = set$1(_[t], typename.name, callback); - else if (callback == null) for (t in _) _[t] = set$1(_[t], typename.name, null); - } - - return this; - }, - copy: function() { - var copy = {}, _ = this._; - for (var t in _) copy[t] = _[t].slice(); - return new Dispatch(copy); - }, - call: function(type, that) { - if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2]; - if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); - for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); - }, - apply: function(type, that, args) { - if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); - for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); - } -}; - -function get$1(type, name) { - for (var i = 0, n = type.length, c; i < n; ++i) { - if ((c = type[i]).name === name) { - return c.value; - } - } -} - -function set$1(type, name, callback) { - for (var i = 0, n = type.length; i < n; ++i) { - if (type[i].name === name) { - type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1)); - break; - } - } - if (callback != null) type.push({name: name, value: callback}); - return type; -} - -var xhtml = "http://www.w3.org/1999/xhtml"; - -var namespaces = { - svg: "http://www.w3.org/2000/svg", - xhtml: xhtml, - xlink: "http://www.w3.org/1999/xlink", - xml: "http://www.w3.org/XML/1998/namespace", - xmlns: "http://www.w3.org/2000/xmlns/" -}; - -function namespace(name) { - var prefix = name += "", i = prefix.indexOf(":"); - if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); - return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; // eslint-disable-line no-prototype-builtins -} - -function creatorInherit(name) { - return function() { - var document = this.ownerDocument, - uri = this.namespaceURI; - return uri === xhtml && document.documentElement.namespaceURI === xhtml - ? document.createElement(name) - : document.createElementNS(uri, name); - }; -} - -function creatorFixed(fullname) { - return function() { - return this.ownerDocument.createElementNS(fullname.space, fullname.local); - }; -} - -function creator(name) { - var fullname = namespace(name); - return (fullname.local - ? creatorFixed - : creatorInherit)(fullname); -} - -function none() {} - -function selector(selector) { - return selector == null ? none : function() { - return this.querySelector(selector); - }; -} - -function selection_select(select) { - if (typeof select !== "function") select = selector(select); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { - if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { - if ("__data__" in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - } - } - } - - return new Selection$1(subgroups, this._parents); -} - -// Given something array like (or null), returns something that is strictly an -// array. This is used to ensure that array-like objects passed to d3.selectAll -// or selection.selectAll are converted into proper arrays when creating a -// selection; we don’t ever want to create a selection backed by a live -// HTMLCollection or NodeList. However, note that selection.selectAll will use a -// static NodeList as a group, since it safely derived from querySelectorAll. -function array(x) { - return x == null ? [] : Array.isArray(x) ? x : Array.from(x); -} - -function empty$1() { - return []; -} - -function selectorAll(selector) { - return selector == null ? empty$1 : function() { - return this.querySelectorAll(selector); - }; -} - -function arrayAll(select) { - return function() { - return array(select.apply(this, arguments)); - }; -} - -function selection_selectAll(select) { - if (typeof select === "function") select = arrayAll(select); - else select = selectorAll(select); - - for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - subgroups.push(select.call(node, node.__data__, i, group)); - parents.push(node); - } - } - } - - return new Selection$1(subgroups, parents); -} - -function matcher(selector) { - return function() { - return this.matches(selector); - }; -} - -function childMatcher(selector) { - return function(node) { - return node.matches(selector); - }; -} - -var find = Array.prototype.find; - -function childFind(match) { - return function() { - return find.call(this.children, match); - }; -} - -function childFirst() { - return this.firstElementChild; -} - -function selection_selectChild(match) { - return this.select(match == null ? childFirst - : childFind(typeof match === "function" ? match : childMatcher(match))); -} - -var filter = Array.prototype.filter; - -function children() { - return Array.from(this.children); -} - -function childrenFilter(match) { - return function() { - return filter.call(this.children, match); - }; -} - -function selection_selectChildren(match) { - return this.selectAll(match == null ? children - : childrenFilter(typeof match === "function" ? match : childMatcher(match))); -} - -function selection_filter(match) { - if (typeof match !== "function") match = matcher(match); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Selection$1(subgroups, this._parents); -} - -function sparse(update) { - return new Array(update.length); -} - -function selection_enter() { - return new Selection$1(this._enter || this._groups.map(sparse), this._parents); -} - -function EnterNode(parent, datum) { - this.ownerDocument = parent.ownerDocument; - this.namespaceURI = parent.namespaceURI; - this._next = null; - this._parent = parent; - this.__data__ = datum; -} - -EnterNode.prototype = { - constructor: EnterNode, - appendChild: function(child) { return this._parent.insertBefore(child, this._next); }, - insertBefore: function(child, next) { return this._parent.insertBefore(child, next); }, - querySelector: function(selector) { return this._parent.querySelector(selector); }, - querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); } -}; - -function constant$3(x) { - return function() { - return x; - }; -} - -function bindIndex(parent, group, enter, update, exit, data) { - var i = 0, - node, - groupLength = group.length, - dataLength = data.length; - - // Put any non-null nodes that fit into update. - // Put any null nodes into enter. - // Put any remaining data into enter. - for (; i < dataLength; ++i) { - if (node = group[i]) { - node.__data__ = data[i]; - update[i] = node; - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Put any non-null nodes that don’t fit into exit. - for (; i < groupLength; ++i) { - if (node = group[i]) { - exit[i] = node; - } - } -} - -function bindKey(parent, group, enter, update, exit, data, key) { - var i, - node, - nodeByKeyValue = new Map, - groupLength = group.length, - dataLength = data.length, - keyValues = new Array(groupLength), - keyValue; - - // Compute the key for each node. - // If multiple nodes have the same key, the duplicates are added to exit. - for (i = 0; i < groupLength; ++i) { - if (node = group[i]) { - keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + ""; - if (nodeByKeyValue.has(keyValue)) { - exit[i] = node; - } else { - nodeByKeyValue.set(keyValue, node); - } - } - } - - // Compute the key for each datum. - // If there a node associated with this key, join and add it to update. - // If there is not (or the key is a duplicate), add it to enter. - for (i = 0; i < dataLength; ++i) { - keyValue = key.call(parent, data[i], i, data) + ""; - if (node = nodeByKeyValue.get(keyValue)) { - update[i] = node; - node.__data__ = data[i]; - nodeByKeyValue.delete(keyValue); - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Add any remaining nodes that were not bound to data to exit. - for (i = 0; i < groupLength; ++i) { - if ((node = group[i]) && (nodeByKeyValue.get(keyValues[i]) === node)) { - exit[i] = node; - } - } -} - -function datum(node) { - return node.__data__; -} - -function selection_data(value, key) { - if (!arguments.length) return Array.from(this, datum); - - var bind = key ? bindKey : bindIndex, - parents = this._parents, - groups = this._groups; - - if (typeof value !== "function") value = constant$3(value); - - for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) { - var parent = parents[j], - group = groups[j], - groupLength = group.length, - data = arraylike(value.call(parent, parent && parent.__data__, j, parents)), - dataLength = data.length, - enterGroup = enter[j] = new Array(dataLength), - updateGroup = update[j] = new Array(dataLength), - exitGroup = exit[j] = new Array(groupLength); - - bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); - - // Now connect the enter nodes to their following update node, such that - // appendChild can insert the materialized enter node before this node, - // rather than at the end of the parent node. - for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) { - if (previous = enterGroup[i0]) { - if (i0 >= i1) i1 = i0 + 1; - while (!(next = updateGroup[i1]) && ++i1 < dataLength); - previous._next = next || null; - } - } - } - - update = new Selection$1(update, parents); - update._enter = enter; - update._exit = exit; - return update; -} - -// Given some data, this returns an array-like view of it: an object that -// exposes a length property and allows numeric indexing. Note that unlike -// selectAll, this isn’t worried about “live” collections because the resulting -// array will only be used briefly while data is being bound. (It is possible to -// cause the data to change while iterating by using a key function, but please -// don’t; we’d rather avoid a gratuitous copy.) -function arraylike(data) { - return typeof data === "object" && "length" in data - ? data // Array, TypedArray, NodeList, array-like - : Array.from(data); // Map, Set, iterable, string, or anything else -} - -function selection_exit() { - return new Selection$1(this._exit || this._groups.map(sparse), this._parents); -} - -function selection_join(onenter, onupdate, onexit) { - var enter = this.enter(), update = this, exit = this.exit(); - if (typeof onenter === "function") { - enter = onenter(enter); - if (enter) enter = enter.selection(); - } else { - enter = enter.append(onenter + ""); - } - if (onupdate != null) { - update = onupdate(update); - if (update) update = update.selection(); - } - if (onexit == null) exit.remove(); else onexit(exit); - return enter && update ? enter.merge(update).order() : update; -} - -function selection_merge(context) { - var selection = context.selection ? context.selection() : context; - - for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { - for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group0[i] || group1[i]) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Selection$1(merges, this._parents); -} - -function selection_order() { - - for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) { - for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) { - if (node = group[i]) { - if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next); - next = node; - } - } - } - - return this; -} - -function selection_sort(compare) { - if (!compare) compare = ascending$1; - - function compareNode(a, b) { - return a && b ? compare(a.__data__, b.__data__) : !a - !b; - } - - for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group[i]) { - sortgroup[i] = node; - } - } - sortgroup.sort(compareNode); - } - - return new Selection$1(sortgroups, this._parents).order(); -} - -function ascending$1(a, b) { - return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; -} - -function selection_call() { - var callback = arguments[0]; - arguments[0] = this; - callback.apply(null, arguments); - return this; -} - -function selection_nodes() { - return Array.from(this); -} - -function selection_node() { - - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length; i < n; ++i) { - var node = group[i]; - if (node) return node; - } - } - - return null; -} - -function selection_size() { - let size = 0; - for (const node of this) ++size; // eslint-disable-line no-unused-vars - return size; -} - -function selection_empty() { - return !this.node(); -} - -function selection_each(callback) { - - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if (node = group[i]) callback.call(node, node.__data__, i, group); - } - } - - return this; -} - -function attrRemove$1(name) { - return function() { - this.removeAttribute(name); - }; -} - -function attrRemoveNS$1(fullname) { - return function() { - this.removeAttributeNS(fullname.space, fullname.local); - }; -} - -function attrConstant$1(name, value) { - return function() { - this.setAttribute(name, value); - }; -} - -function attrConstantNS$1(fullname, value) { - return function() { - this.setAttributeNS(fullname.space, fullname.local, value); - }; -} - -function attrFunction$1(name, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.removeAttribute(name); - else this.setAttribute(name, v); - }; -} - -function attrFunctionNS$1(fullname, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.removeAttributeNS(fullname.space, fullname.local); - else this.setAttributeNS(fullname.space, fullname.local, v); - }; -} - -function selection_attr(name, value) { - var fullname = namespace(name); - - if (arguments.length < 2) { - var node = this.node(); - return fullname.local - ? node.getAttributeNS(fullname.space, fullname.local) - : node.getAttribute(fullname); - } - - return this.each((value == null - ? (fullname.local ? attrRemoveNS$1 : attrRemove$1) : (typeof value === "function" - ? (fullname.local ? attrFunctionNS$1 : attrFunction$1) - : (fullname.local ? attrConstantNS$1 : attrConstant$1)))(fullname, value)); -} - -function defaultView(node) { - return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node - || (node.document && node) // node is a Window - || node.defaultView; // node is a Document -} - -function styleRemove$1(name) { - return function() { - this.style.removeProperty(name); - }; -} - -function styleConstant$1(name, value, priority) { - return function() { - this.style.setProperty(name, value, priority); - }; -} - -function styleFunction$1(name, value, priority) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.style.removeProperty(name); - else this.style.setProperty(name, v, priority); - }; -} - -function selection_style(name, value, priority) { - return arguments.length > 1 - ? this.each((value == null - ? styleRemove$1 : typeof value === "function" - ? styleFunction$1 - : styleConstant$1)(name, value, priority == null ? "" : priority)) - : styleValue(this.node(), name); -} - -function styleValue(node, name) { - return node.style.getPropertyValue(name) - || defaultView(node).getComputedStyle(node, null).getPropertyValue(name); -} - -function propertyRemove(name) { - return function() { - delete this[name]; - }; -} - -function propertyConstant(name, value) { - return function() { - this[name] = value; - }; -} - -function propertyFunction(name, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) delete this[name]; - else this[name] = v; - }; -} - -function selection_property(name, value) { - return arguments.length > 1 - ? this.each((value == null - ? propertyRemove : typeof value === "function" - ? propertyFunction - : propertyConstant)(name, value)) - : this.node()[name]; -} - -function classArray(string) { - return string.trim().split(/^|\s+/); -} - -function classList(node) { - return node.classList || new ClassList(node); -} - -function ClassList(node) { - this._node = node; - this._names = classArray(node.getAttribute("class") || ""); -} - -ClassList.prototype = { - add: function(name) { - var i = this._names.indexOf(name); - if (i < 0) { - this._names.push(name); - this._node.setAttribute("class", this._names.join(" ")); - } - }, - remove: function(name) { - var i = this._names.indexOf(name); - if (i >= 0) { - this._names.splice(i, 1); - this._node.setAttribute("class", this._names.join(" ")); - } - }, - contains: function(name) { - return this._names.indexOf(name) >= 0; - } -}; - -function classedAdd(node, names) { - var list = classList(node), i = -1, n = names.length; - while (++i < n) list.add(names[i]); -} - -function classedRemove(node, names) { - var list = classList(node), i = -1, n = names.length; - while (++i < n) list.remove(names[i]); -} - -function classedTrue(names) { - return function() { - classedAdd(this, names); - }; -} - -function classedFalse(names) { - return function() { - classedRemove(this, names); - }; -} - -function classedFunction(names, value) { - return function() { - (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names); - }; -} - -function selection_classed(name, value) { - var names = classArray(name + ""); - - if (arguments.length < 2) { - var list = classList(this.node()), i = -1, n = names.length; - while (++i < n) if (!list.contains(names[i])) return false; - return true; - } - - return this.each((typeof value === "function" - ? classedFunction : value - ? classedTrue - : classedFalse)(names, value)); -} - -function textRemove() { - this.textContent = ""; -} - -function textConstant$1(value) { - return function() { - this.textContent = value; - }; -} - -function textFunction$1(value) { - return function() { - var v = value.apply(this, arguments); - this.textContent = v == null ? "" : v; - }; -} - -function selection_text(value) { - return arguments.length - ? this.each(value == null - ? textRemove : (typeof value === "function" - ? textFunction$1 - : textConstant$1)(value)) - : this.node().textContent; -} - -function htmlRemove() { - this.innerHTML = ""; -} - -function htmlConstant(value) { - return function() { - this.innerHTML = value; - }; -} - -function htmlFunction(value) { - return function() { - var v = value.apply(this, arguments); - this.innerHTML = v == null ? "" : v; - }; -} - -function selection_html(value) { - return arguments.length - ? this.each(value == null - ? htmlRemove : (typeof value === "function" - ? htmlFunction - : htmlConstant)(value)) - : this.node().innerHTML; -} - -function raise() { - if (this.nextSibling) this.parentNode.appendChild(this); -} - -function selection_raise() { - return this.each(raise); -} - -function lower() { - if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild); -} - -function selection_lower() { - return this.each(lower); -} - -function selection_append(name) { - var create = typeof name === "function" ? name : creator(name); - return this.select(function() { - return this.appendChild(create.apply(this, arguments)); - }); -} - -function constantNull() { - return null; -} - -function selection_insert(name, before) { - var create = typeof name === "function" ? name : creator(name), - select = before == null ? constantNull : typeof before === "function" ? before : selector(before); - return this.select(function() { - return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null); - }); -} - -function remove() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); -} - -function selection_remove() { - return this.each(remove); -} - -function selection_cloneShallow() { - var clone = this.cloneNode(false), parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; -} - -function selection_cloneDeep() { - var clone = this.cloneNode(true), parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; -} - -function selection_clone(deep) { - return this.select(deep ? selection_cloneDeep : selection_cloneShallow); -} - -function selection_datum(value) { - return arguments.length - ? this.property("__data__", value) - : this.node().__data__; -} - -function contextListener(listener) { - return function(event) { - listener.call(this, event, this.__data__); - }; -} - -function parseTypenames(typenames) { - return typenames.trim().split(/^|\s+/).map(function(t) { - var name = "", i = t.indexOf("."); - if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); - return {type: t, name: name}; - }); -} - -function onRemove(typename) { - return function() { - var on = this.__on; - if (!on) return; - for (var j = 0, i = -1, m = on.length, o; j < m; ++j) { - if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) { - this.removeEventListener(o.type, o.listener, o.options); - } else { - on[++i] = o; - } - } - if (++i) on.length = i; - else delete this.__on; - }; -} - -function onAdd(typename, value, options) { - return function() { - var on = this.__on, o, listener = contextListener(value); - if (on) for (var j = 0, m = on.length; j < m; ++j) { - if ((o = on[j]).type === typename.type && o.name === typename.name) { - this.removeEventListener(o.type, o.listener, o.options); - this.addEventListener(o.type, o.listener = listener, o.options = options); - o.value = value; - return; - } - } - this.addEventListener(typename.type, listener, options); - o = {type: typename.type, name: typename.name, value: value, listener: listener, options: options}; - if (!on) this.__on = [o]; - else on.push(o); - }; -} - -function selection_on(typename, value, options) { - var typenames = parseTypenames(typename + ""), i, n = typenames.length, t; - - if (arguments.length < 2) { - var on = this.node().__on; - if (on) for (var j = 0, m = on.length, o; j < m; ++j) { - for (i = 0, o = on[j]; i < n; ++i) { - if ((t = typenames[i]).type === o.type && t.name === o.name) { - return o.value; - } - } - } - return; - } - - on = value ? onAdd : onRemove; - for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options)); - return this; -} - -function dispatchEvent(node, type, params) { - var window = defaultView(node), - event = window.CustomEvent; - - if (typeof event === "function") { - event = new event(type, params); - } else { - event = window.document.createEvent("Event"); - if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail; - else event.initEvent(type, false, false); - } - - node.dispatchEvent(event); -} - -function dispatchConstant(type, params) { - return function() { - return dispatchEvent(this, type, params); - }; -} - -function dispatchFunction(type, params) { - return function() { - return dispatchEvent(this, type, params.apply(this, arguments)); - }; -} - -function selection_dispatch(type, params) { - return this.each((typeof params === "function" - ? dispatchFunction - : dispatchConstant)(type, params)); -} - -function* selection_iterator() { - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if (node = group[i]) yield node; - } - } -} - -var root$1 = [null]; - -function Selection$1(groups, parents) { - this._groups = groups; - this._parents = parents; -} - -function selection() { - return new Selection$1([[document.documentElement]], root$1); -} - -function selection_selection() { - return this; -} - -Selection$1.prototype = selection.prototype = { - constructor: Selection$1, - select: selection_select, - selectAll: selection_selectAll, - selectChild: selection_selectChild, - selectChildren: selection_selectChildren, - filter: selection_filter, - data: selection_data, - enter: selection_enter, - exit: selection_exit, - join: selection_join, - merge: selection_merge, - selection: selection_selection, - order: selection_order, - sort: selection_sort, - call: selection_call, - nodes: selection_nodes, - node: selection_node, - size: selection_size, - empty: selection_empty, - each: selection_each, - attr: selection_attr, - style: selection_style, - property: selection_property, - classed: selection_classed, - text: selection_text, - html: selection_html, - raise: selection_raise, - lower: selection_lower, - append: selection_append, - insert: selection_insert, - remove: selection_remove, - clone: selection_clone, - datum: selection_datum, - on: selection_on, - dispatch: selection_dispatch, - [Symbol.iterator]: selection_iterator -}; - -function select(selector) { - return typeof selector === "string" - ? new Selection$1([[document.querySelector(selector)]], [document.documentElement]) - : new Selection$1([[selector]], root$1); -} - -var nextId = 0; - -function Local() { - this._ = "@" + (++nextId).toString(36); -} - -Local.prototype = { - constructor: Local, - get: function(node) { - var id = this._; - while (!(id in node)) if (!(node = node.parentNode)) return; - return node[id]; - }, - set: function(node, value) { - return node[this._] = value; - }, - remove: function(node) { - return this._ in node && delete node[this._]; - }, - toString: function() { - return this._; - } -}; - -function sourceEvent(event) { - let sourceEvent; - while (sourceEvent = event.sourceEvent) event = sourceEvent; - return event; -} - -function pointer(event, node) { - event = sourceEvent(event); - if (node === undefined) node = event.currentTarget; - if (node) { - var svg = node.ownerSVGElement || node; - if (svg.createSVGPoint) { - var point = svg.createSVGPoint(); - point.x = event.clientX, point.y = event.clientY; - point = point.matrixTransform(node.getScreenCTM().inverse()); - return [point.x, point.y]; - } - if (node.getBoundingClientRect) { - var rect = node.getBoundingClientRect(); - return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop]; - } - } - return [event.pageX, event.pageY]; -} - -function pointers(events, node) { - if (events.target) { // i.e., instanceof Event, not TouchList or iterable - events = sourceEvent(events); - if (node === undefined) node = events.currentTarget; - events = events.touches || [events]; - } - return Array.from(events, event => pointer(event, node)); -} - -// These are typically used in conjunction with noevent to ensure that we can -// preventDefault on the event. -const nonpassive = {passive: false}; -const nonpassivecapture = {capture: true, passive: false}; - -function nopropagation(event) { - event.stopImmediatePropagation(); -} - -function noevent(event) { - event.preventDefault(); - event.stopImmediatePropagation(); -} - -function nodrag(view) { - var root = view.document.documentElement, - selection = select(view).on("dragstart.drag", noevent, nonpassivecapture); - if ("onselectstart" in root) { - selection.on("selectstart.drag", noevent, nonpassivecapture); - } else { - root.__noselect = root.style.MozUserSelect; - root.style.MozUserSelect = "none"; - } -} - -function yesdrag(view, noclick) { - var root = view.document.documentElement, - selection = select(view).on("dragstart.drag", null); - if (noclick) { - selection.on("click.drag", noevent, nonpassivecapture); - setTimeout(function() { selection.on("click.drag", null); }, 0); - } - if ("onselectstart" in root) { - selection.on("selectstart.drag", null); - } else { - root.style.MozUserSelect = root.__noselect; - delete root.__noselect; - } -} - -var constant$2 = x => () => x; - -function DragEvent(type, { - sourceEvent, - subject, - target, - identifier, - active, - x, y, dx, dy, - dispatch -}) { - Object.defineProperties(this, { - type: {value: type, enumerable: true, configurable: true}, - sourceEvent: {value: sourceEvent, enumerable: true, configurable: true}, - subject: {value: subject, enumerable: true, configurable: true}, - target: {value: target, enumerable: true, configurable: true}, - identifier: {value: identifier, enumerable: true, configurable: true}, - active: {value: active, enumerable: true, configurable: true}, - x: {value: x, enumerable: true, configurable: true}, - y: {value: y, enumerable: true, configurable: true}, - dx: {value: dx, enumerable: true, configurable: true}, - dy: {value: dy, enumerable: true, configurable: true}, - _: {value: dispatch} - }); -} - -DragEvent.prototype.on = function() { - var value = this._.on.apply(this._, arguments); - return value === this._ ? this : value; -}; - -// Ignore right-click, since that should open the context menu. -function defaultFilter(event) { - return !event.ctrlKey && !event.button; -} - -function defaultContainer() { - return this.parentNode; -} - -function defaultSubject(event, d) { - return d == null ? {x: event.x, y: event.y} : d; -} - -function defaultTouchable() { - return navigator.maxTouchPoints || ("ontouchstart" in this); -} - -function drag() { - var filter = defaultFilter, - container = defaultContainer, - subject = defaultSubject, - touchable = defaultTouchable, - gestures = {}, - listeners = dispatch("start", "drag", "end"), - active = 0, - mousedownx, - mousedowny, - mousemoving, - touchending, - clickDistance2 = 0; - - function drag(selection) { - selection - .on("mousedown.drag", mousedowned) - .filter(touchable) - .on("touchstart.drag", touchstarted) - .on("touchmove.drag", touchmoved, nonpassive) - .on("touchend.drag touchcancel.drag", touchended) - .style("touch-action", "none") - .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)"); - } - - function mousedowned(event, d) { - if (touchending || !filter.call(this, event, d)) return; - var gesture = beforestart(this, container.call(this, event, d), event, d, "mouse"); - if (!gesture) return; - select(event.view) - .on("mousemove.drag", mousemoved, nonpassivecapture) - .on("mouseup.drag", mouseupped, nonpassivecapture); - nodrag(event.view); - nopropagation(event); - mousemoving = false; - mousedownx = event.clientX; - mousedowny = event.clientY; - gesture("start", event); - } - - function mousemoved(event) { - noevent(event); - if (!mousemoving) { - var dx = event.clientX - mousedownx, dy = event.clientY - mousedowny; - mousemoving = dx * dx + dy * dy > clickDistance2; - } - gestures.mouse("drag", event); - } - - function mouseupped(event) { - select(event.view).on("mousemove.drag mouseup.drag", null); - yesdrag(event.view, mousemoving); - noevent(event); - gestures.mouse("end", event); - } - - function touchstarted(event, d) { - if (!filter.call(this, event, d)) return; - var touches = event.changedTouches, - c = container.call(this, event, d), - n = touches.length, i, gesture; - - for (i = 0; i < n; ++i) { - if (gesture = beforestart(this, c, event, d, touches[i].identifier, touches[i])) { - nopropagation(event); - gesture("start", event, touches[i]); - } - } - } - - function touchmoved(event) { - var touches = event.changedTouches, - n = touches.length, i, gesture; - - for (i = 0; i < n; ++i) { - if (gesture = gestures[touches[i].identifier]) { - noevent(event); - gesture("drag", event, touches[i]); - } - } - } - - function touchended(event) { - var touches = event.changedTouches, - n = touches.length, i, gesture; - - if (touchending) clearTimeout(touchending); - touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed! - for (i = 0; i < n; ++i) { - if (gesture = gestures[touches[i].identifier]) { - nopropagation(event); - gesture("end", event, touches[i]); - } - } - } - - function beforestart(that, container, event, d, identifier, touch) { - var dispatch = listeners.copy(), - p = pointer(touch || event, container), dx, dy, - s; - - if ((s = subject.call(that, new DragEvent("beforestart", { - sourceEvent: event, - target: drag, - identifier, - active, - x: p[0], - y: p[1], - dx: 0, - dy: 0, - dispatch - }), d)) == null) return; - - dx = s.x - p[0] || 0; - dy = s.y - p[1] || 0; - - return function gesture(type, event, touch) { - var p0 = p, n; - switch (type) { - case "start": gestures[identifier] = gesture, n = active++; break; - case "end": delete gestures[identifier], --active; // falls through - case "drag": p = pointer(touch || event, container), n = active; break; - } - dispatch.call( - type, - that, - new DragEvent(type, { - sourceEvent: event, - subject: s, - target: drag, - identifier, - active: n, - x: p[0] + dx, - y: p[1] + dy, - dx: p[0] - p0[0], - dy: p[1] - p0[1], - dispatch - }), - d - ); - }; - } - - drag.filter = function(_) { - return arguments.length ? (filter = typeof _ === "function" ? _ : constant$2(!!_), drag) : filter; - }; - - drag.container = function(_) { - return arguments.length ? (container = typeof _ === "function" ? _ : constant$2(_), drag) : container; - }; - - drag.subject = function(_) { - return arguments.length ? (subject = typeof _ === "function" ? _ : constant$2(_), drag) : subject; - }; - - drag.touchable = function(_) { - return arguments.length ? (touchable = typeof _ === "function" ? _ : constant$2(!!_), drag) : touchable; - }; - - drag.on = function() { - var value = listeners.on.apply(listeners, arguments); - return value === listeners ? drag : value; - }; - - drag.clickDistance = function(_) { - return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2); - }; - - return drag; -} - -function ascending(a, b) { - return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; -} - -function descending(a, b) { - return a == null || b == null ? NaN - : b < a ? -1 - : b > a ? 1 - : b >= a ? 0 - : NaN; -} - -function bisector(f) { - let compare1, compare2, delta; - - // If an accessor is specified, promote it to a comparator. In this case we - // can test whether the search value is (self-) comparable. We can’t do this - // for a comparator (except for specific, known comparators) because we can’t - // tell if the comparator is symmetric, and an asymmetric comparator can’t be - // used to test whether a single value is comparable. - if (f.length !== 2) { - compare1 = ascending; - compare2 = (d, x) => ascending(f(d), x); - delta = (d, x) => f(d) - x; - } else { - compare1 = f === ascending || f === descending ? f : zero$1$1; - compare2 = f; - delta = f; - } - - function left(a, x, lo = 0, hi = a.length) { - if (lo < hi) { - if (compare1(x, x) !== 0) return hi; - do { - const mid = (lo + hi) >>> 1; - if (compare2(a[mid], x) < 0) lo = mid + 1; - else hi = mid; - } while (lo < hi); - } - return lo; - } - - function right(a, x, lo = 0, hi = a.length) { - if (lo < hi) { - if (compare1(x, x) !== 0) return hi; - do { - const mid = (lo + hi) >>> 1; - if (compare2(a[mid], x) <= 0) lo = mid + 1; - else hi = mid; - } while (lo < hi); - } - return lo; - } - - function center(a, x, lo = 0, hi = a.length) { - const i = left(a, x, lo, hi - 1); - return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i; - } - - return {left, center, right}; -} - -function zero$1$1() { - return 0; -} - -function number$2(x) { - return x === null ? NaN : +x; -} - -const ascendingBisect = bisector(ascending); -const bisectRight = ascendingBisect.right; -bisector(number$2).center; - -const e10 = Math.sqrt(50), - e5 = Math.sqrt(10), - e2 = Math.sqrt(2); - -function tickSpec(start, stop, count) { - const step = (stop - start) / Math.max(0, count), - power = Math.floor(Math.log10(step)), - error = step / Math.pow(10, power), - factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1; - let i1, i2, inc; - if (power < 0) { - inc = Math.pow(10, -power) / factor; - i1 = Math.round(start * inc); - i2 = Math.round(stop * inc); - if (i1 / inc < start) ++i1; - if (i2 / inc > stop) --i2; - inc = -inc; - } else { - inc = Math.pow(10, power) * factor; - i1 = Math.round(start / inc); - i2 = Math.round(stop / inc); - if (i1 * inc < start) ++i1; - if (i2 * inc > stop) --i2; - } - if (i2 < i1 && 0.5 <= count && count < 2) return tickSpec(start, stop, count * 2); - return [i1, i2, inc]; -} - -function ticks(start, stop, count) { - stop = +stop, start = +start, count = +count; - if (!(count > 0)) return []; - if (start === stop) return [start]; - const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count) : tickSpec(start, stop, count); - if (!(i2 >= i1)) return []; - const n = i2 - i1 + 1, ticks = new Array(n); - if (reverse) { - if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) / -inc; - else for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) * inc; - } else { - if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) / -inc; - else for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) * inc; - } - return ticks; -} - -function tickIncrement(start, stop, count) { - stop = +stop, start = +start, count = +count; - return tickSpec(start, stop, count)[2]; -} - -function tickStep(start, stop, count) { - stop = +stop, start = +start, count = +count; - const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count) : tickIncrement(start, stop, count); - return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc); -} - -function initRange(domain, range) { - switch (arguments.length) { - case 0: break; - case 1: this.range(domain); break; - default: this.range(range).domain(domain); break; - } - return this; -} - -var constant$1 = x => () => x; - -function linear$1(a, d) { - return function(t) { - return a + t * d; - }; -} - -function exponential(a, b, y) { - return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) { - return Math.pow(a + t * b, y); - }; -} - -function gamma$1(y) { - return (y = +y) === 1 ? nogamma : function(a, b) { - return b - a ? exponential(a, b, y) : constant$1(isNaN(a) ? b : a); - }; -} - -function nogamma(a, b) { - var d = b - a; - return d ? linear$1(a, d) : constant$1(isNaN(a) ? b : a); -} - -var interpolateRgb = (function rgbGamma(y) { - var color = gamma$1(y); - - function rgb$1(start, end) { - var r = color((start = rgb(start)).r, (end = rgb(end)).r), - g = color(start.g, end.g), - b = color(start.b, end.b), - opacity = nogamma(start.opacity, end.opacity); - return function(t) { - start.r = r(t); - start.g = g(t); - start.b = b(t); - start.opacity = opacity(t); - return start + ""; - }; - } - - rgb$1.gamma = rgbGamma; - - return rgb$1; -})(1); - -function numberArray(a, b) { - if (!b) b = []; - var n = a ? Math.min(b.length, a.length) : 0, - c = b.slice(), - i; - return function(t) { - for (i = 0; i < n; ++i) c[i] = a[i] * (1 - t) + b[i] * t; - return c; - }; -} - -function isNumberArray(x) { - return ArrayBuffer.isView(x) && !(x instanceof DataView); -} - -function genericArray(a, b) { - var nb = b ? b.length : 0, - na = a ? Math.min(nb, a.length) : 0, - x = new Array(na), - c = new Array(nb), - i; - - for (i = 0; i < na; ++i) x[i] = interpolate$1(a[i], b[i]); - for (; i < nb; ++i) c[i] = b[i]; - - return function(t) { - for (i = 0; i < na; ++i) c[i] = x[i](t); - return c; - }; -} - -function date$1(a, b) { - var d = new Date; - return a = +a, b = +b, function(t) { - return d.setTime(a * (1 - t) + b * t), d; - }; -} - -function interpolateNumber(a, b) { - return a = +a, b = +b, function(t) { - return a * (1 - t) + b * t; - }; -} - -function object(a, b) { - var i = {}, - c = {}, - k; - - if (a === null || typeof a !== "object") a = {}; - if (b === null || typeof b !== "object") b = {}; - - for (k in b) { - if (k in a) { - i[k] = interpolate$1(a[k], b[k]); - } else { - c[k] = b[k]; - } - } - - return function(t) { - for (k in i) c[k] = i[k](t); - return c; - }; -} - -var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, - reB = new RegExp(reA.source, "g"); - -function zero(b) { - return function() { - return b; - }; -} - -function one(b) { - return function(t) { - return b(t) + ""; - }; -} - -function interpolateString(a, b) { - var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b - am, // current match in a - bm, // current match in b - bs, // string preceding current number in b, if any - i = -1, // index in s - s = [], // string constants and placeholders - q = []; // number interpolators - - // Coerce inputs to strings. - a = a + "", b = b + ""; - - // Interpolate pairs of numbers in a & b. - while ((am = reA.exec(a)) - && (bm = reB.exec(b))) { - if ((bs = bm.index) > bi) { // a string precedes the next number in b - bs = b.slice(bi, bs); - if (s[i]) s[i] += bs; // coalesce with previous string - else s[++i] = bs; - } - if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match - if (s[i]) s[i] += bm; // coalesce with previous string - else s[++i] = bm; - } else { // interpolate non-matching numbers - s[++i] = null; - q.push({i: i, x: interpolateNumber(am, bm)}); - } - bi = reB.lastIndex; - } - - // Add remains of b. - if (bi < b.length) { - bs = b.slice(bi); - if (s[i]) s[i] += bs; // coalesce with previous string - else s[++i] = bs; - } - - // Special optimization for only a single match. - // Otherwise, interpolate each of the numbers and rejoin the string. - return s.length < 2 ? (q[0] - ? one(q[0].x) - : zero(b)) - : (b = q.length, function(t) { - for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }); -} - -function interpolate$1(a, b) { - var t = typeof b, c; - return b == null || t === "boolean" ? constant$1(b) - : (t === "number" ? interpolateNumber - : t === "string" ? ((c = color(b)) ? (b = c, interpolateRgb) : interpolateString) - : b instanceof color ? interpolateRgb - : b instanceof Date ? date$1 - : isNumberArray(b) ? numberArray - : Array.isArray(b) ? genericArray - : typeof b.valueOf !== "function" && typeof b.toString !== "function" || isNaN(b) ? object - : interpolateNumber)(a, b); -} - -function interpolateRound(a, b) { - return a = +a, b = +b, function(t) { - return Math.round(a * (1 - t) + b * t); - }; -} - -var degrees = 180 / Math.PI; - -var identity$3 = { - translateX: 0, - translateY: 0, - rotate: 0, - skewX: 0, - scaleX: 1, - scaleY: 1 -}; - -function decompose(a, b, c, d, e, f) { - var scaleX, scaleY, skewX; - if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX; - if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX; - if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY; - if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX; - return { - translateX: e, - translateY: f, - rotate: Math.atan2(b, a) * degrees, - skewX: Math.atan(skewX) * degrees, - scaleX: scaleX, - scaleY: scaleY - }; -} - -var svgNode; - -/* eslint-disable no-undef */ -function parseCss(value) { - const m = new (typeof DOMMatrix === "function" ? DOMMatrix : WebKitCSSMatrix)(value + ""); - return m.isIdentity ? identity$3 : decompose(m.a, m.b, m.c, m.d, m.e, m.f); -} - -function parseSvg(value) { - if (value == null) return identity$3; - if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g"); - svgNode.setAttribute("transform", value); - if (!(value = svgNode.transform.baseVal.consolidate())) return identity$3; - value = value.matrix; - return decompose(value.a, value.b, value.c, value.d, value.e, value.f); -} - -function interpolateTransform(parse, pxComma, pxParen, degParen) { - - function pop(s) { - return s.length ? s.pop() + " " : ""; - } - - function translate(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push("translate(", null, pxComma, null, pxParen); - q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); - } else if (xb || yb) { - s.push("translate(" + xb + pxComma + yb + pxParen); - } - } - - function rotate(a, b, s, q) { - if (a !== b) { - if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path - q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: interpolateNumber(a, b)}); - } else if (b) { - s.push(pop(s) + "rotate(" + b + degParen); - } - } - - function skewX(a, b, s, q) { - if (a !== b) { - q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: interpolateNumber(a, b)}); - } else if (b) { - s.push(pop(s) + "skewX(" + b + degParen); - } - } - - function scale(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push(pop(s) + "scale(", null, ",", null, ")"); - q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); - } else if (xb !== 1 || yb !== 1) { - s.push(pop(s) + "scale(" + xb + "," + yb + ")"); - } - } - - return function(a, b) { - var s = [], // string constants and placeholders - q = []; // number interpolators - a = parse(a), b = parse(b); - translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); - rotate(a.rotate, b.rotate, s, q); - skewX(a.skewX, b.skewX, s, q); - scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); - a = b = null; // gc - return function(t) { - var i = -1, n = q.length, o; - while (++i < n) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; - }; -} - -var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)"); -var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")"); - -function constants(x) { - return function() { - return x; - }; -} - -function number$1(x) { - return +x; -} - -var unit = [0, 1]; - -function identity$2(x) { - return x; -} - -function normalize$2(a, b) { - return (b -= (a = +a)) - ? function(x) { return (x - a) / b; } - : constants(isNaN(b) ? NaN : 0.5); -} - -function clamper(a, b) { - var t; - if (a > b) t = a, a = b, b = t; - return function(x) { return Math.max(a, Math.min(b, x)); }; -} - -// normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. -// interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. -function bimap(domain, range, interpolate) { - var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1]; - if (d1 < d0) d0 = normalize$2(d1, d0), r0 = interpolate(r1, r0); - else d0 = normalize$2(d0, d1), r0 = interpolate(r0, r1); - return function(x) { return r0(d0(x)); }; -} - -function polymap(domain, range, interpolate) { - var j = Math.min(domain.length, range.length) - 1, - d = new Array(j), - r = new Array(j), - i = -1; - - // Reverse descending domains. - if (domain[j] < domain[0]) { - domain = domain.slice().reverse(); - range = range.slice().reverse(); - } - - while (++i < j) { - d[i] = normalize$2(domain[i], domain[i + 1]); - r[i] = interpolate(range[i], range[i + 1]); - } - - return function(x) { - var i = bisectRight(domain, x, 1, j) - 1; - return r[i](d[i](x)); - }; -} - -function copy$1(source, target) { - return target - .domain(source.domain()) - .range(source.range()) - .interpolate(source.interpolate()) - .clamp(source.clamp()) - .unknown(source.unknown()); -} - -function transformer$2() { - var domain = unit, - range = unit, - interpolate = interpolate$1, - transform, - untransform, - unknown, - clamp = identity$2, - piecewise, - output, - input; - - function rescale() { - var n = Math.min(domain.length, range.length); - if (clamp !== identity$2) clamp = clamper(domain[0], domain[n - 1]); - piecewise = n > 2 ? polymap : bimap; - output = input = null; - return scale; - } - - function scale(x) { - return x == null || isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x))); - } - - scale.invert = function(y) { - return clamp(untransform((input || (input = piecewise(range, domain.map(transform), interpolateNumber)))(y))); - }; - - scale.domain = function(_) { - return arguments.length ? (domain = Array.from(_, number$1), rescale()) : domain.slice(); - }; - - scale.range = function(_) { - return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); - }; - - scale.rangeRound = function(_) { - return range = Array.from(_), interpolate = interpolateRound, rescale(); - }; - - scale.clamp = function(_) { - return arguments.length ? (clamp = _ ? true : identity$2, rescale()) : clamp !== identity$2; - }; - - scale.interpolate = function(_) { - return arguments.length ? (interpolate = _, rescale()) : interpolate; - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - return function(t, u) { - transform = t, untransform = u; - return rescale(); - }; -} - -function continuous() { - return transformer$2()(identity$2, identity$2); -} - -function formatDecimal(x) { - return Math.abs(x = Math.round(x)) >= 1e21 - ? x.toLocaleString("en").replace(/,/g, "") - : x.toString(10); -} - -// Computes the decimal coefficient and exponent of the specified number x with -// significant digits p, where x is positive and p is in [1, 21] or undefined. -// For example, formatDecimalParts(1.23) returns ["123", 0]. -function formatDecimalParts(x, p) { - if (!isFinite(x) || x === 0) return null; // NaN, ±Infinity, ±0 - var i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e"), coefficient = x.slice(0, i); - - // The string returned by toExponential either has the form \d\.\d+e[-+]\d+ - // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). - return [ - coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, - +x.slice(i + 1) - ]; -} - -function exponent(x) { - return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN; -} - -function formatGroup(grouping, thousands) { - return function(value, width) { - var i = value.length, - t = [], - j = 0, - g = grouping[0], - length = 0; - - while (i > 0 && g > 0) { - if (length + g + 1 > width) g = Math.max(1, width - length); - t.push(value.substring(i -= g, i + g)); - if ((length += g + 1) > width) break; - g = grouping[j = (j + 1) % grouping.length]; - } - - return t.reverse().join(thousands); - }; -} - -function formatNumerals(numerals) { - return function(value) { - return value.replace(/[0-9]/g, function(i) { - return numerals[+i]; - }); - }; -} - -// [[fill]align][sign][symbol][0][width][,][.precision][~][type] -var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; - -function formatSpecifier(specifier) { - if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); - var match; - return new FormatSpecifier({ - fill: match[1], - align: match[2], - sign: match[3], - symbol: match[4], - zero: match[5], - width: match[6], - comma: match[7], - precision: match[8] && match[8].slice(1), - trim: match[9], - type: match[10] - }); -} - -formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof - -function FormatSpecifier(specifier) { - this.fill = specifier.fill === undefined ? " " : specifier.fill + ""; - this.align = specifier.align === undefined ? ">" : specifier.align + ""; - this.sign = specifier.sign === undefined ? "-" : specifier.sign + ""; - this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + ""; - this.zero = !!specifier.zero; - this.width = specifier.width === undefined ? undefined : +specifier.width; - this.comma = !!specifier.comma; - this.precision = specifier.precision === undefined ? undefined : +specifier.precision; - this.trim = !!specifier.trim; - this.type = specifier.type === undefined ? "" : specifier.type + ""; -} - -FormatSpecifier.prototype.toString = function() { - return this.fill - + this.align - + this.sign - + this.symbol - + (this.zero ? "0" : "") - + (this.width === undefined ? "" : Math.max(1, this.width | 0)) - + (this.comma ? "," : "") - + (this.precision === undefined ? "" : "." + Math.max(0, this.precision | 0)) - + (this.trim ? "~" : "") - + this.type; -}; - -// Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k. -function formatTrim(s) { - out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) { - switch (s[i]) { - case ".": i0 = i1 = i; break; - case "0": if (i0 === 0) i0 = i; i1 = i; break; - default: if (!+s[i]) break out; if (i0 > 0) i0 = 0; break; - } - } - return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; -} - -var prefixExponent; - -function formatPrefixAuto(x, p) { - var d = formatDecimalParts(x, p); - if (!d) return prefixExponent = undefined, x.toPrecision(p); - var coefficient = d[0], - exponent = d[1], - i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, - n = coefficient.length; - return i === n ? coefficient - : i > n ? coefficient + new Array(i - n + 1).join("0") - : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) - : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y! -} - -function formatRounded(x, p) { - var d = formatDecimalParts(x, p); - if (!d) return x + ""; - var coefficient = d[0], - exponent = d[1]; - return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient - : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) - : coefficient + new Array(exponent - coefficient.length + 2).join("0"); -} - -var formatTypes = { - "%": (x, p) => (x * 100).toFixed(p), - "b": (x) => Math.round(x).toString(2), - "c": (x) => x + "", - "d": formatDecimal, - "e": (x, p) => x.toExponential(p), - "f": (x, p) => x.toFixed(p), - "g": (x, p) => x.toPrecision(p), - "o": (x) => Math.round(x).toString(8), - "p": (x, p) => formatRounded(x * 100, p), - "r": formatRounded, - "s": formatPrefixAuto, - "X": (x) => Math.round(x).toString(16).toUpperCase(), - "x": (x) => Math.round(x).toString(16) -}; - -function identity$1(x) { - return x; -} - -var map = Array.prototype.map, - prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"]; - -function formatLocale$1(locale) { - var group = locale.grouping === undefined || locale.thousands === undefined ? identity$1 : formatGroup(map.call(locale.grouping, Number), locale.thousands + ""), - currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "", - currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "", - decimal = locale.decimal === undefined ? "." : locale.decimal + "", - numerals = locale.numerals === undefined ? identity$1 : formatNumerals(map.call(locale.numerals, String)), - percent = locale.percent === undefined ? "%" : locale.percent + "", - minus = locale.minus === undefined ? "−" : locale.minus + "", - nan = locale.nan === undefined ? "NaN" : locale.nan + ""; - - function newFormat(specifier, options) { - specifier = formatSpecifier(specifier); - - var fill = specifier.fill, - align = specifier.align, - sign = specifier.sign, - symbol = specifier.symbol, - zero = specifier.zero, - width = specifier.width, - comma = specifier.comma, - precision = specifier.precision, - trim = specifier.trim, - type = specifier.type; - - // The "n" type is an alias for ",g". - if (type === "n") comma = true, type = "g"; - - // The "" type, and any invalid type, is an alias for ".12~g". - else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = "g"; - - // If zero fill is specified, padding goes after sign and before digits. - if (zero || (fill === "0" && align === "=")) zero = true, fill = "0", align = "="; - - // Compute the prefix and suffix. - // For SI-prefix, the suffix is lazily computed. - var prefix = (options && options.prefix !== undefined ? options.prefix : "") + (symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : ""), - suffix = (symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : "") + (options && options.suffix !== undefined ? options.suffix : ""); - - // What format function should we use? - // Is this an integer type? - // Can this type generate exponential notation? - var formatType = formatTypes[type], - maybeSuffix = /[defgprs%]/.test(type); - - // Set the default precision if not specified, - // or clamp the specified precision to the supported range. - // For significant precision, it must be in [1, 21]. - // For fixed precision, it must be in [0, 20]. - precision = precision === undefined ? 6 - : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) - : Math.max(0, Math.min(20, precision)); - - function format(value) { - var valuePrefix = prefix, - valueSuffix = suffix, - i, n, c; - - if (type === "c") { - valueSuffix = formatType(value) + valueSuffix; - value = ""; - } else { - value = +value; - - // Determine the sign. -0 is not less than 0, but 1 / -0 is! - var valueNegative = value < 0 || 1 / value < 0; - - // Perform the initial formatting. - value = isNaN(value) ? nan : formatType(Math.abs(value), precision); - - // Trim insignificant zeros. - if (trim) value = formatTrim(value); - - // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign. - if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; - - // Compute the prefix and suffix. - valuePrefix = (valueNegative ? (sign === "(" ? sign : minus) : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; - valueSuffix = (type === "s" && !isNaN(value) && prefixExponent !== undefined ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); - - // Break the formatted value into the integer “value” part that can be - // grouped, and fractional or exponential “suffix” part that is not. - if (maybeSuffix) { - i = -1, n = value.length; - while (++i < n) { - if (c = value.charCodeAt(i), 48 > c || c > 57) { - valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; - value = value.slice(0, i); - break; - } - } - } - } - - // If the fill character is not "0", grouping is applied before padding. - if (comma && !zero) value = group(value, Infinity); - - // Compute the padding. - var length = valuePrefix.length + value.length + valueSuffix.length, - padding = length < width ? new Array(width - length + 1).join(fill) : ""; - - // If the fill character is "0", grouping is applied after padding. - if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; - - // Reconstruct the final output based on the desired alignment. - switch (align) { - case "<": value = valuePrefix + value + valueSuffix + padding; break; - case "=": value = valuePrefix + padding + value + valueSuffix; break; - case "^": value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); break; - default: value = padding + valuePrefix + value + valueSuffix; break; - } - - return numerals(value); - } - - format.toString = function() { - return specifier + ""; - }; - - return format; - } - - function formatPrefix(specifier, value) { - var e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, - k = Math.pow(10, -e), - f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier), {suffix: prefixes[8 + e / 3]}); - return function(value) { - return f(k * value); - }; - } - - return { - format: newFormat, - formatPrefix: formatPrefix - }; -} - -var locale$1; -var format; -var formatPrefix; - -defaultLocale$1({ - thousands: ",", - grouping: [3], - currency: ["$", ""] -}); - -function defaultLocale$1(definition) { - locale$1 = formatLocale$1(definition); - format = locale$1.format; - formatPrefix = locale$1.formatPrefix; - return locale$1; -} - -function precisionFixed(step) { - return Math.max(0, -exponent(Math.abs(step))); -} - -function precisionPrefix(step, value) { - return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); -} - -function precisionRound(step, max) { - step = Math.abs(step), max = Math.abs(max) - step; - return Math.max(0, exponent(max) - exponent(step)) + 1; -} - -function tickFormat(start, stop, count, specifier) { - var step = tickStep(start, stop, count), - precision; - specifier = formatSpecifier(specifier == null ? ",f" : specifier); - switch (specifier.type) { - case "s": { - var value = Math.max(Math.abs(start), Math.abs(stop)); - if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision; - return formatPrefix(specifier, value); - } - case "": - case "e": - case "g": - case "p": - case "r": { - if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e"); - break; - } - case "f": - case "%": { - if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2; - break; - } - } - return format(specifier); -} - -function linearish(scale) { - var domain = scale.domain; - - scale.ticks = function(count) { - var d = domain(); - return ticks(d[0], d[d.length - 1], count == null ? 10 : count); - }; - - scale.tickFormat = function(count, specifier) { - var d = domain(); - return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier); - }; - - scale.nice = function(count) { - if (count == null) count = 10; - - var d = domain(); - var i0 = 0; - var i1 = d.length - 1; - var start = d[i0]; - var stop = d[i1]; - var prestep; - var step; - var maxIter = 10; - - if (stop < start) { - step = start, start = stop, stop = step; - step = i0, i0 = i1, i1 = step; - } - - while (maxIter-- > 0) { - step = tickIncrement(start, stop, count); - if (step === prestep) { - d[i0] = start; - d[i1] = stop; - return domain(d); - } else if (step > 0) { - start = Math.floor(start / step) * step; - stop = Math.ceil(stop / step) * step; - } else if (step < 0) { - start = Math.ceil(start * step) / step; - stop = Math.floor(stop * step) / step; - } else { - break; - } - prestep = step; - } - - return scale; - }; - - return scale; -} - -function linear() { - var scale = continuous(); - - scale.copy = function() { - return copy$1(scale, linear()); - }; - - initRange.apply(scale, arguments); - - return linearish(scale); -} - -function nice(domain, interval) { - domain = domain.slice(); - - var i0 = 0, - i1 = domain.length - 1, - x0 = domain[i0], - x1 = domain[i1], - t; - - if (x1 < x0) { - t = i0, i0 = i1, i1 = t; - t = x0, x0 = x1, x1 = t; - } - - domain[i0] = interval.floor(x0); - domain[i1] = interval.ceil(x1); - return domain; -} - -function transformLog(x) { - return Math.log(x); -} - -function transformExp(x) { - return Math.exp(x); -} - -function transformLogn(x) { - return -Math.log(-x); -} - -function transformExpn(x) { - return -Math.exp(-x); -} - -function pow10(x) { - return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; -} - -function powp(base) { - return base === 10 ? pow10 - : base === Math.E ? Math.exp - : x => Math.pow(base, x); -} - -function logp(base) { - return base === Math.E ? Math.log - : base === 10 && Math.log10 - || base === 2 && Math.log2 - || (base = Math.log(base), x => Math.log(x) / base); -} - -function reflect(f) { - return (x, k) => -f(-x, k); -} - -function loggish(transform) { - const scale = transform(transformLog, transformExp); - const domain = scale.domain; - let base = 10; - let logs; - let pows; - - function rescale() { - logs = logp(base), pows = powp(base); - if (domain()[0] < 0) { - logs = reflect(logs), pows = reflect(pows); - transform(transformLogn, transformExpn); - } else { - transform(transformLog, transformExp); - } - return scale; - } - - scale.base = function(_) { - return arguments.length ? (base = +_, rescale()) : base; - }; - - scale.domain = function(_) { - return arguments.length ? (domain(_), rescale()) : domain(); - }; - - scale.ticks = count => { - const d = domain(); - let u = d[0]; - let v = d[d.length - 1]; - const r = v < u; - - if (r) ([u, v] = [v, u]); - - let i = logs(u); - let j = logs(v); - let k; - let t; - const n = count == null ? 10 : +count; - let z = []; - - if (!(base % 1) && j - i < n) { - i = Math.floor(i), j = Math.ceil(j); - if (u > 0) for (; i <= j; ++i) { - for (k = 1; k < base; ++k) { - t = i < 0 ? k / pows(-i) : k * pows(i); - if (t < u) continue; - if (t > v) break; - z.push(t); - } - } else for (; i <= j; ++i) { - for (k = base - 1; k >= 1; --k) { - t = i > 0 ? k / pows(-i) : k * pows(i); - if (t < u) continue; - if (t > v) break; - z.push(t); - } - } - if (z.length * 2 < n) z = ticks(u, v, n); - } else { - z = ticks(i, j, Math.min(j - i, n)).map(pows); - } - return r ? z.reverse() : z; - }; - - scale.tickFormat = (count, specifier) => { - if (count == null) count = 10; - if (specifier == null) specifier = base === 10 ? "s" : ","; - if (typeof specifier !== "function") { - if (!(base % 1) && (specifier = formatSpecifier(specifier)).precision == null) specifier.trim = true; - specifier = format(specifier); - } - if (count === Infinity) return specifier; - const k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate? - return d => { - let i = d / pows(Math.round(logs(d))); - if (i * base < base - 0.5) i *= base; - return i <= k ? specifier(d) : ""; - }; - }; - - scale.nice = () => { - return domain(nice(domain(), { - floor: x => pows(Math.floor(logs(x))), - ceil: x => pows(Math.ceil(logs(x))) - })); - }; - - return scale; -} - -function log$1() { - const scale = loggish(transformer$2()).domain([1, 10]); - scale.copy = () => copy$1(scale, log$1()).base(scale.base()); - initRange.apply(scale, arguments); - return scale; -} - -function transformSymlog(c) { - return function(x) { - return Math.sign(x) * Math.log1p(Math.abs(x / c)); - }; -} - -function transformSymexp(c) { - return function(x) { - return Math.sign(x) * Math.expm1(Math.abs(x)) * c; - }; -} - -function symlogish(transform) { - var c = 1, scale = transform(transformSymlog(c), transformSymexp(c)); - - scale.constant = function(_) { - return arguments.length ? transform(transformSymlog(c = +_), transformSymexp(c)) : c; - }; - - return linearish(scale); -} - -function symlog() { - var scale = symlogish(transformer$2()); - - scale.copy = function() { - return copy$1(scale, symlog()).constant(scale.constant()); - }; - - return initRange.apply(scale, arguments); -} - -const t0 = new Date, t1 = new Date; - -function timeInterval(floori, offseti, count, field) { - - function interval(date) { - return floori(date = arguments.length === 0 ? new Date : new Date(+date)), date; - } - - interval.floor = (date) => { - return floori(date = new Date(+date)), date; - }; - - interval.ceil = (date) => { - return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date; - }; - - interval.round = (date) => { - const d0 = interval(date), d1 = interval.ceil(date); - return date - d0 < d1 - date ? d0 : d1; - }; - - interval.offset = (date, step) => { - return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date; - }; - - interval.range = (start, stop, step) => { - const range = []; - start = interval.ceil(start); - step = step == null ? 1 : Math.floor(step); - if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date - let previous; - do range.push(previous = new Date(+start)), offseti(start, step), floori(start); - while (previous < start && start < stop); - return range; - }; - - interval.filter = (test) => { - return timeInterval((date) => { - if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1); - }, (date, step) => { - if (date >= date) { - if (step < 0) while (++step <= 0) { - while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty - } else while (--step >= 0) { - while (offseti(date, 1), !test(date)) {} // eslint-disable-line no-empty - } - } - }); - }; - - if (count) { - interval.count = (start, end) => { - t0.setTime(+start), t1.setTime(+end); - floori(t0), floori(t1); - return Math.floor(count(t0, t1)); - }; - - interval.every = (step) => { - step = Math.floor(step); - return !isFinite(step) || !(step > 0) ? null - : !(step > 1) ? interval - : interval.filter(field - ? (d) => field(d) % step === 0 - : (d) => interval.count(0, d) % step === 0); - }; - } - - return interval; -} - -const millisecond = timeInterval(() => { - // noop -}, (date, step) => { - date.setTime(+date + step); -}, (start, end) => { - return end - start; -}); - -// An optimized implementation for this simple case. -millisecond.every = (k) => { - k = Math.floor(k); - if (!isFinite(k) || !(k > 0)) return null; - if (!(k > 1)) return millisecond; - return timeInterval((date) => { - date.setTime(Math.floor(date / k) * k); - }, (date, step) => { - date.setTime(+date + step * k); - }, (start, end) => { - return (end - start) / k; - }); -}; - -millisecond.range; - -const durationSecond = 1000; -const durationMinute = durationSecond * 60; -const durationHour = durationMinute * 60; -const durationDay = durationHour * 24; -const durationWeek = durationDay * 7; -const durationMonth = durationDay * 30; -const durationYear = durationDay * 365; - -const second = timeInterval((date) => { - date.setTime(date - date.getMilliseconds()); -}, (date, step) => { - date.setTime(+date + step * durationSecond); -}, (start, end) => { - return (end - start) / durationSecond; -}, (date) => { - return date.getUTCSeconds(); -}); - -second.range; - -const timeMinute = timeInterval((date) => { - date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond); -}, (date, step) => { - date.setTime(+date + step * durationMinute); -}, (start, end) => { - return (end - start) / durationMinute; -}, (date) => { - return date.getMinutes(); -}); - -timeMinute.range; - -const utcMinute = timeInterval((date) => { - date.setUTCSeconds(0, 0); -}, (date, step) => { - date.setTime(+date + step * durationMinute); -}, (start, end) => { - return (end - start) / durationMinute; -}, (date) => { - return date.getUTCMinutes(); -}); - -utcMinute.range; - -const timeHour = timeInterval((date) => { - date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute); -}, (date, step) => { - date.setTime(+date + step * durationHour); -}, (start, end) => { - return (end - start) / durationHour; -}, (date) => { - return date.getHours(); -}); - -timeHour.range; - -const utcHour = timeInterval((date) => { - date.setUTCMinutes(0, 0, 0); -}, (date, step) => { - date.setTime(+date + step * durationHour); -}, (start, end) => { - return (end - start) / durationHour; -}, (date) => { - return date.getUTCHours(); -}); - -utcHour.range; - -const timeDay = timeInterval( - date => date.setHours(0, 0, 0, 0), - (date, step) => date.setDate(date.getDate() + step), - (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay, - date => date.getDate() - 1 -); - -timeDay.range; - -const utcDay = timeInterval((date) => { - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCDate(date.getUTCDate() + step); -}, (start, end) => { - return (end - start) / durationDay; -}, (date) => { - return date.getUTCDate() - 1; -}); - -utcDay.range; - -const unixDay = timeInterval((date) => { - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCDate(date.getUTCDate() + step); -}, (start, end) => { - return (end - start) / durationDay; -}, (date) => { - return Math.floor(date / durationDay); -}); - -unixDay.range; - -function timeWeekday(i) { - return timeInterval((date) => { - date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); - date.setHours(0, 0, 0, 0); - }, (date, step) => { - date.setDate(date.getDate() + step * 7); - }, (start, end) => { - return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek; - }); -} - -const timeSunday = timeWeekday(0); -const timeMonday = timeWeekday(1); -const timeTuesday = timeWeekday(2); -const timeWednesday = timeWeekday(3); -const timeThursday = timeWeekday(4); -const timeFriday = timeWeekday(5); -const timeSaturday = timeWeekday(6); - -timeSunday.range; -timeMonday.range; -timeTuesday.range; -timeWednesday.range; -timeThursday.range; -timeFriday.range; -timeSaturday.range; - -function utcWeekday(i) { - return timeInterval((date) => { - date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); - date.setUTCHours(0, 0, 0, 0); - }, (date, step) => { - date.setUTCDate(date.getUTCDate() + step * 7); - }, (start, end) => { - return (end - start) / durationWeek; - }); -} - -const utcSunday = utcWeekday(0); -const utcMonday = utcWeekday(1); -const utcTuesday = utcWeekday(2); -const utcWednesday = utcWeekday(3); -const utcThursday = utcWeekday(4); -const utcFriday = utcWeekday(5); -const utcSaturday = utcWeekday(6); - -utcSunday.range; -utcMonday.range; -utcTuesday.range; -utcWednesday.range; -utcThursday.range; -utcFriday.range; -utcSaturday.range; - -const timeMonth = timeInterval((date) => { - date.setDate(1); - date.setHours(0, 0, 0, 0); -}, (date, step) => { - date.setMonth(date.getMonth() + step); -}, (start, end) => { - return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; -}, (date) => { - return date.getMonth(); -}); - -timeMonth.range; - -const utcMonth = timeInterval((date) => { - date.setUTCDate(1); - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCMonth(date.getUTCMonth() + step); -}, (start, end) => { - return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; -}, (date) => { - return date.getUTCMonth(); -}); - -utcMonth.range; - -const timeYear = timeInterval((date) => { - date.setMonth(0, 1); - date.setHours(0, 0, 0, 0); -}, (date, step) => { - date.setFullYear(date.getFullYear() + step); -}, (start, end) => { - return end.getFullYear() - start.getFullYear(); -}, (date) => { - return date.getFullYear(); -}); - -// An optimized implementation for this simple case. -timeYear.every = (k) => { - return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { - date.setFullYear(Math.floor(date.getFullYear() / k) * k); - date.setMonth(0, 1); - date.setHours(0, 0, 0, 0); - }, (date, step) => { - date.setFullYear(date.getFullYear() + step * k); - }); -}; - -timeYear.range; - -const utcYear = timeInterval((date) => { - date.setUTCMonth(0, 1); - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCFullYear(date.getUTCFullYear() + step); -}, (start, end) => { - return end.getUTCFullYear() - start.getUTCFullYear(); -}, (date) => { - return date.getUTCFullYear(); -}); - -// An optimized implementation for this simple case. -utcYear.every = (k) => { - return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { - date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); - date.setUTCMonth(0, 1); - date.setUTCHours(0, 0, 0, 0); - }, (date, step) => { - date.setUTCFullYear(date.getUTCFullYear() + step * k); - }); -}; - -utcYear.range; - -function ticker(year, month, week, day, hour, minute) { - - const tickIntervals = [ - [second, 1, durationSecond], - [second, 5, 5 * durationSecond], - [second, 15, 15 * durationSecond], - [second, 30, 30 * durationSecond], - [minute, 1, durationMinute], - [minute, 5, 5 * durationMinute], - [minute, 15, 15 * durationMinute], - [minute, 30, 30 * durationMinute], - [ hour, 1, durationHour ], - [ hour, 3, 3 * durationHour ], - [ hour, 6, 6 * durationHour ], - [ hour, 12, 12 * durationHour ], - [ day, 1, durationDay ], - [ day, 2, 2 * durationDay ], - [ week, 1, durationWeek ], - [ month, 1, durationMonth ], - [ month, 3, 3 * durationMonth ], - [ year, 1, durationYear ] - ]; - - function ticks(start, stop, count) { - const reverse = stop < start; - if (reverse) [start, stop] = [stop, start]; - const interval = count && typeof count.range === "function" ? count : tickInterval(start, stop, count); - const ticks = interval ? interval.range(start, +stop + 1) : []; // inclusive stop - return reverse ? ticks.reverse() : ticks; - } - - function tickInterval(start, stop, count) { - const target = Math.abs(stop - start) / count; - const i = bisector(([,, step]) => step).right(tickIntervals, target); - if (i === tickIntervals.length) return year.every(tickStep(start / durationYear, stop / durationYear, count)); - if (i === 0) return millisecond.every(Math.max(tickStep(start, stop, count), 1)); - const [t, step] = tickIntervals[target / tickIntervals[i - 1][2] < tickIntervals[i][2] / target ? i - 1 : i]; - return t.every(step); - } - - return [ticks, tickInterval]; -} -const [timeTicks, timeTickInterval] = ticker(timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute); - -function localDate(d) { - if (0 <= d.y && d.y < 100) { - var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); - date.setFullYear(d.y); - return date; - } - return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); -} - -function utcDate(d) { - if (0 <= d.y && d.y < 100) { - var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); - date.setUTCFullYear(d.y); - return date; - } - return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); -} - -function newDate(y, m, d) { - return {y: y, m: m, d: d, H: 0, M: 0, S: 0, L: 0}; -} - -function formatLocale(locale) { - var locale_dateTime = locale.dateTime, - locale_date = locale.date, - locale_time = locale.time, - locale_periods = locale.periods, - locale_weekdays = locale.days, - locale_shortWeekdays = locale.shortDays, - locale_months = locale.months, - locale_shortMonths = locale.shortMonths; - - var periodRe = formatRe(locale_periods), - periodLookup = formatLookup(locale_periods), - weekdayRe = formatRe(locale_weekdays), - weekdayLookup = formatLookup(locale_weekdays), - shortWeekdayRe = formatRe(locale_shortWeekdays), - shortWeekdayLookup = formatLookup(locale_shortWeekdays), - monthRe = formatRe(locale_months), - monthLookup = formatLookup(locale_months), - shortMonthRe = formatRe(locale_shortMonths), - shortMonthLookup = formatLookup(locale_shortMonths); - - var formats = { - "a": formatShortWeekday, - "A": formatWeekday, - "b": formatShortMonth, - "B": formatMonth, - "c": null, - "d": formatDayOfMonth, - "e": formatDayOfMonth, - "f": formatMicroseconds, - "g": formatYearISO, - "G": formatFullYearISO, - "H": formatHour24, - "I": formatHour12, - "j": formatDayOfYear, - "L": formatMilliseconds, - "m": formatMonthNumber, - "M": formatMinutes, - "p": formatPeriod, - "q": formatQuarter, - "Q": formatUnixTimestamp, - "s": formatUnixTimestampSeconds, - "S": formatSeconds, - "u": formatWeekdayNumberMonday, - "U": formatWeekNumberSunday, - "V": formatWeekNumberISO, - "w": formatWeekdayNumberSunday, - "W": formatWeekNumberMonday, - "x": null, - "X": null, - "y": formatYear, - "Y": formatFullYear, - "Z": formatZone, - "%": formatLiteralPercent - }; - - var utcFormats = { - "a": formatUTCShortWeekday, - "A": formatUTCWeekday, - "b": formatUTCShortMonth, - "B": formatUTCMonth, - "c": null, - "d": formatUTCDayOfMonth, - "e": formatUTCDayOfMonth, - "f": formatUTCMicroseconds, - "g": formatUTCYearISO, - "G": formatUTCFullYearISO, - "H": formatUTCHour24, - "I": formatUTCHour12, - "j": formatUTCDayOfYear, - "L": formatUTCMilliseconds, - "m": formatUTCMonthNumber, - "M": formatUTCMinutes, - "p": formatUTCPeriod, - "q": formatUTCQuarter, - "Q": formatUnixTimestamp, - "s": formatUnixTimestampSeconds, - "S": formatUTCSeconds, - "u": formatUTCWeekdayNumberMonday, - "U": formatUTCWeekNumberSunday, - "V": formatUTCWeekNumberISO, - "w": formatUTCWeekdayNumberSunday, - "W": formatUTCWeekNumberMonday, - "x": null, - "X": null, - "y": formatUTCYear, - "Y": formatUTCFullYear, - "Z": formatUTCZone, - "%": formatLiteralPercent - }; - - var parses = { - "a": parseShortWeekday, - "A": parseWeekday, - "b": parseShortMonth, - "B": parseMonth, - "c": parseLocaleDateTime, - "d": parseDayOfMonth, - "e": parseDayOfMonth, - "f": parseMicroseconds, - "g": parseYear, - "G": parseFullYear, - "H": parseHour24, - "I": parseHour24, - "j": parseDayOfYear, - "L": parseMilliseconds, - "m": parseMonthNumber, - "M": parseMinutes, - "p": parsePeriod, - "q": parseQuarter, - "Q": parseUnixTimestamp, - "s": parseUnixTimestampSeconds, - "S": parseSeconds, - "u": parseWeekdayNumberMonday, - "U": parseWeekNumberSunday, - "V": parseWeekNumberISO, - "w": parseWeekdayNumberSunday, - "W": parseWeekNumberMonday, - "x": parseLocaleDate, - "X": parseLocaleTime, - "y": parseYear, - "Y": parseFullYear, - "Z": parseZone, - "%": parseLiteralPercent - }; - - // These recursive directive definitions must be deferred. - formats.x = newFormat(locale_date, formats); - formats.X = newFormat(locale_time, formats); - formats.c = newFormat(locale_dateTime, formats); - utcFormats.x = newFormat(locale_date, utcFormats); - utcFormats.X = newFormat(locale_time, utcFormats); - utcFormats.c = newFormat(locale_dateTime, utcFormats); - - function newFormat(specifier, formats) { - return function(date) { - var string = [], - i = -1, - j = 0, - n = specifier.length, - c, - pad, - format; - - if (!(date instanceof Date)) date = new Date(+date); - - while (++i < n) { - if (specifier.charCodeAt(i) === 37) { - string.push(specifier.slice(j, i)); - if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i); - else pad = c === "e" ? " " : "0"; - if (format = formats[c]) c = format(date, pad); - string.push(c); - j = i + 1; - } - } - - string.push(specifier.slice(j, i)); - return string.join(""); - }; - } - - function newParse(specifier, Z) { - return function(string) { - var d = newDate(1900, undefined, 1), - i = parseSpecifier(d, specifier, string += "", 0), - week, day; - if (i != string.length) return null; - - // If a UNIX timestamp is specified, return it. - if ("Q" in d) return new Date(d.Q); - if ("s" in d) return new Date(d.s * 1000 + ("L" in d ? d.L : 0)); - - // If this is utcParse, never use the local timezone. - if (Z && !("Z" in d)) d.Z = 0; - - // The am-pm flag is 0 for AM, and 1 for PM. - if ("p" in d) d.H = d.H % 12 + d.p * 12; - - // If the month was not specified, inherit from the quarter. - if (d.m === undefined) d.m = "q" in d ? d.q : 0; - - // Convert day-of-week and week-of-year to day-of-year. - if ("V" in d) { - if (d.V < 1 || d.V > 53) return null; - if (!("w" in d)) d.w = 1; - if ("Z" in d) { - week = utcDate(newDate(d.y, 0, 1)), day = week.getUTCDay(); - week = day > 4 || day === 0 ? utcMonday.ceil(week) : utcMonday(week); - week = utcDay.offset(week, (d.V - 1) * 7); - d.y = week.getUTCFullYear(); - d.m = week.getUTCMonth(); - d.d = week.getUTCDate() + (d.w + 6) % 7; - } else { - week = localDate(newDate(d.y, 0, 1)), day = week.getDay(); - week = day > 4 || day === 0 ? timeMonday.ceil(week) : timeMonday(week); - week = timeDay.offset(week, (d.V - 1) * 7); - d.y = week.getFullYear(); - d.m = week.getMonth(); - d.d = week.getDate() + (d.w + 6) % 7; - } - } else if ("W" in d || "U" in d) { - if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0; - day = "Z" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay(); - d.m = 0; - d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day + 5) % 7 : d.w + d.U * 7 - (day + 6) % 7; - } - - // If a time zone is specified, all fields are interpreted as UTC and then - // offset according to the specified time zone. - if ("Z" in d) { - d.H += d.Z / 100 | 0; - d.M += d.Z % 100; - return utcDate(d); - } - - // Otherwise, all fields are in local time. - return localDate(d); - }; - } - - function parseSpecifier(d, specifier, string, j) { - var i = 0, - n = specifier.length, - m = string.length, - c, - parse; - - while (i < n) { - if (j >= m) return -1; - c = specifier.charCodeAt(i++); - if (c === 37) { - c = specifier.charAt(i++); - parse = parses[c in pads ? specifier.charAt(i++) : c]; - if (!parse || ((j = parse(d, string, j)) < 0)) return -1; - } else if (c != string.charCodeAt(j++)) { - return -1; - } - } - - return j; - } - - function parsePeriod(d, string, i) { - var n = periodRe.exec(string.slice(i)); - return n ? (d.p = periodLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseShortWeekday(d, string, i) { - var n = shortWeekdayRe.exec(string.slice(i)); - return n ? (d.w = shortWeekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseWeekday(d, string, i) { - var n = weekdayRe.exec(string.slice(i)); - return n ? (d.w = weekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseShortMonth(d, string, i) { - var n = shortMonthRe.exec(string.slice(i)); - return n ? (d.m = shortMonthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseMonth(d, string, i) { - var n = monthRe.exec(string.slice(i)); - return n ? (d.m = monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseLocaleDateTime(d, string, i) { - return parseSpecifier(d, locale_dateTime, string, i); - } - - function parseLocaleDate(d, string, i) { - return parseSpecifier(d, locale_date, string, i); - } - - function parseLocaleTime(d, string, i) { - return parseSpecifier(d, locale_time, string, i); - } - - function formatShortWeekday(d) { - return locale_shortWeekdays[d.getDay()]; - } - - function formatWeekday(d) { - return locale_weekdays[d.getDay()]; - } - - function formatShortMonth(d) { - return locale_shortMonths[d.getMonth()]; - } - - function formatMonth(d) { - return locale_months[d.getMonth()]; - } - - function formatPeriod(d) { - return locale_periods[+(d.getHours() >= 12)]; - } - - function formatQuarter(d) { - return 1 + ~~(d.getMonth() / 3); - } - - function formatUTCShortWeekday(d) { - return locale_shortWeekdays[d.getUTCDay()]; - } - - function formatUTCWeekday(d) { - return locale_weekdays[d.getUTCDay()]; - } - - function formatUTCShortMonth(d) { - return locale_shortMonths[d.getUTCMonth()]; - } - - function formatUTCMonth(d) { - return locale_months[d.getUTCMonth()]; - } - - function formatUTCPeriod(d) { - return locale_periods[+(d.getUTCHours() >= 12)]; - } - - function formatUTCQuarter(d) { - return 1 + ~~(d.getUTCMonth() / 3); - } - - return { - format: function(specifier) { - var f = newFormat(specifier += "", formats); - f.toString = function() { return specifier; }; - return f; - }, - parse: function(specifier) { - var p = newParse(specifier += "", false); - p.toString = function() { return specifier; }; - return p; - }, - utcFormat: function(specifier) { - var f = newFormat(specifier += "", utcFormats); - f.toString = function() { return specifier; }; - return f; - }, - utcParse: function(specifier) { - var p = newParse(specifier += "", true); - p.toString = function() { return specifier; }; - return p; - } - }; -} - -var pads = {"-": "", "_": " ", "0": "0"}, - numberRe = /^\s*\d+/, // note: ignores next directive - percentRe = /^%/, - requoteRe = /[\\^$*+?|[\]().{}]/g; - -function pad(value, fill, width) { - var sign = value < 0 ? "-" : "", - string = (sign ? -value : value) + "", - length = string.length; - return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); -} - -function requote(s) { - return s.replace(requoteRe, "\\$&"); -} - -function formatRe(names) { - return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); -} - -function formatLookup(names) { - return new Map(names.map((name, i) => [name.toLowerCase(), i])); -} - -function parseWeekdayNumberSunday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 1)); - return n ? (d.w = +n[0], i + n[0].length) : -1; -} - -function parseWeekdayNumberMonday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 1)); - return n ? (d.u = +n[0], i + n[0].length) : -1; -} - -function parseWeekNumberSunday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.U = +n[0], i + n[0].length) : -1; -} - -function parseWeekNumberISO(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.V = +n[0], i + n[0].length) : -1; -} - -function parseWeekNumberMonday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.W = +n[0], i + n[0].length) : -1; -} - -function parseFullYear(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 4)); - return n ? (d.y = +n[0], i + n[0].length) : -1; -} - -function parseYear(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1; -} - -function parseZone(d, string, i) { - var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6)); - return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1; -} - -function parseQuarter(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 1)); - return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1; -} - -function parseMonthNumber(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.m = n[0] - 1, i + n[0].length) : -1; -} - -function parseDayOfMonth(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.d = +n[0], i + n[0].length) : -1; -} - -function parseDayOfYear(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 3)); - return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; -} - -function parseHour24(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.H = +n[0], i + n[0].length) : -1; -} - -function parseMinutes(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.M = +n[0], i + n[0].length) : -1; -} - -function parseSeconds(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.S = +n[0], i + n[0].length) : -1; -} - -function parseMilliseconds(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 3)); - return n ? (d.L = +n[0], i + n[0].length) : -1; -} - -function parseMicroseconds(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 6)); - return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1; -} - -function parseLiteralPercent(d, string, i) { - var n = percentRe.exec(string.slice(i, i + 1)); - return n ? i + n[0].length : -1; -} - -function parseUnixTimestamp(d, string, i) { - var n = numberRe.exec(string.slice(i)); - return n ? (d.Q = +n[0], i + n[0].length) : -1; -} - -function parseUnixTimestampSeconds(d, string, i) { - var n = numberRe.exec(string.slice(i)); - return n ? (d.s = +n[0], i + n[0].length) : -1; -} - -function formatDayOfMonth(d, p) { - return pad(d.getDate(), p, 2); -} - -function formatHour24(d, p) { - return pad(d.getHours(), p, 2); -} - -function formatHour12(d, p) { - return pad(d.getHours() % 12 || 12, p, 2); -} - -function formatDayOfYear(d, p) { - return pad(1 + timeDay.count(timeYear(d), d), p, 3); -} - -function formatMilliseconds(d, p) { - return pad(d.getMilliseconds(), p, 3); -} - -function formatMicroseconds(d, p) { - return formatMilliseconds(d, p) + "000"; -} - -function formatMonthNumber(d, p) { - return pad(d.getMonth() + 1, p, 2); -} - -function formatMinutes(d, p) { - return pad(d.getMinutes(), p, 2); -} - -function formatSeconds(d, p) { - return pad(d.getSeconds(), p, 2); -} - -function formatWeekdayNumberMonday(d) { - var day = d.getDay(); - return day === 0 ? 7 : day; -} - -function formatWeekNumberSunday(d, p) { - return pad(timeSunday.count(timeYear(d) - 1, d), p, 2); -} - -function dISO(d) { - var day = d.getDay(); - return (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d); -} - -function formatWeekNumberISO(d, p) { - d = dISO(d); - return pad(timeThursday.count(timeYear(d), d) + (timeYear(d).getDay() === 4), p, 2); -} - -function formatWeekdayNumberSunday(d) { - return d.getDay(); -} - -function formatWeekNumberMonday(d, p) { - return pad(timeMonday.count(timeYear(d) - 1, d), p, 2); -} - -function formatYear(d, p) { - return pad(d.getFullYear() % 100, p, 2); -} - -function formatYearISO(d, p) { - d = dISO(d); - return pad(d.getFullYear() % 100, p, 2); -} - -function formatFullYear(d, p) { - return pad(d.getFullYear() % 10000, p, 4); -} - -function formatFullYearISO(d, p) { - var day = d.getDay(); - d = (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d); - return pad(d.getFullYear() % 10000, p, 4); -} - -function formatZone(d) { - var z = d.getTimezoneOffset(); - return (z > 0 ? "-" : (z *= -1, "+")) - + pad(z / 60 | 0, "0", 2) - + pad(z % 60, "0", 2); -} - -function formatUTCDayOfMonth(d, p) { - return pad(d.getUTCDate(), p, 2); -} - -function formatUTCHour24(d, p) { - return pad(d.getUTCHours(), p, 2); -} - -function formatUTCHour12(d, p) { - return pad(d.getUTCHours() % 12 || 12, p, 2); -} - -function formatUTCDayOfYear(d, p) { - return pad(1 + utcDay.count(utcYear(d), d), p, 3); -} - -function formatUTCMilliseconds(d, p) { - return pad(d.getUTCMilliseconds(), p, 3); -} - -function formatUTCMicroseconds(d, p) { - return formatUTCMilliseconds(d, p) + "000"; -} - -function formatUTCMonthNumber(d, p) { - return pad(d.getUTCMonth() + 1, p, 2); -} - -function formatUTCMinutes(d, p) { - return pad(d.getUTCMinutes(), p, 2); -} - -function formatUTCSeconds(d, p) { - return pad(d.getUTCSeconds(), p, 2); -} - -function formatUTCWeekdayNumberMonday(d) { - var dow = d.getUTCDay(); - return dow === 0 ? 7 : dow; -} - -function formatUTCWeekNumberSunday(d, p) { - return pad(utcSunday.count(utcYear(d) - 1, d), p, 2); -} - -function UTCdISO(d) { - var day = d.getUTCDay(); - return (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); -} - -function formatUTCWeekNumberISO(d, p) { - d = UTCdISO(d); - return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2); -} - -function formatUTCWeekdayNumberSunday(d) { - return d.getUTCDay(); -} - -function formatUTCWeekNumberMonday(d, p) { - return pad(utcMonday.count(utcYear(d) - 1, d), p, 2); -} - -function formatUTCYear(d, p) { - return pad(d.getUTCFullYear() % 100, p, 2); -} - -function formatUTCYearISO(d, p) { - d = UTCdISO(d); - return pad(d.getUTCFullYear() % 100, p, 2); -} - -function formatUTCFullYear(d, p) { - return pad(d.getUTCFullYear() % 10000, p, 4); -} - -function formatUTCFullYearISO(d, p) { - var day = d.getUTCDay(); - d = (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); - return pad(d.getUTCFullYear() % 10000, p, 4); -} - -function formatUTCZone() { - return "+0000"; -} - -function formatLiteralPercent() { - return "%"; -} - -function formatUnixTimestamp(d) { - return +d; -} - -function formatUnixTimestampSeconds(d) { - return Math.floor(+d / 1000); -} - -var locale; -var timeFormat; -var utcFormat; -var utcParse; - -defaultLocale({ - dateTime: "%x, %X", - date: "%-m/%-d/%Y", - time: "%-I:%M:%S %p", - periods: ["AM", "PM"], - days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -}); - -function defaultLocale(definition) { - locale = formatLocale(definition); - timeFormat = locale.format; - locale.parse; - utcFormat = locale.utcFormat; - utcParse = locale.utcParse; - return locale; -} - -var isoSpecifier = "%Y-%m-%dT%H:%M:%S.%LZ"; - -function formatIsoNative(date) { - return date.toISOString(); -} - -Date.prototype.toISOString - ? formatIsoNative - : utcFormat(isoSpecifier); - -function parseIsoNative(string) { - var date = new Date(string); - return isNaN(date) ? null : date; -} - -+new Date("2000-01-01T00:00:00.000Z") - ? parseIsoNative - : utcParse(isoSpecifier); - -function date(t) { - return new Date(t); -} - -function number(t) { - return t instanceof Date ? +t : +new Date(+t); -} - -function calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format) { - var scale = continuous(), - invert = scale.invert, - domain = scale.domain; - - var formatMillisecond = format(".%L"), - formatSecond = format(":%S"), - formatMinute = format("%I:%M"), - formatHour = format("%I %p"), - formatDay = format("%a %d"), - formatWeek = format("%b %d"), - formatMonth = format("%B"), - formatYear = format("%Y"); - - function tickFormat(date) { - return (second(date) < date ? formatMillisecond - : minute(date) < date ? formatSecond - : hour(date) < date ? formatMinute - : day(date) < date ? formatHour - : month(date) < date ? (week(date) < date ? formatDay : formatWeek) - : year(date) < date ? formatMonth - : formatYear)(date); - } - - scale.invert = function(y) { - return new Date(invert(y)); - }; - - scale.domain = function(_) { - return arguments.length ? domain(Array.from(_, number)) : domain().map(date); - }; - - scale.ticks = function(interval) { - var d = domain(); - return ticks(d[0], d[d.length - 1], interval == null ? 10 : interval); - }; - - scale.tickFormat = function(count, specifier) { - return specifier == null ? tickFormat : format(specifier); - }; - - scale.nice = function(interval) { - var d = domain(); - if (!interval || typeof interval.range !== "function") interval = tickInterval(d[0], d[d.length - 1], interval == null ? 10 : interval); - return interval ? domain(nice(d, interval)) : scale; - }; - - scale.copy = function() { - return copy$1(scale, calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format)); - }; - - return scale; -} - -function time() { - return initRange.apply(calendar(timeTicks, timeTickInterval, timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute, second, timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]), arguments); -} - -function constant(x) { - return function constant() { - return x; - }; -} - -const abs = Math.abs; -const atan2 = Math.atan2; -const cos = Math.cos; -const max = Math.max; -const min = Math.min; -const sin = Math.sin; -const sqrt = Math.sqrt; - -const epsilon = 1e-12; -const pi = Math.PI; -const halfPi = pi / 2; -const tau = 2 * pi; - -function acos(x) { - return x > 1 ? 0 : x < -1 ? pi : Math.acos(x); -} - -function asin(x) { - return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x); -} - -function withPath(shape) { - let digits = 3; - - shape.digits = function(_) { - if (!arguments.length) return digits; - if (_ == null) { - digits = null; - } else { - const d = Math.floor(_); - if (!(d >= 0)) throw new RangeError(`invalid digits: ${_}`); - digits = d; - } - return shape; - }; - - return () => new Path$2(digits); -} - -function arcInnerRadius(d) { - return d.innerRadius; -} - -function arcOuterRadius(d) { - return d.outerRadius; -} - -function arcStartAngle(d) { - return d.startAngle; -} - -function arcEndAngle(d) { - return d.endAngle; -} - -function arcPadAngle(d) { - return d && d.padAngle; // Note: optional! -} - -function intersect$1(x0, y0, x1, y1, x2, y2, x3, y3) { - var x10 = x1 - x0, y10 = y1 - y0, - x32 = x3 - x2, y32 = y3 - y2, - t = y32 * x10 - x32 * y10; - if (t * t < epsilon) return; - t = (x32 * (y0 - y2) - y32 * (x0 - x2)) / t; - return [x0 + t * x10, y0 + t * y10]; -} - -// Compute perpendicular offset line of length rc. -// http://mathworld.wolfram.com/Circle-LineIntersection.html -function cornerTangents(x0, y0, x1, y1, r1, rc, cw) { - var x01 = x0 - x1, - y01 = y0 - y1, - lo = (cw ? rc : -rc) / sqrt(x01 * x01 + y01 * y01), - ox = lo * y01, - oy = -lo * x01, - x11 = x0 + ox, - y11 = y0 + oy, - x10 = x1 + ox, - y10 = y1 + oy, - x00 = (x11 + x10) / 2, - y00 = (y11 + y10) / 2, - dx = x10 - x11, - dy = y10 - y11, - d2 = dx * dx + dy * dy, - r = r1 - rc, - D = x11 * y10 - x10 * y11, - d = (dy < 0 ? -1 : 1) * sqrt(max(0, r * r * d2 - D * D)), - cx0 = (D * dy - dx * d) / d2, - cy0 = (-D * dx - dy * d) / d2, - cx1 = (D * dy + dx * d) / d2, - cy1 = (-D * dx + dy * d) / d2, - dx0 = cx0 - x00, - dy0 = cy0 - y00, - dx1 = cx1 - x00, - dy1 = cy1 - y00; - - // Pick the closer of the two intersection points. - // TODO Is there a faster way to determine which intersection to use? - if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; - - return { - cx: cx0, - cy: cy0, - x01: -ox, - y01: -oy, - x11: cx0 * (r1 / r - 1), - y11: cy0 * (r1 / r - 1) - }; -} - -function arc() { - var innerRadius = arcInnerRadius, - outerRadius = arcOuterRadius, - cornerRadius = constant(0), - padRadius = null, - startAngle = arcStartAngle, - endAngle = arcEndAngle, - padAngle = arcPadAngle, - context = null, - path = withPath(arc); - - function arc() { - var buffer, - r, - r0 = +innerRadius.apply(this, arguments), - r1 = +outerRadius.apply(this, arguments), - a0 = startAngle.apply(this, arguments) - halfPi, - a1 = endAngle.apply(this, arguments) - halfPi, - da = abs(a1 - a0), - cw = a1 > a0; - - if (!context) context = buffer = path(); - - // Ensure that the outer radius is always larger than the inner radius. - if (r1 < r0) r = r1, r1 = r0, r0 = r; - - // Is it a point? - if (!(r1 > epsilon)) context.moveTo(0, 0); - - // Or is it a circle or annulus? - else if (da > tau - epsilon) { - context.moveTo(r1 * cos(a0), r1 * sin(a0)); - context.arc(0, 0, r1, a0, a1, !cw); - if (r0 > epsilon) { - context.moveTo(r0 * cos(a1), r0 * sin(a1)); - context.arc(0, 0, r0, a1, a0, cw); - } - } - - // Or is it a circular or annular sector? - else { - var a01 = a0, - a11 = a1, - a00 = a0, - a10 = a1, - da0 = da, - da1 = da, - ap = padAngle.apply(this, arguments) / 2, - rp = (ap > epsilon) && (padRadius ? +padRadius.apply(this, arguments) : sqrt(r0 * r0 + r1 * r1)), - rc = min(abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments)), - rc0 = rc, - rc1 = rc, - t0, - t1; - - // Apply padding? Note that since r1 ≥ r0, da1 ≥ da0. - if (rp > epsilon) { - var p0 = asin(rp / r0 * sin(ap)), - p1 = asin(rp / r1 * sin(ap)); - if ((da0 -= p0 * 2) > epsilon) p0 *= (cw ? 1 : -1), a00 += p0, a10 -= p0; - else da0 = 0, a00 = a10 = (a0 + a1) / 2; - if ((da1 -= p1 * 2) > epsilon) p1 *= (cw ? 1 : -1), a01 += p1, a11 -= p1; - else da1 = 0, a01 = a11 = (a0 + a1) / 2; - } - - var x01 = r1 * cos(a01), - y01 = r1 * sin(a01), - x10 = r0 * cos(a10), - y10 = r0 * sin(a10); - - // Apply rounded corners? - if (rc > epsilon) { - var x11 = r1 * cos(a11), - y11 = r1 * sin(a11), - x00 = r0 * cos(a00), - y00 = r0 * sin(a00), - oc; - - // Restrict the corner radius according to the sector angle. If this - // intersection fails, it’s probably because the arc is too small, so - // disable the corner radius entirely. - if (da < pi) { - if (oc = intersect$1(x01, y01, x00, y00, x11, y11, x10, y10)) { - var ax = x01 - oc[0], - ay = y01 - oc[1], - bx = x11 - oc[0], - by = y11 - oc[1], - kc = 1 / sin(acos((ax * bx + ay * by) / (sqrt(ax * ax + ay * ay) * sqrt(bx * bx + by * by))) / 2), - lc = sqrt(oc[0] * oc[0] + oc[1] * oc[1]); - rc0 = min(rc, (r0 - lc) / (kc - 1)); - rc1 = min(rc, (r1 - lc) / (kc + 1)); - } else { - rc0 = rc1 = 0; - } - } - } - - // Is the sector collapsed to a line? - if (!(da1 > epsilon)) context.moveTo(x01, y01); - - // Does the sector’s outer ring have rounded corners? - else if (rc1 > epsilon) { - t0 = cornerTangents(x00, y00, x01, y01, r1, rc1, cw); - t1 = cornerTangents(x11, y11, x10, y10, r1, rc1, cw); - - context.moveTo(t0.cx + t0.x01, t0.cy + t0.y01); - - // Have the corners merged? - if (rc1 < rc) context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); - - // Otherwise, draw the two corners and the ring. - else { - context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); - context.arc(0, 0, r1, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw); - context.arc(t1.cx, t1.cy, rc1, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); - } - } - - // Or is the outer ring just a circular arc? - else context.moveTo(x01, y01), context.arc(0, 0, r1, a01, a11, !cw); - - // Is there no inner ring, and it’s a circular sector? - // Or perhaps it’s an annular sector collapsed due to padding? - if (!(r0 > epsilon) || !(da0 > epsilon)) context.lineTo(x10, y10); - - // Does the sector’s inner ring (or point) have rounded corners? - else if (rc0 > epsilon) { - t0 = cornerTangents(x10, y10, x11, y11, r0, -rc0, cw); - t1 = cornerTangents(x01, y01, x00, y00, r0, -rc0, cw); - - context.lineTo(t0.cx + t0.x01, t0.cy + t0.y01); - - // Have the corners merged? - if (rc0 < rc) context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); - - // Otherwise, draw the two corners and the ring. - else { - context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); - context.arc(0, 0, r0, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), cw); - context.arc(t1.cx, t1.cy, rc0, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); - } - } - - // Or is the inner ring just a circular arc? - else context.arc(0, 0, r0, a10, a00, cw); - } - - context.closePath(); - - if (buffer) return context = null, buffer + "" || null; - } - - arc.centroid = function() { - var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, - a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - pi / 2; - return [cos(a) * r, sin(a) * r]; - }; - - arc.innerRadius = function(_) { - return arguments.length ? (innerRadius = typeof _ === "function" ? _ : constant(+_), arc) : innerRadius; - }; - - arc.outerRadius = function(_) { - return arguments.length ? (outerRadius = typeof _ === "function" ? _ : constant(+_), arc) : outerRadius; - }; - - arc.cornerRadius = function(_) { - return arguments.length ? (cornerRadius = typeof _ === "function" ? _ : constant(+_), arc) : cornerRadius; - }; - - arc.padRadius = function(_) { - return arguments.length ? (padRadius = _ == null ? null : typeof _ === "function" ? _ : constant(+_), arc) : padRadius; - }; - - arc.startAngle = function(_) { - return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant(+_), arc) : startAngle; - }; - - arc.endAngle = function(_) { - return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant(+_), arc) : endAngle; - }; - - arc.padAngle = function(_) { - return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant(+_), arc) : padAngle; - }; - - arc.context = function(_) { - return arguments.length ? ((context = _ == null ? null : _), arc) : context; - }; - - return arc; -} - -var frame = 0, // is an animation frame pending? - timeout$1 = 0, // is a timeout pending? - interval = 0, // are any timers active? - pokeDelay = 1000, // how frequently we check for clock skew - taskHead, - taskTail, - clockLast = 0, - clockNow = 0, - clockSkew = 0, - clock = typeof performance === "object" && performance.now ? performance : Date, - setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); }; - -function now() { - return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew); -} - -function clearNow() { - clockNow = 0; -} - -function Timer$1() { - this._call = - this._time = - this._next = null; -} - -Timer$1.prototype = timer.prototype = { - constructor: Timer$1, - restart: function(callback, delay, time) { - if (typeof callback !== "function") throw new TypeError("callback is not a function"); - time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); - if (!this._next && taskTail !== this) { - if (taskTail) taskTail._next = this; - else taskHead = this; - taskTail = this; - } - this._call = callback; - this._time = time; - sleep(); - }, - stop: function() { - if (this._call) { - this._call = null; - this._time = Infinity; - sleep(); - } - } -}; - -function timer(callback, delay, time) { - var t = new Timer$1; - t.restart(callback, delay, time); - return t; -} - -function timerFlush() { - now(); // Get the current time, if not already set. - ++frame; // Pretend we’ve set an alarm, if we haven’t already. - var t = taskHead, e; - while (t) { - if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e); - t = t._next; - } - --frame; -} - -function wake() { - clockNow = (clockLast = clock.now()) + clockSkew; - frame = timeout$1 = 0; - try { - timerFlush(); - } finally { - frame = 0; - nap(); - clockNow = 0; - } -} - -function poke() { - var now = clock.now(), delay = now - clockLast; - if (delay > pokeDelay) clockSkew -= delay, clockLast = now; -} - -function nap() { - var t0, t1 = taskHead, t2, time = Infinity; - while (t1) { - if (t1._call) { - if (time > t1._time) time = t1._time; - t0 = t1, t1 = t1._next; - } else { - t2 = t1._next, t1._next = null; - t1 = t0 ? t0._next = t2 : taskHead = t2; - } - } - taskTail = t0; - sleep(time); -} - -function sleep(time) { - if (frame) return; // Soonest alarm already set, or will be. - if (timeout$1) timeout$1 = clearTimeout(timeout$1); - var delay = time - clockNow; // Strictly less than if we recomputed clockNow. - if (delay > 24) { - if (time < Infinity) timeout$1 = setTimeout(wake, time - clock.now() - clockSkew); - if (interval) interval = clearInterval(interval); - } else { - if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay); - frame = 1, setFrame(wake); - } -} - -function timeout(callback, delay, time) { - var t = new Timer$1; - delay = delay == null ? 0 : +delay; - t.restart(elapsed => { - t.stop(); - callback(elapsed + delay); - }, delay, time); - return t; -} - -var emptyOn = dispatch("start", "end", "cancel", "interrupt"); -var emptyTween = []; - -var CREATED = 0; -var SCHEDULED = 1; -var STARTING = 2; -var STARTED = 3; -var RUNNING = 4; -var ENDING = 5; -var ENDED = 6; - -function schedule(node, name, id, index, group, timing) { - var schedules = node.__transition; - if (!schedules) node.__transition = {}; - else if (id in schedules) return; - create(node, id, { - name: name, - index: index, // For context during callback. - group: group, // For context during callback. - on: emptyOn, - tween: emptyTween, - time: timing.time, - delay: timing.delay, - duration: timing.duration, - ease: timing.ease, - timer: null, - state: CREATED - }); -} - -function init(node, id) { - var schedule = get(node, id); - if (schedule.state > CREATED) throw new Error("too late; already scheduled"); - return schedule; -} - -function set(node, id) { - var schedule = get(node, id); - if (schedule.state > STARTED) throw new Error("too late; already running"); - return schedule; -} - -function get(node, id) { - var schedule = node.__transition; - if (!schedule || !(schedule = schedule[id])) throw new Error("transition not found"); - return schedule; -} - -function create(node, id, self) { - var schedules = node.__transition, - tween; - - // Initialize the self timer when the transition is created. - // Note the actual delay is not known until the first callback! - schedules[id] = self; - self.timer = timer(schedule, 0, self.time); - - function schedule(elapsed) { - self.state = SCHEDULED; - self.timer.restart(start, self.delay, self.time); - - // If the elapsed delay is less than our first sleep, start immediately. - if (self.delay <= elapsed) start(elapsed - self.delay); - } - - function start(elapsed) { - var i, j, n, o; - - // If the state is not SCHEDULED, then we previously errored on start. - if (self.state !== SCHEDULED) return stop(); - - for (i in schedules) { - o = schedules[i]; - if (o.name !== self.name) continue; - - // While this element already has a starting transition during this frame, - // defer starting an interrupting transition until that transition has a - // chance to tick (and possibly end); see d3/d3-transition#54! - if (o.state === STARTED) return timeout(start); - - // Interrupt the active transition, if any. - if (o.state === RUNNING) { - o.state = ENDED; - o.timer.stop(); - o.on.call("interrupt", node, node.__data__, o.index, o.group); - delete schedules[i]; - } - - // Cancel any pre-empted transitions. - else if (+i < id) { - o.state = ENDED; - o.timer.stop(); - o.on.call("cancel", node, node.__data__, o.index, o.group); - delete schedules[i]; - } - } - - // Defer the first tick to end of the current frame; see d3/d3#1576. - // Note the transition may be canceled after start and before the first tick! - // Note this must be scheduled before the start event; see d3/d3-transition#16! - // Assuming this is successful, subsequent callbacks go straight to tick. - timeout(function() { - if (self.state === STARTED) { - self.state = RUNNING; - self.timer.restart(tick, self.delay, self.time); - tick(elapsed); - } - }); - - // Dispatch the start event. - // Note this must be done before the tween are initialized. - self.state = STARTING; - self.on.call("start", node, node.__data__, self.index, self.group); - if (self.state !== STARTING) return; // interrupted - self.state = STARTED; - - // Initialize the tween, deleting null tween. - tween = new Array(n = self.tween.length); - for (i = 0, j = -1; i < n; ++i) { - if (o = self.tween[i].value.call(node, node.__data__, self.index, self.group)) { - tween[++j] = o; - } - } - tween.length = j + 1; - } - - function tick(elapsed) { - var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1), - i = -1, - n = tween.length; - - while (++i < n) { - tween[i].call(node, t); - } - - // Dispatch the end event. - if (self.state === ENDING) { - self.on.call("end", node, node.__data__, self.index, self.group); - stop(); - } - } - - function stop() { - self.state = ENDED; - self.timer.stop(); - delete schedules[id]; - for (var i in schedules) return; // eslint-disable-line no-unused-vars - delete node.__transition; - } -} - -function interrupt(node, name) { - var schedules = node.__transition, - schedule, - active, - empty = true, - i; - - if (!schedules) return; - - name = name == null ? null : name + ""; - - for (i in schedules) { - if ((schedule = schedules[i]).name !== name) { empty = false; continue; } - active = schedule.state > STARTING && schedule.state < ENDING; - schedule.state = ENDED; - schedule.timer.stop(); - schedule.on.call(active ? "interrupt" : "cancel", node, node.__data__, schedule.index, schedule.group); - delete schedules[i]; - } - - if (empty) delete node.__transition; -} - -function selection_interrupt(name) { - return this.each(function() { - interrupt(this, name); - }); -} - -function tweenRemove(id, name) { - var tween0, tween1; - return function() { - var schedule = set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = tween0 = tween; - for (var i = 0, n = tween1.length; i < n; ++i) { - if (tween1[i].name === name) { - tween1 = tween1.slice(); - tween1.splice(i, 1); - break; - } - } - } - - schedule.tween = tween1; - }; -} - -function tweenFunction(id, name, value) { - var tween0, tween1; - if (typeof value !== "function") throw new Error; - return function() { - var schedule = set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = (tween0 = tween).slice(); - for (var t = {name: name, value: value}, i = 0, n = tween1.length; i < n; ++i) { - if (tween1[i].name === name) { - tween1[i] = t; - break; - } - } - if (i === n) tween1.push(t); - } - - schedule.tween = tween1; - }; -} - -function transition_tween(name, value) { - var id = this._id; - - name += ""; - - if (arguments.length < 2) { - var tween = get(this.node(), id).tween; - for (var i = 0, n = tween.length, t; i < n; ++i) { - if ((t = tween[i]).name === name) { - return t.value; - } - } - return null; - } - - return this.each((value == null ? tweenRemove : tweenFunction)(id, name, value)); -} - -function tweenValue(transition, name, value) { - var id = transition._id; - - transition.each(function() { - var schedule = set(this, id); - (schedule.value || (schedule.value = {}))[name] = value.apply(this, arguments); - }); - - return function(node) { - return get(node, id).value[name]; - }; -} - -function interpolate(a, b) { - var c; - return (typeof b === "number" ? interpolateNumber - : b instanceof color ? interpolateRgb - : (c = color(b)) ? (b = c, interpolateRgb) - : interpolateString)(a, b); -} - -function attrRemove(name) { - return function() { - this.removeAttribute(name); - }; -} - -function attrRemoveNS(fullname) { - return function() { - this.removeAttributeNS(fullname.space, fullname.local); - }; -} - -function attrConstant(name, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = this.getAttribute(name); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function attrConstantNS(fullname, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = this.getAttributeNS(fullname.space, fullname.local); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function attrFunction(name, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0, value1 = value(this), string1; - if (value1 == null) return void this.removeAttribute(name); - string0 = this.getAttribute(name); - string1 = value1 + ""; - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function attrFunctionNS(fullname, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0, value1 = value(this), string1; - if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local); - string0 = this.getAttributeNS(fullname.space, fullname.local); - string1 = value1 + ""; - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function transition_attr(name, value) { - var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate; - return this.attrTween(name, typeof value === "function" - ? (fullname.local ? attrFunctionNS : attrFunction)(fullname, i, tweenValue(this, "attr." + name, value)) - : value == null ? (fullname.local ? attrRemoveNS : attrRemove)(fullname) - : (fullname.local ? attrConstantNS : attrConstant)(fullname, i, value)); -} - -function attrInterpolate(name, i) { - return function(t) { - this.setAttribute(name, i.call(this, t)); - }; -} - -function attrInterpolateNS(fullname, i) { - return function(t) { - this.setAttributeNS(fullname.space, fullname.local, i.call(this, t)); - }; -} - -function attrTweenNS(fullname, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i); - return t0; - } - tween._value = value; - return tween; -} - -function attrTween(name, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i); - return t0; - } - tween._value = value; - return tween; -} - -function transition_attrTween(name, value) { - var key = "attr." + name; - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - var fullname = namespace(name); - return this.tween(key, (fullname.local ? attrTweenNS : attrTween)(fullname, value)); -} - -function delayFunction(id, value) { - return function() { - init(this, id).delay = +value.apply(this, arguments); - }; -} - -function delayConstant(id, value) { - return value = +value, function() { - init(this, id).delay = value; - }; -} - -function transition_delay(value) { - var id = this._id; - - return arguments.length - ? this.each((typeof value === "function" - ? delayFunction - : delayConstant)(id, value)) - : get(this.node(), id).delay; -} - -function durationFunction(id, value) { - return function() { - set(this, id).duration = +value.apply(this, arguments); - }; -} - -function durationConstant(id, value) { - return value = +value, function() { - set(this, id).duration = value; - }; -} - -function transition_duration(value) { - var id = this._id; - - return arguments.length - ? this.each((typeof value === "function" - ? durationFunction - : durationConstant)(id, value)) - : get(this.node(), id).duration; -} - -function easeConstant(id, value) { - if (typeof value !== "function") throw new Error; - return function() { - set(this, id).ease = value; - }; -} - -function transition_ease(value) { - var id = this._id; - - return arguments.length - ? this.each(easeConstant(id, value)) - : get(this.node(), id).ease; -} - -function easeVarying(id, value) { - return function() { - var v = value.apply(this, arguments); - if (typeof v !== "function") throw new Error; - set(this, id).ease = v; - }; -} - -function transition_easeVarying(value) { - if (typeof value !== "function") throw new Error; - return this.each(easeVarying(this._id, value)); -} - -function transition_filter(match) { - if (typeof match !== "function") match = matcher(match); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Transition(subgroups, this._parents, this._name, this._id); -} - -function transition_merge(transition) { - if (transition._id !== this._id) throw new Error; - - for (var groups0 = this._groups, groups1 = transition._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { - for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group0[i] || group1[i]) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Transition(merges, this._parents, this._name, this._id); -} - -function start(name) { - return (name + "").trim().split(/^|\s+/).every(function(t) { - var i = t.indexOf("."); - if (i >= 0) t = t.slice(0, i); - return !t || t === "start"; - }); -} - -function onFunction(id, name, listener) { - var on0, on1, sit = start(name) ? init : set; - return function() { - var schedule = sit(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener); - - schedule.on = on1; - }; -} - -function transition_on(name, listener) { - var id = this._id; - - return arguments.length < 2 - ? get(this.node(), id).on.on(name) - : this.each(onFunction(id, name, listener)); -} - -function removeFunction(id) { - return function() { - var parent = this.parentNode; - for (var i in this.__transition) if (+i !== id) return; - if (parent) parent.removeChild(this); - }; -} - -function transition_remove() { - return this.on("end.remove", removeFunction(this._id)); -} - -function transition_select(select) { - var name = this._name, - id = this._id; - - if (typeof select !== "function") select = selector(select); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { - if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { - if ("__data__" in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - schedule(subgroup[i], name, id, i, subgroup, get(node, id)); - } - } - } - - return new Transition(subgroups, this._parents, name, id); -} - -function transition_selectAll(select) { - var name = this._name, - id = this._id; - - if (typeof select !== "function") select = selectorAll(select); - - for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - for (var children = select.call(node, node.__data__, i, group), child, inherit = get(node, id), k = 0, l = children.length; k < l; ++k) { - if (child = children[k]) { - schedule(child, name, id, k, children, inherit); - } - } - subgroups.push(children); - parents.push(node); - } - } - } - - return new Transition(subgroups, parents, name, id); -} - -var Selection = selection.prototype.constructor; - -function transition_selection() { - return new Selection(this._groups, this._parents); -} - -function styleNull(name, interpolate) { - var string00, - string10, - interpolate0; - return function() { - var string0 = styleValue(this, name), - string1 = (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, string10 = string1); - }; -} - -function styleRemove(name) { - return function() { - this.style.removeProperty(name); - }; -} - -function styleConstant(name, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = styleValue(this, name); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function styleFunction(name, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0 = styleValue(this, name), - value1 = value(this), - string1 = value1 + ""; - if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function styleMaybeRemove(id, name) { - var on0, on1, listener0, key = "style." + name, event = "end." + key, remove; - return function() { - var schedule = set(this, id), - on = schedule.on, - listener = schedule.value[key] == null ? remove || (remove = styleRemove(name)) : undefined; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0 || listener0 !== listener) (on1 = (on0 = on).copy()).on(event, listener0 = listener); - - schedule.on = on1; - }; -} - -function transition_style(name, value, priority) { - var i = (name += "") === "transform" ? interpolateTransformCss : interpolate; - return value == null ? this - .styleTween(name, styleNull(name, i)) - .on("end.style." + name, styleRemove(name)) - : typeof value === "function" ? this - .styleTween(name, styleFunction(name, i, tweenValue(this, "style." + name, value))) - .each(styleMaybeRemove(this._id, name)) - : this - .styleTween(name, styleConstant(name, i, value), priority) - .on("end.style." + name, null); -} - -function styleInterpolate(name, i, priority) { - return function(t) { - this.style.setProperty(name, i.call(this, t), priority); - }; -} - -function styleTween(name, value, priority) { - var t, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority); - return t; - } - tween._value = value; - return tween; -} - -function transition_styleTween(name, value, priority) { - var key = "style." + (name += ""); - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - return this.tween(key, styleTween(name, value, priority == null ? "" : priority)); -} - -function textConstant(value) { - return function() { - this.textContent = value; - }; -} - -function textFunction(value) { - return function() { - var value1 = value(this); - this.textContent = value1 == null ? "" : value1; - }; -} - -function transition_text(value) { - return this.tween("text", typeof value === "function" - ? textFunction(tweenValue(this, "text", value)) - : textConstant(value == null ? "" : value + "")); -} - -function textInterpolate(i) { - return function(t) { - this.textContent = i.call(this, t); - }; -} - -function textTween(value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && textInterpolate(i); - return t0; - } - tween._value = value; - return tween; -} - -function transition_textTween(value) { - var key = "text"; - if (arguments.length < 1) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - return this.tween(key, textTween(value)); -} - -function transition_transition() { - var name = this._name, - id0 = this._id, - id1 = newId(); - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - var inherit = get(node, id0); - schedule(node, name, id1, i, group, { - time: inherit.time + inherit.delay + inherit.duration, - delay: 0, - duration: inherit.duration, - ease: inherit.ease - }); - } - } - } - - return new Transition(groups, this._parents, name, id1); -} - -function transition_end() { - var on0, on1, that = this, id = that._id, size = that.size(); - return new Promise(function(resolve, reject) { - var cancel = {value: reject}, - end = {value: function() { if (--size === 0) resolve(); }}; - - that.each(function() { - var schedule = set(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) { - on1 = (on0 = on).copy(); - on1._.cancel.push(cancel); - on1._.interrupt.push(cancel); - on1._.end.push(end); - } - - schedule.on = on1; - }); - - // The selection was empty, resolve end immediately - if (size === 0) resolve(); - }); -} - -var id = 0; - -function Transition(groups, parents, name, id) { - this._groups = groups; - this._parents = parents; - this._name = name; - this._id = id; -} - -function newId() { - return ++id; -} - -var selection_prototype = selection.prototype; - -Transition.prototype = { - constructor: Transition, - select: transition_select, - selectAll: transition_selectAll, - selectChild: selection_prototype.selectChild, - selectChildren: selection_prototype.selectChildren, - filter: transition_filter, - merge: transition_merge, - selection: transition_selection, - transition: transition_transition, - call: selection_prototype.call, - nodes: selection_prototype.nodes, - node: selection_prototype.node, - size: selection_prototype.size, - empty: selection_prototype.empty, - each: selection_prototype.each, - on: transition_on, - attr: transition_attr, - attrTween: transition_attrTween, - style: transition_style, - styleTween: transition_styleTween, - text: transition_text, - textTween: transition_textTween, - remove: transition_remove, - tween: transition_tween, - delay: transition_delay, - duration: transition_duration, - ease: transition_ease, - easeVarying: transition_easeVarying, - end: transition_end, - [Symbol.iterator]: selection_prototype[Symbol.iterator] -}; - -function cubicInOut(t) { - return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; -} - -var defaultTiming = { - time: null, // Set on use. - delay: 0, - duration: 250, - ease: cubicInOut -}; - -function inherit$1(node, id) { - var timing; - while (!(timing = node.__transition) || !(timing = timing[id])) { - if (!(node = node.parentNode)) { - throw new Error(`transition ${id} not found`); - } - } - return timing; -} - -function selection_transition(name) { - var id, - timing; - - if (name instanceof Transition) { - id = name._id, name = name._name; - } else { - id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + ""; - } - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - schedule(node, name, id, i, group, timing || inherit$1(node, id)); - } - } - } - - return new Transition(groups, this._parents, name, id); -} - -selection.prototype.interrupt = selection_interrupt; -selection.prototype.transition = selection_transition; - -const clTLinearGradient = 'TLinearGradient', clTRadialGradient = 'TRadialGradient', - kWhite = 0, kBlack = 1; - -/** @summary Covert value between 0 and 1 into decimal string using scale factor, used for colors coding - * @private */ -function toDec(num, scale = 255) { - return Math.round(num * scale).toString(10); -} - -/** @summary Convert alfa value from rgba to string - * @private */ -function toAlfa(a) { - const res = a.toFixed(2); - if ((res.length === 4) && (res[3] === '0')) - return res.slice(0, 3); - return res; -} - -/** @summary Convert r,g,b,a values to string - * @private */ -function toColor(r, g, b, a = 1) { - return (a !== undefined) && (a !== 1) - ? `rgba(${toDec(r)}, ${toDec(g)}, ${toDec(b)}, ${toAlfa(a)})` - : `rgb(${toDec(r)}, ${toDec(g)}, ${toDec(b)})`; -} - -/** @summary Convert color string to unify node.js and browser - * @private */ -function convertColor(col) { - return (isNodeJs() || (isBatchMode() && settings.ApproxTextSize)) && (col[0] === '#' || col[0] === 'r') ? color(col).formatRgb() : col; -} - -/** @summary list of global root colors - * @private */ -let gbl_colors_list = []; - -/** @summary Generates all root colors, used also in jstests to reset colors - * @private */ -function createRootColors() { - function conv(arg) { - const r = Number.parseInt(arg.slice(0, 2), 16), - g = Number.parseInt(arg.slice(2, 4), 16), - b = Number.parseInt(arg.slice(4, 6), 16); - return `rgb(${r}, ${g}, ${b})`; - } - - const colorMap = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', conv('59d454'), conv('5954d9'), 'white']; - colorMap[110] = 'white'; - - const moreCol = [ - { n: 11, s: 'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff18006f2da8a52a2ab2beb55790fcf89c20e42536964a8b9c9ca17a21dd1845fbff5e02c91f16c849a9adad7d86c8dd578dff6563643f90daffa90ebd1f0194a4a2832db6a96b59e76300b9ac7071758192daddb2b2b2' }, - { n: 201, s: '5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1' }, - { n: 390, s: 'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300' }, - { n: 406, s: 'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300' }, - { n: 422, s: 'cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333' }, - { n: 590, s: 'cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033' }, - { n: 606, s: 'ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033' }, - { n: 622, s: 'ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000' }, - { n: 791, s: 'ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a' }, - { n: 920, s: 'cdcdcd9a9a9a666666333333' }]; - - moreCol.forEach(entry => { - const s = entry.s; - for (let n = 0; n < s.length; n += 6) { - const num = entry.n + n / 6; - colorMap[num] = conv(s.slice(n, n + 6)); - } - }); - - gbl_colors_list = colorMap; -} - -/** @summary Get list of colors - * @private */ -function getRootColors() { - return gbl_colors_list; -} - -/** @summary Produces rgb code for TColor object - * @private */ -function getRGBfromTColor(col) { - if (col?._typename !== clTColor) - return null; - - const rgb = toColor(col.fRed, col.fGreen, col.fBlue, col.fAlpha); - - switch (rgb) { - case 'rgb(255, 255, 255)': return 'white'; - case 'rgb(0, 0, 0)': return 'black'; - case 'rgb(255, 0, 0)': return 'red'; - case 'rgb(0, 255, 0)': return 'green'; - case 'rgb(0, 0, 255)': return 'blue'; - case 'rgb(255, 255, 0)': return 'yellow'; - case 'rgb(255, 0, 255)': return 'magenta'; - case 'rgb(0, 255, 255)': return 'cyan'; - } - return rgb; -} - -/** @summary Return list of grey colors for the original array - * @private */ -function getGrayColors(rgb_array) { - const gray_colors = []; - - if (!rgb_array) - rgb_array = getRootColors(); - - for (let n = 0; n < rgb_array.length; ++n) { - if (!rgb_array[n]) - continue; - const rgb = color(rgb_array[n]), - gray = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; - rgb.r = rgb.g = rgb.b = gray; - gray_colors[n] = rgb.formatRgb(); - } - - return gray_colors; -} - -/** @summary Add new colors from object array - * @private */ -function extendRootColors(jsarr, objarr, grayscale) { - if (!jsarr) { - jsarr = []; - for (let n = 0; n < gbl_colors_list.length; ++n) - jsarr[n] = gbl_colors_list[n]; - } - - if (!objarr) - return jsarr; - - let rgb_array = objarr; - if (objarr._typename && objarr.arr) { - rgb_array = []; - for (let n = 0; n < objarr.arr.length; ++n) { - const col = objarr.arr[n]; - if ((col?._typename === clTLinearGradient) || (col?._typename === clTRadialGradient)) { - rgb_array[col.fNumber] = col; - col.toString = () => 'white'; - continue; - } - - if (col?._typename !== clTColor) - continue; - - if ((col.fNumber >= 0) && (col.fNumber <= 10000)) - rgb_array[col.fNumber] = getRGBfromTColor(col); - } - } - - for (let n = 0; n < rgb_array.length; ++n) { - if (rgb_array[n] && (jsarr[n] !== rgb_array[n])) - jsarr[n] = rgb_array[n]; - } - - return grayscale ? getGrayColors(jsarr) : jsarr; -} - -/** @summary Set global list of colors. - * @desc Either TObjArray of TColor instances or just plain array with rgb() code. - * List of colors typically stored together with TCanvas primitives - * @private */ -function adoptRootColors(objarr) { - extendRootColors(gbl_colors_list, objarr); -} - -/** @summary Return ROOT color by index - * @desc Color numbering corresponds typical ROOT colors - * @return {string} with RGB color code or existing color name like 'cyan' - * @private */ -function getColor(indx) { - return gbl_colors_list[indx]; -} - -/** @summary Search for specified color in the list of colors - * @return Color index or -1 if fails - * @private */ -function findColor(name) { - if (!name) - return -1; - for (let indx = 0; indx < gbl_colors_list.length; ++indx) { - if (gbl_colors_list[indx] === name) - return indx; - } - return -1; -} - -/** @summary Add new color - * @param {string} rgb - color name or just string with rgb value - * @param {Array} [lst] - optional colors list, to which add colors - * @param {number} [indx] - optional color index in provided colors list - * @return {number} index of new color - * @private */ -function addColor(rgb, lst, indx) { - if (!lst) - lst = gbl_colors_list; - - if ((rgb[0] === '#') && (isNodeJs() || (isBatchMode() && settings.ApproxTextSize))) - rgb = color(rgb).formatRgb(); - - if (indx !== undefined) { - if (Number.isInteger(indx) && (indx > 0)) - lst[indx] = rgb; - return indx; - } - - indx = lst.indexOf(rgb); - if (indx >= 0) - return indx; - lst.push(rgb); - return lst.length - 1; -} - -/** - * @summary Color palette handle - * - * @private - */ - -class ColorPalette { - - /** @summary constructor */ - constructor(arr, grayscale) { - this.palette = grayscale ? getGrayColors(arr) : arr; - } - - /** @summary Returns color index which correspond to contour index of provided length */ - calcColorIndex(i, len) { - const plen = this.palette.length, theColor = Math.floor((i + 0.99) * plen / (len - 1)); - return (theColor > plen - 1) ? plen - 1 : theColor; - } - - /** @summary Returns color with provided index */ - getColor(indx) { return this.palette[indx]; } - - /** @summary Returns number of colors in the palette */ - getLength() { return this.palette.length; } - - /** @summary Calculate color for given i and len */ - calcColor(i, len) { return this.getColor(this.calcColorIndex(i, len)); } - -} // class ColorPalette - -function createDefaultPalette(grayscale) { - const hue2rgb = (p, q, t) => { - if (t < 0) - t += 1; - if (t > 1) - t -= 1; - if (t < 1 / 6) - return p + (q - p) * 6 * t; - if (t < 1 / 2) - return q; - if (t < 2 / 3) - return p + (q - p) * (2 / 3 - t) * 6; - return p; - }, HLStoRGB = (h, l, s) => { - const q = l + s - l * s, - p = 2 * l - q, - r = hue2rgb(p, q, h + 1 / 3), - g = hue2rgb(p, q, h), - b = hue2rgb(p, q, h - 1 / 3); - return toColor(r, g, b); - }, minHue = 0, maxHue = 280, maxPretty = 50, palette = []; - for (let i = 0; i < maxPretty; ++i) { - const hue = (maxHue - (i + 1) * ((maxHue - minHue) / maxPretty)) / 360; - palette.push(HLStoRGB(hue, 0.5, 1)); - } - return new ColorPalette(palette, grayscale); -} - -function createGrayPalette() { - const palette = []; - for (let i = 0; i < 50; ++i) { - const code = toDec((i + 2) / 60); - palette.push(`rgb(${code}, ${code}, ${code})`); - } - return new ColorPalette(palette); -} - -/** @summary Set list of colors for specified color palette - * @desc One also can redefine existing palette - * Array should contain several colors in RGB format like `rgb(10,10,10)` or `#ff00ff` - * @private */ - -const customPalettes = {}; - -function setColorPalette(id, colors) { - if (!Number.isInteger(id) || (id < 0) || !colors?.length) - return false; - - customPalettes[id] = colors; - return true; -} - -/** @summary Create color palette - * @private */ -function getColorPalette(id, grayscale) { - id = id || settings.Palette; - if (customPalettes[id]) - return new ColorPalette(customPalettes[id], grayscale); - - if ((id > 0) && (id < 10)) - return createGrayPalette(); - if (id < 51) - return createDefaultPalette(grayscale); - if (id > 113) - id = 57; - const stops = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]; - let rgb; - /* eslint-disable @stylistic/js/comma-spacing */ - switch (id) { - // Deep Sea - case 51: rgb = [[0,9,13,17,24,32,27,25,29],[0,0,0,2,37,74,113,160,221],[28,42,59,78,98,129,154,184,221]]; break; - // Grey Scale - case 52: rgb = [[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255]]; break; - // Dark Body Radiator - case 53: rgb = [[0,45,99,156,212,230,237,234,242],[0,0,0,45,101,168,238,238,243],[0,1,1,3,9,8,11,95,230]]; break; - // Two-color hue (dark blue through neutral gray to bright yellow) - case 54: rgb = [[0,22,44,68,93,124,160,192,237],[0,16,41,67,93,125,162,194,241],[97,100,99,99,93,68,44,26,74]]; break; - // Rain Bow - case 55: rgb = [[0,5,15,35,102,196,208,199,110],[0,48,124,192,206,226,97,16,0],[99,142,198,201,90,22,13,8,2]]; break; - // Inverted Dark Body Radiator - case 56: rgb = [[242,234,237,230,212,156,99,45,0],[243,238,238,168,101,45,0,0,0],[230,95,11,8,9,3,1,1,0]]; break; - // Bird (default, keep float for backward compatibility) - case 57: - rgb = [[53.091,15.096,19.89,5.916,45.951,135.1755,208.743,253.878,248.982], - [42.432,91.7745,128.5455,163.6845,183.039,191.046,186.864,200.481,250.716], - [134.9715,221.442,213.8175,201.807,163.8375,118.881,89.2245,50.184,13.7445]]; - break; - // Cubehelix - case 58: rgb = [[0,24,2,54,176,236,202,194,255],[0,29,92,129,117,120,176,236,255],[0,68,80,34,57,172,252,245,255]]; break; - // Green Red Violet - case 59: rgb = [[13,23,25,63,76,104,137,161,206],[95,67,37,21,0,12,35,52,79],[4,3,2,6,11,22,49,98,208]]; break; - // Blue Red Yellow - case 60: rgb = [[0,61,89,122,143,160,185,204,231],[0,0,0,0,14,37,72,132,235],[0,140,224,144,4,5,6,9,13]]; break; - // Ocean - case 61: rgb = [[14,7,2,0,5,11,55,131,229],[105,56,26,1,42,74,131,171,229],[2,21,35,60,92,113,160,185,229]]; break; - // Color Printable On Grey - case 62: rgb = [[0,0,0,70,148,231,235,237,244],[0,0,0,0,0,69,67,216,244],[0,102,228,231,177,124,137,20,244]]; break; - // Alpine - case 63: rgb = [[50,56,63,68,93,121,165,192,241],[66,81,91,96,111,128,155,189,241],[97,91,75,65,77,103,143,167,217]]; break; - // Aquamarine - case 64: rgb = [[145,166,167,156,131,114,101,112,132],[158,178,179,181,163,154,144,152,159],[190,199,201,192,176,169,160,166,190]]; break; - // Army - case 65: rgb = [[93,91,99,108,130,125,132,155,174],[126,124,128,129,131,121,119,153,173],[103,94,87,85,80,85,107,120,146]]; break; - // Atlantic - case 66: rgb = [[24,40,69,90,104,114,120,132,103],[29,52,94,127,150,162,159,151,101],[29,52,96,132,162,181,184,186,131]]; break; - // Aurora - case 67: rgb = [[46,38,61,92,113,121,132,150,191],[46,36,40,69,110,135,131,92,34],[46,80,74,70,81,105,165,211,225]]; break; - // Avocado - case 68: rgb = [[0,4,12,30,52,101,142,190,237],[0,40,86,121,140,172,187,213,240],[0,9,14,18,21,23,27,35,101]]; break; - // Beach - case 69: rgb = [[198,206,206,211,198,181,161,171,244],[103,133,150,172,178,174,163,175,244],[49,54,55,66,91,130,184,224,244]]; break; - // Black Body - case 70: rgb = [[243,243,240,240,241,239,186,151,129],[0,46,99,149,194,220,183,166,147],[6,8,36,91,169,235,246,240,233]]; break; - // Blue Green Yellow - case 71: rgb = [[22,19,19,25,35,53,88,139,210],[0,32,69,108,135,159,183,198,215],[77,96,110,116,110,100,90,78,70]]; break; - // Brown Cyan - case 72: rgb = [[68,116,165,182,189,180,145,111,71],[37,82,135,178,204,225,221,202,147],[16,55,105,147,196,226,232,224,178]]; break; - // CMYK - case 73: rgb = [[61,99,136,181,213,225,198,136,24],[149,140,96,83,132,178,190,135,22],[214,203,168,135,110,100,111,113,22]]; break; - // Candy - case 74: rgb = [[76,120,156,183,197,180,162,154,140],[34,35,42,69,102,137,164,188,197],[64,69,78,105,142,177,205,217,198]]; break; - // Cherry - case 75: rgb = [[37,102,157,188,196,214,223,235,251],[37,29,25,37,67,91,132,185,251],[37,32,33,45,66,98,137,187,251]]; break; - // Coffee - case 76: rgb = [[79,100,119,137,153,172,192,205,250],[63,79,93,103,115,135,167,196,250],[51,59,66,61,62,70,110,160,250]]; break; - // Dark Rain Bow - case 77: rgb = [[43,44,50,66,125,172,178,155,157],[63,63,85,101,138,163,122,51,39],[121,101,58,44,47,55,57,44,43]]; break; - // Dark Terrain - case 78: rgb = [[0,41,62,79,90,87,99,140,228],[0,57,81,93,85,70,71,125,228],[95,91,91,82,60,43,44,112,228]]; break; - // Fall - case 79: rgb = [[49,59,72,88,114,141,176,205,222],[78,72,66,57,59,75,106,142,173],[78,55,46,40,39,39,40,41,47]]; break; - // Fruit Punch - case 80: rgb = [[243,222,201,185,165,158,166,187,219],[94,108,132,135,125,96,68,51,61],[7,9,12,19,45,89,118,146,118]]; break; - // Fuchsia - case 81: rgb = [[19,44,74,105,137,166,194,206,220],[19,28,40,55,82,110,159,181,220],[19,42,68,96,129,157,188,203,220]]; break; - // Grey Yellow - case 82: rgb = [[33,44,70,99,140,165,199,211,216],[38,50,76,105,140,165,191,189,167],[55,67,97,124,140,166,163,129,52]]; break; - // Green Brown Terrain - case 83: rgb = [[0,33,73,124,136,152,159,171,223],[0,43,92,124,134,126,121,144,223],[0,43,68,76,73,64,72,114,223]]; break; - // Green Pink - case 84: rgb = [[5,18,45,124,193,223,205,128,49],[48,134,207,230,193,113,28,0,7],[6,15,41,121,193,226,208,130,49]]; break; - // Island - case 85: rgb = [[180,106,104,135,164,188,189,165,144],[72,126,154,184,198,207,205,190,179],[41,120,158,188,194,181,145,100,62]]; break; - // Lake - case 86: rgb = [[57,72,94,117,136,154,174,192,215],[0,33,68,109,140,171,192,196,209],[116,137,173,201,200,201,203,190,187]]; break; - // Light Temperature - case 87: rgb = [[31,71,123,160,210,222,214,199,183],[40,117,171,211,231,220,190,132,65],[234,214,228,222,210,160,105,60,34]]; break; - // Light Terrain - case 88: rgb = [[123,108,109,126,154,172,188,196,218],[184,138,130,133,154,175,188,196,218],[208,130,109,99,110,122,150,171,218]]; break; - // Mint - case 89: rgb = [[105,106,122,143,159,172,176,181,207],[252,197,194,187,174,162,153,136,125],[146,133,144,155,163,167,166,162,174]]; break; - // Neon - case 90: rgb = [[171,141,145,152,154,159,163,158,177],[236,143,100,63,53,55,44,31,6],[59,48,46,44,42,54,82,112,179]]; break; - // Pastel - case 91: rgb = [[180,190,209,223,204,228,205,152,91],[93,125,147,172,181,224,233,198,158],[236,218,160,133,114,132,162,220,218]]; break; - // Pearl - case 92: rgb = [[225,183,162,135,115,111,119,145,211],[205,177,166,135,124,117,117,132,172],[186,165,155,135,126,130,150,178,226]]; break; - // Pigeon - case 93: rgb = [[39,43,59,63,80,116,153,177,223],[39,43,59,74,91,114,139,165,223],[39,50,59,70,85,115,151,176,223]]; break; - // Plum - case 94: rgb = [[0,38,60,76,84,89,101,128,204],[0,10,15,23,35,57,83,123,199],[0,11,22,40,63,86,97,94,85]]; break; - // Red Blue - case 95: rgb = [[94,112,141,165,167,140,91,49,27],[27,46,88,135,166,161,135,97,58],[42,52,81,106,139,158,155,137,116]]; break; - // Rose - case 96: rgb = [[30,49,79,117,135,151,146,138,147],[63,60,72,90,94,94,68,46,16],[18,28,41,56,62,63,50,36,21]]; break; - // Rust - case 97: rgb = [[0,30,63,101,143,152,169,187,230],[0,14,28,42,58,61,67,74,91],[39,26,21,18,15,14,14,13,13]]; break; - // Sandy Terrain - case 98: rgb = [[149,140,164,179,182,181,131,87,61],[62,70,107,136,144,138,117,87,74],[40,38,45,49,49,49,38,32,34]]; break; - // Sienna - case 99: rgb = [[99,112,148,165,179,182,183,183,208],[39,40,57,79,104,127,148,161,198],[15,16,18,33,51,79,103,129,177]]; break; - // Solar - case 100: rgb = [[99,116,154,174,200,196,201,201,230],[0,0,8,32,58,83,119,136,173],[5,6,7,9,9,14,17,19,24]]; break; - // South West - case 101: rgb = [[82,106,126,141,155,163,142,107,66],[62,44,69,107,135,152,149,132,119],[39,25,31,60,73,68,49,72,188]]; break; - // Starry Night - case 102: rgb = [[18,29,44,72,116,158,184,208,221],[27,46,71,105,146,177,189,190,183],[39,55,80,108,130,133,124,100,76]]; break; - // Sunset - case 103: rgb = [[0,48,119,173,212,224,228,228,245],[0,13,30,47,79,127,167,205,245],[0,68,75,43,16,22,55,128,245]]; break; - // Temperature Map - case 104: rgb = [[34,70,129,187,225,226,216,193,179],[48,91,147,194,226,229,196,110,12],[234,212,216,224,206,110,53,40,29]]; break; - // Thermometer - case 105: rgb = [[30,55,103,147,174,203,188,151,105],[0,65,138,182,187,175,121,53,9],[191,202,212,208,171,140,97,57,30]]; break; - // Valentine - case 106: rgb = [[112,97,113,125,138,159,178,188,225],[16,17,24,37,56,81,110,136,189],[38,35,46,59,78,103,130,152,201]]; break; - // Visible Spectrum - case 107: rgb = [[18,72,5,23,29,201,200,98,29],[0,0,43,167,211,117,0,0,0],[51,203,177,26,10,9,8,3,0]]; break; - // Water Melon - case 108: rgb = [[19,42,64,88,118,147,175,187,205],[19,55,89,125,154,169,161,129,70],[19,32,47,70,100,128,145,130,75]]; break; - // Cool - case 109: rgb = [[33,31,42,68,86,111,141,172,227],[255,175,145,106,88,55,15,0,0],[255,205,202,203,208,205,203,206,231]]; break; - // Copper - case 110: rgb = [[0,25,50,79,110,145,181,201,254],[0,16,30,46,63,82,101,124,179],[0,12,21,29,39,49,61,74,103]]; break; - // Gist Earth - case 111: rgb = [[0,13,30,44,72,120,156,200,247],[0,36,84,117,141,153,151,158,247],[0,94,100,82,56,66,76,131,247]]; break; - // Viridis - case 112: rgb = [[26,51,43,33,28,35,74,144,246],[9,24,55,87,118,150,180,200,222],[30,96,112,114,112,101,72,35,0]]; break; - // Cividis - case 113: rgb = [[0,5,65,97,124,156,189,224,255],[32,54,77,100,123,148,175,203,234],[77,110,107,111,120,119,111,94,70]]; break; - default: return createDefaultPalette(); - } - /* eslint-enable @stylistic/js/comma-spacing */ - - const NColors = 255, Red = rgb[0], Green = rgb[1], Blue = rgb[2], palette = []; - - for (let g = 1; g < stops.length; g++) { - // create the colors... - const nColorsGradient = Math.round(Math.floor(NColors * stops[g]) - Math.floor(NColors * stops[g - 1])); - for (let c = 0; c < nColorsGradient; c++) { - const col = 'rgb(' + toDec(Red[g - 1] + c * (Red[g] - Red[g - 1]) / nColorsGradient, 1) + ', ' + - toDec(Green[g - 1] + c * (Green[g] - Green[g - 1]) / nColorsGradient, 1) + ', ' + - toDec(Blue[g - 1] + c * (Blue[g] - Blue[g - 1]) / nColorsGradient, 1) + ')'; - palette.push(col); - } - } - - return new ColorPalette(palette, grayscale); -} - - -/** @summary Decode list of ROOT colors coded by TWebCanvas - * @private */ -function decodeWebCanvasColors(oper) { - const colors = [], arr = oper.split(';'), - convert_rgb = isNodeJs() || (isBatchMode() && settings.ApproxTextSize); - for (let n = 0; n < arr.length; ++n) { - const name = arr[n]; - let p = name.indexOf(':'); - if (p > 0) { - const col = `rgb(${name.slice(p + 1)})`; - colors[parseInt(name.slice(0, p))] = convert_rgb ? color(col).formatRgb() : col; - continue; - } - p = name.indexOf('='); - if (p > 0) { - let col = `rgba(${name.slice(p + 1)})`; - if (convert_rgb) { - col = color(col); - col.opacity = (Math.round(col.opacity * 255) / 255).toFixed(2); - col = col.formatRgb(); - } - colors[parseInt(name.slice(0, p))] = col; - continue; - } - p = name.indexOf('#'); - if (p < 0) - continue; - - const colindx = parseInt(name.slice(0, p)), - data = JSON.parse(name.slice(p + 1)), - grad = { _typename: data[0] === 10 ? clTLinearGradient : clTRadialGradient, fNumber: colindx, fType: data[0] }; - - let cnt = 1; - - grad.fCoordinateMode = Math.round(data[cnt++]); - const nsteps = Math.round(data[cnt++]); - grad.fColorPositions = data.slice(cnt, cnt + nsteps); - cnt += nsteps; - grad.fColors = data.slice(cnt, cnt + 4 * nsteps); - cnt += 4 * nsteps; - grad.fStart = { fX: data[cnt++], fY: data[cnt++] }; - grad.fEnd = { fX: data[cnt++], fY: data[cnt++] }; - if (grad._typename === clTRadialGradient && cnt < data.length) { - grad.fR1 = data[cnt++]; - grad.fR2 = data[cnt]; - } - - colors[colindx] = grad; - } - - return colors; -} - - -createRootColors(); - -/** @summary Standard prefix for SVG file context as data url - * @private */ -const prSVG = 'data:image/svg+xml;charset=utf-8,', -/** @summary Standard prefix for JSON file context as data url - * @private */ - prJSON = 'data:application/json;charset=utf-8,', - /** @summary Standard prefix for HTML file context as data url - * @private */ - prHTML = 'data:text/html;charset=utf-8,'; - - -/** @summary Returns visible rect of element - * @param {object} elem - d3.select object with element - * @param {string} [sizearg] - which size method is used - * @desc sizearg = 'bbox' use getBBox, works only with SVG - * sizearg = 'full' - full size of element, using getBoundingClientRect function - * sizearg = 'nopadding' - excludes padding area - * With node.js can use 'width' and 'height' attributes when provided in element - * @private */ -function getElementRect(elem, sizearg) { - if (!elem || elem.empty()) - return { x: 0, y: 0, width: 0, height: 0 }; - - if ((isNodeJs() && (sizearg !== 'bbox')) || elem.property('_batch_mode')) - return { x: 0, y: 0, width: parseInt(elem.attr('width')), height: parseInt(elem.attr('height')) }; - - const styleValue = name => { - let value = elem.style(name); - if (!value || !isStr(value)) - return 0; - value = parseFloat(value.replace('px', '')); - return !Number.isFinite(value) ? 0 : Math.round(value); - }; - - let rect = elem.node().getBoundingClientRect(); - if ((sizearg === 'bbox') && (parseFloat(rect.width) > 0)) - rect = elem.node().getBBox(); - - const res = { x: 0, y: 0, width: parseInt(rect.width), height: parseInt(rect.height) }; - if (rect.left !== undefined) { - res.x = parseInt(rect.left); - res.y = parseInt(rect.top); - } else if (rect.x !== undefined) { - res.x = parseInt(rect.x); - res.y = parseInt(rect.y); - } - - if ((sizearg === undefined) || (sizearg === 'nopadding')) { - // this is size exclude padding area - res.width -= styleValue('padding-left') + styleValue('padding-right'); - res.height -= styleValue('padding-top') + styleValue('padding-bottom'); - } - - return res; -} - - -/** @summary Calculate absolute position of provided element in canvas - * @private */ -function getAbsPosInCanvas(sel, pos) { - if (!pos) - return pos; - - while (!sel.empty() && !sel.classed('root_canvas')) { - const cl = sel.attr('class'); - if (cl && ((cl.indexOf('root_frame') >= 0) || (cl.indexOf('__root_pad_') >= 0))) { - pos.x += sel.property('draw_x') || 0; - pos.y += sel.property('draw_y') || 0; - } - sel = select(sel.node().parentNode); - } - return pos; -} - - -/** @summary Converts numeric value to string according to specified format. - * @param {number} value - value to convert - * @param {string} [fmt='6.4g'] - format can be like 5.4g or 4.2e or 6.4f - * @param {boolean} [ret_fmt] - when true returns array with value and actual format like ['0.1','6.4f'] - * @return {string|Array} - converted value or array with value and actual format - * @private */ -function floatToString(value, fmt, ret_fmt) { - if ((fmt === true) || (fmt === 'double')) - fmt = settings.DoubleFormat || '10.8g'; - else if ((fmt === false) || (fmt === 'float')) - fmt = settings.FloatFormat || '8.6g'; - else if (fmt === 'g') - fmt = '7.5g'; - else if (!fmt || !isStr(fmt)) - fmt = '6.4g'; - - fmt = fmt.trim(); - const len = fmt.length; - if (len < 2) - return ret_fmt ? [value.toFixed(4), '6.4f'] : value.toFixed(4); - - const kind = fmt[len - 1].toLowerCase(), - compact = (len > 1) && (fmt[len - 2] === 'c') ? 'c' : ''; - fmt = fmt.slice(0, len - (compact ? 2 : 1)); - - if (kind === 'g') { - const se = floatToString(value, fmt + 'ce', true), - sg = floatToString(value, fmt + 'cf', true), - res = se[0].length < sg[0].length || ((sg[0] === '0') && value) ? se : sg; - return ret_fmt ? res : res[0]; - } - - let isexp, prec = fmt.indexOf('.'); - prec = (prec < 0) ? 4 : parseInt(fmt.slice(prec + 1)); - if (!Number.isInteger(prec) || (prec <= 0)) - prec = 4; - - switch (kind) { - case 'e': - isexp = true; - break; - case 'f': - isexp = false; - break; - default: - isexp = false; - prec = 4; - } - - if (isexp) { - let se = value.toExponential(prec); - - if (compact) { - const pnt = se.indexOf('.'), - pe = se.toLowerCase().indexOf('e'); - if ((pnt > 0) && (pe > pnt)) { - let p = pe; - while ((p > pnt) && (se[p - 1] === '0')) - p--; - if (p === pnt + 1) - p--; - if (p !== pe) - se = se.slice(0, p) + se.slice(pe); - } - } - - return ret_fmt ? [se, `${prec + 2}.${prec}${compact}e`] : se; - } - - let sg = value.toFixed(prec); - - if (compact) { - let l = 0; - while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) - l++; - - let diff = sg.length - l - prec; - if (sg.indexOf('.') > l) - diff--; - - if (diff) { - prec -= diff; - if (prec < 0) - prec = 0; - else if (prec > 20) - prec = 20; - sg = value.toFixed(prec); - } - - const pnt = sg.indexOf('.'); - if (pnt > 0) { - let p = sg.length; - while ((p > pnt) && (sg[p - 1] === '0')) - p--; - if (p === pnt + 1) - p--; - sg = sg.slice(0, p); - } - - if (sg === '-0') - sg = '0'; - } - - return ret_fmt ? [sg, `${prec + 2}.${prec}${compact}f`] : sg; -} - - -/** @summary Draw options interpreter - * @private */ -class DrawOptions { - - constructor(opt) { - if (isStr(opt)) { - this.origin = opt.trim(); - this.opt = this.origin.toUpperCase(); - } else - this.opt = this.origin = ''; - this.part = this.partO = ''; - } - - /** @summary Returns true if remaining options are empty or contain only separators symbols. */ - empty() { return !this.opt ? true : !this.opt.replace(/[ ;_,]/g, ''); } - - /** @summary Returns remaining part of the draw options. */ - remain() { return this.opt; } - - /** @summary Remove [pos, pos2) part from the string */ - #cut(pos, pos2) { - this.opt = this.opt.slice(0, pos) + this.opt.slice(pos2); - this.origin = this.origin.slice(0, pos) + this.origin.slice(pos2); - } - - /** @summary Checks if given option exists */ - check(name, postpart) { - const pos = this.opt.indexOf(name); - if (pos < 0) - return false; - this.#cut(pos, pos + name.length); - this.part = ''; - if (!postpart) - return true; - - let pos2 = pos; - const is_array = postpart === 'array'; - if (is_array) { - if (this.opt[pos2] !== '[') - return false; - while ((pos2 < this.opt.length) && (this.opt[pos2] !== ']')) - pos2++; - if (++pos2 > this.opt.length) - return false; - } else { - while ((pos2 < this.opt.length) && (this.opt[pos2] !== ' ') && (this.opt[pos2] !== ',') && (this.opt[pos2] !== ';')) - pos2++; - } - if (pos2 > pos) { - this.part = this.opt.slice(pos, pos2); - this.partO = this.origin.slice(pos, pos2); - this.#cut(pos, pos2); - } - - if (is_array) { - try { - this.array = JSON.parse(this.part); - } catch { - this.array = undefined; - } - return this.array?.length !== undefined; - } - - if (postpart !== 'color') - return true; - - if (((this.part.length === 6) || (this.part.length === 8)) && this.part.match(/^[a-fA-F0-9]+/)) { - this.color = addColor('#' + this.part); - return true; - } - - this.color = this.partAsInt(1) - 1; - if (this.color >= 0) - return true; - for (let col = 0; col < 8; ++col) { - if (getColor(col).toUpperCase() === this.part) { - this.color = col; - return true; - } - } - return false; - } - - /** @summary Returns (original) part after found options. */ - getPart(origin) { return origin ? this.partO : this.part; } - - /** @summary Returns remaining part of found option as integer. */ - partAsInt(offset, dflt) { - let mult = 1; - const last = this.part ? this.part.at(-1) : ''; - if (last === 'K') - mult = 1e3; - else if (last === 'M') - mult = 1e6; - else if (last === 'G') - mult = 1e9; - let val = this.part.replace(/^\D+/g, ''); - val = val ? parseInt(val, 10) : Number.NaN; - return !Number.isInteger(val) ? (dflt || 0) : mult * val + (offset || 0); - } - - /** @summary Returns remaining part of found option as float. */ - partAsFloat(offset, dflt) { - let val = this.part.replace(/^\D+/g, ''); - val = val ? parseFloat(val) : Number.NaN; - return !Number.isFinite(val) ? (dflt || 0) : val + (offset || 0); - } - -} // class DrawOptions - - -/** @summary Simple random generator with controlled seed - * @private */ -class TRandom { - - constructor(i) { - if (i !== undefined) - this.seed(i); - } - - /** @summary Seed simple random generator */ - seed(i) { - i = Math.abs(i); - if (i > 1e8) - i = Math.abs(1e8 * Math.sin(i)); - else if (i < 1) - i *= 1e8; - this.m_w = Math.round(i); - this.m_z = 987654321; - } - - /** @summary Produce random value between 0 and 1 */ - random() { - if (this.m_z === undefined) - return Math.random(); - this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >> 16)) & 0xffffffff; - this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >> 16)) & 0xffffffff; - let result = ((this.m_z << 16) + this.m_w) & 0xffffffff; - result /= 4294967296; - return result + 0.5; - } - -} // class TRandom - - -/** @summary Build smooth SVG curve using Bezier - * @desc Reuse code from https://stackoverflow.com/questions/62855310 - * @private */ -function buildSvgCurve(p, args) { - if (!args) - args = {}; - if (!args.line) - args.calc = true; - else if (args.ndig === undefined) - args.ndig = 0; - - let npnts = p.length; - if (npnts < 3) - args.line = true; - - args.t = args.t ?? 0.2; - - if ((args.ndig === undefined) || args.height) { - args.maxy = p[0].gry; - args.mindiff = 100; - for (let i = 1; i < npnts; i++) { - args.maxy = Math.max(args.maxy, p[i].gry); - args.mindiff = Math.min(args.mindiff, Math.abs(p[i].grx - p[i - 1].grx), Math.abs(p[i].gry - p[i - 1].gry)); - } - if (args.ndig === undefined) - args.ndig = args.mindiff > 20 ? 0 : (args.mindiff > 5 ? 1 : 2); - } - - const end_point = (pnt1, pnt2, sign) => { - const len = Math.sqrt((pnt2.gry - pnt1.gry) ** 2 + (pnt2.grx - pnt1.grx) ** 2) * args.t, - a2 = Math.atan2(pnt2.dgry, pnt2.dgrx), - a1 = Math.atan2(sign * (pnt2.gry - pnt1.gry), sign * (pnt2.grx - pnt1.grx)); - - pnt1.dgrx = len * Math.cos(2 * a1 - a2); - pnt1.dgry = len * Math.sin(2 * a1 - a2); - }, conv = val => { - if (!args.ndig || (Math.round(val) === val)) - return val.toFixed(0); - let s = val.toFixed(args.ndig), p1 = s.length - 1; - while (s[p1] === '0') - p1--; - if (s[p1] === '.') - p1--; - s = s.slice(0, p1 + 1); - return (s === '-0') ? '0' : s; - }; - - if (args.calc) { - for (let i = 1; i < npnts - 1; i++) { - p[i].dgrx = (p[i + 1].grx - p[i - 1].grx) * args.t; - p[i].dgry = (p[i + 1].gry - p[i - 1].gry) * args.t; - } - - if (npnts > 2) { - end_point(p[0], p[1], 1); - end_point(p[npnts - 1], p[npnts - 2], -1); - } else if (p.length === 2) { - p[0].dgrx = (p[1].grx - p[0].grx) * args.t; - p[0].dgry = (p[1].gry - p[0].gry) * args.t; - p[1].dgrx = -p[0].dgrx; - p[1].dgry = -p[0].dgry; - } - } - - let path = `${args.cmd ?? 'M'}${conv(p[0].grx)},${conv(p[0].gry)}`; - - if (!args.line) { - let i0 = 1; - if (args.qubic) { - npnts--; i0++; - path += `Q${conv(p[1].grx - p[1].dgrx)},${conv(p[1].gry - p[1].dgry)},${conv(p[1].grx)},${conv(p[1].gry)}`; - } - path += `C${conv(p[i0 - 1].grx + p[i0 - 1].dgrx)},${conv(p[i0 - 1].gry + p[i0 - 1].dgry)},${conv(p[i0].grx - p[i0].dgrx)},${conv(p[i0].gry - p[i0].dgry)},${conv(p[i0].grx)},${conv(p[i0].gry)}`; - - // continue with simpler points - for (let i = i0 + 1; i < npnts; i++) - path += `S${conv(p[i].grx - p[i].dgrx)},${conv(p[i].gry - p[i].dgry)},${conv(p[i].grx)},${conv(p[i].gry)}`; - - if (args.qubic) - path += `Q${conv(p[npnts].grx - p[npnts].dgrx)},${conv(p[npnts].gry - p[npnts].dgry)},${conv(p[npnts].grx)},${conv(p[npnts].gry)}`; - } else if (npnts < 10000) { - // build simple curve - - let acc_x = 0, acc_y = 0, currx = Math.round(p[0].grx), curry = Math.round(p[0].gry); - - const flush = () => { - if (acc_x) { - path += 'h' + acc_x; - acc_x = 0; - } - if (acc_y) { - path += 'v' + acc_y; - acc_y = 0; - } - }; - - for (let n = 1; n < npnts; ++n) { - const bin = p[n], - dx = Math.round(bin.grx) - currx, - dy = Math.round(bin.gry) - curry; - if (dx && dy) { - flush(); - path += `l${dx},${dy}`; - } else if (!dx && dy) { - if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) - flush(); - acc_y += dy; - } else if (dx && !dy) { - if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) - flush(); - acc_x += dx; - } - currx += dx; - curry += dy; - } - - flush(); - } else { - // build line with trying optimize many vertical moves - let currx = Math.round(p[0].grx), curry = Math.round(p[0].gry), - cminy = curry, cmaxy = curry, prevy = curry; - - for (let n = 1; n < npnts; ++n) { - const bin = p[n], - lastx = Math.round(bin.grx), - lasty = Math.round(bin.gry), - dx = lastx - currx; - if (dx === 0) { - // if X not change, just remember amplitude and - cminy = Math.min(cminy, lasty); - cmaxy = Math.max(cmaxy, lasty); - prevy = lasty; - continue; - } - - if (cminy !== cmaxy) { - if (cminy !== curry) - path += `v${cminy - curry}`; - path += `v${cmaxy - cminy}`; - if (cmaxy !== prevy) - path += `v${prevy - cmaxy}`; - curry = prevy; - } - const dy = lasty - curry; - if (dy) - path += `l${dx},${dy}`; - else - path += `h${dx}`; - currx = lastx; - curry = lasty; - prevy = cminy = cmaxy = lasty; - } - - if (cminy !== cmaxy) { - if (cminy !== curry) - path += `v${cminy - curry}`; - path += `v${cmaxy - cminy}`; - if (cmaxy !== prevy) - path += `v${prevy - cmaxy}`; - } - } - - if (args.height) - args.close = `L${conv(p.at(-1).grx)},${conv(Math.max(args.maxy, args.height))}H${conv(p[0].grx)}Z`; - - return path; -} - -/** @summary Compress SVG code, produced from drawing - * @desc removes extra info or empty elements - * @private */ -function compressSVG(svg) { - svg = svg.replace(/url\("#(\w+)"\)/g, 'url(#$1)') // decode all URL - .replace(/ class="\w*"/g, '') // remove all classes - .replace(/ pad="\w*"/g, '') // remove all pad ids - .replace(/ title=""/g, '') // remove all empty titles - .replace(/ style=""/g, '') // remove all empty styles - .replace(/<\/g>/g, '') // remove all empty groups with transform - .replace(/<\/g>/g, '') // remove hidden title - .replace(/<\/g>/g, ''); // remove all empty groups - - // remove all empty frame svg, typically appears in 3D drawings, maybe should be improved in frame painter itself - svg = svg.replace(/<\/svg>/g, ''); - - return svg; -} - - -/** - * @summary Base painter class - * - */ - -class BasePainter { - - #divid; // either id of DOM element or element itself - #selected_main; // d3.select for dom elements - #hitemname; // item name in the hpainter - #hdrawopt; // draw option in the hpainter - #hpainter; // assigned hpainter - - /** @summary constructor - * @param {object|string} [dom] - dom element or id of dom element */ - constructor(dom) { - this.#divid = null; // either id of DOM element or element itself - if (dom) - this.setDom(dom); - } - - /** @summary Assign painter to specified DOM element - * @param {string|object} elem - element ID or DOM Element - * @desc Normally DOM element should be already assigned in constructor - * @protected */ - setDom(elem) { - if (elem !== undefined) { - this.#divid = elem; - this.#selected_main = null; - } - } - - /** @summary Returns assigned dom element */ - getDom() { return this.#divid; } - - /** @summary Returns argument for draw function */ - getDrawDom() { return this.#divid; } - - /** @summary Selects main HTML element assigned for drawing - * @desc if main element was layout, returns main element inside layout - * @param {string} [is_direct] - if 'origin' specified, returns original element even if actual drawing moved to some other place - * @return {object} d3.select object for main element for drawing */ - selectDom(is_direct) { - if (!this.#divid) - return select(null); - - let res = this.#selected_main; - if (!res) { - if (isStr(this.#divid)) { - let id = this.#divid; - if (id[0] !== '#') - id = '#' + id; - res = select(id); - if (!res.empty()) - this.#divid = res.node(); - } else - res = select(this.#divid); - this.#selected_main = res; - } - - if (!res || res.empty() || (is_direct === 'origin')) - return res; - - const use_enlarge = res.property('use_enlarge'), - layout = res.property('layout') || 'simple', - layout_selector = (layout === 'simple') ? '' : res.property('layout_selector'); - - if (layout_selector) - res = res.select(layout_selector); - - // one could redirect here - if (!is_direct && !res.empty() && use_enlarge) - res = select(getDocument().getElementById('jsroot_enlarge_div')); - - return res; - } - - /** @summary Access/change top painter - * @private */ - #accessTopPainter(on) { - const chld = this.selectDom().node()?.firstChild; - if (!chld) - return null; - if (on === true) - chld.painter = this; - else if ((on === false) && (chld.painter === this)) - delete chld.painter; - return chld.painter; - } - - /** @summary Set painter, stored in first child element - * @desc Only make sense after first drawing is completed and any child element add to configured DOM - * @protected */ - setTopPainter() { this.#accessTopPainter(true); } - - /** @summary Return top painter set for the selected dom element - * @protected */ - getTopPainter() { return this.#accessTopPainter(); } - - /** @summary Clear reference on top painter - * @protected */ - clearTopPainter() { this.#accessTopPainter(false); } - - /** @summary Generic method to cleanup painter - * @desc Removes all visible elements and all internal data */ - cleanup(keep_origin) { - this.clearTopPainter(); - const origin = this.selectDom('origin'); - if (!origin.empty() && !keep_origin) - origin.html(''); - this.#divid = null; - this.#selected_main = undefined; - - if (isFunc(this.#hpainter?.removePainter)) - this.#hpainter.removePainter(this); - - this.#hitemname = undefined; - this.#hdrawopt = undefined; - this.#hpainter = undefined; - } - - /** @summary Checks if draw elements were resized and drawing should be updated - * @return {boolean} true if resize was detected - * @protected - * @abstract */ - checkResize(/* arg */) {} - - /** @summary Function checks if geometry of main div was changed. - * @desc take into account enlarge state, used only in PadPainter class - * @return size of area when main div is drawn - * @private */ - testMainResize(check_level, new_size, height_factor) { - const enlarge = this.enlargeMain('state'), - origin = this.selectDom('origin'), - main = this.selectDom(), - lmt = 5; // minimal size - - if ((enlarge !== 'on') && new_size?.width && new_size?.height) { - origin.style('width', new_size.width + 'px') - .style('height', new_size.height + 'px'); - } - - const rect_origin = getElementRect(origin, true), - can_resize = origin.attr('can_resize'); - let do_resize = false; - - if ((can_resize === 'height') && height_factor && Math.abs(rect_origin.width * height_factor - rect_origin.height) > 0.1 * rect_origin.width) - do_resize = true; - - if (((rect_origin.height <= lmt) || (rect_origin.width <= lmt)) && can_resize && (can_resize !== 'false')) - do_resize = true; - - if (do_resize && (enlarge !== 'on')) { - // if zero size and can_resize attribute set, change container size - - if (rect_origin.width > lmt) { - height_factor = height_factor || 0.66; - origin.style('height', Math.round(rect_origin.width * height_factor) + 'px'); - } else if (can_resize !== 'height') - origin.style('width', '200px').style('height', '100px'); - } - - const rect = getElementRect(main), - old_h = main.property('_jsroot_height'), - old_w = main.property('_jsroot_width'); - - rect.changed = false; - - if (!rect.width && !rect.height && !main.empty() && main.attr('style')) { - const ws = main.style('width'), hs = main.style('height'); - if (isStr(ws) && isStr(hs) && ws.match(/^\d+px$/) && hs.match(/^\d+px$/)) { - rect.width = parseInt(ws.slice(0, ws.length - 2)); - rect.height = parseInt(hs.slice(0, hs.length - 2)); - } - } - - if (old_h && old_w && (old_h > 0) && (old_w > 0)) { - if ((old_h !== rect.height) || (old_w !== rect.width)) - rect.changed = (check_level > 1) || (rect.width / old_w < 0.99) || (rect.width / old_w > 1.01) || (rect.height / old_h < 0.99) || (rect.height / old_h > 1.01); - } else - rect.changed = true; - - if (rect.changed) - main.property('_jsroot_height', rect.height).property('_jsroot_width', rect.width); - - // after change enlarge state always mark main element as resized - if (origin.property('did_enlarge')) { - rect.changed = true; - origin.property('did_enlarge', false); - } - - return rect; - } - - /** @summary Try enlarge main drawing element to full HTML page. - * @param {string|boolean} action - defines that should be done - * @desc Possible values for action parameter: - * - true - try to enlarge - * - false - revert enlarge state - * - 'toggle' - toggle enlarge state - * - 'state' - only returns current enlarge state - * - 'verify' - check if element can be enlarged - * if action not specified, just return possibility to enlarge main div - * @protected */ - enlargeMain(action, skip_warning) { - const main = this.selectDom(true), - origin = this.selectDom('origin'), - doc = getDocument(); - - if (main.empty() || !settings.CanEnlarge || (origin.property('can_enlarge') === false)) - return false; - - if ((action === undefined) || (action === 'verify')) - return true; - - const state = origin.property('use_enlarge') ? 'on' : 'off'; - - if (action === 'state') - return state; - - if (action === 'toggle') - action = (state === 'off'); - - let enlarge = select(doc.getElementById('jsroot_enlarge_div')); - - if ((action === true) && (state !== 'on')) { - if (!enlarge.empty()) - return false; - - enlarge = select(doc.body) - .append('div') - .attr('id', 'jsroot_enlarge_div') - .attr('style', 'position: fixed; margin: 0px; border: 0px; padding: 0px; left: 1px; top: 1px; bottom: 1px; right: 1px; background: white; opacity: 0.95; z-index: 100; overflow: hidden;'); - - const rect1 = getElementRect(main), - rect2 = getElementRect(enlarge); - - // if new enlarge area not big enough, do not do it - if ((rect2.width <= rect1.width) || (rect2.height <= rect1.height)) { - if (rect2.width * rect2.height < rect1.width * rect1.height) { - if (!skip_warning) - console.log(`Enlarged area ${rect2.width} x ${rect2.height} smaller then original drawing ${rect1.width} x ${rect1.height}`); - enlarge.remove(); - return false; - } - } - - while (main.node().childNodes.length) - enlarge.node().appendChild(main.node().firstChild); - - origin.property('use_enlarge', true); - origin.property('did_enlarge', true); - return true; - } - if ((action === false) && (state !== 'off')) { - while (enlarge.node()?.childNodes.length) - main.node().appendChild(enlarge.node().firstChild); - - enlarge.remove(); - origin.property('use_enlarge', false); - origin.property('did_enlarge', true); - return true; - } - - return false; - } - - /** @summary Set item name, associated with the painter - * @desc Used by {@link HierarchyPainter} - * @private */ - setItemName(name, opt, hpainter) { - this.#hitemname = isStr(name) ? name : undefined; - // only update draw option, never delete. - if (isStr(opt)) - this.#hdrawopt = opt; - - this.#hpainter = hpainter; - } - - /** @summary Returns assigned histogram painter */ - getHPainter() { return this.#hpainter; } - - /** @summary Returns assigned item name - * @desc Used with {@link HierarchyPainter} to identify drawn item name */ - getItemName() { return this.#hitemname ?? null; } - - /** @summary Returns assigned item draw option - * @desc Used with {@link HierarchyPainter} to identify drawn item option */ - getItemDrawOpt() { return this.#hdrawopt ?? ''; } - -} // class BasePainter - -/** @summary Load and initialize JSDOM from nodes - * @return {Promise} with d3 selection for d3_body - * @private */ -async function _loadJSDOM() { - return import('jsdom').then(handle => { - if (!internals.nodejs_window) { - internals.nodejs_window = (new handle.JSDOM('hello')).window; - internals.nodejs_document = internals.nodejs_window.document; // used with three.js - internals.nodejs_body = select(internals.nodejs_document).select('body'); // get d3 handle for body - } - - return { JSDOM: handle.JSDOM, doc: internals.nodejs_document, body: internals.nodejs_body }; - }); -} - -/** @summary Return translate string for transform attribute of some svg element - * @return string or null if x and y are zeros - * @private */ -function makeTranslate(g, x, y, scale = 1) { - if (!isObject(g)) { - scale = y; - y = x; - x = g; - g = null; - } - let res = y ? `translate(${x},${y})` : (x ? `translate(${x})` : null); - if (scale && scale !== 1) { - if (res) - res += ' '; - else - res = ''; - res += `scale(${scale.toFixed(3)})`; - } - - return g ? g.attr('transform', res) : res; -} - - -/** @summary Configure special style used for highlight or dragging elements - * @private */ -function addHighlightStyle(elem, drag) { - if (drag) { - elem.style('stroke', 'steelblue') - .style('fill-opacity', '0.1'); - } else { - elem.style('stroke', '#4572A7') - .style('fill', '#4572A7') - .style('opacity', '0'); - } -} - -/** @summary Create image based on SVG - * @param {string} svg - svg code of the image - * @param {string} [image_format] - image format like 'png', 'jpeg' or 'webp' - * @param {Objects} [args] - optional arguments - * @param {boolean} [args.as_buffer] - return image as buffer - * @return {Promise} with produced image in base64 form or as Buffer (or canvas when no image_format specified) - * @private */ -async function svgToImage(svg, image_format, args) { - if ((args === true) || (args === false)) - args = { as_buffer: args }; - - if (image_format === 'svg') - return svg; - - if (image_format === 'pdf') - return internals.makePDF ? internals.makePDF(svg, args) : null; - - // required with df104.py/df105.py example with RCanvas or any special symbols in TLatex - const doctype = '', - is_rgba = image_format === 'rgba'; - - if (isNodeJs()) { - if (image_format === 'jpeg') { - console.log('JPEG image format not supported in node.js, use PNG'); - return null; - } - svg = encodeURIComponent(doctype + svg); - svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { - const c = String.fromCharCode('0x' + p1); - return c === '%' ? '%25' : c; - }); - - const img_src = 'data:image/svg+xml;base64,' + btoa_func(decodeURIComponent(svg)); - - // Use the newer and stabler `resvg-js` backend for converting SVG to PNG - if (settings.UseResvgJs) { - return import('@resvg/resvg-js').then(({ Resvg }) => { - const rawSvg = decodeURIComponent(svg), // raw SVG XML - resvg = new Resvg(rawSvg), // Initialize Resvg and create the PNG buffer - renderData = resvg.render(), - pngBuffer = renderData.asPng(); - - // Return raw RGBA pixels if caller requested it - if (is_rgba) { - return { - width: renderData.width, - height: renderData.height, - data: renderData.pixels - }; - } - - if (args?.as_buffer) - return pngBuffer; - - return 'data:image/png;base64,' + pngBuffer.toString('base64'); - }); - } - - // Fallback to `node-canvas` - return import('canvas').then(async handle => { - return handle.default.loadImage(img_src).then(img => { - const canvas = handle.default.createCanvas(img.width, img.height); - - canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height); - - if (args?.as_buffer) - return canvas.toBuffer('image/' + image_format); - - return image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas; - }); - }); - } - - const img_src = URL.createObjectURL(new Blob([doctype + svg], { type: 'image/svg+xml;charset=utf-8' })); - - return new Promise(resolveFunc => { - const image = document.createElement('img'); - - image.onload = function() { - URL.revokeObjectURL(img_src); - - const canvas = document.createElement('canvas'); - canvas.width = image.width; - canvas.height = image.height; - - canvas.getContext('2d').drawImage(image, 0, 0); - - if (args?.as_buffer && image_format) - canvas.toBlob(blob => blob.arrayBuffer().then(resolveFunc), 'image/' + image_format); - else - resolveFunc(image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas); - }; - image.onerror = function(arg) { - URL.revokeObjectURL(img_src); - console.log(`IMAGE ERROR ${arg}`); - resolveFunc(null); - }; - - image.setAttribute('src', img_src); - }); -} - -/** @summary Convert ROOT TDatime object into Date - * @desc Always use UTC to avoid any variation between timezones */ -function getTDatime(dt) { - const y = (dt.fDatime >>> 26) + 1995, - m = ((dt.fDatime << 6) >>> 28) - 1, - d = (dt.fDatime << 10) >>> 27, - h = (dt.fDatime << 15) >>> 27, - min = (dt.fDatime << 20) >>> 26, - s = (dt.fDatime << 26) >>> 26; - return new Date(Date.UTC(y, m, d, h, min, s)); -} - -/** @summary Convert Date object into string used configured time zone - * @desc Time zone stored in settings.TimeZone */ -function convertDate(dt) { - let res = ''; - - if (settings.TimeZone && isStr(settings.TimeZone)) { - try { - res = dt.toLocaleString('en-GB', { timeZone: settings.TimeZone }); - } catch { - res = ''; - } - } - return res || dt.toLocaleString('en-GB'); -} - -/** @summary Box decorations - * @private */ -function getBoxDecorations(xx, yy, ww, hh, bmode, pww, phh) { - const side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2 * pww - ww}v${hh - 2 * phh}l${-pww},${phh}z`, - side2 = `M${xx + ww},${yy + hh}v${-hh}l${-pww},${phh}v${hh - 2 * phh}h${2 * pww - ww}l${-pww},${phh}z`; - return bmode > 0 ? [side1, side2] : [side2, side1]; -} - -const kArial = 'Arial', kTimes = 'Times New Roman', kCourier = 'Courier New', kVerdana = 'Verdana', kSymbol = 'RootSymbol', kWingdings = 'Wingdings', - // average width taken from symbols.html, counted only for letters and digits - root_fonts = [null, // index 0 not exists - { n: kTimes, s: 'italic', aw: 0.5314 }, - { n: kTimes, w: 'bold', aw: 0.5809 }, - { n: kTimes, s: 'italic', w: 'bold', aw: 0.5540 }, - { n: kArial, aw: 0.5778 }, - { n: kArial, s: 'oblique', aw: 0.5783 }, - { n: kArial, w: 'bold', aw: 0.6034 }, - { n: kArial, s: 'oblique', w: 'bold', aw: 0.6030 }, - { n: kCourier, aw: 0.6003 }, - { n: kCourier, s: 'oblique', aw: 0.6004 }, - { n: kCourier, w: 'bold', aw: 0.6003 }, - { n: kCourier, s: 'oblique', w: 'bold', aw: 0.6005 }, - { n: kSymbol, aw: 0.5521, file: 'symbol.ttf' }, - { n: kTimes, aw: 0.5521 }, - { n: kWingdings, aw: 0.5664, file: 'wingding.ttf' }, - { n: kSymbol, s: 'oblique', aw: 0.5314, file: 'symbol.ttf' }, - { n: kVerdana, aw: 0.5664 }, - { n: kVerdana, s: 'italic', aw: 0.5495 }, - { n: kVerdana, w: 'bold', aw: 0.5748 }, - { n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }], - // list of loaded fonts including handling of multiple simultaneous requests - gFontFiles = {}; - -/** @summary Read font file from some pre-configured locations - * @return {Promise} with base64 code of the font - * @private */ -async function loadFontFile(fname) { - let entry = gFontFiles[fname]; - if (entry?.base64) - return entry?.base64; - - if (entry?.promises !== undefined) { - return new Promise(resolveFunc => { - entry.promises.push(resolveFunc); - }); - } - - entry = gFontFiles[fname] = { promises: [] }; - - const locations = []; - if (fname.indexOf('/') >= 0) - locations.push(''); // just use file name as is - else { - locations.push(exports.source_dir + 'fonts/'); - if (isNodeJs()) - locations.push('../../fonts/'); - else if (exports.source_dir.indexOf('jsrootsys/') >= 0) { - locations.unshift(exports.source_dir.replace(/jsrootsys/g, 'rootsys_fonts')); - locations.unshift(exports.source_dir.replace(/jsrootsys/g, 'rootsys/fonts')); - } - } - - function completeReading(base64) { - entry.base64 = base64; - const arr = entry.promises; - delete entry.promises; - arr.forEach(func => func(base64)); - return base64; - } - - async function tryNext() { - if (!locations.length) { - completeReading(null); - throw new Error(`Fail to load ${fname} font`); - } - let path = locations.shift() + fname; - console.log('loading font', path); - const pr = isNodeJs() ? import('fs').then(fs => { - const prefix = 'file://' + (process?.platform === 'win32' ? '/' : ''); - if (path.indexOf(prefix) === 0) - path = path.slice(prefix.length); - return fs.readFileSync(path).toString('base64'); - }) : httpRequest(path, 'bin').then(buf => btoa_func(buf)); - - return pr.then(res => { return res ? completeReading(res) : tryNext();}).catch(() => tryNext()); - } - - return tryNext(); -} - - -/** - * @summary Helper class for font handling - * @private - */ - -class FontHandler { - - /** @summary constructor */ - constructor(fontIndex, size, scale) { - if (scale && (size < 1)) { - size *= scale; - this.scaled = true; - } - - this.size = Math.round(size); - this.scale = scale; - this.index = 0; - - this.func = this.setFont.bind(this); - - let cfg; - - if (fontIndex && isObject(fontIndex)) - cfg = fontIndex; - else { - if (fontIndex && Number.isInteger(fontIndex)) - this.index = Math.floor(fontIndex / 10); - cfg = root_fonts[this.index]; - } - - if (cfg) { - this.cfg = cfg; - this.setNameStyleWeight(cfg.n, cfg.s, cfg.w, cfg.aw, cfg.format, cfg.base64); - } else - this.setNameStyleWeight(kArial); - } - - /** @summary Should returns true if font has to be loaded before - * @private */ - needLoad() { return this.cfg?.file && !this.isSymbol && !this.base64; } - - /** @summary Async function to load font - * @private */ - async load() { - if (!this.needLoad()) - return true; - return loadFontFile(this.cfg.file).then(base64 => { - this.cfg.base64 = this.base64 = base64; - this.format = 'ttf'; - return Boolean(base64); - }); - } - - /** @summary Directly set name, style and weight for the font - * @private */ - setNameStyleWeight(name, style, weight, aver_width, format, base64) { - this.name = name; - this.style = style || null; - this.weight = weight || null; - this.aver_width = aver_width || (weight ? 0.58 : 0.55); - this.format = format; // format of custom font, ttf by default - this.base64 = base64; // indication of custom font - if (!settings.LoadSymbolTtf && ((this.name === kSymbol) || (this.name === kWingdings))) { - this.isSymbol = this.name; - this.name = kTimes; - } else - this.isSymbol = ''; - } - - /** @summary Set painter for which font will be applied */ - setPainter(painter) { - this.painter = painter; - } - - /** @summary Force setting of style and weight, used in latex */ - setUseFullStyle(flag) { - this.full_style = flag; - } - - /** @summary Assigns font-related attributes */ - addCustomFontToSvg(svg) { - if (!this.base64 || !this.name) - return; - const clname = 'custom_font_' + this.name.replace(/[^a-zA-Z0-9]/g, '_'), fmt = 'ttf'; - let defs = svg.selectChild('.canvas_defs'); - if (defs.empty()) - defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); - const entry = defs.selectChild('.' + clname); - if (entry.empty()) { - defs.append('style') - .attr('class', clname) - .property('$fontcfg', this.cfg || null) - .text(`@font-face { font-family: "${this.name}"; font-weight: normal; font-style: normal; src: url(data:application/font-${fmt};charset=utf-8;base64,${this.base64}); }`); - } - } - - /** @summary Assigns font-related attributes */ - setFont(selection) { - if (this.base64 && this.painter) - this.addCustomFontToSvg(this.painter.getCanvSvg()); - - selection.attr('font-family', this.name) - .attr('font-size', this.size) - .attr(':xml:space', 'preserve'); - this.setFontStyle(selection); - } - - /** @summary Assigns only font style attributes */ - setFontStyle(selection) { - selection.attr('font-weight', this.weight || (this.full_style ? 'normal' : null)) - .attr('font-style', this.style || (this.full_style ? 'normal' : null)); - } - - /** @summary Set font size (optional) */ - setSize(size) { this.size = Math.round(size); } - - /** @summary Set text color (optional) */ - setColor(color) { this.color = color; } - - /** @summary Set text align (optional) */ - setAlign(align) { this.align = align; } - - /** @summary Set text angle (optional) */ - setAngle(angle) { this.angle = angle; } - - /** @summary Align angle to step raster, add optional offset */ - roundAngle(step, offset) { - this.angle = parseInt(this.angle || 0); - if (!Number.isInteger(this.angle)) - this.angle = 0; - this.angle = Math.round(this.angle / step) * step + (offset || 0); - if (this.angle < 0) - this.angle += 360; - else if (this.angle >= 360) - this.angle -= 360; - } - - /** @summary Clears all font-related attributes */ - clearFont(selection) { - selection.attr('font-family', null) - .attr('font-size', null) - .attr(':xml:space', null) - .attr('font-weight', null) - .attr('font-style', null); - } - - /** @summary Returns true in case of monospace font - * @private */ - isMonospace() { - const n = this.name.toLowerCase(); - return (n.indexOf('courier') === 0) || (n === 'monospace') || (n === 'monaco'); - } - - /** @summary Return full font declaration which can be set as font property like '12pt Arial bold' - * @private */ - getFontHtml() { - let res = Math.round(this.size) + 'pt ' + this.name; - if (this.weight) - res += ' ' + this.weight; - if (this.style) - res += ' ' + this.style; - return res; - } - - /** @summary Returns font name */ - getFontName() { - return this.isSymbol || this.name || 'none'; - } - -} // class FontHandler - -/** @summary Register custom font - * @private */ -function addCustomFont(index, name, format, base64) { - if (!Number.isInteger(index)) - console.error(`Wrong index ${index} for custom font`); - else - root_fonts[index] = { n: name, format, base64 }; -} - -/** @summary Try to detect and create font handler for SVG text node - * @private */ -function detectPdfFont(node) { - const sz = node.getAttribute('font-size'), - p = sz.indexOf('px'), - sz_pixels = p > 0 ? Number.parseInt(sz.slice(0, p)) : 12; - - let family = node.getAttribute('font-family'), - style = node.getAttribute('font-style'), - weight = node.getAttribute('font-weight'); - - if (family === 'times') - family = kTimes; - else if (family === 'symbol') - family = kSymbol; - else if (family === 'arial') - family = kArial; - else if (family === 'verdana') - family = kVerdana; - if (weight === 'normal') - weight = ''; - if (style === 'normal') - style = ''; - - const fcfg = root_fonts.find(elem => { - return (elem?.n === family) && - ((!weight && !elem.w) || (elem.w === weight)) && - ((!style && !elem.s) || (elem.s === style)); - }); - - return new FontHandler(fcfg || root_fonts[13], sz_pixels); -} - -/* eslint-disable one-var */ - -const symbols_map = { - // greek letters from symbols.ttf - '#alpha': '\u03B1', - '#beta': '\u03B2', - '#chi': '\u03C7', - '#delta': '\u03B4', - '#varepsilon': '\u03B5', - '#phi': '\u03C6', - '#gamma': '\u03B3', - '#eta': '\u03B7', - '#iota': '\u03B9', - '#varphi': '\u03C6', - '#kappa': '\u03BA', - '#lambda': '\u03BB', - '#mu': '\u03BC', - '#nu': '\u03BD', - '#omicron': '\u03BF', - '#pi': '\u03C0', - '#theta': '\u03B8', - '#rho': '\u03C1', - '#sigma': '\u03C3', - '#tau': '\u03C4', - '#upsilon': '\u03C5', - '#varomega': '\u03D6', - '#omega': '\u03C9', - '#xi': '\u03BE', - '#psi': '\u03C8', - '#zeta': '\u03B6', - '#Alpha': '\u0391', - '#Beta': '\u0392', - '#Chi': '\u03A7', - '#Delta': '\u0394', - '#Epsilon': '\u0395', - '#Phi': '\u03A6', - '#Gamma': '\u0393', - '#Eta': '\u0397', - '#Iota': '\u0399', - '#vartheta': '\u03D1', - '#Kappa': '\u039A', - '#Lambda': '\u039B', - '#Mu': '\u039C', - '#Nu': '\u039D', - '#Omicron': '\u039F', - '#Pi': '\u03A0', - '#Theta': '\u0398', - '#Rho': '\u03A1', - '#Sigma': '\u03A3', - '#Tau': '\u03A4', - '#Upsilon': '\u03A5', - '#varsigma': '\u03C2', - '#Omega': '\u03A9', - '#Xi': '\u039E', - '#Psi': '\u03A8', - '#Zeta': '\u0396', - '#varUpsilon': '\u03D2', - '#epsilon': '\u03B5', - - // second set from symbols.ttf - '#leq': '\u2264', - '#/': '\u2044', - '#infty': '\u221E', - '#voidb': '\u0192', - '#club': '\u2663', - '#diamond': '\u2666', - '#heart': '\u2665', - '#spade': '\u2660', - '#leftrightarrow': '\u2194', - '#leftarrow': '\u2190', - '#uparrow': '\u2191', - '#rightarrow': '\u2192', - '#downarrow': '\u2193', - '#circ': '\u2E30', - '#pm': '\xB1', - '#doublequote': '\u2033', - '#geq': '\u2265', - '#times': '\xD7', - '#propto': '\u221D', - '#partial': '\u2202', - '#bullet': '\u2022', - '#divide': '\xF7', - '#neq': '\u2260', - '#equiv': '\u2261', - '#approx': '\u2248', // should be \u2245 ? - '#3dots': '\u2026', - '#cbar': '\x7C', - '#topbar': '\xAF', - '#downleftarrow': '\u21B5', - '#aleph': '\u2135', - '#Jgothic': '\u2111', - '#Rgothic': '\u211C', - '#voidn': '\u2118', - '#otimes': '\u2297', - '#oplus': '\u2295', - '#oslash': '\u2205', - '#cap': '\u2229', - '#cup': '\u222A', - '#supset': '\u2283', - '#supseteq': '\u2287', - '#notsubset': '\u2284', - '#subset': '\u2282', - '#subseteq': '\u2286', - '#in': '\u2208', - '#notin': '\u2209', - '#angle': '\u2220', - '#nabla': '\u2207', - '#oright': '\xAE', - '#ocopyright': '\xA9', - '#trademark': '\u2122', - '#prod': '\u220F', - '#surd': '\u221A', - '#upoint': '\u2027', - '#corner': '\xAC', - '#wedge': '\u2227', - '#vee': '\u2228', - '#Leftrightarrow': '\u21D4', - '#Leftarrow': '\u21D0', - '#Uparrow': '\u21D1', - '#Rightarrow': '\u21D2', - '#Downarrow': '\u21D3', - '#void2': '', // dummy, placeholder - '#LT': '\x3C', - '#void1': '\xAE', - '#copyright': '\xA9', - '#void3': '\u2122', // it is dummy placeholder, TM - '#sum': '\u2211', - '#arctop': '\u239B', - '#lbar': '\u23A2', - '#arcbottom': '\u239D', - '#void4': '', // dummy, placeholder - '#void8': '\u23A2', // same as lbar - '#bottombar': '\u230A', - '#arcbar': '\u23A7', - '#ltbar': '\u23A8', - '#AA': '\u212B', - '#aa': '\xE5', - '#void06': '', - '#GT': '\x3E', - '#int': '\u222B', - '#forall': '\u2200', - '#exists': '\u2203', - '#textendash': '\u2013', - '#textemdash': '\u2014', - // here ends second set from symbols.ttf - - // more greek symbols - '#koppa': '\u03DF', - '#sampi': '\u03E1', - '#stigma': '\u03DB', - '#san': '\u03FB', - '#sho': '\u03F8', - '#varcoppa': '\u03D9', - '#digamma': '\u03DD', - '#Digamma': '\u03DC', - '#Koppa': '\u03DE', - '#varKoppa': '\u03D8', - '#Sampi': '\u03E0', - '#Stigma': '\u03DA', - '#San': '\u03FA', - '#Sho': '\u03F7', - - '#vec': '', - '#dot': '\u22C5', - '#hat': '\xB7', - '#ddot': '', - '#acute': '', - '#grave': '', - '#check': '\u2713', - '#tilde': '\u02DC', - '#slash': '\u2044', - '#hbar': '\u0127', - '#box': '\u25FD', - '#Box': '\u2610', - '#parallel': '\u2225', - '#perp': '\u22A5', - '#odot': '\u2299', - '#left': '', - '#right': '', - '{}': '', - - '#mp': '\u2213', - - '#P': '\u00B6', // paragraph - - // only required for MathJax to provide correct replacement - '#sqrt': '\u221A', - '#bar': '', - '#overline': '', - '#underline': '', - '#strike': '' -}; - - -/** @summary Create a single regex to detect any symbol to replace, apply longer symbols first - * @private */ -const symbolsRegexCache = new RegExp(Object.keys(symbols_map).sort((a, b) => (a.length < b.length ? 1 : (a.length > b.length ? -1 : 0))).join('|'), 'g'); - -/** @summary Simple replacement of latex letters - * @private */ -function translateLaTeX(str) { - while ((str.length > 2) && (str.at(0) === '{') && (str.at(-1) === '}')) - str = str.slice(1, str.length - 1); - - return str.replace(symbolsRegexCache, ch => symbols_map[ch]).replace(/\{\}/g, ''); -} - -// array with relative width of base symbols from range 32..126 -// eslint-disable-next-line -const base_symbols_width = [453,535,661,973,955,1448,1242,324,593,596,778,1011,200,570,200,492,947,885,947,947,947,947,947,947,947,947,511,495,980,1010,987,893,1624,1185,1147,1193,1216,1080,1028,1270,1274,531,910,1177,1004,1521,1252,1276,1111,1276,1164,1056,1073,1215,1159,1596,1150,1124,1065,540,591,540,837,874,572,929,972,879,973,901,569,967,973,453,458,903,453,1477,973,970,972,976,638,846,548,973,870,1285,884,864,835,656,430,656,1069]; - -// eslint-disable-next-line -const extra_symbols_width = {945:1002,946:996,967:917,948:953,949:834,966:1149,947:847,951:989,953:516,954:951,955:913,956:1003,957:862,959:967,960:1070,952:954,961:973,963:1017,964:797,965:944,982:1354,969:1359,958:803,968:1232,950:825,913:1194,914:1153,935:1162,916:1178,917:1086,934:1358,915:1016,919:1275,921:539,977:995,922:1189,923:1170,924:1523,925:1253,927:1281,928:1281,920:1285,929:1102,931:1041,932:1069,933:1135,962:848,937:1279,926:1092,936:1334,918:1067,978:1154,8730:986,8804:940,8260:476,8734:1453,402:811,9827:1170,9830:931,9829:1067,9824:965,8596:1768,8592:1761,8593:895,8594:1761,8595:895,710:695,177:955,8243:680,8805:947,215:995,8733:1124,8706:916,8226:626,247:977,8800:969,8801:1031,8776:976,8230:1552,175:883,8629:1454,8501:1095,8465:1002,8476:1490,8472:1493,8855:1417,8853:1417,8709:1205,8745:1276,8746:1404,8839:1426,8835:1426,8836:1426,8838:1426,8834:1426,8747:480,8712:1426,8713:1426,8736:1608,8711:1551,174:1339,169:1339,8482:1469,8719:1364,729:522,172:1033,8743:1383,8744:1383,8660:1768,8656:1496,8657:1447,8658:1496,8659:1447,8721:1182,9115:882,9144:1000,9117:882,8970:749,9127:1322,9128:1322,8491:1150,229:929,8704:1397,8707:1170,8901:524,183:519,10003:1477,732:692,295:984,9725:1780,9744:1581,8741:737,8869:1390,8857:1421}; - -/** @summary Calculate approximate labels width - * @private */ -function approximateLabelWidth(label, font, fsize) { - if (Number.isInteger(font)) - font = new FontHandler(font, fsize); - - const len = label.length, - symbol_width = (fsize || font.size) * font.aver_width; - if (font.isMonospace()) - return len * symbol_width; - - let sum = 0; - for (let i = 0; i < len; ++i) { - const code = label.charCodeAt(i); - if ((code >= 32) && (code < 127)) - sum += base_symbols_width[code - 32]; - else - sum += extra_symbols_width[code] || 1000; - } - - return sum / 1000 * symbol_width; -} - -/** @summary array defines features supported by latex parser, used by both old and new parsers - * @private */ -const latex_features = [ - { name: '#it{', bi: 'italic' }, // italic - { name: '#bf{', bi: 'bold' }, // bold - { name: '#underline{', deco: 'underline' }, // underline - { name: '#overline{', deco: 'overline' }, // overline - { name: '#strike{', deco: 'line-through' }, // line through - { name: '#kern[', arg: 'float', shift: 'x' }, // horizontal shift - { name: '#lower[', arg: 'float', shift: 'y' }, // vertical shift - { name: '#scale[', arg: 'float' }, // font scale - { name: '#color[', arg: 'int' }, // font color - { name: '#font[', arg: 'int' }, // font face - { name: '#url[', arg: 'string' }, // url link - { name: '_{', low_up: 'low' }, // subscript - { name: '^{', low_up: 'up' }, // superscript - { name: '#bar{', deco: 'overline' /* accent: '\u02C9' */ }, // '\u0305' - { name: '#hat{', accent: '\u02C6', hasw: true }, // '\u0302' - { name: '#check{', accent: '\u02C7', hasw: true }, // '\u030C' - { name: '#acute{', accent: '\u02CA' }, // '\u0301' - { name: '#grave{', accent: '\u02CB' }, // '\u0300' - { name: '#dot{', accent: '\u02D9' }, // '\u0307' - { name: '#ddot{', accent: '\u02BA', hasw: true }, // '\u0308' - { name: '#tilde{', accent: '\u02DC', hasw: true }, // '\u0303' - { name: '#slash{', accent: '\u2215' }, // '\u0337' - { name: '#vec{', accent: '\u02ED', hasw: true }, // '\u0350' arrowhead - { name: '#frac{', twolines: 'line', middle: true }, - { name: '#splitmline{', twolines: true, middle: true }, - { name: '#splitline{', twolines: true }, - { name: '#sqrt[', arg: 'int', sqrt: true }, // root with arbitrary power - { name: '#sqrt{', sqrt: true }, // square root - { name: '#sum', special: '\u2211', w: 0.8, h: 0.9 }, - { name: '#int', special: '\u222B', w: 0.3, h: 1.0 }, - { name: '#left[', right: '#right]', braces: '[]' }, - { name: '#left(', right: '#right)', braces: '()' }, - { name: '#left{', right: '#right}', braces: '{}' }, - { name: '#left|', right: '#right|', braces: '||' }, - { name: '#[]{', braces: '[]' }, - { name: '#(){', braces: '()' }, - { name: '#{}{', braces: '{}' }, - { name: '#||{', braces: '||' } -]; - -// taken from: https://sites.math.washington.edu/~marshall/cxseminar/symbol.htm, starts from 33 -// eslint-disable-next-line -const symbolsMap = [0,8704,0,8707,0,0,8717,0,0,8727,0,0,8722,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8773,913,914,935,916,917,934,915,919,921,977,922,923,924,925,927,928,920,929,931,932,933,962,937,926,936,918,0,8756,0,8869,0,0,945,946,967,948,949,966,947,951,953,981,954,955,956,957,959,960,952,961,963,964,965,982,969,958,968,950,0,402,0,8764,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,978,8242,8804,8260,8734,0,9827,9830,9829,9824,8596,8592,8593,8594,8595,0,0,8243,8805,0,8733,8706,8729,0,8800,8801,8776,8230,0,0,8629,8501,8465,8476,8472,8855,8853,8709,8745,8746,8835,8839,8836,8834,8838,8712,8713,8736,8711,0,0,8482,8719,8730,8901,0,8743,8744,8660,8656,8657,8658,8659,9674,9001,0,0,8482,8721,0,0,0,0,0,0,0,0,0,0,8364,9002,8747,8992,0,8993]; - -// taken from http://www.alanwood.net/demos/wingdings.html, starts from 33 -// eslint-disable-next-line -const wingdingsMap = [128393,9986,9985,128083,128365,128366,128367,128383,9990,128386,128387,128234,128235,128236,128237,128193,128194,128196,128463,128464,128452,8987,128430,128432,128434,128435,128436,128427,128428,9991,9997,128398,9996,128076,128077,128078,9756,9758,9757,9759,128400,9786,128528,9785,128163,9760,127987,127985,9992,9788,128167,10052,128326,10014,128328,10016,10017,9770,9775,2384,9784,9800,9801,9802,9803,9804,9805,9806,9807,9808,9809,9810,9811,128624,128629,9679,128318,9632,9633,128912,10065,10066,11047,10731,9670,10070,11045,8999,11193,8984,127989,127990,128630,128631,0,9450,9312,9313,9314,9315,9316,9317,9318,9319,9320,9321,9471,10102,10103,10104,10105,10106,10107,10108,10109,10110,10111,128610,128608,128609,128611,128606,128604,128605,128607,183,8226,9642,9898,128902,128904,9673,9678,128319,9642,9723,128962,10022,9733,10038,10036,10041,10037,11216,8982,10209,8977,11217,10026,10032,128336,128337,128338,128339,128340,128341,128342,128343,128344,128345,128346,128347,11184,11185,11186,11187,11188,11189,11190,11191,128618,128619,128597,128596,128599,128598,128592,128593,128594,128595,9003,8998,11160,11162,11161,11163,11144,11146,11145,11147,129128,129130,129129,129131,129132,129133,129135,129134,129144,129146,129145,129147,129148,129149,129151,129150,8678,8680,8679,8681,11012,8691,11008,11009,11011,11010,129196,129197,128502,10004,128503,128505]; - -const symbolsPdfMap = {}; - -/** @summary Return code for symbols from symbols.ttf - * @desc Used in PDF generation - * @private */ -function remapSymbolTtfCode(code) { - if (!symbolsPdfMap[0x3B1]) { - let cnt = 0; - for (const key in symbols_map) { - const symbol = symbols_map[key]; - if (symbol.length === 1) { - let letter; - if (cnt < 54) { - const opGreek = cnt; - // see code in TLatex.cxx, line 1302 - letter = 97 + opGreek; - if (opGreek > 25) - letter -= 58; - if (opGreek === 52) - letter = 0o241; // varUpsilon - if (opGreek === 53) - letter = 0o316; // epsilon - } else { - // see code in TLatex.cxx, line 1323 - const opSpec = cnt - 54; - letter = 0o243 + opSpec; - switch (opSpec) { - case 75: letter = 0o305; break; // AA Angstroem - case 76: letter = 0o345; break; // aa Angstroem - case 80: letter = 0o42; break; // #forall - case 81: letter = 0o44; break; // #exists - case 82: letter = 0o55; break; // #textendash - case 83: letter = 0o276; break; // #textemdash - } - } - const scode = symbol.charCodeAt(0); - if (scode > 0x80) - symbolsPdfMap[scode] = letter; - } - if (++cnt > 54 + 84) - break; - } - for (let k = 0; k < symbolsMap.length; ++k) { - const scode2 = symbolsMap[k]; - if (scode2) - symbolsPdfMap[scode2] = k + 33; - } - } - return symbolsPdfMap[code] ?? code; -} - - -/** @summary Reformat text node if it includes greek or special symbols - * @desc Used in PDF generation where greek symbols are not available - * @private */ -function replaceSymbolsInTextNode(node) { - if (node.$text && node.$font) { - node.$originalHTML = node.innerHTML; - node.$originalFont = node.getAttribute('font-family'); - - node.innerHTML = node.$text; - if (settings.LoadSymbolTtf) - node.setAttribute('font-family', node.$font.isSymbol); - else - node.setAttribute('font-family', (node.$font.isSymbol === kWingdings) ? 'zapfdingbats' : 'symbol'); - return node.$font.isSymbol; - } - - if (node.childNodes.length !== 1) - return false; - const txt = node.textContent; - if (!txt) - return false; - let new_html = '', lasti = -1; - for (let i = 0; i < txt.length; i++) { - const code = txt.charCodeAt(i), - newcode = remapSymbolTtfCode(code); - if (code !== newcode) { - new_html += txt.slice(lasti + 1, i) + `${String.fromCharCode(newcode)} `; - lasti = i; - } - } - - if (lasti < 0) - return false; - - if (lasti < txt.length - 1) - new_html += txt.slice(lasti + 1, txt.length); - - node.$originalHTML = node.innerHTML; - node.$originalFont = node.getAttribute('font-family'); - node.innerHTML = new_html; - return kSymbol; -} - - -/** @summary Replace codes from symbols.ttf into normal font - when symbols.ttf cannot be used - * @private */ -function replaceSymbols(s, name) { - const m = name === kWingdings ? wingdingsMap : symbolsMap; - let res = ''; - for (let k = 0; k < s.length; ++k) { - const code = s.charCodeAt(k), - new_code = (code > 32) ? m[code - 33] : 0; - res += String.fromCodePoint(new_code || code); - } - return res; -} - -/** @summary Just add plain text to the SVG text elements - * @private */ -function producePlainText(painter, txt_node, arg) { - arg.plain = true; - if (arg.simple_latex) - arg.text = translateLaTeX(arg.text); // replace latex symbols - if (arg.font?.isSymbol) { - txt_node.text(replaceSymbols(arg.text, arg.font.isSymbol)); - txt_node.property('$text', arg.text); - txt_node.property('$font', arg.font); - } else - txt_node.text(arg.text); -} - -/** @summary Check if plain text - * @private */ -function isPlainText(txt) { - return !txt || ((txt.indexOf('#') < 0) && (txt.indexOf('{') < 0)); -} - -/** @summary translate TLatex and draw inside provided g element - * @desc use together with normal elements - * @private */ -function parseLatex(node, arg, label, curr) { - let nelements = 0; - - const currG = () => { - if (!curr.g) - curr.g = node.append('svg:g'); - return curr.g; - }; - - const shiftX = dx => { curr.x += Math.round(dx); }; - - const extendPosition = (x1, y1, x2, y2) => { - if (!curr.rect) - curr.rect = { x1, y1, x2, y2 }; - else { - curr.rect.x1 = Math.min(curr.rect.x1, x1); - curr.rect.y1 = Math.min(curr.rect.y1, y1); - curr.rect.x2 = Math.max(curr.rect.x2, x2); - curr.rect.y2 = Math.max(curr.rect.y2, y2); - } - - curr.rect.last_y1 = y1; // upper position of last symbols - - curr.rect.width = curr.rect.x2 - curr.rect.x1; - curr.rect.height = curr.rect.y2 - curr.rect.y1; - - if (!curr.parent) - arg.text_rect = curr.rect; - }; - - const addSpaces = nspaces => { - extendPosition(curr.x, curr.y, curr.x + nspaces * curr.fsize * 0.4, curr.y); - shiftX(nspaces * curr.fsize * 0.4); - }; - - /** Position pos.g node which directly attached to curr.g and uses curr.g coordinates */ - const positionGNode = (pos, x, y, inside_gg) => { - x = Math.round(x); - y = Math.round(y); - - makeTranslate(pos.g, x, y); - pos.rect.x1 += x; - pos.rect.x2 += x; - pos.rect.y1 += y; - pos.rect.y2 += y; - - if (inside_gg) - extendPosition(curr.x + pos.rect.x1, curr.y + pos.rect.y1, curr.x + pos.rect.x2, curr.y + pos.rect.y2); - else - extendPosition(pos.rect.x1, pos.rect.y1, pos.rect.x2, pos.rect.y2); - }; - - /** Create special sub-container for elements like sqrt or braces */ - const createGG = is_a => { - const gg = currG(); - - // this is indicator that gg element will be the only one, one can use directly main container - if ((nelements === 1) && !label && !curr.x && !curr.y && !is_a) - return gg; - - return makeTranslate(gg.append(is_a ? 'svg:a' : 'svg:g'), curr.x, curr.y); - }; - - const extractSubLabel = (check_first, lbrace, rbrace) => { - let pos = 0, n = 1, extra_braces = false; - if (!lbrace) - lbrace = '{'; - if (!rbrace) - rbrace = '}'; - - const match = br => (pos + br.length <= label.length) && (label.slice(pos, pos + br.length) === br); - - if (check_first) { - if (!match(lbrace)) { - console.log(`not starting with ${lbrace} in ${label}`); - return -1; - } - label = label.slice(lbrace.length); - } - - while (n && (pos < label.length)) { - if (match(lbrace)) { - n++; - pos += lbrace.length; - } else if (match(rbrace)) { - n--; - pos += rbrace.length; - if ((n === 0) && (typeof check_first === 'string') && match(check_first + lbrace)) { - // handle special case like a^{b}^{2} should mean a^{b^{2}} - n++; - pos += lbrace.length + check_first.length; - check_first = true; - extra_braces = true; - } - } else pos++; - } - if (n) { - console.log(`mismatch with open ${lbrace} and closing ${rbrace} in ${label}`); - return -1; - } - - let sublabel = label.slice(0, pos - rbrace.length); - - if (extra_braces) - sublabel = lbrace + sublabel + rbrace; - - label = label.slice(pos); - - return sublabel; - }; - - const createPath = (gg, d, dofill) => { - return gg.append('svg:path') - .attr('d', d || 'M0,0') // provide dummy d value as placeholder, preserve order of attributes - .style('stroke', dofill ? 'none' : (curr.color || arg.color)) - .style('stroke-width', dofill ? null : Math.max(1, Math.round(curr.fsize * (curr.font.weight ? 0.1 : 0.07)))) - .style('fill', dofill ? (curr.color || arg.color) : 'none'); - }; - - const createSubPos = fscale => { - return { - lvl: curr.lvl + 1, x: 0, y: 0, fsize: curr.fsize * (fscale || 1), - color: curr.color, font: curr.font, parent: curr, - painter: curr.painter, italic: curr.italic, bold: curr.bold - }; - }; - - while (label) { - let best = label.length, found = null; - - for (let n = 0; n < latex_features.length; ++n) { - const pos = label.indexOf(latex_features[n].name); - if ((pos >= 0) && (pos < best)) { - best = pos; - found = latex_features[n]; - } - } - - if (best > 0) { - const alone = (best === label.length) && (nelements === 0) && !found; - - nelements++; - - let s = translateLaTeX(label.slice(0, best)), - nbeginspaces = 0, nendspaces = 0; - - while ((nbeginspaces < s.length) && (s[nbeginspaces] === ' ')) - nbeginspaces++; - - if (nbeginspaces > 0) { - addSpaces(nbeginspaces); - s = s.slice(nbeginspaces); - } - - while ((nendspaces < s.length) && (s[s.length - 1 - nendspaces] === ' ')) - nendspaces++; - - if (nendspaces > 0) - s = s.slice(0, s.length - nendspaces); - - if (s || alone) { - // if single text element created, place it directly in the node - const g = curr.g || (alone ? node : currG()), - elem = g.append('svg:text'); - - if (alone && !curr.g) - curr.g = elem; - - // apply font attributes only once, inherited by all other elements - if (curr.ufont) { - curr.font.setPainter(arg.painter); - curr.font.setFont(curr.g); - } - - if (curr.bold !== undefined) - curr.g.attr('font-weight', curr.bold ? 'bold' : 'normal'); - - if (curr.italic !== undefined) - curr.g.attr('font-style', curr.italic ? 'italic' : 'normal'); - - // set fill color directly to element - elem.attr('fill', curr.color || arg.color || null); - - // set font size directly to element to avoid complex control - elem.attr('font-size', Math.max(1, Math.round(curr.fsize))); - - if (curr.font?.isSymbol) { - elem.text(replaceSymbols(s, curr.font.isSymbol)); - elem.property('$text', s); - elem.property('$font', curr.font); - } else - elem.text(s); - - const rect = !isNodeJs() && !settings.ApproxTextSize && !arg.fast - ? getElementRect(elem, 'nopadding') - : { height: curr.fsize * 1.2, width: approximateLabelWidth(s, curr.font, curr.fsize) }; - - if (curr.x) - elem.attr('x', curr.x); - if (curr.y) - elem.attr('y', curr.y); - - // for single symbols like f,l,i,j one gets wrong estimation of total width, use it in sup/sub-scripts - const xgap = (s.length === 1) && !curr.font.isMonospace() && ('lfij'.indexOf(s) >= 0) ? 0.1 * curr.fsize : 0; - - extendPosition(curr.x, curr.y - rect.height * 0.8, curr.x + rect.width, curr.y + rect.height * 0.2); - - if (!alone) { - shiftX(rect.width + xgap); - addSpaces(nendspaces); - curr.xgap = 0; - } else if (curr.deco) { - elem.attr('text-decoration', curr.deco); - curr.deco = ''; // inform that decoration was applied - } else - curr.xgap = xgap; // may be used in accent or somewhere else - } else - addSpaces(nendspaces); - } - - if (!found) - return true; - - // remove preceding block and tag itself - label = label.slice(best + found.name.length); - - nelements++; - - if (found.accent) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const gg = createGG(), - subpos = createSubPos(), - reduce = (sublabel.length !== 1) ? 1 : (((sublabel >= 'a') && (sublabel <= 'z') && ('tdbfhkli'.indexOf(sublabel) < 0)) ? 0.75 : 0.9); - - parseLatex(gg, arg, sublabel, subpos); - - const minw = curr.fsize * 0.6, - y1 = Math.round(subpos.rect.y1 * reduce), - dy2 = Math.round(curr.fsize * 0.1), dy = dy2 * 2, - dot = `a${dy2},${dy2},0,0,1,${dy},0a${dy2},${dy2},0,0,1,${-dy},0z`; - let xpos = 0, w = subpos.rect.width; - - // shift symbol when it is too small - if (found.hasw && (w < minw)) { - w = minw; - xpos = (minw - subpos.rect.width) / 2; - } - - const w5 = Math.round(w * 0.5), w3 = Math.round(w * 0.3), w2 = w5 - w3, w8 = w5 + w3; - w = w5 * 2; - - positionGNode(subpos, xpos, 0, true); - - switch (found.name) { - case '#check{': createPath(gg, `M${w2},${y1 - dy}L${w5},${y1}L${w8},${y1 - dy}`); break; - case '#acute{': createPath(gg, `M${w5},${y1}l${dy},${-dy}`); break; - case '#grave{': createPath(gg, `M${w5},${y1}l${-dy},${-dy}`); break; - case '#dot{': createPath(gg, `M${w5 - dy2},${y1}${dot}`, true); break; - case '#ddot{': createPath(gg, `M${w5 - 3 * dy2},${y1}${dot} M${w5 + dy2},${y1}${dot}`, true); break; - case '#tilde{': createPath(gg, `M${w2},${y1} a${w3},${dy},0,0,1,${w3},0 a${w3},${dy},0,0,0,${w3},0`); break; - case '#slash{': createPath(gg, `M${w},${y1}L0,${Math.round(subpos.rect.y2)}`); break; - case '#vec{': createPath(gg, `M${w2},${y1}H${w8}M${w8 - dy},${y1 - dy}l${dy},${dy}l${-dy},${dy}`); break; - default: createPath(gg, `M${w2},${y1}L${w5},${y1 - dy}L${w8},${y1}`); // #hat{ - } - - shiftX(subpos.rect.width + (subpos.xgap ?? 0)); - - continue; - } - - if (found.twolines) { - curr.twolines = true; - - const line1 = extractSubLabel(), - line2 = extractSubLabel(true); - if ((line1 === -1) || (line2 === -1)) - return false; - - const gg = createGG(), - fscale = curr.parent?.twolines ? 0.7 : 1, - subpos1 = createSubPos(fscale); - - parseLatex(gg, arg, line1, subpos1); - - const path = found.twolines === 'line' ? createPath(gg) : null, - subpos2 = createSubPos(fscale); - - parseLatex(gg, arg, line2, subpos2); - - const w = Math.max(subpos1.rect.width, subpos2.rect.width), - dw = subpos1.rect.width - subpos2.rect.width, - dy = -curr.fsize * 0.35; // approximate position of middle line - - positionGNode(subpos1, found.middle && (dw < 0) ? -dw / 2 : 0, dy - subpos1.rect.y2, true); - - positionGNode(subpos2, found.middle && (dw > 0) ? dw / 2 : 0, dy - subpos2.rect.y1, true); - - path?.attr('d', `M0,${Math.round(dy)}h${Math.round(w - curr.fsize * 0.1)}`); - - shiftX(w); - - delete curr.twolines; - - continue; - } - - const extractLowUp = name => { - const res = {}; - - if (name) { - label = '{' + label; - res[name] = extractSubLabel(name === 'low' ? '_' : '^'); - if (res[name] === -1) - return false; - } - - while (label) { - if ((label[0] === '_') && !res.low) { - label = label.slice(1); - res.low = extractSubLabel('_'); - if (res.low === -1) { - console.log(`error with ${found.name} low limit ${label}`); - return false; - } - } else if ((label[0] === '^') && !res.up) { - label = label.slice(1); - res.up = extractSubLabel('^'); - if (res.up === -1) { - console.log(`error with ${found.name} upper limit ${label}`); - return false; - } - } else break; - } - return res; - }; - - if (found.low_up) { - const subs = extractLowUp(found.low_up); - if (!subs) - return false; - - const x = curr.x, dx = 0.03 * curr.fsize, ylow = 0.25 * curr.fsize; - let pos_up, pos_low, w1 = 0, w2 = 0, yup = -curr.fsize; - - if (subs.up) { - pos_up = createSubPos(0.6); - parseLatex(currG(), arg, subs.up, pos_up); - } - - if (subs.low) { - pos_low = createSubPos(0.6); - parseLatex(currG(), arg, subs.low, pos_low); - } - - if (pos_up) { - if (!pos_low && curr.rect) - yup = Math.min(yup, curr.rect.last_y1); - positionGNode(pos_up, x + dx, yup - pos_up.rect.y1 - curr.fsize * 0.1); - w1 = pos_up.rect.width; - } - - if (pos_low) { - positionGNode(pos_low, x + dx, ylow - pos_low.rect.y2 + curr.fsize * 0.1); - w2 = pos_low.rect.width; - } - - shiftX(dx + Math.max(w1, w2)); - - continue; - } - - if (found.special) { - // this is sum and integral, now make fix height, later can adjust to right-content size - - const subs = extractLowUp() || {}, - gg = createGG(), path = createPath(gg), - h = Math.round(curr.fsize * 1.7), w = Math.round(curr.fsize), r = Math.round(h * 0.1); - let x_up, x_low; - - if (found.name === '#sum') { - x_up = x_low = w / 2; - path.attr('d', `M${w},${Math.round(-0.75 * h)}h${-w}l${Math.round(0.4 * w)},${Math.round(0.3 * h)}l${Math.round(-0.4 * w)},${Math.round(0.7 * h)}h${w}`); - } else { - x_up = 3 * r; - x_low = r; - path.attr('d', `M0,${Math.round(0.25 * h - r)}a${r},${r},0,0,0,${2 * r},0v${2 * r - h}a${r},${r},0,1,1,${2 * r},0`); - // path.attr('transform','skewX(-3)'); could use skewX for italic-like style - } - - extendPosition(curr.x, curr.y - 0.6 * h, curr.x + w, curr.y + 0.4 * h); - - if (subs.low) { - const subpos1 = createSubPos(0.6); - parseLatex(gg, arg, subs.low, subpos1); - positionGNode(subpos1, (x_low - subpos1.rect.width / 2), 0.25 * h - subpos1.rect.y1, true); - } - - if (subs.up) { - const subpos2 = createSubPos(0.6); - parseLatex(gg, arg, subs.up, subpos2); - positionGNode(subpos2, (x_up - subpos2.rect.width / 2), -0.75 * h - subpos2.rect.y2, true); - } - - shiftX(w); - - continue; - } - - if (found.braces) { - const rbrace = found.right, - lbrace = rbrace ? found.name : '{', - sublabel = extractSubLabel(false, lbrace, rbrace), - gg = createGG(), - subpos = createSubPos(), - path1 = createPath(gg); - - parseLatex(gg, arg, sublabel, subpos); - - const path2 = createPath(gg), - w = Math.max(2, Math.round(curr.fsize * 0.2)), - r = subpos.rect, dy = Math.round(r.y2 - r.y1), - r_y1 = Math.round(r.y1), r_width = Math.round(r.width); - - switch (found.braces) { - case '||': - path1.attr('d', `M${w},${r_y1}v${dy}`); - path2.attr('d', `M${3 * w + r_width},${r_y1}v${dy}`); - break; - case '[]': - path1.attr('d', `M${2 * w},${r_y1}h${-w}v${dy}h${w}`); - path2.attr('d', `M${2 * w + r_width},${r_y1}h${w}v${dy}h${-w}`); - break; - case '{}': - path1.attr('d', `M${2 * w},${r_y1}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}`); - path2.attr('d', `M${2 * w + r_width},${r_y1}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}`); - break; - default: // () - path1.attr('d', `M${w},${r_y1}a${4 * dy},${4 * dy},0,0,0,0,${dy}`); - path2.attr('d', `M${3 * w + r_width},${r_y1}a${4 * dy},${4 * dy},0,0,1,0,${dy}`); - } - - positionGNode(subpos, 2 * w, 0, true); - - extendPosition(curr.x, curr.y + r.y1, curr.x + 4 * w + r.width, curr.y + r.y2); - - shiftX(4 * w + r.width); - - continue; - } - - if (found.deco) { - const sublabel = extractSubLabel(), - gg = createGG(), - subpos = createSubPos(); - - subpos.deco = found.deco; - - parseLatex(gg, arg, sublabel, subpos); - - const r = subpos.rect; - switch (subpos.deco) { - case 'underline': - createPath(gg, `M0,${Math.round(r.y2)}h${Math.round(r.width)}`); - break; - case 'overline': - createPath(gg, `M0,${Math.round(r.y1)}h${Math.round(r.width)}`); - break; - case 'line-through': - createPath(gg, `M0,${Math.round(0.45 * r.y1 + 0.55 * r.y2)}h${Math.round(r.width)}`); - break; - } - - positionGNode(subpos, 0, 0, true); - shiftX(r.width); - continue; - } - - if (found.bi) { // bold or italic - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const subpos = createSubPos(); - - subpos[found.bi] = !subpos[found.bi]; - - parseLatex(currG(), arg, sublabel, subpos); - - positionGNode(subpos, curr.x, curr.y); - - shiftX(subpos.rect.width); - - continue; - } - - let foundarg = 0; - - if (found.arg) { - const pos = label.indexOf(']{'); - if (pos < 0) { - console.log('missing argument for ', found.name); - return false; - } - foundarg = label.slice(0, pos); - if (found.arg === 'int') { - foundarg = parseInt(foundarg); - if (!Number.isInteger(foundarg)) { - console.log('wrong int argument', label.slice(0, pos)); - return false; - } - } else if (found.arg === 'float') { - foundarg = parseFloat(foundarg); - if (!Number.isFinite(foundarg)) { - console.log('wrong float argument', label.slice(0, pos)); - return false; - } - } - label = label.slice(pos + 2); - } - - if (found.shift) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const subpos = createSubPos(); - - parseLatex(currG(), arg, sublabel, subpos); - - let shiftx = 0, shifty = 0; - if (found.shift === 'x') - shiftx = foundarg * subpos.rect.width; - else - shifty = foundarg * subpos.rect.height; - - positionGNode(subpos, curr.x + shiftx, curr.y + shifty); - - shiftX(subpos.rect.width * (shiftx > 0 ? 1 + foundarg : 1)); - - continue; - } - - if (found.name === '#url[') { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const gg = createGG(true), - subpos = createSubPos(); - - gg.attr('href', foundarg); - if (!isBatchMode() && !curr.painter?.isBatchMode()) { - gg.on('mouseenter', () => gg.style('text-decoration', 'underline')) - .on('mouseleave', () => gg.style('text-decoration', null)) - .append('svg:title').text(`link on ${foundarg}`); - } - - parseLatex(gg, arg, sublabel, subpos); - - positionGNode(subpos, 0, 0, true); - shiftX(subpos.rect.width); - continue; - } - - if ((found.name === '#color[') || (found.name === '#scale[') || (found.name === '#font[')) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const subpos = createSubPos(); - - if (found.name === '#color[') - subpos.color = curr.painter.getColor(foundarg); - else if (found.name === '#font[') { - subpos.font = new FontHandler(foundarg, subpos.fsize); - // here symbols embedding not works, use replacement - if ((subpos.font.name === kSymbol) && !subpos.font.isSymbol) { - subpos.font.isSymbol = kSymbol; - subpos.font.name = kTimes; - } - subpos.font.setUseFullStyle(true); // while embedding - need to enforce full style - subpos.ufont = true; // mark that custom font is applied - } else - subpos.fsize *= foundarg; - - parseLatex(currG(), arg, sublabel, subpos); - - positionGNode(subpos, curr.x, curr.y); - - shiftX(subpos.rect.width); - - continue; - } - - if (found.sqrt) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const gg = createGG(), subpos = createSubPos(); - let subpos0; - - if (found.arg) { - subpos0 = createSubPos(0.7); - parseLatex(gg, arg, foundarg.toString(), subpos0); - } - - // placeholder for the sqrt sign - const path = createPath(gg); - - parseLatex(gg, arg, sublabel, subpos); - - const r = subpos.rect, - h = Math.round(r.height), - h1 = Math.round(r.height * 0.1), - w = Math.round(r.width), midy = Math.round((r.y1 + r.y2) / 2), - f2 = Math.round(curr.fsize * 0.2), r_y2 = Math.round(r.y2); - - if (subpos0) - positionGNode(subpos0, 0, midy - subpos0.fsize * 0.3, true); - - path.attr('d', `M0,${midy}h${h1}l${h1},${r_y2 - midy - f2}l${h1},${f2 - h}h${Math.round(h * 0.2 + w)}v${h1}`); - - positionGNode(subpos, h * 0.4, 0, true); - - extendPosition(curr.x, curr.y + r.y1 - curr.fsize * 0.1, curr.x + w + h * 0.6, curr.y + r.y2); - - shiftX(w + h * 0.6); - - continue; - } - } - - return true; -} - -/** @summary translate TLatex and draw inside provided g element - * @desc use together with normal elements - * @private */ -function produceLatex(painter, node, arg) { - const pos = { lvl: 0, g: node, x: 0, y: 0, dx: 0, dy: -0.1, fsize: arg.font_size, font: arg.font, parent: null, painter }; - return parseLatex(node, arg, arg.text, pos); -} - -let _mj_loading; - -/** @summary Load MathJax functionality, - * @desc one need not only to load script but wait for initialization - * @private */ -async function loadMathjax() { - const loading = _mj_loading !== undefined; - - if (!loading && (typeof globalThis.MathJax !== 'undefined')) - return globalThis.MathJax; - - if (!loading) - _mj_loading = []; - - const promise = new Promise(resolve => { - if (_mj_loading) - _mj_loading.push(resolve); - else - resolve(globalThis.MathJax); - }); - - if (loading) - return promise; - - const svg = { - scale: 1, // global scaling factor for all expressions - minScale: 0.5, // smallest scaling factor to use - mtextInheritFont: false, // true to make mtext elements use surrounding font - merrorInheritFont: true, // true to make merror text use surrounding font - mathmlSpacing: false, // true for MathML spacing rules, false for TeX rules - skipAttributes: {}, // RFDa and other attributes NOT to copy to the output - exFactor: 0.5, // default size of ex in em units - displayAlign: 'center', // default for indentalign when set to 'auto' - displayIndent: '0', // default for indentshift when set to 'auto' - fontCache: 'local', // or 'global' or 'none' - localID: null, // ID to use for local font cache (for single equation processing) - internalSpeechTitles: true, // insert tags with speech content - titleID: 0 // initial id number to use for aria-labeledby titles - }; - - if (!isNodeJs()) { - window.MathJax = { - options: { - enableMenu: false - }, - loader: { - load: ['[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] - }, - tex: { - packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } - }, - svg, - startup: { - ready() { - MathJax.startup.defaultReady(); - const arr = _mj_loading; - _mj_loading = undefined; - arr.forEach(func => func(globalThis.MathJax)); - } - } - }; - - let mj_dir = '../mathjax/3.2.0'; - if (browser.webwindow && exports.source_dir.indexOf('https://root.cern/js') < 0 && exports.source_dir.indexOf('https://jsroot.gsi.de') < 0) - mj_dir = 'mathjax'; - - return loadScript(exports.source_dir + mj_dir + '/es5/tex-svg.js') - .catch(() => loadScript('https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js')) - .then(() => promise); - } - - let JSDOM; - - return _loadJSDOM().then(handle => { - JSDOM = handle.JSDOM; - return import('mathjax'); - }).then(mj0 => { - // return Promise with mathjax loading - mj0.init({ - loader: { - load: ['input/tex', 'output/svg', '[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] - }, - tex: { - packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } - }, - svg, - config: { - JSDOM - }, - startup: { - typeset: false, - ready() { - const mj = MathJax; - - mj.startup.registerConstructor('jsdomAdaptor', () => { - return new mj._.adaptors.HTMLAdaptor.HTMLAdaptor(new mj.config.config.JSDOM().window); - }); - mj.startup.useAdaptor('jsdomAdaptor', true); - mj.startup.defaultReady(); - const arr = _mj_loading; - _mj_loading = undefined; - arr.forEach(func => func(mj)); - } - } - }); - - return promise; - }); -} - -const math_symbols_map = { - '#LT': '\\langle', - '#GT': '\\rangle', - '#club': '\\clubsuit', - '#spade': '\\spadesuit', - '#heart': '\\heartsuit', - '#diamond': '\\diamondsuit', - '#voidn': '\\wp', - '#voidb': 'f', - '#copyright': '(c)', - '#ocopyright': '(c)', - '#trademark': 'TM', - '#void3': 'TM', - '#oright': 'R', - '#void1': 'R', - '#3dots': '\\ldots', - '#lbar': '\\mid', - '#void8': '\\mid', - '#divide': '\\div', - '#Jgothic': '\\Im', - '#Rgothic': '\\Re', - '#doublequote': '"', - '#plus': '+', - '#minus': '-', - '#/': '/', - '#upoint': '.', - '#aa': '\\mathring{a}', - '#AA': '\\mathring{A}', - '#omicron': 'o', - '#Alpha': 'A', - '#Beta': 'B', - '#Epsilon': 'E', - '#Zeta': 'Z', - '#Eta': 'H', - '#Iota': 'I', - '#Kappa': 'K', - '#Mu': 'M', - '#Nu': 'N', - '#Omicron': 'O', - '#Rho': 'P', - '#Tau': 'T', - '#Chi': 'X', - '#varomega': '\\varpi', - '#corner': '?', - '#ltbar': '?', - '#bottombar': '?', - '#notsubset': '?', - '#arcbottom': '?', - '#cbar': '?', - '#arctop': '?', - '#topbar': '?', - '#arcbar': '?', - '#downleftarrow': '?', - '#splitline': '\\genfrac{}{}{0pt}{}', - '#it': '\\textit', - '#bf': '\\textbf', - '#frac': '\\frac', - '#left{': '\\lbrace', - '#right}': '\\rbrace', - '#left\\[': '\\lbrack', - '#right\\]': '\\rbrack', - '#\\[\\]{': '\\lbrack', - ' } ': '\\rbrack', - '#\\[': '\\lbrack', - '#\\]': '\\rbrack', - '#{': '\\lbrace', - '#}': '\\rbrace', - ' ': '\\;' -}; - -const mathjax_remap = { - upDelta: 'Updelta', - upGamma: 'Upgamma', - upLambda: 'Uplambda', - upOmega: 'Upomega', - upPhi: 'Upphi', - upPi: 'Uppi', - upPsi: 'Uppsi', - upSigma: 'Upsigma', - upTheta: 'Uptheta', - upUpsilon: 'Upupsilon', - upXi: 'Upxi', - notcong: 'ncong', - notgeq: 'ngeq', - notgr: 'ngtr', - notless: 'nless', - notleq: 'nleq', - notsucc: 'nsucc', - notprec: 'nprec', - notsubseteq: 'nsubseteq', - notsupseteq: 'nsupseteq', - openclubsuit: 'clubsuit', - openspadesuit: 'spadesuit', - dasharrow: 'dashrightarrow', - comp: 'circ', - iiintop: 'iiint', - iintop: 'iint', - ointop: 'oint' -}; - -const mathjax_unicode = { - Digamma: 0x3DC, - upDigamma: 0x3DC, - digamma: 0x3DD, - updigamma: 0x3DD, - Koppa: 0x3DE, - koppa: 0x3DF, - upkoppa: 0x3DF, - upKoppa: 0x3DE, - VarKoppa: 0x3D8, - upVarKoppa: 0x3D8, - varkoppa: 0x3D9, - upvarkoppa: 0x3D9, - varkappa: 0x3BA, // not found archaic kappa - use normal - upvarkappa: 0x3BA, - varbeta: 0x3D0, // not found archaic beta - use normal - upvarbeta: 0x3D0, - Sampi: 0x3E0, - upSampi: 0x3E0, - sampi: 0x3E1, - upsampi: 0x3E1, - Stigma: 0x3DA, - upStigma: 0x3DA, - stigma: 0x3DB, - upstigma: 0x3DB, - San: 0x3FA, - upSan: 0x3FA, - san: 0x3FB, - upsan: 0x3FB, - Sho: 0x3F7, - upSho: 0x3F7, - sho: 0x3F8, - upsho: 0x3F8, - P: 0xB6, - aa: 0xB0, - bulletdashcirc: 0x22B7, - circdashbullet: 0x22B6, - downuparrows: 0x21F5, - updownarrows: 0x21C5, - dashdownarrow: 0x21E3, - dashuparrow: 0x21E1, - complement: 0x2201, - dbar: 0x18C, - ddddot: 0x22EF, - dddot: 0x22EF, - ddots: 0x22F1, - defineequal: 0x225D, - defineeq: 0x225D, - downdownharpoons: 0x2965, - downupharpoons: 0x296F, - updownharpoons: 0x296E, - upupharpoons: 0x2963, - hateq: 0x2259, - ldbrack: 0x27E6, - rdbrack: 0x27E7, - leadsfrom: 0x219C, - leftsquigarrow: 0x21DC, - lightning: 0x2607, - napprox: 0x2249, - nasymp: 0x226D, - nequiv: 0x2262, - nsimeq: 0x2244, - nsubseteq: 0x2288, - nsubset: 0x2284, - notapprox: 0x2249, - notasymp: 0x226D, - notequiv: 0x2262, - notni: 0x220C, - notsimeq: 0x2244, - notsubseteq: 0x2288, - notsubset: 0x2284, - notsupseteq: 0x2289, - notsupset: 0x2285, - nsupset: 0x2285, - setdif: 0x2216, - simarrow: 0x2972, - t: 0x2040, - u: 0x2C7, - v: 0x2C7, - undercurvearrowright: 0x293B, - updbar: 0x18C, - wwbar: 0x2015, - awointop: 0x2232, - awoint: 0x2233, - barintop: 0x2A1C, - barint: 0x2A1B, - cwintop: 0x2231, // no opposite direction, use same - cwint: 0x2231, - cwointop: 0x2233, - cwoint: 0x2232, - oiiintop: 0x2230, - oiiint: 0x2230, - oiintop: 0x222F, - oiint: 0x222F, - slashintop: 0x2A0F, - slashint: 0x2A0F -}; - -const mathjax_asis = ['"', '\'', '`', '=', '~']; - -/** @summary Function translates ROOT TLatex into MathJax format - * @private */ -function translateMath(str, kind, color, painter) { - if (kind !== 2) { - for (const x in math_symbols_map) - str = str.replace(new RegExp(x, 'g'), math_symbols_map[x]); - - for (const x in symbols_map) { - if (x.length > 2) - str = str.replace(new RegExp(x, 'g'), '\\' + x.slice(1)); - } - - // replace all #color[]{} occurrences - let clean = '', first = true; - while (str) { - let p = str.indexOf('#color['); - if ((p < 0) && first) { - clean = str; - break; - } - first = false; - if (p) { - const norm = (p < 0) ? str : str.slice(0, p); - clean += norm; - if (p < 0) - break; - } - - str = str.slice(p + 7); - p = str.indexOf(']{'); - if (p <= 0) - break; - const colindx = parseInt(str.slice(0, p)); - if (!Number.isInteger(colindx)) - break; - const col = painter.getColor(colindx); - let cnt = 1; - str = str.slice(p + 2); - p = -1; - while (cnt && (++p < str.length)) { - if (str[p] === '{') - cnt++; - else if (str[p] === '}') - cnt--; - } - if (cnt) - break; - - const part = str.slice(0, p); - str = str.slice(p + 1); - if (part) - clean += `\\color{${col}}{${part}}`; - } - - str = clean; - } else { - if (str === '\\^') - str = '\\unicode{0x5E}'; - if (str === '\\vec') - str = '\\unicode{0x2192}'; - str = str.replace(/\\\./g, '\\unicode{0x2E}').replace(/\\\^/g, '\\hat'); - for (const x in mathjax_unicode) - str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\unicode{0x${mathjax_unicode[x].toString(16)}}`); - mathjax_asis.forEach(symbol => { - str = str.replace(new RegExp(`(\\\\${symbol})`, 'g'), `\\unicode{0x${symbol.charCodeAt(0).toString(16)}}`); - }); - for (const x in mathjax_remap) - str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\${mathjax_remap[x]}`); - } - - if (!isStr(color)) - return str; - - // MathJax SVG converter use colors in normal form - // if (color.indexOf('rgb(') >= 0) - // color = color.replace(/rgb/g, '[RGB]') - // .replace(/\(/g, '{') - // .replace(/\)/g, '}'); - return `\\color{${color}}{${str}}`; -} - -/** @summary Workaround to fix size attributes in MathJax SVG - * @private */ -function repairMathJaxSvgSize(painter, mj_node, svg, arg) { - const transform = value => { - if (!value || !isStr(value) || (value.length < 3)) - return null; - const p = value.indexOf('ex'); - if ((p < 0) || (p !== value.length - 2)) - return null; - value = parseFloat(value.slice(0, p)); - return Number.isFinite(value) ? value * arg.font.size * 0.5 : null; - }; - - let width = transform(svg.getAttribute('width')), - height = transform(svg.getAttribute('height')), - valign = svg.getAttribute('style'); - - if (valign && (valign.length > 18) && valign.indexOf('vertical-align:') === 0) { - const p = valign.indexOf('ex;'); - valign = ((p > 0) && (p === valign.length - 3)) ? transform(valign.slice(15, valign.length - 1)) : null; - } else - valign = null; - - width = (!width || (width <= 0.5)) ? 1 : Math.round(width); - height = (!height || (height <= 0.5)) ? 1 : Math.round(height); - - svg.setAttribute('width', width); - svg.setAttribute('height', height); - svg.removeAttribute('style'); - - if (!isNodeJs()) { - const box = getElementRect(mj_node, 'bbox'); - width = 1.05 * box.width; height = 1.05 * box.height; - } - - arg.valign = valign; - - if (arg.scale) - painter.scaleTextDrawing(Math.max(width / arg.width, height / arg.height), arg.draw_g); -} - -/** @summary Apply attributes to mathjax drawing - * @private */ -function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_factor) { - let mw = parseInt(svg.attr('width')), - mh = parseInt(svg.attr('height')); - - if (isNodeJs()) { - // workaround for NaN in viewBox produced by MathJax - const vb = svg.attr('viewBox'); - if (isStr(vb) && vb.indexOf('NaN') > 0) - svg.attr('viewBox', vb.replaceAll('NaN', '600')); - // console.log('Problematic viewBox', vb, svg.select('text').node()?.innerHTML); - } - - if (Number.isInteger(mh) && Number.isInteger(mw)) { - if (svg_factor > 0) { - mw /= svg_factor; - mh /= svg_factor; - svg.attr('width', Math.round(mw)).attr('height', Math.round(mh)); - } - } else { - const box = getElementRect(mj_node, 'bbox'); // sizes before rotation - mw = box.width || mw || 100; - mh = box.height || mh || 10; - } - - if ((svg_factor > 0) && arg.valign) - arg.valign /= svg_factor; - - if (arg.valign === null) - arg.valign = (font_size - mh) / 2; - - const sign = { x: 1, y: 1 }; - let nx = 'x', ny = 'y'; - if (arg.rotate === 180) - sign.x = sign.y = -1; - else if ((arg.rotate === 270) || (arg.rotate === 90)) { - sign.x = (arg.rotate === 270) ? -1 : 1; - sign.y = -sign.x; - nx = 'y'; - ny = 'x'; // replace names to which align applied - } - - if (arg.align[0] === 'middle') - arg[nx] += sign.x * (arg.width - mw) / 2; - else if (arg.align[0] === 'end') - arg[nx] += sign.x * (arg.width - mw); - - if (arg.align[1] === 'middle') - arg[ny] += sign.y * (arg.height - mh) / 2; - else if (arg.align[1] === 'bottom') - arg[ny] += sign.y * (arg.height - mh); - else if (arg.align[1] === 'bottom-base') - arg[ny] += sign.y * (arg.height - mh - arg.valign); - - let trans = makeTranslate(arg.x, arg.y) || ''; - if (arg.rotate) - trans += `${trans ? ' ' : ''}rotate(${arg.rotate})`; - - mj_node.attr('transform', trans || null).attr('visibility', null); -} - -/** @summary Produce text with MathJax - * @private */ -async function produceMathjax(painter, mj_node, arg) { - const mtext = translateMath(arg.text, arg.latex, arg.color, painter), - options = { em: arg.font.size, ex: arg.font.size / 2, family: arg.font.name, scale: 1, containerWidth: -1, lineWidth: 100000 }; - - return loadMathjax() - .then(mj => mj.tex2svgPromise(mtext, options)) - .then(elem => { - // when adding element to new node, it will be removed from original parent - const svg = elem.querySelector('svg'); - - mj_node.append(() => svg); - - repairMathJaxSvgSize(painter, mj_node, svg, arg); - - arg.mj_func = applyAttributesToMathJax; - return true; - }); -} - -/** @summary Just typeset HTML node with MathJax - * @private */ -async function typesetMathjax(node) { - return loadMathjax().then(mj => mj.typesetPromise(node ? [node] : undefined)); -} - -// list of marker types which can have line widths -const root_50_67 = [2, 3, 5, 4, 25, 26, 27, 28, 30, 32, 35, 36, 37, 38, 40, 42, 44, 46], - // internal recoding of root markers - root_markers = [ - 0, 1, 2, 3, 4, // 0..4 - 5, 106, 107, 104, 1, // 5..9 - 1, 1, 1, 1, 1, // 10..14 - 1, 1, 1, 1, 1, // 15..19 - 104, 125, 126, 132, 4, // 20..24 - 25, 26, 27, 28, 130, // 25..29 - 30, 3, 32, 127, 128, // 30..34 - 35, 36, 37, 38, 137, // 35..39 - 40, 140, 42, 142, 44, // 40..44 - 144, 46, 146, 148, 149]; // 45..49 - - -/** - * @summary Handle for marker attributes - * @private - */ - -class TAttMarkerHandler { - - /** @summary constructor - * @param {object} args - attributes or arguments of {@link TAttMarkerHandler#setArgs} */ - constructor(args) { - this.x0 = this.y0 = 0; - this.color = 'black'; - this.style = 1; - this.size = 8; - this.scale = 1; - this.stroke = true; - this.fill = true; - this.marker = ''; - this.ndig = 0; - this.used = true; - this.changed = false; - this.func = this.apply.bind(this); - this.setArgs(args); - this.changed = false; - } - - /** @summary Set marker attributes. - * @param {object} args - arguments can be - * @param {object} args.attr - instance of TAttrMarker (or derived class) or - * @param {string} args.color - color in HTML form like grb(1,4,5) or 'green' - * @param {number} args.style - marker style - * @param {number} args.size - marker size - * @param {number} [args.refsize] - when specified and marker size < 1, marker size will be calculated relative to that size */ - setArgs(args) { - if (isObject(args) && (typeof args.fMarkerStyle === 'number')) - args = { attr: args }; - - if (args.attr) { - args.color ??= args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor); - if (!args.style || (args.style < 0)) - args.style = args.attr.fMarkerStyle; - args.size ??= args.attr.fMarkerSize; - } - - this.color = args.color; - this.style = args.style; - this.size = args.size; - this.refsize = args.refsize; - - this.#configure(); - } - - /** @summary Set usage flag of attribute */ - setUsed(flag) { - this.used = flag; - } - - /** @summary Reset position, used for optimization of drawing of multiple markers - * @private */ - resetPos() { this.lastx = this.lasty = null; } - - /** @summary Create marker path for given position. - * @desc When drawing many elementary points, created path may depend from previously produced markers. - * @param {number} x - first coordinate - * @param {number} y - second coordinate - * @return {string} path string */ - create(x, y) { - if (!this.optimized) - return `M${(x + this.x0).toFixed(this.ndig)},${(y + this.y0).toFixed(this.ndig)}${this.marker}`; - - // use optimized handling with relative position - const xx = Math.round(x), yy = Math.round(y); - let mv = `M${xx},${yy}`; - if (this.lastx !== null) { - if ((xx === this.lastx) && (yy === this.lasty)) - mv = ''; // pathological case, but let exclude it - else { - const m2 = `m${xx - this.lastx},${yy - this.lasty}`; - if (m2.length < mv.length) - mv = m2; - } - } - this.lastx = xx + 1; - this.lasty = yy; - return mv + 'h1'; - } - - /** @summary Returns full size of marker */ - getFullSize() { return this.scale * this.size; } - - /** @summary Returns approximate length of produced marker string */ - getMarkerLength() { return this.marker ? this.marker.length : 10; } - - /** @summary Change marker attributes. - * @param {string} color - marker color - * @param {number} style - marker style - * @param {number} size - marker size */ - change(color, style, size) { - this.changed = true; - - if (color !== undefined) - this.color = color; - if ((style !== undefined) && (style >= 0)) - this.style = style; - if (size !== undefined) - this.size = size; - - this.#configure(); - } - - /** @summary Prepare object to create marker - * @private */ - #configure() { - this.x0 = this.y0 = 0; - - if ((this.style === 1) || (this.style === 777)) { - this.fill = false; - this.marker = 'h1'; - this.size = 1; - this.optimized = true; - this.resetPos(); - return true; - } - - this.optimized = false; - this.lwidth = 1; - - let style = this.style; - if (style >= 50) { - this.lwidth = 2 + Math.floor((style - 50) / root_50_67.length); - style = root_50_67[(style - 50) % root_50_67.length]; - } - - const marker_kind = root_markers[style] ?? 104, - shape = marker_kind % 100; - - this.fill = (marker_kind >= 100); - - this.scale = this.refsize || 8; // v7 defines refsize as 1 or pad height - - const size = this.getFullSize(); - - this.ndig = (size > 7) ? 0 : ((size > 2) ? 1 : 2); - if (shape === 30) - this.ndig++; // increase precision for star - let s1 = size.toFixed(this.ndig); - const s2 = (size / 2).toFixed(this.ndig), - s3 = (size / 3).toFixed(this.ndig), - s4 = (size / 4).toFixed(this.ndig), - s8 = (size / 8).toFixed(this.ndig), - s38 = (size * 3 / 8).toFixed(this.ndig), - s34 = (size * 3 / 4).toFixed(this.ndig); - - switch (shape) { - case 0: // empty - this.marker = ''; - break; - case 1: // dot - this.marker = 'h1'; - break; - case 2: // plus - this.y0 = -size / 2; - this.marker = `v${s1}m-${s2},-${s2}h${s1}`; - break; - case 3: // asterisk - this.y0 = -size / 2; - this.marker = `v${s1}m-${s2},-${s2}h${s1}m-${s8},-${s38}l-${s34},${s34}m${s34},0l-${s34},-${s34}`; - break; - case 4: // circle - this.x0 = -parseFloat(s2); - s1 = (parseFloat(s2) * 2).toFixed(this.ndig); - this.marker = `a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,-${s1},0z`; - break; - case 5: // multiply - this.x0 = this.y0 = -3 / 8 * size; - this.marker = `l${s34},${s34}m0,-${s34}l-${s34},${s34}`; - break; - case 6: // small dot - this.x0 = -1; - this.marker = 'a1,1,0,1,0,2,0a1,1,0,1,0,-2,0z'; - break; - case 7: // medium dot - this.x0 = -1.5; - this.marker = 'a1.5,1.5,0,1,0,3,0a1.5,1.5,0,1,0,-3,0z'; - break; - case 25: // square - this.x0 = this.y0 = -size / 2; - this.marker = `v${s1}h${s1}v-${s1}z`; - break; - case 26: // triangle-up - this.y0 = -size / 2; - this.marker = `l-${s2},${s1}h${s1}z`; - break; - case 27: // diamond - this.y0 = -size / 2; - this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; - break; - case 28: // cross - this.x0 = this.y0 = size / 6; - this.marker = `h${s3}v-${s3}h-${s3}v-${s3}h-${s3}v${s3}h-${s3}v${s3}h${s3}v${s3}h${s3}z`; - break; - case 30: { // star - this.y0 = -size / 2; - const s56 = (size * 5 / 6).toFixed(this.ndig), s58 = (size * 5 / 8).toFixed(this.ndig); - this.marker = `l${s3},${s1}l-${s56},-${s58}h${s1}l-${s56},${s58}z`; - break; - } - case 32: // triangle-down - this.y0 = size / 2; - this.marker = `l-${s2},-${s1}h${s1}z`; - break; - case 35: - this.x0 = -size / 2; - this.marker = `l${s2},${s2}l${s2},-${s2}l-${s2},-${s2}zh${s1}m-${s2},-${s2}v${s1}`; - break; - case 36: - this.x0 = this.y0 = -size / 2; - this.marker = `h${s1}v${s1}h-${s1}zl${s1},${s1}m0,-${s1}l-${s1},${s1}`; - break; - case 37: - this.x0 = -size / 2; - this.marker = `h${s1}l-${s4},-${s2}l-${s2},${s1}h${s2}l-${s2},-${s1}z`; - break; - case 38: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `h${s2}l${s4},${s4}v${s2}l-${s4},${s4}h-${s2}l-${s4},-${s4}v-${s2}zm${s4},0v${s1}m-${s2},-${s2}h${s1}`; - break; - case 40: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `l${s2},${s1}l${s4},-${s4}l-${s1},-${s2}zm${s2},0l-${s2},${s1}l-${s4},-${s4}l${s1},-${s2}z`; - break; - case 42: - this.y0 = -size / 2; - this.marker = `l${s8},${s38}l${s38},${s8}l-${s38},${s8}l-${s8},${s38}l-${s8},-${s38}l-${s38},-${s8}l${s38},-${s8}z`; - break; - case 44: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `h${s2}l-${s8},${s38}l${s38},-${s8}v${s2}l-${s38},-${s8}l${s8},${s38}h-${s2}l${s8},-${s38}l-${s38},${s8}v-${s2}l${s38},${s8}z`; - break; - case 46: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `l${s4},${s4}l${s4},-${s4}l${s4},${s4}l-${s4},${s4}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}l-${s4},${s4}l-${s4},-${s4}l${s4},-${s4}l-${s4},-${s4}z`; - break; - case 48: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm0,${s2}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm-${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}z`; - break; - case 49: - this.x0 = -size / 6; - this.y0 = -size / 2; - this.marker = `h${s3}v${s3}h-${s3}zm${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},-${s3}h${s3}v${s3}h-${s3}z`; - break; - default: // diamond - this.y0 = -size / 2; - this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; - break; - } - - return true; - } - - /** @summary get stroke color */ - getStrokeColor() { return this.stroke ? this.color : 'none'; } - - /** @summary get fill color */ - getFillColor() { return this.fill ? this.color : 'none'; } - - /** @summary returns true if marker attributes will produce empty (invisible) output */ - empty() { return (this.color === 'none') || (!this.fill && !this.stroke) || !this.marker; } - - /** @summary Apply marker styles to created element */ - apply(selection) { - this.used = true; - selection.style('stroke', this.stroke ? this.color : 'none') - .style('stroke-width', this.stroke && (this.lwidth > 1) ? this.lwidth : null) - .style('fill', this.fill ? this.color : 'none'); - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets. - * @private */ - verifyDirectChange(/* painter */) { - this.change(this.color, parseInt(this.style), parseFloat(this.size)); - } - - /** @summary Create sample with marker in given SVG element - * @param {selection} svg - SVG element - * @param {number} width - width of sample SVG - * @param {number} height - height of sample SVG - * @private */ - createSample(svg, width, height, plain) { - if (plain) - svg = select(svg); - this.resetPos(); - svg.append('path') - .attr('d', this.create(width / 2, height / 2)) - .call(this.func); - } - -} // class TAttMarkerHandler - -/** - * @summary Handle for fill attributes - * @private - */ - -class TAttFillHandler { - - #disabled; // if fill disabled - - /** @summary constructor - * @param {object} args - arguments see {@link TAttFillHandler#setArgs} for more info - * @param {number} [args.kind = 2] - 1 means object drawing where combination fillcolor == 0 and fillstyle == 1001 means no filling, - * 2 means all other objects where such combination is white-color filling */ - constructor(args) { - this.color = 'none'; - this.colorindx = 0; - this.pattern = 0; - this.used = true; - this.kind = args.kind || 2; - this.changed = false; - this.func = this.apply.bind(this); - this.setArgs(args); - this.changed = false; // unset change property - } - - /** @summary Set fill style as arguments - * @param {object} args - different arguments to set fill attributes - * @param {object} [args.attr] - TAttFill object - * @param {number} [args.color] - color id - * @param {number} [args.pattern] - fill pattern id - * @param {object} [args.svg] - SVG element to store newly created patterns - * @param {string} [args.color_as_svg] - color in SVG format */ - setArgs(args) { - if (isObject(args.attr)) { - args.pattern ??= args.attr.fFillStyle; - args.color ??= args.attr.fFillColor; - } - - if (args.enable !== undefined) - this.enable(args.enable); - - const was_changed = this.changed; // preserve changed state - this.change(args.color, args.pattern, args.svg, args.color_as_svg, args.painter); - this.changed = was_changed; - } - - /** @summary Apply fill style to selection */ - apply(selection) { - if (this.#disabled) { - selection.style('fill', 'none'); - return; - } - - this.used = true; - - selection.style('fill', this.getFillColor()); - - if ('opacity' in this) - selection.style('opacity', this.opacity); - - if ('antialias' in this) - selection.style('antialias', this.antialias); - } - - /** @summary Returns fill color (or pattern url) */ - getFillColor() { return this.pattern_url || this.color; } - - /** @summary Returns fill color without pattern url. - * @desc If empty, alternative color will be provided - * @param {string} [alt] - alternative color which returned when fill color not exists - * @private */ - getFillColorAlt(alt) { return this.color && (this.color !== 'none') ? this.color : alt; } - - /** @summary Returns true if color not specified or fill style not specified */ - empty() { - const fill = this.getFillColor(); - return !fill || (fill === 'none'); - } - - /** @summary Enable or disable fill usage - if disabled only 'fill: none' will be applied */ - enable(on) { - this.#disabled = ((on === undefined) || on) ? undefined : true; - } - - /** @summary Set usage flag of attribute */ - setUsed(flag) { - this.used = flag; - } - - /** @summary Returns true if fill attributes has real color */ - hasColor() { - return this.color && (this.color !== 'none'); - } - - /** @summary Set solid fill color as fill pattern - * @param {string} col - solid color */ - setSolidColor(col) { - delete this.pattern_url; - this.color = col; - this.pattern = 1001; - } - - /** @summary Set fill color opacity */ - setOpacity(o) { - this.opacity = o; - } - - /** @summary Check if solid fill is used, also color can be checked - * @param {string} [solid_color] - when specified, checks if fill color matches */ - isSolid(solid_color) { - if ((this.pattern !== 1001) || this.gradient) - return false; - return !solid_color || (solid_color === this.color); - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets - * @private */ - verifyDirectChange(painter) { - if (isStr(this.pattern)) - this.pattern = parseInt(this.pattern); - if (!Number.isInteger(this.pattern)) - this.pattern = 0; - - this.change(this.color, this.pattern, painter?.getCanvSvg(), true, painter); - } - - /** @summary Method to change fill attributes. - * @param {number} color - color index - * @param {number} pattern - pattern index - * @param {selection} svg - top canvas element for pattern storages - * @param {string} [color_as_svg] - when color is string, interpret as normal SVG color - * @param {object} [painter] - when specified, used to extract color by index */ - change(color$1, pattern, svg, color_as_svg, painter) { - delete this.pattern_url; - delete this.gradient; - this.changed = true; - - if ((color$1 !== undefined) && Number.isInteger(parseInt(color$1)) && !color_as_svg) - this.colorindx = parseInt(color$1); - - if ((pattern !== undefined) && Number.isInteger(parseInt(pattern))) { - this.pattern = parseInt(pattern); - delete this.opacity; - delete this.antialias; - } - - if ((this.pattern === 1000) && (this.colorindx === 0)) { - this.pattern_url = 'white'; - return true; - } - - if (this.pattern === 1000) - this.pattern = 1001; - - if (this.pattern < 1001) { - this.pattern_url = 'none'; - return true; - } - - if (this.isSolid() && (this.colorindx === 0) && (this.kind === 1) && !color_as_svg) { - this.pattern_url = 'none'; - return true; - } - - let indx = this.colorindx; - - if (color_as_svg) { - this.color = color$1; - if (color$1 !== 'none') - indx = color(color$1).hex().slice(1); // fictional index produced from color code - } else - this.color = painter ? painter.getColor(indx) : getColor(indx); - - if (!isStr(this.color)) { - if (isObject(this.color) && (this.color?._typename === clTLinearGradient || this.color?._typename === clTRadialGradient)) - this.gradient = this.color; - this.color = 'none'; - } - - if (this.isSolid()) - return true; - - if (!this.gradient) { - if ((this.pattern >= 4000) && (this.pattern <= 4100)) { - // special transparent colors (use for sub-pads) - this.opacity = (this.pattern - 4000) / 100; - return true; - } - if ((this.pattern < 3000) || (this.color === 'none')) - return false; - } - - if (!svg || svg.empty()) - return false; - - let id, lines = '', lfill = null, fills = '', fills2 = '', w = 2, h = 2; - - if (this.gradient) - id = `grad_${this.gradient.fNumber}`; - else { - id = `pat_${this.pattern}_${indx}`; - - switch (this.pattern) { - case 3001: - w = h = 2; - fills = 'M0,0h1v1h-1zM1,1h1v1h-1z'; - break; - case 3002: - w = 4; - h = 2; - fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; - break; - case 3003: - w = h = 4; - fills = 'M2,1h1v1h-1zM0,3h1v1h-1z'; - break; - case 3004: - w = h = 8; - lines = 'M8,0L0,8'; - break; - case 3005: - w = h = 8; - lines = 'M0,0L8,8'; - break; - case 3006: - w = h = 4; - lines = 'M1,0v4'; - break; - case 3007: - w = h = 4; - lines = 'M0,1h4'; - break; - case 3008: - w = h = 10; - fills = 'M0,3v-3h3ZM7,0h3v3ZM0,7v3h3ZM7,10h3v-3ZM5,2l3,3l-3,3l-3,-3Z'; - lines = 'M0,3l5,5M3,10l5,-5M10,7l-5,-5M7,0l-5,5'; - break; - case 3009: - w = h = 12; - lines = 'M0,0A6,6,0,0,0,12,0M6,6A6,6,0,0,0,12,12M6,6A6,6,0,0,1,0,12'; - lfill = 'none'; - break; - case 3010: // bricks - w = h = 10; - lines = 'M0,2h10M0,7h10M2,0v2M7,2v5M2,7v3'; - break; - case 3011: - w = 9; - h = 18; - lines = 'M5,0v8M2,1l6,6M8,1l-6,6M9,9v8M6,10l3,3l-3,3M0,9v8M3,10l-3,3l3,3'; - lfill = 'none'; - break; - case 3012: - w = 10; - h = 20; - lines = 'M5,1A4,4,0,0,0,5,9A4,4,0,0,0,5,1M0,11A4,4,0,0,1,0,19M10,11A4,4,0,0,0,10,19'; - lfill = 'none'; - break; - case 3013: - w = h = 7; - lines = 'M0,0L7,7M7,0L0,7'; - lfill = 'none'; - break; - case 3014: - w = h = 16; - lines = 'M0,0h16v16h-16v-16M0,12h16M12,0v16M4,0v8M4,4h8M0,8h8M8,4v8'; - lfill = 'none'; - break; - case 3015: - w = 6; - h = 12; - lines = 'M2,1A2,2,0,0,0,2,5A2,2,0,0,0,2,1M0,7A2,2,0,0,1,0,11M6,7A2,2,0,0,0,6,11'; - lfill = 'none'; - break; - case 3016: - w = 12; - h = 7; - lines = 'M0,1A3,2,0,0,1,3,3A3,2,0,0,0,9,3A3,2,0,0,1,12,1'; - lfill = 'none'; - break; - case 3017: - w = h = 4; - lines = 'M3,1l-2,2'; - break; - case 3018: - w = h = 4; - lines = 'M1,1l2,2'; - break; - case 3019: - w = h = 12; - lines = 'M1,6A5,5,0,0,0,11,6A5,5,0,0,0,1,6h-1h1A5,5,0,0,1,6,11v1v-1A5,5,0,0,1,11,6h1h-1A5,5,0,0,1,6,1v-1v1A5,5,0,0,1,1,6'; - lfill = 'none'; - break; - case 3020: - w = 7; - h = 12; - lines = 'M1,0A2,3,0,0,0,3,3A2,3,0,0,1,3,9A2,3,0,0,0,1,12'; - lfill = 'none'; - break; - case 3021: // left stairs - w = h = 8; - lines = 'M8,2h-2v4h-4v2M2,0v2h-2'; - lfill = 'none'; - break; - case 3022: // right stairs - w = h = 8; - lines = 'M0,2h2v4h4v2M6,0v2h2'; - lfill = 'none'; - break; - case 3023: - w = h = 8; - fills = 'M4,0h4v4zM8,4v4h-4z'; - fills2 = 'M4,0L0,4L4,8L8,4Z'; - break; - case 3024: - w = h = 16; - fills = 'M0,8v8h2v-8zM8,0v8h2v-8M4,14v2h12v-2z'; - fills2 = 'M0,2h8v6h4v-6h4v12h-12v-6h-4z'; - break; - case 3025: - w = h = 18; - fills = 'M5,13v-8h8ZM18,0v18h-18l5,-5h8v-8Z'; - break; - default: { - if ((this.pattern > 3025) && (this.pattern < 3100)) { - // same as 3002, see TGX11.cxx, line 2234 - w = 4; - h = 2; - fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; - break; - } - - const code = this.pattern % 1000, - k = code % 10, - j = ((code - k) % 100) / 10, - i = (code - j * 10 - k) / 100; - if (!i) - break; - - // use flexible hatches only possible when single pattern is used, - // otherwise it is not possible to adjust pattern dimension that both hatches match with each other - const use_new = (j === k) || (j === 0) || (j === 5) || (j === 9) || (k === 0) || (k === 5) || (k === 9), - pp = painter?.getPadPainter(), - scale_size = pp ? Math.max(pp.getPadWidth(), pp.getPadHeight()) : 600, - spacing_original = Math.max(0.1, gStyle.fHatchesSpacing * scale_size * 0.001), - hatches_spacing = Math.max(1, Math.round(spacing_original)) * 6, - sz = i * hatches_spacing; // axis distance between lines - - id += use_new ? `_hn${Math.round(spacing_original * 100)}` : `_ho${hatches_spacing}`; - - w = h = 6 * sz; // we use at least 6 steps - - const produce_old = (dy, swap) => { - const pos = []; - let step = sz, y1 = 0, max = h, y2, x1, x2; - - // reduce step for smaller angles to keep normal distance approx same - if (Math.abs(dy) < 3) - step = Math.round(sz / 12 * 9); - if (dy === 0) { - step = Math.round(sz / 12 * 8); - y1 = step / 2; - } else if (dy > 0) - max -= step; - else - y1 = step; - - while (y1 <= max) { - y2 = y1 + dy * step; - if (y2 < 0) { - x2 = Math.round(y1 / (y1 - y2) * w); - pos.push(0, y1, x2, 0); - pos.push(w, h - y1, w - x2, h); - } else if (y2 > h) { - x2 = Math.round((h - y1) / (y2 - y1) * w); - pos.push(0, y1, x2, h); - pos.push(w, h - y1, w - x2, 0); - } else - pos.push(0, y1, w, y2); - y1 += step; - } - for (let b = 0; b < pos.length; b += 4) { - if (swap) { - x1 = pos[b + 1]; - y1 = pos[b]; - x2 = pos[b + 3]; - y2 = pos[b + 2]; - } else { - x1 = pos[b]; - y1 = pos[b + 1]; - x2 = pos[b + 2]; - y2 = pos[b + 3]; - } - lines += `M${x1},${y1}`; - if (y2 === y1) - lines += `h${x2 - x1}`; - else if (x2 === x1) - lines += `v${y2 - y1}`; - else - lines += `L${x2},${y2}`; - } - }; - - /* eslint-disable-next-line one-var */ - const produce_new = (_aa, _bb, angle, swapx) => { - if ((angle === 0) || (angle === 90)) { - const dy = i * spacing_original * 3, - nsteps = Math.round(h / dy), - dyreal = h / nsteps; - let yy = dyreal / 2; - - while (yy < h) { - if (angle === 0) - lines += `M0,${Math.round(yy)}h${w}`; - else - lines += `M${Math.round(yy)},0v${h}`; - yy += dyreal; - } - - return; - } - - const a = angle / 180 * Math.PI, - dy = i * spacing_original * 3 / Math.cos(a), - hside = Math.tan(a) * w, - hside_steps = Math.round(hside / dy), - dyreal = hside / hside_steps, - nsteps = Math.floor(h / dyreal); - - h = Math.round(nsteps * dyreal); - - let yy = nsteps * dyreal; - - while (Math.abs(yy - h) < 0.1) - yy -= dyreal; - - while (yy + hside > 0) { - let x1 = 0, y1 = yy, x2 = w, y2 = yy + hside; - - if (y1 < -1e-5) { - // cut at the begin - x1 = -y1 / hside * w; - y1 = 0; - } else if (y2 > h) { - // cut at the end - x2 = (h - y1) / hside * w; - y2 = h; - } - - if (swapx) { - x1 = w - x1; - x2 = w - x2; - } - - lines += `M${Math.round(x1)},${Math.round(y1)}L${Math.round(x2)},${Math.round(y2)}`; - yy -= dyreal; - } - }; - - /* eslint-disable-next-line one-var */ - const func = use_new ? produce_new : produce_old; - - let horiz = false, vertical = false; - - switch (j) { - case 0: horiz = true; break; - case 1: func(1, false, 10); break; - case 2: func(2, false, 20); break; - case 3: func(3, false, 30); break; - case 4: func(6, false, 45); break; - case 6: func(3, true, 60); break; - case 7: func(2, true, 70); break; - case 8: func(1, true, 80); break; - case 9: vertical = true; break; - } - - switch (k) { - case 0: horiz = true; break; - case 1: func(-1, false, 10, true); break; - case 2: func(-2, false, 20, true); break; - case 3: func(-3, false, 30, true); break; - case 4: func(-6, false, 45, true); break; - case 6: func(-3, true, 60, true); break; - case 7: func(-2, true, 70, true); break; - case 8: func(-1, true, 80, true); break; - case 9: vertical = true; break; - } - - if (horiz) - func(0, false, 0); - if (vertical) - func(0, true, 90); - - break; - } - } - - if (!fills && !lines) - return false; - } - - this.pattern_url = `url(#${id})`; - this.antialias = false; - - let defs = svg.selectChild('.canvas_defs'); - if (defs.empty()) - defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); - - if (defs.selectChild('.' + id).empty()) { - if (this.gradient) { - const is_linear = this.gradient._typename === clTLinearGradient, - grad = defs.append(is_linear ? 'svg:linearGradient' : 'svg:radialGradient') - .attr('id', id).attr('class', id), - conv = v => { return v === Math.round(v) ? v.toFixed(0) : v.toFixed(2); }; - if (is_linear) { - grad.attr('x1', conv(this.gradient.fStart.fX)) - .attr('y1', conv(1 - this.gradient.fStart.fY)) - .attr('x2', conv(this.gradient.fEnd.fX)) - .attr('y2', conv(1 - this.gradient.fEnd.fY)); - } else { - grad.attr('cx', conv(this.gradient.fStart.fX)) - .attr('cy', conv(1 - this.gradient.fStart.fY)) - .attr('cr', conv(this.gradient.fR1)); - } - for (let n = 0; n < this.gradient.fColorPositions.length; ++n) { - const pos = this.gradient.fColorPositions[n], - col = toColor(this.gradient.fColors[n * 4], this.gradient.fColors[n * 4 + 1], this.gradient.fColors[n * 4 + 2]); - grad.append('svg:stop').attr('offset', `${Math.round(pos * 100)}%`) - .attr('stop-color', col) - .attr('stop-opacity', `${Math.round(this.gradient.fColors[n * 4 + 3] * 100)}%`); - } - } else { - const patt = defs.append('svg:pattern') - .attr('id', id).attr('class', id).attr('patternUnits', 'userSpaceOnUse') - .attr('width', w).attr('height', h); - - if (fills2) { - const col = rgb(this.color); - col.r = Math.round((col.r + 255) / 2); - col.g = Math.round((col.g + 255) / 2); - col.b = Math.round((col.b + 255) / 2); - patt.append('svg:path').attr('d', fills2).style('fill', col); - } - if (fills) - patt.append('svg:path').attr('d', fills).style('fill', this.color); - if (lines) - patt.append('svg:path').attr('d', lines).style('stroke', this.color).style('stroke-width', gStyle.fHatchesLineWidth || 1).style('fill', lfill); - } - } - - return true; - } - - /** @summary Create sample of fill pattern inside SVG - * @private */ - createSample(svg, width, height, plain) { - // we need to create extra handle to change - if (plain) - svg = select(svg); - - const sample = new TAttFillHandler({ svg, pattern: this.pattern, color: this.color, color_as_svg: true }); - - svg.append('path') - .attr('d', `M0,0h${width}v${height}h${-width}z`) - .call(sample.func); - } - - /** @summary Save fill attributes to style - * @private */ - saveToStyle(name_color, name_pattern) { - if (name_color) { - const indx = this.colorindx ?? findColor(this.color); - if (indx >= 0) - gStyle[name_color] = indx; - } - if (name_pattern) - gStyle[name_pattern] = this.pattern; - } - -} // class TAttFillHandler - -const root_line_styles = [ - '', '', '3, 3', '1, 2', - '3, 4, 1, 4', '5, 3, 1, 3', '5, 3, 1, 3, 1, 3, 1, 3', '5, 5', - '5, 3, 1, 3, 1, 3', '20, 5', '20, 10, 1, 10', '1, 3']; - -/** - * @summary Handle for line attributes - * @private - */ - -class TAttLineHandler { - - /** @summary constructor - * @param {object} args - attributes or arguments of {@link TAttLineHandler#setArgs} */ - constructor(args) { - this.func = this.apply.bind(this); - this.used = true; - if (args._typename && (args.fLineStyle !== undefined)) - args = { attr: args }; - this.setArgs(args); - } - - /** @summary Set line attributes. - * @param {object} args - specify attributes by different ways - * @param {object} args.attr - TAttLine object with appropriate data members or - * @param {string} args.color - color in html like rgb(255,0,0) or 'red' or '#ff0000' - * @param {number} args.style - line style number - * @param {number} args.width - line width */ - setArgs(args) { - if (args.attr) { - this.color_index = args.attr.fLineColor; - args.color = args.color0 || (args.painter?.getColor(this.color_index) ?? getColor(this.color_index)); - args.width ??= args.attr.fLineWidth; - args.style ??= args.attr.fLineStyle; - } else if (isStr(args.color)) { - if ((args.color !== 'none') && !args.width) - args.width = 1; - } else if (typeof args.color === 'number') { - this.color_index = args.color; - args.color = args.painter?.getColor(args.color) ?? getColor(args.color); - } - - if (args.width === undefined) - args.width = (args.color && args.color !== 'none') ? 1 : 0; - - this.nocolor = args.nocolor; - this.color = (args.width === 0) || this.nocolor ? 'none' : args.color; - this.width = args.width; - this.style = args.style; - this.pattern = args.pattern || root_line_styles[this.style] || null; - - if (args.can_excl) { - this.excl_side = this.excl_width = 0; - if (Math.abs(this.width) > 99) { - // exclusion graph - this.excl_side = (this.width < 0) ? -1 : 1; - this.excl_width = Math.floor(Math.abs(this.width) / 100) * 5; - this.width = Math.abs(this.width % 100); // line width - } - } - - // if custom color number used, use lightgrey color to show lines - if (!this.color && (this.width > 0)) - this.color = 'lightgrey'; - } - - /** @summary Change exclusion attributes */ - changeExcl(side, width) { - if (width !== undefined) - this.excl_width = width; - if (side !== undefined) { - this.excl_side = side; - if (side && !this.excl_width) - this.excl_width = 20; - } - this.changed = true; - } - - /** @summary returns true if line attribute is empty and will not be applied. */ - empty() { return this.color === 'none'; } - - /** @summary Set usage flag of attribute */ - setUsed(flag) { - this.used = flag; - } - - /** @summary set border parameters, used for rect drawing */ - setBorder(rx, ry) { - this.rx = rx; - this.ry = ry; - this.func = this.applyBorder.bind(this); - } - - /** @summary Applies line attribute to selection. - * @param {object} selection - d3.js selection */ - apply(selection) { - this.used = true; - if (this.empty()) { - selection.style('stroke', null) - .style('stroke-width', null) - .style('stroke-dasharray', null); - } else { - selection.style('stroke', this.color) - .style('stroke-width', this.width) - .style('stroke-dasharray', this.pattern); - } - } - - /** @summary Applies line and border attribute to selection. - * @param {object} selection - d3.js selection */ - applyBorder(selection) { - this.used = true; - if (this.empty()) { - selection.attr('rx', null) - .attr('ry', null) - .style('stroke', null) - .style('stroke-width', null) - .style('stroke-dasharray', null); - } else { - selection.attr('rx', this.rx || null) - .attr('ry', this.ry || null) - .style('stroke', this.color) - .style('stroke-width', this.width) - .style('stroke-dasharray', this.pattern); - } - } - - /** @summary Change line attributes */ - change(color, width, style) { - if (color !== undefined) { - if (this.color !== color) - delete this.color_index; - this.color = color; - } - if (width !== undefined) - this.width = width; - if (style !== undefined) { - this.style = style; - this.pattern = root_line_styles[this.style] || null; - } - this.changed = true; - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets. - * @private */ - verifyDirectChange(/* painter */) { - this.change(this.color, parseInt(this.width), parseInt(this.style)); - } - - /** @summary Create sample element inside primitive SVG - used in context menu */ - createSample(svg, width, height, plain) { - if (plain) - svg = select(svg); - svg.append('path') - .attr('d', `M0,${height / 2}h${width}`) - .call(this.func); - } - - /** @summary Save attributes values to gStyle */ - saveToStyle(name_color, name_width, name_style) { - if (name_color) { - const indx = (this.color_index !== undefined) ? this.color_index : findColor(this.color); - if (indx >= 0) - gStyle[name_color] = indx; - } - if (name_width) - gStyle[name_width] = this.width; - if (name_style) - gStyle[name_style] = this.style; - } - -} // class TAttLineHandler - -/** @summary Get svg string for specified line style - * @private */ -function getSvgLineStyle(indx) { - if ((indx < 0) || (indx >= root_line_styles.length)) - indx = 11; - return root_line_styles[indx]; -} - -function calcTextSize(sz, sz0, fact, pp) { - if (!sz) - sz = sz0 || 0; - - if (sz >= 1) - return Math.round(sz * (pp?.getPadScale() || 1)); - - return Math.round(sz * Math.min(pp?.getPadWidth() ?? 1000, pp?.getPadHeight() ?? 1000) * (fact || 1)); -} - -/** - * @summary Handle for text attributes - * @private - */ - -class TAttTextHandler { - - /** @summary constructor - * @param {object} args - attributes or arguments of {@link TAttTextHandler#setArgs} */ - constructor(args) { - this.used = true; - if (args._typename && (args.fTextFont !== undefined)) - args = { attr: args }; - this.setArgs(args); - } - - /** @summary Set text attributes. - * @param {object} args - specify attributes by different ways - * @param {object} args.attr - TAttText object with appropriate data members or - * @param {object} args.attr_alt - alternative TAttText object with appropriate data members if values are 0 - * @param {string} args.color - color in html like rgb(255,0,0) or 'red' or '#ff0000' - * @param {number} args.align - text align - * @param {number} args.angle - text angle - * @param {number} args.font - font index - * @param {number} args.size - text size */ - setArgs(args) { - if (args.attr) { - args.font = args.attr.fTextFont || args.attr_alt?.fTextFont || 0; - args.size = args.attr.fTextSize || args.attr_alt?.fTextSize || 0; - this.color_index = args.attr.fTextColor || args.attr_alt?.fTextColor || 0; - args.color = args.painter?.getColor(this.color_index) ?? getColor(this.color_index); - args.align = args.attr.fTextAlign || args.attr_alt?.fTextAlign || 0; - args.angle = args.attr.fTextAngle || args.attr_alt?.fTextAngle || 0; - } else if (typeof args.color === 'number') { - this.color_index = args.color; - args.color = args.painter?.getColor(args.color) ?? getColor(args.color); - } - - this.font = args.font; - this.size = args.size; - this.color = args.color; - this.align = args.align; - this.angle = args.angle; - - this.can_rotate = args.can_rotate ?? true; - this.angle_used = false; - this.align_used = false; - } - - /** @summary returns true if line attribute is empty and will not be applied. */ - empty() { return this.color === 'none'; } - - /** @summary Change text attributes */ - change(font, size, color, align, angle) { - if (font !== undefined) - this.font = font; - if (size !== undefined) - this.size = size; - if (color !== undefined) { - if (this.color !== color) - delete this.color_index; - this.color = color; - } - if (align !== undefined) - this.align = align; - if (angle !== undefined) - this.angle = angle; - this.changed = true; - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets. - * @private */ - verifyDirectChange(/* painter */) { - this.change(parseInt(this.font), parseFloat(this.size), this.color, parseInt(this.align), parseInt(this.angle)); - } - - /** @summary Create argument for drawText method */ - createArg(arg) { - if (!arg) - arg = {}; - this.align_used = !arg.noalign && !arg.align; - if (this.align_used) - arg.align = this.align; - this.angle_used = !arg.norotate && this.can_rotate; - if (this.angle_used && this.angle) - arg.rotate = -this.angle; // SVG rotation angle has different sign - arg.color = this.color || 'black'; - return arg; - } - - /** @summary Provides pixel size */ - getSize(pp, fact, zero_size) { return calcTextSize(this.size, zero_size, fact, pp); } - - /** @summary Returns alternating size - which defined by sz1 variable */ - getAltSize(sz1, pp) { return calcTextSize(sz1, this.size, 1, pp); } - - /** @summary Get font index - without precision */ - getGedFont() { return Math.floor(this.font / 10); } - - /** @summary Change text font from GED */ - setGedFont(value) { - const v = parseInt(value); - if ((v > 0) && (v < 17)) - this.change(v * 10 + (this.font % 10)); - return this.font; - } - -} // class TAttTextHandler - -/** @summary returns true if pad painter - * @private */ -function isPadPainter(p) { - return isFunc(p?.getRootPad) && isFunc(p?.forEachPainterInPad); -} - -/** @summary returns canvas painter from DOM element - * @private */ -function getDomCanvasPainter(dom) { - const elem = dom?.select('.root_canvas'); - return !elem || elem.empty() ? null : elem.property('pad_painter'); -} - -/** - * @summary Painter class for ROOT objects - * - */ - -class ObjectPainter extends BasePainter { - - #draw_object; // drawn object - #draw_g; // element for object drawing - #pad_painter_ref; // reference of pad painter - #main_painter; // WeakRef to main painter in the pad - #primary_ref; // reference of primary painter - if any - #snapid; // assigned online identifier - #is_primary; // if primary painter - #secondary_id; // id of this painter in relation to primary painter - #options; // current options object - #options_store; // stored draw options used to check changes - #user_tooltip_handler; // configured user tooltip handler - #user_tooltip_timeout; // timeout configured with tooltip handler - #user_tooltip_timerid; // setTimemout id for processing user tooltip - #user_context_menu; // function for user context menu - #special_draw_area; // current special draw area like projection - #root_colors; // custom colors list - #fillatt; // fill attribute - #lineatt; // line attribute - #markeratt; // marker attribute - #textatt; // text attribute - - /** @summary constructor - * @param {object|string} dom - dom element or identifier or pad painter - * @param {object} obj - object to draw - * @param {string} [opt] - object draw options */ - constructor(dom, obj, opt) { - const pp = isPadPainter(dom) ? dom : null; - - super(pp?.getDom() ?? dom); - - this.setPadPainter(pp); - - this.#draw_g = undefined; // container for all drawn objects - this.assignObject(obj); - if (isStr(opt)) - this.#options = { original: opt }; - } - - /** @summary Assign object to the painter - * @protected */ - assignObject(obj) { this.#draw_object = isObject(obj) ? obj : null; } - - /** @summary Returns drawn object */ - getObject() { return this.#draw_object; } - - /** @summary Assign new pad painter - * @protected */ - setPadPainter(pp) { this.#pad_painter_ref = pp ? new WeakRef(pp) : undefined; } - - /** @summary returns pad painter where object is drawn - * @protected */ - getPadPainter() { return this.#pad_painter_ref?.deref(); } - - /** @summary returns canvas painter - * @protected */ - getCanvPainter() { - let pp = this.getPadPainter(); - while (pp && !pp.isCanvas()) - pp = pp.getPadPainter(); - return pp; - } - - /** @summary Indicates that drawing runs in batch mode - * @private */ - isBatchMode() { return isBatchMode() ? true : (this.getCanvPainter()?.isBatchMode() ?? false); } - - /** @summary Assign snapid to the painter - * @desc Identifier used to communicate with server side and identifies object on the server - * @private */ - assignSnapId(id) { this.#snapid = id; } - - /** @summary Provides identifier on server for requested sub-element */ - getSnapId(subelem) { return !this.#snapid ? '' : (this.#snapid + (subelem ? '#' + subelem : '')); } - - /** @summary Returns true if snapid was assigned */ - hasSnapId() { return this.#snapid !== undefined; } - - /** @summary Generic method to cleanup painter. - * @desc Remove object drawing and (in case of main painter) also main HTML components - * @protected */ - cleanup() { - this.removeG(); - - let keep_origin = true; - - if (this.isMainPainter()) { - const pp = this.getPadPainter(); - if (!pp || pp.isCanvas('auto')) - keep_origin = false; - } - - // cleanup all existing references - this.#pad_painter_ref = undefined; - this.#main_painter = null; - this.#draw_object = null; - this.#snapid = undefined; - this.#is_primary = undefined; - this.#primary_ref = undefined; - this.#secondary_id = undefined; - - // remove attributes objects (if any) - this.deleteAttr(); - this.#root_colors = undefined; - this.#options = undefined; - this.#options_store = undefined; - - // remove extra fields from v7 painters - delete this.rstyle; - delete this.csstype; - - super.cleanup(keep_origin); - } - - /** @summary Returns drawn object name */ - getObjectName() { return this.getObject()?.fName ?? ''; } - - /** @summary Returns drawn object class name */ - getClassName() { return this.getObject()?._typename ?? ''; } - - /** @summary Checks if drawn object matches with provided typename - * @param {string|object} arg - typename (or object with _typename member) - * @protected */ - matchObjectType(arg) { - const clname = this.getClassName(); - if (!arg || !clname) - return false; - if (isStr(arg)) - return arg === clname; - if (isStr(arg._typename)) - return arg._typename === clname; - return Boolean(clname.match(arg)); - } - - /** @summary Change item name - * @desc When available, used for svg:title property - * @private */ - setItemName(name, opt, hpainter) { - super.setItemName(name, opt, hpainter); - if (this._no_default_title || !name) - return; - const can = this.getCanvSvg(); - if (!can.empty()) - can.select('title').text(name); - else - this.selectDom().attr('title', name); - const cp = this.getCanvPainter(); - if (cp && ((cp === this) || (this.isMainPainter() && (cp === this.getPadPainter())))) - cp.drawItemNameOnCanvas(name); - } - - /** @summary Create options and copy new args - * @return options - * @private */ - setOptions(new_options, as_is) { - if (as_is) - this.#options = new_options; - else { - if (!this.#options) - this.#options = {}; - Object.assign(this.#options, new_options); - } - return this.#options; - } - - /** @summary Return actual options */ - getOptions(as_is) { - if (!as_is && !this.#options) - this.#options = {}; - return this.#options; - } - - /** @summary Emulate old options property */ - get options() { return this.getOptions(); } - - /** @summary Store actual options together with original string - * @private */ - storeDrawOpt(original) { - if (!this.#options) - return; - if (!original) - original = ''; - const pp = original.indexOf(';;'); - if (pp >= 0) - original = original.slice(0, pp); - this.#options.original = original; - this.#options_store = Object.assign({}, this.#options); - } - - /** @summary Return dom argument for object drawing - * @desc Can be used to draw other objects on same pad / same dom element - * @protected */ - getDrawDom() { return this.getPadPainter() || this.getDom(); } - - /** @summary Return actual draw options as string - * @param {boolean} ignore_pad - do not include pad settings into histogram draw options - * @desc if options are not modified - returns original string which was specified for object draw */ - getDrawOpt(ignore_pad) { - if (!this.#options) - return ''; - - if (isFunc(this.#options.asString)) { - let changed = false; - const pp = this.getPadPainter(); - if (!this.#options_store || pp?.options._interactively_changed) - changed = true; - else { - for (const k in this.#options_store) { - if (this.#options[k] !== this.#options_store[k]) { - if ((k[0] !== '_') && (k[0] !== '$') && (k[0].toLowerCase() !== k[0])) - changed = true; - } - } - } - - if (changed && isFunc(this.#options.asString)) - return this.#options.asString(this.isMainPainter(), ignore_pad ? null : pp?.getRootPad()); - } - - return this.#options.original || ''; // nothing better, return original draw option - } - - /** @summary Returns array with supported draw options as configured in draw.mjs - * @desc works via pad painter and only when module was loaded */ - getSupportedDrawOptions() { - const pp = this.getPadPainter(), - cl = this.getClassName(); - - if (!cl || !isFunc(pp?.getObjectDrawSettings)) - return []; - - return pp.getObjectDrawSettings(getKindForType(cl), 'nosame')?.opts; - } - - /** @summary Central place to update objects drawing - * @param {object} obj - new version of object, values will be updated in original object - * @param {string} [opt] - when specified, new draw options - * @return {boolean|Promise} for object redraw - * @desc Two actions typically done by redraw - update object content via {@link ObjectPainter#updateObject} and - * then redraw correspondent pad via {@link ObjectPainter#redrawPad}. If possible one should redefine - * only updateObject function and keep this function unchanged. But for some special painters this function is the - * only way to control how object can be update while requested from the server - * @protected */ - redrawObject(obj, opt) { - if (!this.updateObject(obj, opt)) - return false; - const doc = this.isBatchMode() ? null : getDocument(), - current = doc?.body.style.cursor; - if (doc) - doc.body.style.cursor = 'wait'; - return this.redrawPad().then(res => { - if (doc) - doc.body.style.cursor = current; - return res; - }); - } - - /** @summary Generic method to update object content. - * @desc Default implementation just copies first-level members to current object - * @param {object} obj - object with new data - * @param {string} [_opt] - option which will be used for redrawing - * @protected */ - updateObject(obj, _opt) { - if (!this.matchObjectType(obj)) - return false; - Object.assign(this.getObject(), obj); - return true; - } - - /** @summary Returns string with object hint - * @desc It is either item name or object name or class name. - * Such string typically used as object tooltip. - * If result string larger than 20 symbols, it will be shorten. */ - getObjectHint() { - const iname = this.getItemName(); - if (iname) - return (iname.length > 20) ? '...' + iname.slice(iname.length - 17) : iname; - return this.getObjectName() || this.getClassName() || ''; - } - - /** @summary Set colors list - * @protected */ - setColors(lst) { this.#root_colors = lst; } - - /** @summary Return colors list - * @protected */ - getColors(force) { - if (!this.#root_colors && force) - this.setColors(this.getCanvPainter()?.getColors() || getRootColors()); - return this.#root_colors; - } - - /** @summary returns color from current list of colors - * @desc First checks canvas painter and then just access global list of colors - * @param {number} indx - color index - * @return {string} with SVG color name or rgb() - * @protected */ - getColor(indx) { return this.getColors(true)[indx]; } - - /** @summary Add color to list of colors - * @desc Returned color index can be used as color number in all other draw functions - * @return {number} new color index - * @protected */ - addColor(color) { - const lst = this.getColors(true), - indx = lst.indexOf(color); - if (indx >= 0) - return indx; - lst.push(color); - return lst.length - 1; - } - - /** @summary returns tooltip allowed flag - * @desc If available, checks in canvas painter - * @private */ - isTooltipAllowed() { - const src = this.getCanvPainter() || this; - return src.tooltip_allowed; - } - - /** @summary change tooltip allowed flag - * @param {boolean|string} [on = true] set tooltip allowed state or 'toggle' - * @private */ - setTooltipAllowed(on = true) { - const src = this.getCanvPainter() || this; - src.tooltip_allowed = (on === 'toggle') ? !src.tooltip_allowed : on; - } - - /** @summary Checks if draw elements were resized and drawing should be updated. - * @desc Redirects to {@link TPadPainter#checkCanvasResize} - * @private */ - checkResize(arg) { - return this.getCanvPainter()?.checkCanvasResize(arg); - } - - /** @summary removes element with object drawing - * @desc generic method to delete all graphical elements, associated with the painter - * @protected */ - removeG() { - this.#draw_g?.remove(); - this.#draw_g = undefined; - } - - /** @summary Returns created element used for object drawing - * @desc Element should be created by {@link ObjectPainter#createG} - * @protected */ - getG() { return this.#draw_g; } - - /** @summary introduce property for backward compatibility */ - get draw_g() { return this.#draw_g; } - - /** @summary Assign G element used for object drawing - * @protected */ - setG(g) { - this.#draw_g = g; - return g; - } - - /** @summary Append svg::path to G - * @protected */ - appendPath(d) { return this.#draw_g.append('svg:path').attr('d', d); } - - /** @summary (re)creates svg:g element for object drawings - * @desc either one attach svg:g to pad primitives (default) - * or svg:g element created in specified frame layer ('main_layer' will be used when true specified) - * @param {boolean|string} [frame_layer] - when specified, element will be created inside frame layer, otherwise in the pad - * @protected */ - createG(frame_layer, use_a = false) { - let layer; - - const pp = this.getPadPainter(); - - if (frame_layer === 'frame2d') { - const fp = this.getFramePainter(); - frame_layer = fp && !fp.mode3d; - } - - if (frame_layer) { - const frame = pp.getFrameSvg(); - if (frame.empty()) { - console.error('Not found frame to create g element inside'); - return frame; - } - if (!isStr(frame_layer)) - frame_layer = 'main_layer'; - layer = frame.selectChild('.' + frame_layer); - } else - layer = pp.getLayerSvg('primitives_layer'); - - if (this.#draw_g && this.#draw_g.node().parentNode !== layer.node()) { - console.log('g element changes its layer!'); - this.removeG(); - } - - if (this.#draw_g) { - // clear all elements, keep g element on its place - this.#draw_g.selectAll('*').remove(); - } else { - this.#draw_g = layer.append(use_a ? 'svg:a' : 'svg:g'); - - if (!frame_layer) - layer.selectChildren('.most_upper_primitives').raise(); - } - - // set attributes for debugging, both should be there for opt out them later - const clname = this.getClassName(), objname = this.getObjectName(); - if (objname || clname) { - this.#draw_g.attr('objname', (objname || 'name').replace(/[^\w]/g, '_')) - .attr('objtype', (clname || 'type').replace(/[^\w]/g, '_')); - } - - this.#draw_g.property('in_frame', Boolean(frame_layer)); // indicates coordinate system - - return this.#draw_g; - } - - /** @summary Bring draw element to the front */ - bringToFront(check_online) { - if (!this.#draw_g) - return; - const prnt = this.#draw_g.node().parentNode; - prnt?.appendChild(this.#draw_g.node()); - - if (check_online && this.getSnapId()) { - const pp = this.getPadPainter(); - if (pp?.getSnapId()) - this.getCanvPainter()?.sendWebsocket('POPOBJ:' + JSON.stringify([pp.getSnapId(), this.getSnapId()])); - } - } - - /** @summary Assign is_primary flag - * @private */ - setPrimary(flag = true) { this.#is_primary = flag; } - - /** @summary Return is_primary flag - * @private */ - isPrimary() { return this.#is_primary; } - - /** @summary Assign secondary id - * @private */ - setSecondaryId(primary, name) { - primary.setPrimary(true); // mark as primary, used later - this.#primary_ref = new WeakRef(primary); - this.#secondary_id = name; - } - - /** @summary Returns secondary id - * @private */ - getSecondaryId() { return this.#secondary_id; } - - /** @summary Check if this is secondary painter - * @desc if primary painter provided - check if this really main for this - * @private */ - isSecondary(primary) { - if (!this.#primary_ref) - return false; - return !isObject(primary) ? true : this.#primary_ref.deref() === primary; - } - - /** @summary Return primary object - * @private */ - getPrimary() { return this.#primary_ref?.deref(); } - - /** @summary Canvas main svg element - * @return {object} d3 selection with canvas svg - * @protected */ - getCanvSvg() { return this.selectDom().select('.root_canvas'); } - - /** @summary Method did selects current pad name, now does nothing - * @private - * @deprecated to be removed in v8 */ - selectCurrentPad() { - console.warn('selectCurrentPad is deprecated, will be removed in v8'); - return ''; - } - - /** @summary Return functor, which can convert x and y coordinates into pixels, used for drawing in the pad - * @desc X and Y coordinates can be converted by calling func.x(x) and func.y(y) - * Only can be used for painting in the pad, means CreateG() should be called without arguments - * @param {boolean} isndc - if NDC coordinates will be used - * @param {boolean} [nornd] - if set, return coordinates will not be rounded - * @param {boolean} [use_frame_coordinates] - use frame coordinates even when drawing on the pad - * @protected */ - getAxisToSvgFunc(isndc, nornd, use_frame_coordinates) { - const func = { isndc, nornd }, - use_frame = this.getG()?.property('in_frame'); - if (use_frame || (use_frame_coordinates && !isndc)) - func.fp = this.getFramePainter(); - if (func.fp?.grx && func.fp?.gry) { - func.x0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameX() : 0; - func.y0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameY() : 0; - if (nornd) { - func.x = function(x) { return this.x0 + this.fp.grx(x); }; - func.y = function(y) { return this.y0 + this.fp.gry(y); }; - } else { - func.x = function(x) { return this.x0 + Math.round(this.fp.grx(x)); }; - func.y = function(y) { return this.y0 + Math.round(this.fp.gry(y)); }; - } - } else if (!use_frame) { - const pp = this.getPadPainter(); - func.pad = isndc ? null : pp?.getRootPad(true); // need for NDC conversion - func.padw = pp?.getPadWidth() ?? 10; - func.x = function(value) { - if (this.pad) { - if (this.pad.fLogx) - value = (value > 0) ? Math.log10(value) : this.pad.fUxmin; - value = (value - this.pad.fX1) / (this.pad.fX2 - this.pad.fX1); - } - value *= this.padw; - return this.nornd ? value : Math.round(value); - }; - func.padh = pp?.getPadHeight() ?? 10; - func.y = function(value) { - if (this.pad) { - if (this.pad.fLogy) - value = (value > 0) ? Math.log10(value) : this.pad.fUymin; - value = (value - this.pad.fY1) / (this.pad.fY2 - this.pad.fY1); - } - value = (1 - value) * this.padh; - return this.nornd ? value : Math.round(value); - }; - } else { - console.error(`Problem to create functor for ${this.getClassName()}`); - func.x = () => 0; - func.y = () => 0; - } - return func; - } - - /** @summary Converts x or y coordinate into pad SVG coordinates. - * @desc Only can be used for painting in the pad, means CreateG() should be called without arguments - * @param {string} axis - name like 'x' or 'y' - * @param {number} value - axis value to convert. - * @param {boolean} ndc - is value in NDC coordinates - * @param {boolean} [noround] - skip rounding - * @return {number} value of requested coordinates - * @protected */ - axisToSvg(axis, value, ndc, noround) { - const func = this.getAxisToSvgFunc(ndc, noround); - return func[axis](value); - } - - /** @summary Converts pad SVG x or y coordinates into axis values. - * @desc Reverse transformation for {@link ObjectPainter#axisToSvg} - * @param {string} axis - name like 'x' or 'y' - * @param {number} coord - graphics coordinate. - * @param {boolean} ndc - kind of return value - * @return {number} value of requested coordinates - * @protected */ - svgToAxis(axis, coord, ndc) { - const use_frame = this.getG()?.property('in_frame'); - - if (use_frame) - return this.getFramePainter()?.revertAxis(axis, coord) ?? 0; - - const pp = this.getPadPainter(), - pad = (ndc || !pp) ? null : pp.getRootPad(true); - let value = !pp ? 0 : ((axis === 'y') ? (1 - coord / pp.getPadHeight()) : coord / pp.getPadWidth()); - - if (pad) { - if (axis === 'y') { - value = pad.fY1 + value * (pad.fY2 - pad.fY1); - if (pad.fLogy) - value = Math.pow(10, value); - } else { - value = pad.fX1 + value * (pad.fX2 - pad.fX1); - if (pad.fLogx) - value = Math.pow(10, value); - } - } - - return value; - } - - /** @summary Returns frame painter for current pad - * @desc Pad has direct reference on frame if any - * @protected */ - getFramePainter() { - return this.getPadPainter()?.getFramePainter(); - } - - /** @summary Returns painter for main object on the pad. - * @desc Typically it is first histogram drawn on the pad and which draws frame axes - * But it also can be special use-case as TASImage or TGraphPolargram - * @param {boolean} [not_store] - if true, prevent temporary storage of main painter reference - * @protected */ - getMainPainter(not_store) { - let res = this.#main_painter?.deref(); - if (!res) { - const pp = this.getPadPainter(); - res = pp ? pp.getMainPainter() : this.getTopPainter(); - this.#main_painter = not_store || !res ? null : new WeakRef(res); - } - return res || null; - } - - /** @summary Returns true if this is main painter - * @protected */ - isMainPainter() { return this === this.getMainPainter(); } - - /** @summary Assign this as main painter on the pad - * @desc Main painter typically responsible for axes drawing - * Should not be used by pad/canvas painters, but rather by objects which are drawing axis - * @protected */ - setAsMainPainter(force) { - const pp = this.getPadPainter(); - if (!pp) - this.setTopPainter(); // fallback on BasePainter method - else - pp.setMainPainter(this, force); - } - - /** @summary Add painter to pad list of painters - * @desc Normally called from {@link ensureTCanvas} function when new painter is created - * @protected */ - addToPadPrimitives(pad_painter) { - if (this.#pad_painter_ref) - pad_painter = this.#pad_painter_ref.deref(); - else { - if (!pad_painter) - pad_painter = getDomCanvasPainter(this.selectDom()); // try to detect in DOM - if (pad_painter) - this.#pad_painter_ref = new WeakRef(pad_painter); - } - - if (!pad_painter || (pad_painter === this)) - return null; - - return pad_painter.addToPrimitives(this); - } - - /** @summary Remove painter from pad list of painters - * @desc Can be used from external frameworks to add/remove painters - * @protected */ - removeFromPadPrimitives() { this.getPadPainter()?.removePrimitive(this); } - - /** @summary Creates marker attributes object - * @desc Can be used to produce markers in painter. - * See {@link TAttMarkerHandler} for more info. - * Instance assigned as this.markeratt data member, recognized by GED editor - * @param {object} args - either TAttMarker or see arguments of {@link TAttMarkerHandler} - * @return {object} created handler - * @protected */ - createAttMarker(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args.fMarkerColor !== undefined && args.fMarkerStyle !== undefined && args.fMarkerSize !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#markeratt : null; - - if (!handler) - handler = new TAttMarkerHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#markeratt = handler; - return handler; - } - - /** @summary Creates line attributes object. - * @desc Can be used to produce lines in painter. - * See {@link TAttLineHandler} for more info. - * Instance assigned as this.lineatt data member, recognized by GED editor - * @param {object} args - either TAttLine or see constructor arguments of {@link TAttLineHandler} - * @protected */ - createAttLine(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args.fLineColor !== undefined && args.fLineStyle !== undefined && args.fLineWidth !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#lineatt : null; - - if (!handler) - handler = new TAttLineHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#lineatt = handler; - return handler; - } - - /** @summary Creates text attributes object. - * @param {object} args - either TAttText or see constructor arguments of {@link TAttTextHandler} - * @protected */ - createAttText(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args.fTextFont !== undefined && args.fTextSize !== undefined && args.fTextColor !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#textatt : null; - - if (!handler) - handler = new TAttTextHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#textatt = handler; - return handler; - } - - /** @summary Creates fill attributes object. - * @desc Method dedicated to create fill attributes, bound to canvas SVG - * otherwise newly created patters will not be usable in the canvas - * See {@link TAttFillHandler} for more info. - * Instance assigned as this.fillatt data member, recognized by GED editors - * @param {object} [args] - for special cases one can specify TAttFill as args or number of parameters - * @param {boolean} [args.std = true] - this is standard fill attribute for object and should be used as this.fillatt - * @param {object} [args.attr = null] - object, derived from TAttFill - * @param {number} [args.pattern = undefined] - integer index of fill pattern - * @param {number} [args.color = undefined] - integer index of fill color - * @param {string} [args.color_as_svg = undefined] - color will be specified as SVG string, not as index from color palette - * @param {number} [args.kind = undefined] - some special kind which is handled differently from normal patterns - * @return created handle - * @protected */ - createAttFill(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args._typename && args.fFillColor !== undefined && args.fFillStyle !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#fillatt : null; - - if (!args.svg) - args.svg = this.getCanvSvg(); - - if (!handler) - handler = new TAttFillHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#fillatt = handler; - return handler; - } - - get fillatt() { return this.#fillatt; } - get lineatt() { return this.#lineatt; } - get markeratt() { return this.#markeratt; } - get textatt() { return this.#textatt; } - - /** @summary call function for each painter in the pad - * @desc Iterate over all known painters - * @private */ - forEachPainter(userfunc, kind) { - // iterate over all painters from pad list - let pp = this.getPadPainter(), top = null; - if (!pp) { - top = this.getTopPainter(); - if (isPadPainter(top)) - pp = top; - } - if (pp) - pp.forEachPainterInPad(userfunc, kind); - else if (top && (kind !== 'pads')) - userfunc(top); - } - - /** @summary indicate that redraw was invoked via interactive action (like context menu or zooming) - * @desc Use to catch such action by GED and by server-side - * @return {Promise} when completed - * @private */ - async interactiveRedraw(arg, info, subelem) { - let reason, res; - if (isStr(info) && info.indexOf('exec:')) - reason = info; - - if (arg === 'pad') - res = this.redrawPad(reason); - else if (arg !== false) - res = this.redraw(reason); - - return getPromise(res).then(() => { - if (arg === 'attribute') - return this.getPadPainter()?.redrawLegend(); - }).then(() => { - // inform GED that something changes - const canp = this.getCanvPainter(); - - if (isFunc(canp?.producePadEvent)) - canp.producePadEvent('redraw', this.getPadPainter(), this, null, subelem); - - // inform server that draw options changes - if (isFunc(canp?.processChanges)) - canp.processChanges(info, this, subelem); - - return this; - }); - } - - /** @summary Redraw all objects in the current pad - * @param {string} [reason] - like 'resize' or 'zoom' - * @return {Promise} when pad redraw completed - * @protected */ - async redrawPad(reason) { - return this.getPadPainter()?.redrawPad(reason) ?? false; - } - - /** @summary execute selected menu command, either locally or remotely - * @private */ - executeMenuCommand(method) { - if (method.fName === 'Inspect') - // primitive inspector, keep it here - return this.showInspector(); - - return false; - } - - /** @summary Invoke method for object via WebCanvas functionality - * @desc Requires that painter marked with object identifier (this.#snapid) or identifier provided as second argument - * Canvas painter should exists and in non-readonly mode - * Execution string can look like 'Print()'. - * Many methods call can be chained with 'Print();;Update();;Clear()' - * @private */ - submitCanvExec(exec, snapid) { - if (!exec || !isStr(exec)) - return; - - const canp = this.getCanvPainter(); - if (isFunc(canp?.submitExec)) - canp.submitExec(this, exec, snapid); - } - - /** @summary remove all created draw attributes - * @protected */ - deleteAttr(name) { - if (!name || name === 'line') - this.#lineatt = undefined; - if (!name || name === 'fill') - this.#fillatt = undefined; - if (!name || name === 'marker') - this.#markeratt = undefined; - if (!name || name === 'text') - this.#textatt = undefined; - } - - /** @summary Show object in inspector for provided object - * @protected */ - showInspector(/* opt */) { - return false; - } - - /** @summary Fill context menu for the object - * @private */ - fillContextMenu(menu) { - const cl = this.getClassName(), - name = this.getObjectName(), - p = cl.lastIndexOf('::'), - cl0 = (p > 0) ? cl.slice(p + 2) : cl, - hdr = (cl0 && name) ? `${cl0}:${name}` : (cl0 || name || 'object'), - url = cl ? `${urlClassPrefix}${cl.replaceAll('::', '_1_1')}.html` : ''; - - menu.header(hdr, url); - - const size0 = menu.size(); - - if (isFunc(this.fillContextMenuItems)) - this.fillContextMenuItems(menu); - - if ((menu.size() > size0) && this.showInspector('check')) - menu.add('Inspect', this.showInspector); - - menu.addAttributesMenu(this); - - return menu.size() > size0; - } - - /** @summary shows objects status - * @desc Either used canvas painter method or globally assigned - * When no parameters are specified, just basic object properties are shown - * @private */ - showObjectStatus(name, title, info, info2) { - let cp = this.getCanvPainter(); - - if (!isFunc(cp?.showCanvasStatus)) - cp = null; - - if (!cp && !isFunc(internals.showStatus)) - return false; - - if (this.enlargeMain('state') === 'on') - return false; - - if ((name === undefined) && (title === undefined)) { - const obj = this.getObject(); - if (!obj) - return; - name = this.getItemName() || obj.fName; - title = obj.fTitle || obj._typename; - info = obj._typename; - } - - if (cp) - cp.showCanvasStatus(name, title, info, info2); - else - internals.showStatus(name, title, info, info2); - } - - /** @summary Redraw object - * @desc Basic method, should be reimplemented in all derived objects - * for the case when drawing should be repeated - * @abstract - * @protected */ - redraw(/* reason */) {} - - /** @summary Start text drawing - * @desc required before any text can be drawn - * @param {number} font_face - font id as used in ROOT font attributes - * @param {number} font_size - font size as used in ROOT font attributes - * @param {object} [draw_g] - element where text drawn, by default using main object element - * @param {number} [max_font_size] - maximal font size, used when text can be scaled - * @protected */ - startTextDrawing(font_face, font_size, draw_g, max_font_size, can_async) { - if (!draw_g) - draw_g = this.getG(); - if (!draw_g || draw_g.empty()) - return false; - - const font = (font_size === 'font') ? font_face : new FontHandler(font_face, font_size); - if (can_async && font.needLoad()) - return font; - - font.setPainter(this); // may be required when custom font is used - - draw_g.call(font.func); - - draw_g.property('draw_text_completed', false) // indicate that draw operations submitted - .property('all_args', []) // array of all submitted args, makes easier to analyze them - .property('text_font', font) - .property('text_factor', 0) - .property('max_text_width', 0) // keep maximal text width, use it later - .property('max_font_size', max_font_size) - .property('_fast_drawing', this.getPadPainter()?.isFastDrawing() ?? false); - - if (draw_g.property('_fast_drawing')) - draw_g.property('_font_too_small', (max_font_size && (max_font_size < 5)) || (font.size < 4)); - - return true; - } - - /** @summary Start async text drawing - * @return {Promise} for loading of font if necessary - * @private */ - async startTextDrawingAsync(font_face, font_size, draw_g, max_font_size) { - const font = this.startTextDrawing(font_face, font_size, draw_g, max_font_size, true); - if ((font === true) || (font === false)) - return font; - return font.load().then(res => { - if (!res) - return false; - - return this.startTextDrawing(font, 'font', draw_g, max_font_size); - }); - } - - /** @summary Apply scaling factor to all drawn text in the element - * @desc Can be applied at any time before finishTextDrawing is called - even in the postprocess callbacks of text draw - * @param {number} factor - scaling factor - * @param {object} [draw_g] - drawing element for the text - * @protected */ - scaleTextDrawing(factor, draw_g) { - if (!draw_g) - draw_g = this.getG(); - if (!draw_g || draw_g.empty()) - return; - if (factor && (factor > draw_g.property('text_factor'))) - draw_g.property('text_factor', factor); - } - - /** @summary Analyze if all text draw operations are completed - * @private */ - #checkAllTextDrawing(draw_g, resolveFunc, try_optimize) { - const all_args = draw_g.property('all_args') || []; - let missing = 0; - - all_args.forEach(arg => { - if (!arg.ready) - missing++; - }); - - if (missing > 0) { - if (isFunc(resolveFunc)) { - draw_g.node().textResolveFunc = resolveFunc; - draw_g.node().try_optimize = try_optimize; - } - return; - } - - draw_g.property('all_args', null); // clear all_args property - - // adjust font size (if there are normal text) - const f = draw_g.property('text_factor'), - font = draw_g.property('text_font'), - max_sz = draw_g.property('max_font_size'); - let font_size = font.size, any_text = false, only_text = true; - - if ((f > 0) && ((f < 0.95) || (f > 1.05))) - font.size = Math.max(1, Math.floor(font.size / f)); - - if (max_sz && (font.size > max_sz)) - font.size = max_sz; - - if (font.size !== font_size) { - draw_g.call(font.func); - font_size = font.size; - } - - all_args.forEach(arg => { - if (arg.mj_node && arg.mj_func) { - const svg = arg.mj_node.select('svg'); // MathJax svg - arg.mj_func(this, arg.mj_node, svg, arg, font_size, f); - delete arg.mj_node; // remove reference - only_text = false; - } else if (arg.txt_g) - only_text = false; - }); - - if (!resolveFunc) { - resolveFunc = draw_g.node().textResolveFunc; - try_optimize = draw_g.node().try_optimize; - delete draw_g.node().textResolveFunc; - delete draw_g.node().try_optimize; - } - - const optimize_arr = (try_optimize && only_text) ? [] : null; - - // now process text and latex drawings - all_args.forEach(arg => { - let txt, is_txt, scale = 1; - if (arg.txt_node) { - txt = arg.txt_node; - delete arg.txt_node; - is_txt = true; - if (optimize_arr !== null) - optimize_arr.push(txt); - } else if (arg.txt_g) { - txt = arg.txt_g; - delete arg.txt_g; - is_txt = false; - } else - return; - - txt.attr('visibility', null); - - any_text = true; - - if (arg.width) { - // adjust x position when scale into specified rectangle - if (arg.align[0] === 'middle') - arg.x += arg.width / 2; - else if (arg.align[0] === 'end') - arg.x += arg.width; - } - - if (arg.height) { - if (arg.align[1].indexOf('bottom') === 0) - arg.y += arg.height; - else if (arg.align[1] === 'middle') - arg.y += arg.height / 2; - } - - let dx = 0, dy = 0; - - if (is_txt) { - // handle simple text drawing - - if (isNodeJs()) { - if (arg.scale && (f > 0)) { - arg.box.width *= 1 / f; - arg.box.height *= 1 / f; - } - } else if (!arg.plain && !arg.fast) { - // exact box dimension only required when complex text was build - arg.box = getElementRect(txt, 'bbox'); - } - - if (arg.plain) { - txt.attr('text-anchor', arg.align[0]); - if (arg.align[1] === 'top') - txt.attr('dy', '.8em'); - else if (arg.align[1] === 'middle') { - // if (isNodeJs()) txt.attr('dy', '.4em'); else // old workaround for node.js - txt.attr('dominant-baseline', 'middle'); - } - } else { - txt.attr('text-anchor', 'start'); - dx = ((arg.align[0] === 'middle') ? -0.5 : ((arg.align[0] === 'end') ? -1 : 0)) * arg.box.width; - dy = ((arg.align[1] === 'top') ? (arg.top_shift || 1) : (arg.align[1] === 'middle') ? (arg.mid_shift || 0.5) : 0) * arg.box.height; - } - } else if (arg.text_rect) { - // handle latex drawing - const box = arg.text_rect; - - scale = (f > 0) && (Math.abs(1 - f) > 0.01) ? 1 / f : 1; - - dx = ((arg.align[0] === 'middle') ? -0.5 : ((arg.align[0] === 'end') ? -1 : 0)) * box.width * scale; - - if (arg.align[1] === 'top') - dy = -box.y1 * scale; - else if (arg.align[1] === 'bottom') - dy = -box.y2 * scale; - else if (arg.align[1] === 'middle') - dy = -0.5 * (box.y1 + box.y2) * scale; - } else - console.error('text rect not calcualted - please check code'); - - if (!arg.rotate) { - arg.x += dx; - arg.y += dy; - dx = dy = 0; - } - - // use translate and then rotate to avoid complex sign calculations - let trans = makeTranslate(Math.round(arg.x), Math.round(arg.y)) || ''; - const dtrans = makeTranslate(Math.round(dx), Math.round(dy)), - append = aaa => { - if (trans) - trans += ' '; - trans += aaa; - }; - - if (arg.rotate) - append(`rotate(${Math.round(arg.rotate)})`); - if (scale !== 1) - append(`scale(${scale.toFixed(3)})`); - if (dtrans) - append(dtrans); - if (trans) - txt.attr('transform', trans); - }); - - - // when no any normal text drawn - remove font attributes - if (!any_text) - font.clearFont(draw_g); - - if ((optimize_arr !== null) && (optimize_arr.length > 1)) { - ['fill', 'text-anchor'].forEach(name => { - let first = optimize_arr[0].attr(name); - optimize_arr.forEach(txt_node => { - const value = txt_node.attr(name); - if (!value || (value !== first)) - first = undefined; - }); - if (first) { - draw_g.attr(name, first); - optimize_arr.forEach(txt_node => { txt_node.attr(name, null); }); - } - }); - } - - // if specified, call resolve function - if (resolveFunc) - resolveFunc(this); // IMPORTANT - return painter, may use in draw methods - } - - /** @summary Post-process plain text drawing - * @private */ - #postprocessDrawText(arg, txt_node) { - // complete rectangle with very rough size estimations - arg.box = !isNodeJs() && !settings.ApproxTextSize && !arg.fast - ? getElementRect(txt_node, 'bbox') - : (arg.text_rect || { height: Math.round(1.15 * arg.font_size), width: approximateLabelWidth(arg.text, arg.font, arg.font_size) }); - - txt_node.attr('visibility', 'hidden'); // hide elements until text drawing is finished - - if (arg.box.width > arg.draw_g.property('max_text_width')) - arg.draw_g.property('max_text_width', arg.box.width); - if (arg.scale) - this.scaleTextDrawing(Math.max(1.05 * arg.box.width / arg.width, arg.box.height / arg.height), arg.draw_g); - - arg.result_width = arg.box.width; - arg.result_height = arg.box.height; - - if (isFunc(arg.post_process)) - arg.post_process(this); - - return arg.box.width; - } - - /** @summary Draw text - * @desc The only legal way to draw text, support plain, latex and math text output - * @param {object} arg - different text draw options - * @param {string} arg.text - text to draw - * @param {number|string} [arg.align = 12] - int value like 12 or 31 or string 'end;bottom' - * @param {number} [arg.x = 0] - x position - * @param {number} [arg.y = 0] - y position - * @param {number} [arg.width] - when specified, adjust font size in the specified box - * @param {number} [arg.height] - when specified, adjust font size in the specified box - * @param {boolean} [arg.scale = true] - scale into draw box when width and height parameters are specified - * @param {number} [arg.latex] - 0 - plain text, 1 - normal TLatex, 2 - math - * @param {string} [arg.color=black] - text color - * @param {number} [arg.rotate] - rotation angle - * @param {number} [arg.font_size] - fixed font size - * @param {object} [arg.draw_g] - element where to place text, if not specified central draw_g container is used - * @param {Function} [arg.post_process] - optional function called when specified text is drawn - * @protected */ - drawText(arg) { - if (!arg.text) - arg.text = ''; - - arg.draw_g = arg.draw_g || this.getG(); - if (!arg.draw_g || arg.draw_g.empty()) - return; - - const font = arg.draw_g.property('text_font'); - arg.font = font; // use in latex conversion - - if (font) { - arg.color = arg.color || font.color; - arg.align = arg.align || font.align; - arg.rotate = arg.rotate || font.angle; - } - - let align = ['start', 'middle']; - - if (isStr(arg.align)) { - align = arg.align.split(';'); - if (align.length === 1) - align.push('middle'); - } else if (typeof arg.align === 'number') { - if ((arg.align / 10) >= 3) - align[0] = 'end'; - else if ((arg.align / 10) >= 2) - align[0] = 'middle'; - if ((arg.align % 10) === 0) - align[1] = 'bottom'; - else if ((arg.align % 10) === 1) - align[1] = 'bottom-base'; - else if ((arg.align % 10) === 3) - align[1] = 'top'; - } else if (isObject(arg.align) && (arg.align.length === 2)) - align = arg.align; - - if (arg.latex === undefined) - arg.latex = 1; // 0: text, 1: latex, 2: math - arg.align = align; - arg.x = arg.x || 0; - arg.y = arg.y || 0; - if (arg.scale !== false) - arg.scale = arg.width && arg.height && !arg.font_size; - arg.width = arg.width || 0; - arg.height = arg.height || 0; - - if (arg.draw_g.property('_fast_drawing')) { - if (arg.scale) { - // area too small - ignore such drawing - if (arg.height < 4) - return 0; - } else if (arg.font_size) { - // font size too small - if (arg.font_size < 4) - return 0; - } else if (arg.draw_g.property('_font_too_small')) { - // configure font is too small - ignore drawing - return 0; - } - } - - // include drawing into list of all args - arg.draw_g.property('all_args').push(arg); - arg.ready = false; // indicates if drawing is ready for post-processing - - let use_mathjax = (arg.latex === 2); - const cl = constants$1.Latex; - - if (arg.latex === 1) { - use_mathjax = (settings.Latex === cl.AlwaysMathJax) || - ((settings.Latex === cl.MathJax) && arg.text.match(/[#{\\]/g)) || - arg.text.match(/[\\]/g); - } - - if (!use_mathjax || arg.nomathjax) { - arg.txt_node = arg.draw_g.append('svg:text'); - - if (arg.color) - arg.txt_node.attr('fill', arg.color); - - if (arg.font_size) - arg.txt_node.attr('font-size', arg.font_size); - else - arg.font_size = font.size; - - arg.plain = !arg.latex || (settings.Latex === cl.Off) || (settings.Latex === cl.Symbols); - - arg.simple_latex = arg.latex && (settings.Latex === cl.Symbols); - - if (!arg.plain || arg.simple_latex || arg.font?.isSymbol) { - if (arg.simple_latex || isPlainText(arg.text) || arg.plain) { - arg.simple_latex = true; - producePlainText(this, arg.txt_node, arg); - } else { - arg.txt_node.remove(); // just remove text node - delete arg.txt_node; - arg.txt_g = arg.draw_g.append('svg:g'); - produceLatex(this, arg.txt_g, arg); - } - arg.ready = true; - this.#postprocessDrawText(arg, arg.txt_g || arg.txt_node); - - if (arg.draw_g.property('draw_text_completed')) - this.#checkAllTextDrawing(arg.draw_g); // check if all other elements are completed - return 0; - } - - arg.plain = true; - arg.txt_node.text(arg.text); - arg.ready = true; - - return this.#postprocessDrawText(arg, arg.txt_node); - } - - arg.mj_node = arg.draw_g.append('svg:g').attr('visibility', 'hidden'); // hide text until drawing is finished - - produceMathjax(this, arg.mj_node, arg).then(() => { - arg.ready = true; - if (arg.draw_g.property('draw_text_completed')) - this.#checkAllTextDrawing(arg.draw_g); - }); - - return 0; - } - - /** @summary Finish text drawing - * @desc Should be called to complete all text drawing operations - * @param {Function} [draw_g] - element for text drawing, default is getG() - * @return {Promise} when text drawing completed - * @protected */ - async finishTextDrawing(draw_g, try_optimize) { - if (!draw_g) - draw_g = this.getG(); - if (!draw_g || draw_g.empty()) - return false; - - draw_g.property('draw_text_completed', true); // mark that text drawing is completed - - return new Promise(resolveFunc => { - this.#checkAllTextDrawing(draw_g, resolveFunc, try_optimize); - }); - } - - /** @summary Configure user-defined context menu for the object - * @desc fillmenu_func will be called when context menu is activated - * Arguments fillmenu_func are (menu,kind) - * First is menu object, second is object sub-element like axis 'x' or 'y' - * Function should return promise with menu when items are filled - * @param {Function} fillmenu_func - function to fill custom context menu for object */ - configureUserContextMenu(fillmenu_func) { - this.#user_context_menu = isFunc(fillmenu_func) ? fillmenu_func : undefined; - } - - /** @summary Fill object menu in web canvas - * @private */ - async fillObjectExecMenu(menu, kind) { - if (isFunc(this.#user_context_menu)) - return this.#user_context_menu(menu, kind); - - const canvp = this.getCanvPainter(); - - if (!this.getSnapId() || !canvp || canvp?.isReadonly() || !canvp?.getWebsocket()) - return menu; - - function doExecMenu(arg) { - const execp = menu.exec_painter || this, - cp = execp.getCanvPainter(), - item = menu.exec_items[parseInt(arg)]; - - if (!item?.fName) - return; - - // this is special entry, produced by TWebMenuItem, which recognizes editor entries itself - if (item.fExec === 'Show:Editor') { - if (isFunc(cp?.activateGed)) - cp.activateGed(execp); - return; - } - - if (isFunc(cp?.executeObjectMethod) && cp.executeObjectMethod(execp, item, item.$execid)) - return; - - item.fClassName = execp.getClassName(); - if ((item.$execid.indexOf('#x') > 0) || (item.$execid.indexOf('#y') > 0) || (item.$execid.indexOf('#z') > 0)) - item.fClassName = clTAxis; - - if (execp.executeMenuCommand(item)) - return; - - if (!item.$execid) - return; - - if (!item.fArgs) { - return cp?.v7canvas ? cp.submitExec(execp, item.fExec, kind) - : execp.submitCanvExec(item.fExec, item.$execid); - } - - menu.showMethodArgsDialog(item).then(args => { - if (!args || execp.executeMenuCommand(item, args)) - return; - - const exec = item.fExec.slice(0, item.fExec.length - 1) + args + ')'; - if (cp?.v7canvas) - cp.submitExec(execp, exec, kind); - else - cp?.sendWebsocket(`OBJEXEC:${item.$execid}:${exec}`); - }); - } - - const doFillMenu = (_menu, _reqid, _resolveFunc, reply) => { - // avoid multiple call of the callback after timeout - if (menu._got_menu) - return; - menu._got_menu = true; - - if (reply && (_reqid !== reply.fId)) - console.error(`missmatch between request ${_reqid} and reply ${reply.fId} identifiers`); - - menu.exec_items = reply?.fItems; - - if (menu.exec_items?.length) { - if (_menu.size() > 0) - _menu.separator(); - - let lastclname; - - for (let n = 0; n < menu.exec_items.length; ++n) { - const item = menu.exec_items[n]; - item.$execid = reply.fId; - item.$menu = menu; - - if (item.fClassName && lastclname && (lastclname !== item.fClassName)) { - _menu.endsub(); - lastclname = ''; - } - if (lastclname !== item.fClassName) { - lastclname = item.fClassName; - const p = lastclname.lastIndexOf('::'), - shortname = (p > 0) ? lastclname.slice(p + 2) : lastclname; - - _menu.sub(shortname.replace(/[<>]/g, '_')); - } - - if ((item.fChecked === undefined) || (item.fChecked < 0)) - _menu.add(item.fName, n, doExecMenu); - else - _menu.addchk(item.fChecked, item.fName, n, doExecMenu); - } - - if (lastclname) - _menu.endsub(); - } - - _resolveFunc(_menu); - }, - reqid = this.getSnapId(kind); - - menu._got_menu = false; - - // if menu painter differs from this, remember it for further usage - if (menu.painter) - menu.exec_painter = (menu.painter !== this) ? this : undefined; - - return new Promise(resolveFunc => { - let did_resolve = false; - - function handleResolve(res) { - if (did_resolve) - return; - did_resolve = true; - resolveFunc(res); - } - - // set timeout to avoid menu hanging - setTimeout(() => doFillMenu(menu, reqid, handleResolve), 2000); - - canvp.submitMenuRequest(this, kind, reqid).then(lst => doFillMenu(menu, reqid, handleResolve, lst)); - }); - } - - /** @summary Configure user-defined tooltip handler - * @desc Hook for the users to get tooltip information when mouse cursor moves over then object - * Handler function will be called every time when new data is selected - * @param {Function} handler - function called when tooltip is produced - * @param {number} [tmout = 100] - delay in ms before tooltip delivered */ - configureUserTooltipHandler(handler, tmout = 100) { - if (!handler || !isFunc(handler)) { - this.#user_tooltip_handler = undefined; - this.#user_tooltip_timeout = undefined; - } else { - this.#user_tooltip_handler = handler; - this.#user_tooltip_timeout = tmout; - } - } - - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was performed - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of click will be disabled - * @param {Function} handler - function called when mouse click is done */ - configureUserClickHandler(handler) { - const fp = this.getFramePainter(); - if (isFunc(fp?.configureUserClickHandler)) - fp.configureUserClickHandler(handler); - } - - /** @summary Configure user-defined dblclick handler - * @desc Function will be called every time when double click was called - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of dblclick (unzoom) will be disabled - * @param {Function} handler - function called when mouse double click is done */ - configureUserDblclickHandler(handler) { - const fp = this.getFramePainter(); - if (isFunc(fp?.configureUserDblclickHandler)) - fp.configureUserDblclickHandler(handler); - } - - /** @summary Check if user-defined tooltip function was configured - * @return {boolean} flag is user tooltip handler was configured */ - hasUserTooltip() { - return isFunc(this.#user_tooltip_handler); - } - - /** @summary Provide tooltips data to user-defined function - * @param {object} data - tooltip data - * @private */ - provideUserTooltip(data) { - if (!this.hasUserTooltip()) - return; - - if (this.#user_tooltip_timeout <= 0) - return this.#user_tooltip_handler(data); - - if (this.#user_tooltip_timerid) { - clearTimeout(this.#user_tooltip_timerid); - this.#user_tooltip_timerid = undefined; - } - - if (!data) - return this.#user_tooltip_handler(data); - - // only after timeout user function will be called - this.#user_tooltip_timerid = setTimeout(() => { - this.#user_tooltip_timerid = undefined; - if (this.#user_tooltip_handler) - this.#user_tooltip_handler(data); - }, this.#user_tooltip_timeout); - } - - /** @summary Provide projection areas - * @param {string} kind - 'X', 'Y', 'XY' or '' - * @private */ - async provideSpecialDrawArea(kind) { - if (kind === this.#special_draw_area) - return true; - - return this.getCanvPainter().toggleProjection(kind).then(() => { - this.#special_draw_area = kind; - return true; - }); - } - - /** @summary Draw in special projection areas - * @param {object} obj - object to draw - * @param {string} opt - draw option - * @param {string} kind - '', 'X', 'Y' - * @private */ - async drawInSpecialArea(obj, opt, kind) { - const canp = this.getCanvPainter(); - if (this.#special_draw_area && isFunc(canp?.drawProjection)) - return canp.drawProjection(kind || this.#special_draw_area, obj, opt); - - return false; - } - - /** @summary Get tooltip for painter and specified event position - * @param {object} evnt - object with clientX and clientY positions - * @private */ - getToolTip(evnt) { - if ((evnt?.clientX === undefined) || (evnt?.clientY === undefined)) - return null; - - const frame = this.getPadPainter()?.getFrameSvg(); - if (!frame || frame.empty()) - return null; - const layer = frame.selectChild('.main_layer'); - if (layer.empty()) - return null; - - const pos = pointer(evnt, layer.node()), - pnt = { touch: false, x: pos[0], y: pos[1] }; - - if (isFunc(this.extractToolTip)) - return this.extractToolTip(pnt); - - pnt.disabled = true; - - const res = isFunc(this.processTooltipEvent) ? this.processTooltipEvent(pnt) : null; - - return res?.user_info || res; - } - -} // class ObjectPainter - - -/** @summary Generic text drawing - * @private */ -function drawRawText(dom, txt /* , opt */) { - const painter = new BasePainter(dom); - painter.txt = txt; - - painter.redrawObject = async function(obj) { - this.txt = obj; - return this.drawText(); - }; - - painter.drawText = async function() { - let stxt = (this.txt._typename === clTObjString) ? this.txt.fString : this.txt.value; - if (!isStr(stxt)) - stxt = ''; - - const mathjax = this.txt.mathjax || (settings.Latex === constants$1.Latex.AlwaysMathJax), - frame = this.selectDom(); - let main = frame.select('div'); - if (main.empty()) - main = frame.append('div').attr('style', 'max-width:100%;max-height:100%;overflow:auto'); - else - main.html(''); - - // (re) set painter to first child element, base painter not requires canvas - this.setTopPainter(); - - if (!mathjax && !('as_is' in this.txt)) { - const arr = stxt.split('\n'); - for (let i = 0; i < arr.length; ++i) - main.append('pre').style('margin', '0').text(arr[i]); - } else - main.text(stxt); - - if (mathjax) - typesetMathjax(frame.node()); - - return this; - }; - - return painter.drawText(); -} - -/** @summary Returns canvas painter (if any) for specified DOM element - * @param {string|object} dom - id or DOM element or pad painter - * @private */ -function getElementCanvPainter(dom) { - return isPadPainter(dom) ? dom.getCanvPainter() : getDomCanvasPainter(new ObjectPainter(dom).selectDom()); -} - -/** @summary Returns pad painter (if any) for specified DOM element - * @param {string|object} dom - id or DOM element or pad painter - * @private */ -function getElementPadPainter(dom) { - return isPadPainter(dom) ? dom : new ObjectPainter(dom).getPadPainter(); -} - -/** @summary Returns main painter (if any) for specified HTML element - typically histogram painter - * @param {string|object} dom - id or DOM element or pad painter - * @private */ -function getElementMainPainter(dom) { - return isPadPainter(dom) ? dom.getMainPainter() : new ObjectPainter(dom).getMainPainter(true); -} - -/** @summary Save object, drawn in specified element, as JSON. - * @desc Normally it is TCanvas object with list of primitives - * @param {string|object} dom - id of top div element or directly DOMElement - * @return {string} produced JSON string */ -function drawingJSON(dom) { - return getElementCanvPainter(dom)?.produceJSON() || ''; -} - -let $active_pp = null; - -/** @summary Set active pad painter - * @desc Normally be used to handle key press events, which are global in the web browser - * @param {object} args - functions arguments - * @param {object} args.pp - pad painter - * @param {boolean} [args.active] - is pad activated or not - * @private */ -function selectActivePad(args) { - if (args.active) { - $active_pp?.getFramePainter()?.setFrameActive(false); - $active_pp = args.pp; - $active_pp?.getFramePainter()?.setFrameActive(true); - } else if ($active_pp === args.pp) - $active_pp = null; -} - -/** @summary Returns current active pad - * @desc Should be used only for keyboard handling - * @private */ -function getActivePad() { - return $active_pp; -} - -/** @summary Check resize of drawn element - * @param {string|object} dom - id or DOM element - * @param {boolean|object} arg - options on how to resize - * @desc As first argument dom one should use same argument as for the drawing - * As second argument, one could specify 'true' value to force redrawing of - * the element even after minimal resize - * Or one just supply object with exact sizes like { width:300, height:200, force:true }; - * @example - * import { resize } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; - * resize('drawing', { width: 500, height: 200 }); - * resize(document.querySelector('#drawing'), true); */ -function resize(dom, arg) { - if (arg === true) - arg = { force: true }; - else if (!isObject(arg)) - arg = null; - let done = false; - new ObjectPainter(dom).forEachPainter(painter => { - if (!done && isFunc(painter.checkResize)) - done = painter.checkResize(arg); - }); - return done; -} - - -/** @summary Safely remove all drawings from specified element - * @param {string|object} dom - id or DOM element - * @public - * @example - * import { cleanup } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; - * cleanup('drawing'); - * cleanup(document.querySelector('#drawing')); */ -function cleanup(dom) { - const dummy = new ObjectPainter(dom), lst = []; - dummy.forEachPainter(p => { - if (lst.indexOf(p) < 0) - lst.push(p); - }); - lst.forEach(p => p.cleanup()); - dummy.selectDom().html(''); - return lst; -} - -const EAxisBits = { - kDecimals: BIT(7), - kTickPlus: BIT(9), - kTickMinus: BIT(10), - kAxisRange: BIT(11), - kCenterTitle: BIT(12), - kCenterLabels: BIT(14), - kRotateTitle: BIT(15), - kPalette: BIT(16), - kNoExponent: BIT(17), - kLabelsHori: BIT(18), - kLabelsVert: BIT(19), - kLabelsDown: BIT(20), - kLabelsUp: BIT(21), - kIsInteger: BIT(22), - kMoreLogLabels: BIT(23), - kOppositeTitle: BIT(32) // artificial bit, not possible to set in ROOT -}, kAxisLabels = 'labels', kAxisNormal = 'normal', kAxisFunc = 'func', kAxisTime = 'time'; - -Object.assign(internals.jsroot, { ObjectPainter, cleanup, resize }); - -/** - * @license - * Copyright 2010-2025 Three.js Authors - * SPDX-License-Identifier: MIT - */ -const REVISION = '186'; - -/** - * Represents mouse buttons and interaction types in context of controls. - * - * @type {ConstantsMouse} - * @constant - */ -const MOUSE = { ROTATE: 0, DOLLY: 1, PAN: 2 }; - -/** - * Represents touch interaction types in context of controls. - * - * @type {ConstantsTouch} - * @constant - */ -const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; - -/** - * Disables face culling. - * - * @type {number} - * @constant - */ -const CullFaceNone = 0; - -/** - * Culls back faces. - * - * @type {number} - * @constant - */ -const CullFaceBack = 1; - -/** - * Culls front faces. - * - * @type {number} - * @constant - */ -const CullFaceFront = 2; - -/** - * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm. - * - * @type {number} - * @constant - */ -const PCFShadowMap = 1; - -/** - * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm with - * better soft shadows especially when using low-resolution shadow maps. - * - * @type {number} - * @constant - * @deprecated since r186. Use `PCFShadowMap` instead. - */ -const PCFSoftShadowMap = 2; - -/** - * Filters shadow maps using the Variance Shadow Map (VSM) algorithm. - * When using VSMShadowMap all shadow receivers will also cast shadows. - * - * @type {number} - * @constant - */ -const VSMShadowMap = 3; - -/** - * Only front faces are rendered. - * - * @type {number} - * @constant - */ -const FrontSide = 0; - -/** - * Only back faces are rendered. - * - * @type {number} - * @constant - */ -const BackSide = 1; - -/** - * Both front and back faces are rendered. - * - * @type {number} - * @constant - */ -const DoubleSide = 2; - -/** - * No blending is performed which effectively disables - * alpha transparency. - * - * @type {number} - * @constant - */ -const NoBlending = 0; - -/** - * The default blending. - * - * @type {number} - * @constant - */ -const NormalBlending = 1; - -/** - * Represents additive blending. - * - * @type {number} - * @constant - */ -const AdditiveBlending = 2; - -/** - * Represents subtractive blending. - * - * @type {number} - * @constant - */ -const SubtractiveBlending = 3; - -/** - * Represents multiply blending. - * - * @type {number} - * @constant - */ -const MultiplyBlending = 4; - -/** - * Represents custom blending. - * - * @type {number} - * @constant - */ -const CustomBlending = 5; - -/** - * A `source + destination` blending equation. - * - * @type {number} - * @constant - */ -const AddEquation = 100; - -/** - * A `source - destination` blending equation. - * - * @type {number} - * @constant - */ -const SubtractEquation = 101; - -/** - * A `destination - source` blending equation. - * - * @type {number} - * @constant - */ -const ReverseSubtractEquation = 102; - -/** - * A blend equation that uses the minimum of source and destination. - * - * @type {number} - * @constant - */ -const MinEquation = 103; - -/** - * A blend equation that uses the maximum of source and destination. - * - * @type {number} - * @constant - */ -const MaxEquation = 104; - -/** - * Multiplies all colors by `0`. - * - * @type {number} - * @constant - */ -const ZeroFactor = 200; - -/** - * Multiplies all colors by `1`. - * - * @type {number} - * @constant - */ -const OneFactor = 201; - -/** - * Multiplies all colors by the source colors. - * - * @type {number} - * @constant - */ -const SrcColorFactor = 202; - -/** - * Multiplies all colors by `1` minus each source color. - * - * @type {number} - * @constant - */ -const OneMinusSrcColorFactor = 203; - -/** - * Multiplies all colors by the source alpha value. - * - * @type {number} - * @constant - */ -const SrcAlphaFactor = 204; - -/** - * Multiplies all colors by 1 minus the source alpha value. - * - * @type {number} - * @constant - */ -const OneMinusSrcAlphaFactor = 205; - -/** - * Multiplies all colors by the destination alpha value. - * - * @type {number} - * @constant - */ -const DstAlphaFactor = 206; - -/** - * Multiplies all colors by `1` minus the destination alpha value. - * - * @type {number} - * @constant - */ -const OneMinusDstAlphaFactor = 207; - -/** - * Multiplies all colors by the destination color. - * - * @type {number} - * @constant - */ -const DstColorFactor = 208; - -/** - * Multiplies all colors by `1` minus each destination color. - * - * @type {number} - * @constant - */ -const OneMinusDstColorFactor = 209; - -/** - * Multiplies the RGB colors by the smaller of either the source alpha - * value or the value of `1` minus the destination alpha value. The alpha - * value is multiplied by `1`. - * - * @type {number} - * @constant - */ -const SrcAlphaSaturateFactor = 210; - -/** - * Multiplies all colors by a constant color. - * - * @type {number} - * @constant - */ -const ConstantColorFactor = 211; - -/** - * Multiplies all colors by `1` minus a constant color. - * - * @type {number} - * @constant - */ -const OneMinusConstantColorFactor = 212; - -/** - * Multiplies all colors by a constant alpha value. - * - * @type {number} - * @constant - */ -const ConstantAlphaFactor = 213; - -/** - * Multiplies all colors by 1 minus a constant alpha value. - * - * @type {number} - * @constant - */ -const OneMinusConstantAlphaFactor = 214; - -/** - * Never pass. - * - * @type {number} - * @constant - */ -const NeverDepth = 0; - -/** - * Always pass. - * - * @type {number} - * @constant - */ -const AlwaysDepth = 1; - -/** - * Pass if the incoming value is less than the depth buffer value. - * - * @type {number} - * @constant - */ -const LessDepth = 2; - -/** - * Pass if the incoming value is less than or equal to the depth buffer value. - * - * @type {number} - * @constant - */ -const LessEqualDepth = 3; - -/** - * Pass if the incoming value equals the depth buffer value. - * - * @type {number} - * @constant - */ -const EqualDepth = 4; - -/** - * Pass if the incoming value is greater than or equal to the depth buffer value. - * - * @type {number} - * @constant - */ -const GreaterEqualDepth = 5; - -/** - * Pass if the incoming value is greater than the depth buffer value. - * - * @type {number} - * @constant - */ -const GreaterDepth = 6; - -/** - * Pass if the incoming value is not equal to the depth buffer value. - * - * @type {number} - * @constant - */ -const NotEqualDepth = 7; - -/** - * Multiplies the environment map color with the surface color. - * - * @type {number} - * @constant - */ -const MultiplyOperation = 0; - -/** - * Uses reflectivity to blend between the two colors. - * - * @type {number} - * @constant - */ -const MixOperation = 1; - -/** - * Adds the two colors. - * - * @type {number} - * @constant - */ -const AddOperation = 2; - -/** - * No tone mapping is applied. - * - * @type {number} - * @constant - */ -const NoToneMapping = 0; - -/** - * Linear tone mapping. - * - * @type {number} - * @constant - */ -const LinearToneMapping = 1; - -/** - * Reinhard tone mapping. - * - * @type {number} - * @constant - */ -const ReinhardToneMapping = 2; - -/** - * Cineon tone mapping. - * - * @type {number} - * @constant - */ -const CineonToneMapping = 3; - -/** - * ACES Filmic tone mapping. - * - * @type {number} - * @constant - */ -const ACESFilmicToneMapping = 4; - -/** - * Custom tone mapping. - * - * Expects a custom implementation by modifying shader code of the material's fragment shader. - * - * @type {number} - * @constant - */ -const CustomToneMapping = 5; - -/** - * AgX tone mapping. - * - * @type {number} - * @constant - */ -const AgXToneMapping = 6; - -/** - * Neutral tone mapping. - * - * Implementation based on the Khronos 3D Commerce Group standard tone mapping. - * - * @type {number} - * @constant - */ -const NeutralToneMapping = 7; - -/** - * Maps textures using the geometry's UV coordinates. - * - * @type {number} - * @constant - */ -const UVMapping = 300; - -/** - * Reflection mapping for cube textures. - * - * @type {number} - * @constant - */ -const CubeReflectionMapping = 301; - -/** - * Refraction mapping for cube textures. - * - * @type {number} - * @constant - */ -const CubeRefractionMapping = 302; - -/** - * Reflection mapping for equirectangular textures. - * - * @type {number} - * @constant - */ -const EquirectangularReflectionMapping = 303; - -/** - * Refraction mapping for equirectangular textures. - * - * @type {number} - * @constant - */ -const EquirectangularRefractionMapping = 304; - -/** - * Reflection mapping for PMREM textures. - * - * @type {number} - * @constant - */ -const CubeUVReflectionMapping = 306; - -/** - * The texture will simply repeat to infinity. - * - * @type {number} - * @constant - */ -const RepeatWrapping = 1000; - -/** - * The last pixel of the texture stretches to the edge of the mesh. - * - * @type {number} - * @constant - */ -const ClampToEdgeWrapping = 1001; - -/** - * The texture will repeats to infinity, mirroring on each repeat. - * - * @type {number} - * @constant - */ -const MirroredRepeatWrapping = 1002; - -/** - * Returns the value of the texture element that is nearest (in Manhattan distance) - * to the specified texture coordinates. - * - * @type {number} - * @constant - */ -const NearestFilter = 1003; - -/** - * Chooses the mipmap that most closely matches the size of the pixel being textured - * and uses the `NearestFilter` criterion (the texel nearest to the center of the pixel) - * to produce a texture value. - * - * @type {number} - * @constant - */ -const NearestMipmapNearestFilter = 1004; - -/** - * Chooses the two mipmaps that most closely match the size of the pixel being textured and - * uses the `NearestFilter` criterion to produce a texture value from each mipmap. - * The final texture value is a weighted average of those two values. - * - * @type {number} - * @constant - */ -const NearestMipmapLinearFilter = 1005; - -/** - * Returns the weighted average of the four texture elements that are closest to the specified - * texture coordinates, and can include items wrapped or repeated from other parts of a texture, - * depending on the values of `wrapS` and `wrapT`, and on the exact mapping. - * - * @type {number} - * @constant - */ -const LinearFilter = 1006; - -/** - * Chooses the mipmap that most closely matches the size of the pixel being textured and uses - * the `LinearFilter` criterion (a weighted average of the four texels that are closest to the - * center of the pixel) to produce a texture value. - * - * @type {number} - * @constant - */ -const LinearMipmapNearestFilter = 1007; - -/** - * Chooses the two mipmaps that most closely match the size of the pixel being textured and uses - * the `LinearFilter` criterion to produce a texture value from each mipmap. The final texture value - * is a weighted average of those two values. - * - * @type {number} - * @constant - */ -const LinearMipmapLinearFilter = 1008; - -/** - * An unsigned byte data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedByteType = 1009; - -/** - * A byte data type for textures. - * - * @type {number} - * @constant - */ -const ByteType = 1010; - -/** - * A short data type for textures. - * - * @type {number} - * @constant - */ -const ShortType = 1011; - -/** - * An unsigned short data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedShortType = 1012; - -/** - * An int data type for textures. - * - * @type {number} - * @constant - */ -const IntType = 1013; - -/** - * An unsigned int data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedIntType = 1014; - -/** - * A float data type for textures. - * - * @type {number} - * @constant - */ -const FloatType = 1015; - -/** - * A half float data type for textures. - * - * @type {number} - * @constant - */ -const HalfFloatType = 1016; - -/** - * An unsigned short 4_4_4_4 (packed) data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedShort4444Type = 1017; - -/** - * An unsigned short 5_5_5_1 (packed) data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedShort5551Type = 1018; - -/** - * An unsigned int 24_8 data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedInt248Type = 1020; - -/** - * An unsigned int 5_9_9_9 (packed) data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedInt5999Type = 35902; - -/** - * An unsigned int 10_11_11 (packed) data type for textures. - * - * @type {number} - * @constant - */ -const UnsignedInt101111Type = 35899; - -/** - * Discards the red, green and blue components and reads just the alpha component. - * - * @type {number} - * @constant - */ -const AlphaFormat = 1021; - -/** - * Discards the alpha component and reads the red, green and blue component. - * - * @type {number} - * @constant - */ -const RGBFormat = 1022; - -/** - * Reads the red, green, blue and alpha components. - * - * @type {number} - * @constant - */ -const RGBAFormat = 1023; - -/** - * Reads each element as a single depth value, converts it to floating point, and clamps to the range `[0,1]`. - * - * @type {number} - * @constant - */ -const DepthFormat = 1026; - -/** - * Reads each element is a pair of depth and stencil values. The depth component of the pair is interpreted as - * in `DepthFormat`. The stencil component is interpreted based on the depth + stencil internal format. - * - * @type {number} - * @constant - */ -const DepthStencilFormat = 1027; - -/** - * Discards the green, blue and alpha components and reads just the red component. - * - * @type {number} - * @constant - */ -const RedFormat = 1028; - -/** - * Discards the green, blue and alpha components and reads just the red component. The texels are read as integers instead of floating point. - * - * @type {number} - * @constant - */ -const RedIntegerFormat = 1029; - -/** - * Discards the alpha, and blue components and reads the red, and green components. - * - * @type {number} - * @constant - */ -const RGFormat = 1030; - -/** - * Discards the alpha, and blue components and reads the red, and green components. The texels are read as integers instead of floating point. - * - * @type {number} - * @constant - */ -const RGIntegerFormat = 1031; - -/** - * Reads the red, green, blue and alpha components. The texels are read as integers instead of floating point. - * - * @type {number} - * @constant - */ -const RGBAIntegerFormat = 1033; - -/** - * A DXT1-compressed image in an RGB image format. - * - * @type {number} - * @constant - */ -const RGB_S3TC_DXT1_Format = 33776; - -/** - * A DXT1-compressed image in an RGB image format with a simple on/off alpha value. - * - * @type {number} - * @constant - */ -const RGBA_S3TC_DXT1_Format = 33777; - -/** - * A DXT3-compressed image in an RGBA image format. Compared to a 32-bit RGBA texture, it offers 4:1 compression. - * - * @type {number} - * @constant - */ -const RGBA_S3TC_DXT3_Format = 33778; - -/** - * A DXT5-compressed image in an RGBA image format. It also provides a 4:1 compression, but differs to the DXT3 - * compression in how the alpha compression is done. - * - * @type {number} - * @constant - */ -const RGBA_S3TC_DXT5_Format = 33779; - -/** - * PVRTC RGB compression in 4-bit mode. One block for each 4×4 pixels. - * - * @type {number} - * @constant - */ -const RGB_PVRTC_4BPPV1_Format = 35840; - -/** - * PVRTC RGB compression in 2-bit mode. One block for each 8×4 pixels. - * - * @type {number} - * @constant - */ -const RGB_PVRTC_2BPPV1_Format = 35841; - -/** - * PVRTC RGBA compression in 4-bit mode. One block for each 4×4 pixels. - * - * @type {number} - * @constant - */ -const RGBA_PVRTC_4BPPV1_Format = 35842; - -/** - * PVRTC RGBA compression in 2-bit mode. One block for each 8×4 pixels. - * - * @type {number} - * @constant - */ -const RGBA_PVRTC_2BPPV1_Format = 35843; - -/** - * ETC1 RGB format. - * - * @type {number} - * @constant - */ -const RGB_ETC1_Format = 36196; - -/** - * ETC2 RGB format. - * - * @type {number} - * @constant - */ -const RGB_ETC2_Format = 37492; - -/** - * ETC2 RGBA format. - * - * @type {number} - * @constant - */ -const RGBA_ETC2_EAC_Format = 37496; - -/** - * EAC R11 UNORM format. - * - * @type {number} - * @constant - */ -const R11_EAC_Format = 37488; // 0x9270 - -/** - * EAC R11 SNORM format. - * - * @type {number} - * @constant - */ -const SIGNED_R11_EAC_Format = 37489; // 0x9271 - -/** - * EAC RG11 UNORM format. - * - * @type {number} - * @constant - */ -const RG11_EAC_Format = 37490; // 0x9272 - -/** - * EAC RG11 SNORM format. - * - * @type {number} - * @constant - */ -const SIGNED_RG11_EAC_Format = 37491; // 0x9273 - -/** - * ASTC RGBA 4x4 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_4x4_Format = 37808; - -/** - * ASTC RGBA 5x4 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_5x4_Format = 37809; - -/** - * ASTC RGBA 5x5 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_5x5_Format = 37810; - -/** - * ASTC RGBA 6x5 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_6x5_Format = 37811; - -/** - * ASTC RGBA 6x6 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_6x6_Format = 37812; - -/** - * ASTC RGBA 8x5 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_8x5_Format = 37813; - -/** - * ASTC RGBA 8x6 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_8x6_Format = 37814; - -/** - * ASTC RGBA 8x8 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_8x8_Format = 37815; - -/** - * ASTC RGBA 10x5 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_10x5_Format = 37816; - -/** - * ASTC RGBA 10x6 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_10x6_Format = 37817; - -/** - * ASTC RGBA 10x8 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_10x8_Format = 37818; - -/** - * ASTC RGBA 10x10 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_10x10_Format = 37819; - -/** - * ASTC RGBA 12x10 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_12x10_Format = 37820; - -/** - * ASTC RGBA 12x12 format. - * - * @type {number} - * @constant - */ -const RGBA_ASTC_12x12_Format = 37821; - -/** - * BPTC RGBA format. - * - * @type {number} - * @constant - */ -const RGBA_BPTC_Format = 36492; - -/** - * BPTC Signed RGB format. - * - * @type {number} - * @constant - */ -const RGB_BPTC_SIGNED_Format = 36494; - -/** - * BPTC Unsigned RGB format. - * - * @type {number} - * @constant - */ -const RGB_BPTC_UNSIGNED_Format = 36495; - -/** - * RGTC1 Red format. - * - * @type {number} - * @constant - */ -const RED_RGTC1_Format = 36283; - -/** - * RGTC1 Signed Red format. - * - * @type {number} - * @constant - */ -const SIGNED_RED_RGTC1_Format = 36284; - -/** - * RGTC2 Red Green format. - * - * @type {number} - * @constant - */ -const RED_GREEN_RGTC2_Format = 36285; - -/** - * RGTC2 Signed Red Green format. - * - * @type {number} - * @constant - */ -const SIGNED_RED_GREEN_RGTC2_Format = 36286; - -/** - * The depth value is inverted (1.0 - z) for visualization purposes. - * - * @type {number} - * @constant - */ -const BasicDepthPacking = 3200; - -/** - * Normal information is relative to the underlying surface. - * - * @type {number} - * @constant - */ -const TangentSpaceNormalMap = 0; - -/** - * Normal information is relative to the object orientation. - * - * @type {number} - * @constant - */ -const ObjectSpaceNormalMap = 1; - -// Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available. - -/** - * No color space. - * - * @type {string} - * @constant - */ -const NoColorSpace = ''; - -/** - * sRGB color space. - * - * @type {string} - * @constant - */ -const SRGBColorSpace = 'srgb'; - -/** - * sRGB-linear color space. - * - * @type {string} - * @constant - */ -const LinearSRGBColorSpace = 'srgb-linear'; - -/** - * Linear transfer function. - * - * @type {string} - * @constant - */ -const LinearTransfer = 'linear'; - -/** - * sRGB transfer function. - * - * @type {string} - * @constant - */ -const SRGBTransfer = 'srgb'; - -/** - * Keeps the current value. - * - * @type {number} - * @constant - */ -const KeepStencilOp = 7680; - -/** - * Will always return true. - * - * @type {number} - * @constant - */ -const AlwaysStencilFunc = 519; - -/** - * Never pass. - * - * @type {number} - * @constant - */ -const NeverCompare = 512; - -/** - * Pass if the incoming value is less than the texture value. - * - * @type {number} - * @constant - */ -const LessCompare = 513; - -/** - * Pass if the incoming value equals the texture value. - * - * @type {number} - * @constant - */ -const EqualCompare = 514; - -/** - * Pass if the incoming value is less than or equal to the texture value. - * - * @type {number} - * @constant - */ -const LessEqualCompare = 515; - -/** - * Pass if the incoming value is greater than the texture value. - * - * @type {number} - * @constant - */ -const GreaterCompare = 516; - -/** - * Pass if the incoming value is not equal to the texture value. - * - * @type {number} - * @constant - */ -const NotEqualCompare = 517; - -/** - * Pass if the incoming value is greater than or equal to the texture value. - * - * @type {number} - * @constant - */ -const GreaterEqualCompare = 518; - -/** - * Always pass. - * - * @type {number} - * @constant - */ -const AlwaysCompare = 519; - -/** - * The contents are intended to be specified once by the application, and used many - * times as the source for drawing and image specification commands. - * - * @type {number} - * @constant - */ -const StaticDrawUsage = 35044; - -/** - * GLSL 3 shader code. - * - * @type {string} - * @constant - */ -const GLSL3 = '300 es'; - -/** - * WebGL coordinate system. - * - * @type {number} - * @constant - */ -const WebGLCoordinateSystem = 2000; - -/** - * WebGPU coordinate system. - * - * @type {number} - * @constant - */ -const WebGPUCoordinateSystem = 2001; - -/** - * This type represents mouse buttons and interaction types in context of controls. - * - * @typedef {Object} ConstantsMouse - * @property {number} MIDDLE - The left mouse button. - * @property {number} LEFT - The middle mouse button. - * @property {number} RIGHT - The right mouse button. - * @property {number} ROTATE - A rotate interaction. - * @property {number} DOLLY - A dolly interaction. - * @property {number} PAN - A pan interaction. - **/ - -/** - * This type represents touch interaction types in context of controls. - * - * @typedef {Object} ConstantsTouch - * @property {number} ROTATE - A rotate interaction. - * @property {number} PAN - A pan interaction. - * @property {number} DOLLY_PAN - The dolly-pan interaction. - * @property {number} DOLLY_ROTATE - A dolly-rotate interaction. - **/ - -/** - * This type represents the different timestamp query types. - * - * @typedef {Object} ConstantsTimestampQuery - * @property {string} COMPUTE - A `compute` timestamp query. - * @property {string} RENDER - A `render` timestamp query. - **/ - -/** - * Represents the different interpolation sampling types. - * - * @typedef {Object} ConstantsInterpolationSamplingType - * @property {string} PERSPECTIVE - Perspective-correct interpolation. - * @property {string} LINEAR - Linear interpolation. - * @property {string} FLAT - Flat interpolation. - */ - -/** - * Represents the different interpolation sampling modes. - * - * @typedef {Object} ConstantsInterpolationSamplingMode - * @property {string} NORMAL - Normal sampling mode. - * @property {string} CENTROID - Centroid sampling mode. - * @property {string} SAMPLE - Sample-specific sampling mode. - * @property {string} FIRST - Flat interpolation using the first vertex. - * @property {string} EITHER - Flat interpolation using either vertex. - */ - -/** - * Represents the refresh types of render objects. - * - * @typedef {Object} ConstantsRenderObjectRefreshType - * @property {number} NONE - No refresh required. - * @property {number} SHARED - Only shared uniform buffers require an update. - * @property {number} FULL - The render object requires a full refresh. - */ - -/** - * This modules allows to dispatch event objects on custom JavaScript objects. - * - * Main repository: [eventdispatcher.js](https://github.com/mrdoob/eventdispatcher.js/) - * - * Code Example: - * ```js - * class Car extends EventDispatcher { - * start() { - * this.dispatchEvent( { type: 'start', message: 'vroom vroom!' } ); - * } - *}; - * - * // Using events with the custom object - * const car = new Car(); - * car.addEventListener( 'start', function ( event ) { - * alert( event.message ); - * } ); - * - * car.start(); - * ``` - */ -class EventDispatcher { - - /** - * Adds the given event listener to the given event type. - * - * @param {string} type - The type of event to listen to. - * @param {Function} listener - The function that gets called when the event is fired. - */ - addEventListener( type, listener ) { - - if ( this._listeners === undefined ) this._listeners = {}; - - const listeners = this._listeners; - - if ( listeners[ type ] === undefined ) { - - listeners[ type ] = []; - - } - - if ( listeners[ type ].indexOf( listener ) === -1 ) { - - listeners[ type ].push( listener ); - - } - - } - - /** - * Returns `true` if the given event listener has been added to the given event type. - * - * @param {string} type - The type of event. - * @param {Function} listener - The listener to check. - * @return {boolean} Whether the given event listener has been added to the given event type. - */ - hasEventListener( type, listener ) { - - const listeners = this._listeners; - - if ( listeners === undefined ) return false; - - return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== -1; - - } - - /** - * Removes the given event listener from the given event type. - * - * @param {string} type - The type of event. - * @param {Function} listener - The listener to remove. - */ - removeEventListener( type, listener ) { - - const listeners = this._listeners; - - if ( listeners === undefined ) return; - - const listenerArray = listeners[ type ]; - - if ( listenerArray !== undefined ) { - - const index = listenerArray.indexOf( listener ); - - if ( index !== -1 ) { - - listenerArray.splice( index, 1 ); - - } - - } - - } - - /** - * Dispatches an event object. - * - * @param {Object} event - The event that gets fired. - */ - dispatchEvent( event ) { - - const listeners = this._listeners; - - if ( listeners === undefined ) return; - - const listenerArray = listeners[ event.type ]; - - if ( listenerArray !== undefined ) { - - event.target = this; - - // Make a copy, in case listeners are removed while iterating. - const array = listenerArray.slice( 0 ); - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - array[ i ].call( this, event ); - - } - - event.target = null; - - } - - } - -} - -/** - * Checks if an array contains values that require Uint32 representation. - * - * This function determines whether the array contains any values >= 65535, - * which would require a Uint32Array rather than a Uint16Array for proper storage. - * The function iterates from the end of the array, assuming larger values are - * typically located at the end. - * - * @private - * @param {Array} array - The array to check. - * @return {boolean} True if the array contains values >= 65535, false otherwise. - */ -function arrayNeedsUint32( array ) { - - // assumes larger values usually on last - - for ( let i = array.length - 1; i >= 0; -- i ) { - - if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 - - } - - return false; - -} - -/** - * Creates an XHTML element with the specified tag name. - * - * This function uses the XHTML namespace to create DOM elements, - * ensuring proper element creation in XML-based contexts. - * - * @private - * @param {string} name - The tag name of the element to create (e.g., 'canvas', 'div'). - * @return {HTMLElement} The created XHTML element. - */ -function createElementNS( name ) { - - return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); - -} - -/** - * Creates a canvas element configured for block display. - * - * This is a convenience function that creates a canvas element with - * display style set to 'block', which is commonly used in three.js - * rendering contexts to avoid inline element spacing issues. - * - * @return {HTMLCanvasElement} A canvas element with display set to 'block'. - */ -function createCanvasElement() { - - const canvas = createElementNS( 'canvas' ); - canvas.style.display = 'block'; - return canvas; - -} - -/** - * Internal cache for tracking warning messages to prevent duplicate warnings. - * - * @private - * @type {Object} - */ -const _cache = {}; - -/** - * Logs an informational message with the 'THREE.' prefix. - * - * If a custom console function is set via setConsoleFunction(), it will be used - * instead of the native console.log. The first parameter is treated as the - * method name and is automatically prefixed with 'THREE.'. - * - * @param {...any} params - The message components. The first param is used as - * the method name and prefixed with 'THREE.'. - */ -function log( ...params ) { - - const message = 'THREE.' + params.shift(); - - { - - console.log( message, ...params ); - - } - -} - -/** - * Enhances log/warn/error messages related to TSL. - * - * @param {Array} params - The original message parameters. - * @returns {Array} The filtered and enhanced message parameters. - */ -function enhanceLogMessage( params ) { - - const message = params[ 0 ]; - - if ( typeof message === 'string' && message.startsWith( 'TSL:' ) ) { - - const stackTrace = params[ 1 ]; - - if ( stackTrace && stackTrace.isStackTrace ) { - - params[ 0 ] += ' ' + stackTrace.getLocation(); - - } else { - - params[ 1 ] = 'Stack trace not available. Enable "THREE.Node.captureStackTrace" to capture stack traces.'; - - } - - } - - return params; - -} - -/** - * Logs a warning message with the 'THREE.' prefix. - * - * If a custom console function is set via setConsoleFunction(), it will be used - * instead of the native console.warn. The first parameter is treated as the - * method name and is automatically prefixed with 'THREE.'. - * - * @param {...any} params - The message components. The first param is used as - * the method name and prefixed with 'THREE.'. - */ -function warn( ...params ) { - - params = enhanceLogMessage( params ); - - const message = 'THREE.' + params.shift(); - - { - - const stackTrace = params[ 0 ]; - - if ( stackTrace && stackTrace.isStackTrace ) { - - console.warn( stackTrace.getError( message ) ); - - } else { - - console.warn( message, ...params ); - - } - - } - -} - -/** - * Logs an error message with the 'THREE.' prefix. - * - * If a custom console function is set via setConsoleFunction(), it will be used - * instead of the native console.error. The first parameter is treated as the - * method name and is automatically prefixed with 'THREE.'. - * - * @param {...any} params - The message components. The first param is used as - * the method name and prefixed with 'THREE.'. - */ -function error( ...params ) { - - params = enhanceLogMessage( params ); - - const message = 'THREE.' + params.shift(); - - { - - const stackTrace = params[ 0 ]; - - if ( stackTrace && stackTrace.isStackTrace ) { - - console.error( stackTrace.getError( message ) ); - - } else { - - console.error( message, ...params ); - - } - - } - -} - -/** - * Logs a warning message only once, preventing duplicate warnings. - * - * This function maintains an internal cache of warning messages and will only - * output each unique warning message once. Useful for warnings that may be - * triggered repeatedly but should only be shown to the user once. - * - * @param {...any} params - The warning message components. - */ -function warnOnce( ...params ) { - - const message = params.join( ' ' ); - - if ( message in _cache ) return; - - _cache[ message ] = true; - - warn( ...params ); - -} - -/** - * Asynchronously probes for WebGL sync object completion. - * - * This function creates a promise that resolves when the WebGL sync object - * signals completion or rejects if the sync operation fails. It uses polling - * at the specified interval to check the sync status without blocking the - * main thread. This is useful for GPU-CPU synchronization in WebGL contexts. - * - * @private - * @param {WebGL2RenderingContext} gl - The WebGL rendering context. - * @param {WebGLSync} sync - The WebGL sync object to wait for. - * @param {number} interval - The polling interval in milliseconds. - * @return {Promise} A promise that resolves when the sync completes or rejects if it fails. - */ -function probeAsync( gl, sync, interval ) { - - return new Promise( function ( resolve, reject ) { - - function probe() { - - switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) { - - case gl.WAIT_FAILED: - reject(); - break; - - case gl.TIMEOUT_EXPIRED: - setTimeout( probe, interval ); - break; - - default: - resolve(); - - } - - } - - setTimeout( probe, interval ); - - } ); - -} - -/** - * Used to select the correct depth functions - * when reversed depth buffer is used. - * - * @private - * @type {Object} - */ -const ReversedDepthFuncs = { - [ NeverDepth ]: AlwaysDepth, - [ LessDepth ]: GreaterDepth, - [ EqualDepth ]: NotEqualDepth, - [ LessEqualDepth ]: GreaterEqualDepth, - - [ AlwaysDepth ]: NeverDepth, - [ GreaterDepth ]: LessDepth, - [ NotEqualDepth ]: EqualDepth, - [ GreaterEqualDepth ]: LessEqualDepth, -}; - -const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; - -let _seed = 1234567; - - -const DEG2RAD = Math.PI / 180; -const RAD2DEG = 180 / Math.PI; - -/** - * Generate a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) - * (universally unique identifier). - * - * @return {string} The UUID. - */ -function generateUUID() { - - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 - - const d0 = Math.random() * 0xffffffff | 0; - const d1 = Math.random() * 0xffffffff | 0; - const d2 = Math.random() * 0xffffffff | 0; - const d3 = Math.random() * 0xffffffff | 0; - const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + - _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + - _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + - _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; - - // .toLowerCase() here flattens concatenated strings to save heap memory space. - return uuid.toLowerCase(); - -} - -/** - * Clamps the given value between min and max. - * - * @param {number} value - The value to clamp. - * @param {number} min - The min value. - * @param {number} max - The max value. - * @return {number} The clamped value. - */ -function clamp( value, min, max ) { - - return Math.max( min, Math.min( max, value ) ); - -} - -/** - * Computes the Euclidean modulo of the given parameters that - * is `( ( n % m ) + m ) % m`. - * - * @param {number} n - The first parameter. - * @param {number} m - The second parameter. - * @return {number} The Euclidean modulo. - */ -function euclideanModulo( n, m ) { - - // https://en.wikipedia.org/wiki/Modulo_operation - - return ( ( n % m ) + m ) % m; - -} - -/** - * Performs a linear mapping from range `` to range `` - * for the given value. `a2` must be greater than `a1`. - * - * @param {number} x - The value to be mapped. - * @param {number} a1 - Minimum value for range A. - * @param {number} a2 - Maximum value for range A. - * @param {number} b1 - Minimum value for range B. - * @param {number} b2 - Maximum value for range B. - * @return {number} The mapped value. - */ -function mapLinear( x, a1, a2, b1, b2 ) { - - return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); - -} - -/** - * Returns the percentage in the closed interval `[0, 1]` of the given value - * between the start and end point. - * - * @param {number} x - The start point - * @param {number} y - The end point. - * @param {number} value - A value between start and end. - * @return {number} The interpolation factor. - */ -function inverseLerp( x, y, value ) { - - // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ - - if ( x !== y ) { - - return ( value - x ) / ( y - x ); - - } else { - - return 0; - - } - -} - -/** - * Returns a value linearly interpolated from two known points based on the given interval - - * `t = 0` will return `x` and `t = 1` will return `y`. - * - * @param {number} x - The start point - * @param {number} y - The end point. - * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. - * @return {number} The interpolated value. - */ -function lerp( x, y, t ) { - - return ( 1 - t ) * x + t * y; - -} - -/** - * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta - * time to maintain frame rate independent movement. For details, see - * [Frame rate independent damping using lerp](http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/). - * - * @param {number} x - The current point. - * @param {number} y - The target point. - * @param {number} lambda - A higher lambda value will make the movement more sudden, - * and a lower value will make the movement more gradual. - * @param {number} dt - Delta time in seconds. - * @return {number} The interpolated value. - */ -function damp( x, y, lambda, dt ) { - - return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); - -} - -/** - * Returns a value that alternates between `0` and the given `length` parameter. - * - * @param {number} x - The value to pingpong. - * @param {number} [length=1] - The positive value the function will pingpong to. - * @return {number} The alternated value. - */ -function pingpong( x, length = 1 ) { - - // https://www.desmos.com/calculator/vcsjnyz7x4 - - return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); - -} - -/** - * Returns a value in the range `[0,1]` that represents the percentage that `x` has - * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to - * the `min` and `max`. - * - * See [Smoothstep](http://en.wikipedia.org/wiki/Smoothstep) for more details. - * - * @param {number} x - The value to evaluate based on its position between `min` and `max`. - * @param {number} min - The min value. Any `x` value below `min` will be `0`. `min` must be lower than `max`. - * @param {number} max - The max value. Any `x` value above `max` will be `1`. `max` must be greater than `min`. - * @return {number} The alternated value. - */ -function smoothstep( x, min, max ) { - - if ( x <= min ) return 0; - if ( x >= max ) return 1; - - x = ( x - min ) / ( max - min ); - - return x * x * ( 3 - 2 * x ); - -} - -/** - * A [variation on smoothstep](https://en.wikipedia.org/wiki/Smoothstep#Variations) - * that has zero 1st and 2nd order derivatives at `x=0` and `x=1`. - * - * @param {number} x - The value to evaluate based on its position between `min` and `max`. - * @param {number} min - The min value. Any `x` value below `min` will be `0`. `min` must be lower than `max`. - * @param {number} max - The max value. Any `x` value above `max` will be `1`. `max` must be greater than `min`. - * @return {number} The alternated value. - */ -function smootherstep( x, min, max ) { - - if ( x <= min ) return 0; - if ( x >= max ) return 1; - - x = ( x - min ) / ( max - min ); - - return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); - -} - -/** - * Returns a random integer from `` interval. - * - * @param {number} low - The lower value boundary. - * @param {number} high - The upper value boundary - * @return {number} A random integer. - */ -function randInt( low, high ) { - - return low + Math.floor( Math.random() * ( high - low + 1 ) ); - -} - -/** - * Returns a random float from `` interval. - * - * @param {number} low - The lower value boundary. - * @param {number} high - The upper value boundary - * @return {number} A random float. - */ -function randFloat( low, high ) { - - return low + Math.random() * ( high - low ); - -} - -/** - * Returns a random integer from `<-range/2, range/2>` interval. - * - * @param {number} range - Defines the value range. - * @return {number} A random float. - */ -function randFloatSpread( range ) { - - return range * ( 0.5 - Math.random() ); - -} - -/** - * Returns a deterministic pseudo-random float in the interval `[0, 1]`. - * - * @param {number} [s] - The integer seed. - * @return {number} A random float. - */ -function seededRandom( s ) { - - if ( s !== undefined ) _seed = s; - - // Mulberry32 generator - - let t = _seed += 0x6D2B79F5; - - t = Math.imul( t ^ t >>> 15, t | 1 ); - - t ^= t + Math.imul( t ^ t >>> 7, t | 61 ); - - return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296; - -} - -/** - * Converts degrees to radians. - * - * @param {number} degrees - A value in degrees. - * @return {number} The converted value in radians. - */ -function degToRad( degrees ) { - - return degrees * DEG2RAD; - -} - -/** - * Converts radians to degrees. - * - * @param {number} radians - A value in radians. - * @return {number} The converted value in degrees. - */ -function radToDeg( radians ) { - - return radians * RAD2DEG; - -} - -/** - * Returns `true` if the given integer is a power of two. - * - * @param {number} value - The value to check. - * @return {boolean} Whether the given integer is a power of two or not. - */ -function isPowerOfTwo( value ) { - - return value > 0 && Number.isInteger( value ) && 2 ** Math.round( Math.log2( value ) ) === value; - -} - -/** - * Returns the smallest power of two that is greater than or equal to the given number. - * - * @param {number} value - The value to find a POT for. Must be greater than `0`. - * @return {number} The smallest power of two that is greater than or equal to the given number. - */ -function ceilPowerOfTwo( value ) { - - return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); - -} - -/** - * Returns the largest power of two that is less than or equal to the given number. - * - * @param {number} value - The value to find a POT for. Must be greater than `0`. - * @return {number} The largest power of two that is less than or equal to the given number. - */ -function floorPowerOfTwo( value ) { - - return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); - -} - -/** - * Sets the given quaternion from the [Intrinsic Proper Euler Angles](https://en.wikipedia.org/wiki/Euler_angles) - * defined by the given angles and order. - * - * Rotations are applied to the axes in the order specified by order: - * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. - * - * @param {Quaternion} q - The quaternion to set. - * @param {number} a - The rotation applied to the first axis, in radians. - * @param {number} b - The rotation applied to the second axis, in radians. - * @param {number} c - The rotation applied to the third axis, in radians. - * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. - */ -function setQuaternionFromProperEuler( q, a, b, c, order ) { - - const cos = Math.cos; - const sin = Math.sin; - - const c2 = cos( b / 2 ); - const s2 = sin( b / 2 ); - - const c13 = cos( ( a + c ) / 2 ); - const s13 = sin( ( a + c ) / 2 ); - - const c1_3 = cos( ( a - c ) / 2 ); - const s1_3 = sin( ( a - c ) / 2 ); - - const c3_1 = cos( ( c - a ) / 2 ); - const s3_1 = sin( ( c - a ) / 2 ); - - switch ( order ) { - - case 'XYX': - q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 ); - break; - - case 'YZY': - q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 ); - break; - - case 'ZXZ': - q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 ); - break; - - case 'XZX': - q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 ); - break; - - case 'YXY': - q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 ); - break; - - case 'ZYZ': - q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 ); - break; - - default: - warn( 'MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order ); - - } - -} - -/** - * Denormalizes the given value according to the given typed array. - * - * @param {number} value - The value to denormalize. - * @param {TypedArray} array - The typed array that defines the data type of the value. - * @return {number} The denormalize (float) value in the range `[0,1]`. - */ -function denormalize( value, array ) { - - switch ( array.constructor ) { - - case Float32Array: - - return value; - - case Uint32Array: - - return value / 4294967295.0; - - case Uint16Array: - - return value / 65535.0; - - case Uint8Array: - case Uint8ClampedArray: - - return value / 255.0; - - case Int32Array: - - return Math.max( value / 2147483647.0, -1 ); - - case Int16Array: - - return Math.max( value / 32767.0, -1 ); - - case Int8Array: - - return Math.max( value / 127.0, -1 ); - - default: - - throw new Error( 'THREE.MathUtils: Invalid component type.' ); - - } - -} - -/** - * Normalizes the given value according to the given typed array. - * - * @param {number} value - The float value in the range `[0,1]` to normalize. - * @param {TypedArray} array - The typed array that defines the data type of the value. - * @return {number} The normalize value. - */ -function normalize$1( value, array ) { - - switch ( array.constructor ) { - - case Float32Array: - - return value; - - case Uint32Array: - - return Math.round( value * 4294967295.0 ); - - case Uint16Array: - - return Math.round( value * 65535.0 ); - - case Uint8Array: - case Uint8ClampedArray: - - return Math.round( value * 255.0 ); - - case Int32Array: - - return Math.round( value * 2147483647.0 ); - - case Int16Array: - - return Math.round( value * 32767.0 ); - - case Int8Array: - - return Math.round( value * 127.0 ); - - default: - - throw new Error( 'THREE.MathUtils: Invalid component type.' ); - - } - -} - -/** - * @class - * @classdesc A collection of math utility functions. - * @hideconstructor - */ -const MathUtils = { - DEG2RAD: DEG2RAD, - RAD2DEG: RAD2DEG, - /** - * Generate a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) - * (universally unique identifier). - * - * @static - * @method - * @return {string} The UUID. - */ - generateUUID: generateUUID, - /** - * Clamps the given value between min and max. - * - * @static - * @method - * @param {number} value - The value to clamp. - * @param {number} min - The min value. - * @param {number} max - The max value. - * @return {number} The clamped value. - */ - clamp: clamp, - /** - * Computes the Euclidean modulo of the given parameters that - * is `( ( n % m ) + m ) % m`. - * - * @static - * @method - * @param {number} n - The first parameter. - * @param {number} m - The second parameter. - * @return {number} The Euclidean modulo. - */ - euclideanModulo: euclideanModulo, - /** - * Performs a linear mapping from range `` to range `` - * for the given value. - * - * @static - * @method - * @param {number} x - The value to be mapped. - * @param {number} a1 - Minimum value for range A. - * @param {number} a2 - Maximum value for range A. - * @param {number} b1 - Minimum value for range B. - * @param {number} b2 - Maximum value for range B. - * @return {number} The mapped value. - */ - mapLinear: mapLinear, - /** - * Returns the percentage in the closed interval `[0, 1]` of the given value - * between the start and end point. - * - * @static - * @method - * @param {number} x - The start point - * @param {number} y - The end point. - * @param {number} value - A value between start and end. - * @return {number} The interpolation factor. - */ - inverseLerp: inverseLerp, - /** - * Returns a value linearly interpolated from two known points based on the given interval - - * `t = 0` will return `x` and `t = 1` will return `y`. - * - * @static - * @method - * @param {number} x - The start point - * @param {number} y - The end point. - * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. - * @return {number} The interpolated value. - */ - lerp: lerp, - /** - * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta - * time to maintain frame rate independent movement. For details, see - * [Frame rate independent damping using lerp](http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/). - * - * @static - * @method - * @param {number} x - The current point. - * @param {number} y - The target point. - * @param {number} lambda - A higher lambda value will make the movement more sudden, - * and a lower value will make the movement more gradual. - * @param {number} dt - Delta time in seconds. - * @return {number} The interpolated value. - */ - damp: damp, - /** - * Returns a value that alternates between `0` and the given `length` parameter. - * - * @static - * @method - * @param {number} x - The value to pingpong. - * @param {number} [length=1] - The positive value the function will pingpong to. - * @return {number} The alternated value. - */ - pingpong: pingpong, - /** - * Returns a value in the range `[0,1]` that represents the percentage that `x` has - * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to - * the `min` and `max`. - * - * See [Smoothstep](http://en.wikipedia.org/wiki/Smoothstep) for more details. - * - * @static - * @method - * @param {number} x - The value to evaluate based on its position between min and max. - * @param {number} min - The min value. Any x value below min will be `0`. - * @param {number} max - The max value. Any x value above max will be `1`. - * @return {number} The alternated value. - */ - smoothstep: smoothstep, - /** - * A [variation on smoothstep](https://en.wikipedia.org/wiki/Smoothstep#Variations) - * that has zero 1st and 2nd order derivatives at x=0 and x=1. - * - * @static - * @method - * @param {number} x - The value to evaluate based on its position between min and max. - * @param {number} min - The min value. Any x value below min will be `0`. - * @param {number} max - The max value. Any x value above max will be `1`. - * @return {number} The alternated value. - */ - smootherstep: smootherstep, - /** - * Returns a random integer from `` interval. - * - * @static - * @method - * @param {number} low - The lower value boundary. - * @param {number} high - The upper value boundary - * @return {number} A random integer. - */ - randInt: randInt, - /** - * Returns a random float from `` interval. - * - * @static - * @method - * @param {number} low - The lower value boundary. - * @param {number} high - The upper value boundary - * @return {number} A random float. - */ - randFloat: randFloat, - /** - * Returns a random integer from `<-range/2, range/2>` interval. - * - * @static - * @method - * @param {number} range - Defines the value range. - * @return {number} A random float. - */ - randFloatSpread: randFloatSpread, - /** - * Returns a deterministic pseudo-random float in the interval `[0, 1]`. - * - * @static - * @method - * @param {number} [s] - The integer seed. - * @return {number} A random float. - */ - seededRandom: seededRandom, - /** - * Converts degrees to radians. - * - * @static - * @method - * @param {number} degrees - A value in degrees. - * @return {number} The converted value in radians. - */ - degToRad: degToRad, - /** - * Converts radians to degrees. - * - * @static - * @method - * @param {number} radians - A value in radians. - * @return {number} The converted value in degrees. - */ - radToDeg: radToDeg, - /** - * Returns `true` if the given number is a power of two. - * - * @static - * @method - * @param {number} value - The value to check. - * @return {boolean} Whether the given number is a power of two or not. - */ - isPowerOfTwo: isPowerOfTwo, - /** - * Returns the smallest power of two that is greater than or equal to the given number. - * - * @static - * @method - * @param {number} value - The value to find a POT for. - * @return {number} The smallest power of two that is greater than or equal to the given number. - */ - ceilPowerOfTwo: ceilPowerOfTwo, - /** - * Returns the largest power of two that is less than or equal to the given number. - * - * @static - * @method - * @param {number} value - The value to find a POT for. - * @return {number} The largest power of two that is less than or equal to the given number. - */ - floorPowerOfTwo: floorPowerOfTwo, - /** - * Sets the given quaternion from the [Intrinsic Proper Euler Angles](https://en.wikipedia.org/wiki/Euler_angles) - * defined by the given angles and order. - * - * Rotations are applied to the axes in the order specified by order: - * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. - * - * @static - * @method - * @param {Quaternion} q - The quaternion to set. - * @param {number} a - The rotation applied to the first axis, in radians. - * @param {number} b - The rotation applied to the second axis, in radians. - * @param {number} c - The rotation applied to the third axis, in radians. - * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. - */ - setQuaternionFromProperEuler: setQuaternionFromProperEuler, - /** - * Normalizes the given value according to the given typed array. - * - * @static - * @method - * @param {number} value - The float value in the range `[0,1]` to normalize. - * @param {TypedArray} array - The typed array that defines the data type of the value. - * @return {number} The normalize value. - */ - normalize: normalize$1, - /** - * Denormalizes the given value according to the given typed array. - * - * @static - * @method - * @param {number} value - The value to denormalize. - * @param {TypedArray} array - The typed array that defines the data type of the value. - * @return {number} The denormalize (float) value in the range `[0,1]`. - */ - denormalize: denormalize -}; - -var MathUtils$1 = /*#__PURE__*/Object.freeze({ - __proto__: null, - DEG2RAD: DEG2RAD, - MathUtils: MathUtils, - RAD2DEG: RAD2DEG, - ceilPowerOfTwo: ceilPowerOfTwo, - clamp: clamp, - damp: damp, - degToRad: degToRad, - denormalize: denormalize, - euclideanModulo: euclideanModulo, - floorPowerOfTwo: floorPowerOfTwo, - generateUUID: generateUUID, - inverseLerp: inverseLerp, - isPowerOfTwo: isPowerOfTwo, - lerp: lerp, - mapLinear: mapLinear, - normalize: normalize$1, - pingpong: pingpong, - radToDeg: radToDeg, - randFloat: randFloat, - randFloatSpread: randFloatSpread, - randInt: randInt, - seededRandom: seededRandom, - setQuaternionFromProperEuler: setQuaternionFromProperEuler, - smootherstep: smootherstep, - smoothstep: smoothstep -}); - -/** - * Class representing a 2D vector. A 2D vector is an ordered pair of numbers - * (labeled x and y), which can be used to represent a number of things, such as: - * - * - A point in 2D space (i.e. a position on a plane). - * - A direction and length across a plane. In three.js the length will - * always be the Euclidean distance(straight-line distance) from `(0, 0)` to `(x, y)` - * and the direction is also measured from `(0, 0)` towards `(x, y)`. - * - Any arbitrary ordered pair of numbers. - * - * There are other things a 2D vector can be used to represent, such as - * momentum vectors, complex numbers and so on, however these are the most - * common uses in three.js. - * - * Iterating through a vector instance will yield its components `(x, y)` in - * the corresponding order. - * ```js - * const a = new THREE.Vector2( 0, 1 ); - * - * //no arguments; will be initialised to (0, 0) - * const b = new THREE.Vector2( ); - * - * const d = a.distanceTo( b ); - * ``` - */ -class Vector2 { - - static { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - Vector2.prototype.isVector2 = true; - - } - - /** - * Constructs a new 2D vector. - * - * @param {number} [x=0] - The x value of this vector. - * @param {number} [y=0] - The y value of this vector. - */ - constructor( x = 0, y = 0 ) { - - /** - * The x value of this vector. - * - * @type {number} - */ - this.x = x; - - /** - * The y value of this vector. - * - * @type {number} - */ - this.y = y; - - } - - /** - * Alias for {@link Vector2#x}. - * - * @type {number} - */ - get width() { - - return this.x; - - } - - set width( value ) { - - this.x = value; - - } - - /** - * Alias for {@link Vector2#y}. - * - * @type {number} - */ - get height() { - - return this.y; - - } - - set height( value ) { - - this.y = value; - - } - - /** - * Sets the vector components. - * - * @param {number} x - The value of the x component. - * @param {number} y - The value of the y component. - * @return {Vector2} A reference to this vector. - */ - set( x, y ) { - - this.x = x; - this.y = y; - - return this; - - } - - /** - * Sets the vector components to the same value. - * - * @param {number} scalar - The value to set for all vector components. - * @return {Vector2} A reference to this vector. - */ - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - - return this; - - } - - /** - * Sets the vector's x component to the given value - * - * @param {number} x - The value to set. - * @return {Vector2} A reference to this vector. - */ - setX( x ) { - - this.x = x; - - return this; - - } - - /** - * Sets the vector's y component to the given value - * - * @param {number} y - The value to set. - * @return {Vector2} A reference to this vector. - */ - setY( y ) { - - this.y = y; - - return this; - - } - - /** - * Allows to set a vector component with an index. - * - * @param {number} index - The component index. `0` equals to x, `1` equals to y. - * @param {number} value - The value to set. - * @return {Vector2} A reference to this vector. - */ - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - default: throw new Error( 'THREE.Vector2: index is out of range: ' + index ); - - } - - return this; - - } - - /** - * Returns the value of the vector component which matches the given index. - * - * @param {number} index - The component index. `0` equals to x, `1` equals to y. - * @return {number} A vector component value. - */ - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - default: throw new Error( 'THREE.Vector2: index is out of range: ' + index ); - - } - - } - - /** - * Returns a new vector with copied values from this instance. - * - * @return {Vector2} A clone of this instance. - */ - clone() { - - return new this.constructor( this.x, this.y ); - - } - - /** - * Copies the values of the given vector to this instance. - * - * @param {Vector2} v - The vector to copy. - * @return {Vector2} A reference to this vector. - */ - copy( v ) { - - this.x = v.x; - this.y = v.y; - - return this; - - } - - /** - * Adds the given vector to this instance. - * - * @param {Vector2} v - The vector to add. - * @return {Vector2} A reference to this vector. - */ - add( v ) { - - this.x += v.x; - this.y += v.y; - - return this; - - } - - /** - * Adds the given scalar value to all components of this instance. - * - * @param {number} s - The scalar to add. - * @return {Vector2} A reference to this vector. - */ - addScalar( s ) { - - this.x += s; - this.y += s; - - return this; - - } - - /** - * Adds the given vectors and stores the result in this instance. - * - * @param {Vector2} a - The first vector. - * @param {Vector2} b - The second vector. - * @return {Vector2} A reference to this vector. - */ - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - - return this; - - } - - /** - * Adds the given vector scaled by the given factor to this instance. - * - * @param {Vector2} v - The vector. - * @param {number} s - The factor that scales `v`. - * @return {Vector2} A reference to this vector. - */ - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - - return this; - - } - - /** - * Subtracts the given vector from this instance. - * - * @param {Vector2} v - The vector to subtract. - * @return {Vector2} A reference to this vector. - */ - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - - return this; - - } - - /** - * Subtracts the given scalar value from all components of this instance. - * - * @param {number} s - The scalar to subtract. - * @return {Vector2} A reference to this vector. - */ - subScalar( s ) { - - this.x -= s; - this.y -= s; - - return this; - - } - - /** - * Subtracts the given vectors and stores the result in this instance. - * - * @param {Vector2} a - The first vector. - * @param {Vector2} b - The second vector. - * @return {Vector2} A reference to this vector. - */ - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - - return this; - - } - - /** - * Multiplies the given vector with this instance. - * - * @param {Vector2} v - The vector to multiply. - * @return {Vector2} A reference to this vector. - */ - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - - return this; - - } - - /** - * Multiplies the given scalar value with all components of this instance. - * - * @param {number} scalar - The scalar to multiply. - * @return {Vector2} A reference to this vector. - */ - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - - return this; - - } - - /** - * Divides this instance by the given vector. - * - * @param {Vector2} v - The vector to divide. - * @return {Vector2} A reference to this vector. - */ - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - - return this; - - } - - /** - * Divides this vector by the given scalar. - * - * @param {number} scalar - The scalar to divide. - * @return {Vector2} A reference to this vector. - */ - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - /** - * Multiplies this vector (with an implicit 1 as the 3rd component) by - * the given 3x3 matrix. - * - * @param {Matrix3} m - The matrix to apply. - * @return {Vector2} A reference to this vector. - */ - applyMatrix3( m ) { - - const x = this.x, y = this.y; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; - - return this; - - } - - /** - * If this vector's x or y value is greater than the given vector's x or y - * value, replace that value with the corresponding min value. - * - * @param {Vector2} v - The vector. - * @return {Vector2} A reference to this vector. - */ - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - - return this; - - } - - /** - * If this vector's x or y value is less than the given vector's x or y - * value, replace that value with the corresponding max value. - * - * @param {Vector2} v - The vector. - * @return {Vector2} A reference to this vector. - */ - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - - return this; - - } - - /** - * If this vector's x or y value is greater than the max vector's x or y - * value, it is replaced by the corresponding value. - * If this vector's x or y value is less than the min vector's x or y value, - * it is replaced by the corresponding value. - * - * @param {Vector2} min - The minimum x and y values. - * @param {Vector2} max - The maximum x and y values in the desired range. - * @return {Vector2} A reference to this vector. - */ - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = clamp( this.x, min.x, max.x ); - this.y = clamp( this.y, min.y, max.y ); - - return this; - - } - - /** - * If this vector's x or y values are greater than the max value, they are - * replaced by the max value. - * If this vector's x or y values are less than the min value, they are - * replaced by the min value. - * - * @param {number} minVal - The minimum value the components will be clamped to. - * @param {number} maxVal - The maximum value the components will be clamped to. - * @return {Vector2} A reference to this vector. - */ - clampScalar( minVal, maxVal ) { - - this.x = clamp( this.x, minVal, maxVal ); - this.y = clamp( this.y, minVal, maxVal ); - - return this; - - } - - /** - * If this vector's length is greater than the max value, it is replaced by - * the max value. - * If this vector's length is less than the min value, it is replaced by the - * min value. - * - * @param {number} min - The minimum value the vector length will be clamped to. - * @param {number} max - The maximum value the vector length will be clamped to. - * @return {Vector2} A reference to this vector. - */ - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); - - } - - /** - * The components of this vector are rounded down to the nearest integer value. - * - * @return {Vector2} A reference to this vector. - */ - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - - return this; - - } - - /** - * The components of this vector are rounded up to the nearest integer value. - * - * @return {Vector2} A reference to this vector. - */ - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - - return this; - - } - - /** - * The components of this vector are rounded to the nearest integer value - * - * @return {Vector2} A reference to this vector. - */ - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - - return this; - - } - - /** - * The components of this vector are rounded towards zero (up if negative, - * down if positive) to an integer value. - * - * @return {Vector2} A reference to this vector. - */ - roundToZero() { - - this.x = Math.trunc( this.x ); - this.y = Math.trunc( this.y ); - - return this; - - } - - /** - * Inverts this vector - i.e. sets x = -x and y = -y. - * - * @return {Vector2} A reference to this vector. - */ - negate() { - - this.x = - this.x; - this.y = - this.y; - - return this; - - } - - /** - * Calculates the dot product of the given vector with this instance. - * - * @param {Vector2} v - The vector to compute the dot product with. - * @return {number} The result of the dot product. - */ - dot( v ) { - - return this.x * v.x + this.y * v.y; - - } - - /** - * Calculates the cross product of the given vector with this instance. - * - * @param {Vector2} v - The vector to compute the cross product with. - * @return {number} The result of the cross product. - */ - cross( v ) { - - return this.x * v.y - this.y * v.x; - - } - - /** - * Computes the square of the Euclidean length (straight-line length) from - * (0, 0) to (x, y). If you are comparing the lengths of vectors, you should - * compare the length squared instead as it is slightly more efficient to calculate. - * - * @return {number} The square length of this vector. - */ - lengthSq() { - - return this.x * this.x + this.y * this.y; - - } - - /** - * Computes the Euclidean length (straight-line length) from (0, 0) to (x, y). - * - * @return {number} The length of this vector. - */ - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y ); - - } - - /** - * Computes the Manhattan length of this vector. - * - * @return {number} The length of this vector. - */ - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ); - - } - - /** - * Converts this vector to a unit vector - that is, sets it equal to a vector - * with the same direction as this one, but with a vector length of `1`. - * - * @return {Vector2} A reference to this vector. - */ - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - /** - * Computes the angle in radians of this vector with respect to the positive x-axis. - * - * @return {number} The angle in radians. - */ - angle() { - - const angle = Math.atan2( - this.y, - this.x ) + Math.PI; - - return angle; - - } - - /** - * Returns the angle between the given vector and this instance in radians. - * - * @param {Vector2} v - The vector to compute the angle with. - * @return {number} The angle in radians. - */ - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - - if ( denominator === 0 ) return Math.PI / 2; - - const theta = this.dot( v ) / denominator; - - // clamp, to handle numerical problems - - return Math.acos( clamp( theta, -1, 1 ) ); - - } - - /** - * Computes the distance from the given vector to this instance. - * - * @param {Vector2} v - The vector to compute the distance to. - * @return {number} The distance. - */ - distanceTo( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - } - - /** - * Computes the squared distance from the given vector to this instance. - * If you are just comparing the distance with another distance, you should compare - * the distance squared instead as it is slightly more efficient to calculate. - * - * @param {Vector2} v - The vector to compute the squared distance to. - * @return {number} The squared distance. - */ - distanceToSquared( v ) { - - const dx = this.x - v.x, dy = this.y - v.y; - return dx * dx + dy * dy; - - } - - /** - * Computes the Manhattan distance from the given vector to this instance. - * - * @param {Vector2} v - The vector to compute the Manhattan distance to. - * @return {number} The Manhattan distance. - */ - manhattanDistanceTo( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); - - } - - /** - * Sets this vector to a vector with the same direction as this one, but - * with the specified length. - * - * @param {number} length - The new length of this vector. - * @return {Vector2} A reference to this vector. - */ - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - /** - * Linearly interpolates between the given vector and this instance, where - * alpha is the percent distance along the line - alpha = 0 will be this - * vector, and alpha = 1 will be the given one. - * - * @param {Vector2} v - The vector to interpolate towards. - * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. - * @return {Vector2} A reference to this vector. - */ - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - - return this; - - } - - /** - * Linearly interpolates between the given vectors, where alpha is the percent - * distance along the line - alpha = 0 will be first vector, and alpha = 1 will - * be the second one. The result is stored in this instance. - * - * @param {Vector2} v1 - The first vector. - * @param {Vector2} v2 - The second vector. - * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. - * @return {Vector2} A reference to this vector. - */ - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - - return this; - - } - - /** - * Returns `true` if this vector is equal with the given one. - * - * @param {Vector2} v - The vector to test for equality. - * @return {boolean} Whether this vector is equal with the given one. - */ - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) ); - - } - - /** - * Sets this vector's x value to be `array[ offset ]` and y - * value to be `array[ offset + 1 ]`. - * - * @param {Array} array - An array holding the vector component values. - * @param {number} [offset=0] - The offset into the array. - * @return {Vector2} A reference to this vector. - */ - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - - return this; - - } - - /** - * Writes the components of this vector to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the vector components. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The vector components. - */ - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - - return array; - - } - - /** - * Sets the components of this vector from the given buffer attribute. - * - * @param {BufferAttribute} attribute - The buffer attribute holding vector data. - * @param {number} index - The index into the attribute. - * @return {Vector2} A reference to this vector. - */ - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - - return this; - - } - - /** - * Rotates this vector around the given center by the given angle. - * - * @param {Vector2} center - The point around which to rotate. - * @param {number} angle - The angle to rotate, in radians. - * @return {Vector2} A reference to this vector. - */ - rotateAround( center, angle ) { - - const c = Math.cos( angle ), s = Math.sin( angle ); - - const x = this.x - center.x; - const y = this.y - center.y; - - this.x = x * c - y * s + center.x; - this.y = x * s + y * c + center.y; - - return this; - - } - - /** - * Sets each component of this vector to a pseudo-random value between `0` and - * `1`, excluding `1`. - * - * @return {Vector2} A reference to this vector. - */ - random() { - - this.x = Math.random(); - this.y = Math.random(); - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - - } - -} - -/** - * Class for representing a Quaternion. Quaternions are used in three.js to represent rotations. - * - * Iterating through a vector instance will yield its components `(x, y, z, w)` in - * the corresponding order. - * - * Note that three.js expects Quaternions to be normalized. - * ```js - * const quaternion = new THREE.Quaternion(); - * quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 ); - * - * const vector = new THREE.Vector3( 1, 0, 0 ); - * vector.applyQuaternion( quaternion ); - * ``` - */ -class Quaternion { - - /** - * Constructs a new quaternion. - * - * @param {number} [x=0] - The x value of this quaternion. - * @param {number} [y=0] - The y value of this quaternion. - * @param {number} [z=0] - The z value of this quaternion. - * @param {number} [w=1] - The w value of this quaternion. - */ - constructor( x = 0, y = 0, z = 0, w = 1 ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isQuaternion = true; - - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - } - - /** - * Interpolates between two quaternions via SLERP. This implementation assumes the - * quaternion data are managed in flat arrays. - * - * @param {Array} dst - The destination array. - * @param {number} dstOffset - An offset into the destination array. - * @param {Array} src0 - The source array of the first quaternion. - * @param {number} srcOffset0 - An offset into the first source array. - * @param {Array} src1 - The source array of the second quaternion. - * @param {number} srcOffset1 - An offset into the second source array. - * @param {number} t - The interpolation factor. A value in the range `[0,1]` will interpolate. A value outside the range `[0,1]` will extrapolate. - * @see {@link Quaternion#slerp} - */ - static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - - let x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ]; - - let x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; - - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - - let dot = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1; - - if ( dot < 0 ) { - - x1 = - x1; - y1 = - y1; - z1 = - z1; - w1 = - w1; - - dot = - dot; - - } - - let s = 1 - t; - - if ( dot < 0.9995 ) { - - // slerp - - const theta = Math.acos( dot ); - const sin = Math.sin( theta ); - - s = Math.sin( s * theta ) / sin; - t = Math.sin( t * theta ) / sin; - - x0 = x0 * s + x1 * t; - y0 = y0 * s + y1 * t; - z0 = z0 * s + z1 * t; - w0 = w0 * s + w1 * t; - - } else { - - // for small angles, lerp then normalize - - x0 = x0 * s + x1 * t; - y0 = y0 * s + y1 * t; - z0 = z0 * s + z1 * t; - w0 = w0 * s + w1 * t; - - const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); - - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; - - } - - } - - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - - } - - /** - * Multiplies two quaternions. This implementation assumes the quaternion data are managed - * in flat arrays. - * - * @param {Array} dst - The destination array. - * @param {number} dstOffset - An offset into the destination array. - * @param {Array} src0 - The source array of the first quaternion. - * @param {number} srcOffset0 - An offset into the first source array. - * @param {Array} src1 - The source array of the second quaternion. - * @param {number} srcOffset1 - An offset into the second source array. - * @return {Array} The destination array. - * @see {@link Quaternion#multiplyQuaternions}. - */ - static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - - const x0 = src0[ srcOffset0 ]; - const y0 = src0[ srcOffset0 + 1 ]; - const z0 = src0[ srcOffset0 + 2 ]; - const w0 = src0[ srcOffset0 + 3 ]; - - const x1 = src1[ srcOffset1 ]; - const y1 = src1[ srcOffset1 + 1 ]; - const z1 = src1[ srcOffset1 + 2 ]; - const w1 = src1[ srcOffset1 + 3 ]; - - dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; - dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; - dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; - dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; - - return dst; - - } - - /** - * The x value of this quaternion. - * - * @type {number} - * @default 0 - */ - get x() { - - return this._x; - - } - - set x( value ) { - - this._x = value; - this._onChangeCallback(); - - } - - /** - * The y value of this quaternion. - * - * @type {number} - * @default 0 - */ - get y() { - - return this._y; - - } - - set y( value ) { - - this._y = value; - this._onChangeCallback(); - - } - - /** - * The z value of this quaternion. - * - * @type {number} - * @default 0 - */ - get z() { - - return this._z; - - } - - set z( value ) { - - this._z = value; - this._onChangeCallback(); - - } - - /** - * The w value of this quaternion. - * - * @type {number} - * @default 1 - */ - get w() { - - return this._w; - - } - - set w( value ) { - - this._w = value; - this._onChangeCallback(); - - } - - /** - * Sets the quaternion components. - * - * @param {number} x - The x value of this quaternion. - * @param {number} y - The y value of this quaternion. - * @param {number} z - The z value of this quaternion. - * @param {number} w - The w value of this quaternion. - * @return {Quaternion} A reference to this quaternion. - */ - set( x, y, z, w ) { - - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - this._onChangeCallback(); - - return this; - - } - - /** - * Returns a new quaternion with copied values from this instance. - * - * @return {Quaternion} A clone of this instance. - */ - clone() { - - return new this.constructor( this._x, this._y, this._z, this._w ); - - } - - /** - * Copies the values of the given quaternion to this instance. - * - * @param {Quaternion} quaternion - The quaternion to copy. - * @return {Quaternion} A reference to this quaternion. - */ - copy( quaternion ) { - - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; - - this._onChangeCallback(); - - return this; - - } - - /** - * Sets this quaternion from the rotation specified by the given - * Euler angles. - * - * @param {Euler} euler - The Euler angles. - * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. - * @return {Quaternion} A reference to this quaternion. - */ - setFromEuler( euler, update = true ) { - - const x = euler._x, y = euler._y, z = euler._z, order = euler._order; - - const cos = Math.cos; - const sin = Math.sin; - - const c1 = cos( x / 2 ); - const c2 = cos( y / 2 ); - const c3 = cos( z / 2 ); - - const s1 = sin( x / 2 ); - const s2 = sin( y / 2 ); - const s3 = sin( z / 2 ); - - switch ( order ) { - - case 'XYZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'YXZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - case 'ZXY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'ZYX': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - case 'YZX': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'XZY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - default: - warn( 'Quaternion: .setFromEuler() encountered an unknown order: ' + order ); - - } - - if ( update === true ) this._onChangeCallback(); - - return this; - - } - - /** - * Sets this quaternion from the given axis and angle. - * - * @param {Vector3} axis - The normalized axis. - * @param {number} angle - The angle in radians. - * @return {Quaternion} A reference to this quaternion. - */ - setFromAxisAngle( axis, angle ) { - - const halfAngle = angle / 2, s = Math.sin( halfAngle ); - - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); - - this._onChangeCallback(); - - return this; - - } - - /** - * Sets this quaternion from the given rotation matrix. - * - * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). - * @return {Quaternion} A reference to this quaternion. - */ - setFromRotationMatrix( m ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - const te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - - trace = m11 + m22 + m33; - - if ( trace > 0 ) { - - const s = 0.5 / Math.sqrt( trace + 1.0 ); - - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; - - } else if ( m11 > m22 && m11 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; - - } else if ( m22 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; - - } else { - - const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; - - } - - this._onChangeCallback(); - - return this; - - } - - /** - * Sets this quaternion to the rotation required to rotate the direction vector - * `vFrom` to the direction vector `vTo`. - * - * @param {Vector3} vFrom - The first (normalized) direction vector. - * @param {Vector3} vTo - The second (normalized) direction vector. - * @return {Quaternion} A reference to this quaternion. - */ - setFromUnitVectors( vFrom, vTo ) { - - // assumes direction vectors vFrom and vTo are normalized - - let r = vFrom.dot( vTo ) + 1; - - if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286 - - // vFrom and vTo point in opposite directions - - r = 0; - - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - - this._x = - vFrom.y; - this._y = vFrom.x; - this._z = 0; - this._w = r; - - } else { - - this._x = 0; - this._y = - vFrom.z; - this._z = vFrom.y; - this._w = r; - - } - - } else { - - // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - - this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; - this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; - this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; - this._w = r; - - } - - return this.normalize(); - - } - - /** - * Returns the angle between this quaternion and the given one in radians. - * - * @param {Quaternion} q - The quaternion to compute the angle with. - * @return {number} The angle in radians. - */ - angleTo( q ) { - - return 2 * Math.acos( Math.abs( clamp( this.dot( q ), -1, 1 ) ) ); - - } - - /** - * Rotates this quaternion by a given angular step to the given quaternion. - * The method ensures that the final quaternion will not overshoot `q`. - * - * @param {Quaternion} q - The target quaternion. - * @param {number} step - The angular step in radians. - * @return {Quaternion} A reference to this quaternion. - */ - rotateTowards( q, step ) { - - const angle = this.angleTo( q ); - - if ( angle === 0 ) return this; - - const t = Math.min( 1, step / angle ); - - this.slerp( q, t ); - - return this; - - } - - /** - * Sets this quaternion to the identity quaternion; that is, to the - * quaternion that represents "no rotation". - * - * @return {Quaternion} A reference to this quaternion. - */ - identity() { - - return this.set( 0, 0, 0, 1 ); - - } - - /** - * Inverts this quaternion via {@link Quaternion#conjugate}. The - * quaternion is assumed to have unit length. - * - * @return {Quaternion} A reference to this quaternion. - */ - invert() { - - return this.conjugate(); - - } - - /** - * Returns the rotational conjugate of this quaternion. The conjugate of a - * quaternion represents the same rotation in the opposite direction about - * the rotational axis. - * - * @return {Quaternion} A reference to this quaternion. - */ - conjugate() { - - this._x *= -1; - this._y *= -1; - this._z *= -1; - - this._onChangeCallback(); - - return this; - - } - - /** - * Calculates the dot product of this quaternion and the given one. - * - * @param {Quaternion} v - The quaternion to compute the dot product with. - * @return {number} The result of the dot product. - */ - dot( v ) { - - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - - } - - /** - * Computes the squared Euclidean length (straight-line length) of this quaternion, - * considered as a 4 dimensional vector. This can be useful if you are comparing the - * lengths of two quaternions, as this is a slightly more efficient calculation than - * {@link Quaternion#length}. - * - * @return {number} The squared Euclidean length. - */ - lengthSq() { - - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - - } - - /** - * Computes the Euclidean length (straight-line length) of this quaternion, - * considered as a 4 dimensional vector. - * - * @return {number} The Euclidean length. - */ - length() { - - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - - } - - /** - * Normalizes this quaternion - that is, calculated the quaternion that performs - * the same rotation as this one, but has a length equal to `1`. - * - * @return {Quaternion} A reference to this quaternion. - */ - normalize() { - - let l = this.length(); - - if ( l === 0 ) { - - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; - - } else { - - l = 1 / l; - - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; - - } - - this._onChangeCallback(); - - return this; - - } - - /** - * Multiplies this quaternion by the given one. - * - * @param {Quaternion} q - The quaternion. - * @return {Quaternion} A reference to this quaternion. - */ - multiply( q ) { - - return this.multiplyQuaternions( this, q ); - - } - - /** - * Pre-multiplies this quaternion by the given one. - * - * @param {Quaternion} q - The quaternion. - * @return {Quaternion} A reference to this quaternion. - */ - premultiply( q ) { - - return this.multiplyQuaternions( q, this ); - - } - - /** - * Multiplies the given quaternions and stores the result in this instance. - * - * @param {Quaternion} a - The first quaternion. - * @param {Quaternion} b - The second quaternion. - * @return {Quaternion} A reference to this quaternion. - */ - multiplyQuaternions( a, b ) { - - const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - - this._onChangeCallback(); - - return this; - - } - - /** - * Performs a spherical linear interpolation between this quaternion and the target quaternion. - * - * @param {Quaternion} qb - The target quaternion. - * @param {number} t - The interpolation factor. A value in the range `[0,1]` will interpolate. A value outside the range `[0,1]` will extrapolate. - * @return {Quaternion} A reference to this quaternion. - */ - slerp( qb, t ) { - - let x = qb._x, y = qb._y, z = qb._z, w = qb._w; - - let dot = this.dot( qb ); - - if ( dot < 0 ) { - - x = - x; - y = - y; - z = - z; - w = - w; - - dot = - dot; - - } - - let s = 1 - t; - - if ( dot < 0.9995 ) { - - // slerp - - const theta = Math.acos( dot ); - const sin = Math.sin( theta ); - - s = Math.sin( s * theta ) / sin; - t = Math.sin( t * theta ) / sin; - - this._x = this._x * s + x * t; - this._y = this._y * s + y * t; - this._z = this._z * s + z * t; - this._w = this._w * s + w * t; - - this._onChangeCallback(); - - } else { - - // for small angles, lerp then normalize - - this._x = this._x * s + x * t; - this._y = this._y * s + y * t; - this._z = this._z * s + z * t; - this._w = this._w * s + w * t; - - this.normalize(); // normalize calls _onChangeCallback() - - } - - return this; - - } - - /** - * Performs a spherical linear interpolation between the given quaternions - * and stores the result in this quaternion. - * - * @param {Quaternion} qa - The source quaternion. - * @param {Quaternion} qb - The target quaternion. - * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. - * @return {Quaternion} A reference to this quaternion. - */ - slerpQuaternions( qa, qb, t ) { - - return this.copy( qa ).slerp( qb, t ); - - } - - /** - * Sets this quaternion to a uniformly random, normalized quaternion. - * - * @return {Quaternion} A reference to this quaternion. - */ - random() { - - // Ken Shoemake - // Uniform random rotations - // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992. - - const theta1 = 2 * Math.PI * Math.random(); - const theta2 = 2 * Math.PI * Math.random(); - - const x0 = Math.random(); - const r1 = Math.sqrt( 1 - x0 ); - const r2 = Math.sqrt( x0 ); - - return this.set( - r1 * Math.sin( theta1 ), - r1 * Math.cos( theta1 ), - r2 * Math.sin( theta2 ), - r2 * Math.cos( theta2 ), - ); - - } - - /** - * Returns `true` if this quaternion is equal with the given one. - * - * @param {Quaternion} quaternion - The quaternion to test for equality. - * @return {boolean} Whether this quaternion is equal with the given one. - */ - equals( quaternion ) { - - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - - } - - /** - * Sets this quaternion's components from the given array. - * - * @param {Array} array - An array holding the quaternion component values. - * @param {number} [offset=0] - The offset into the array. - * @return {Quaternion} A reference to this quaternion. - */ - fromArray( array, offset = 0 ) { - - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; - - this._onChangeCallback(); - - return this; - - } - - /** - * Writes the components of this quaternion to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the quaternion components. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The quaternion components. - */ - toArray( array = [], offset = 0 ) { - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; - - return array; - - } - - /** - * Sets the components of this quaternion from the given buffer attribute. - * - * @param {BufferAttribute} attribute - The buffer attribute holding quaternion data. - * @param {number} index - The index into the attribute. - * @return {Quaternion} A reference to this quaternion. - */ - fromBufferAttribute( attribute, index ) { - - this._x = attribute.getX( index ); - this._y = attribute.getY( index ); - this._z = attribute.getZ( index ); - this._w = attribute.getW( index ); - - this._onChangeCallback(); - - return this; - - } - - /** - * This methods defines the serialization result of this class. Returns the - * numerical elements of this quaternion in an array of format `[x, y, z, w]`. - * - * @return {Array} The serialized quaternion. - */ - toJSON() { - - return this.toArray(); - - } - - _onChange( callback ) { - - this._onChangeCallback = callback; - - return this; - - } - - _onChangeCallback() {} - - *[ Symbol.iterator ]() { - - yield this._x; - yield this._y; - yield this._z; - yield this._w; - - } - -} - -/** - * Class representing a 3D vector. A 3D vector is an ordered triplet of numbers - * (labeled x, y and z), which can be used to represent a number of things, such as: - * - * - A point in 3D space. - * - A direction and length in 3D space. In three.js the length will - * always be the Euclidean distance(straight-line distance) from `(0, 0, 0)` to `(x, y, z)` - * and the direction is also measured from `(0, 0, 0)` towards `(x, y, z)`. - * - Any arbitrary ordered triplet of numbers. - * - * There are other things a 3D vector can be used to represent, such as - * momentum vectors and so on, however these are the most - * common uses in three.js. - * - * Iterating through a vector instance will yield its components `(x, y, z)` in - * the corresponding order. - * ```js - * const a = new THREE.Vector3( 0, 1, 0 ); - * - * //no arguments; will be initialised to (0, 0, 0) - * const b = new THREE.Vector3( ); - * - * const d = a.distanceTo( b ); - * ``` - */ -class Vector3 { - - static { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - Vector3.prototype.isVector3 = true; - - } - - /** - * Constructs a new 3D vector. - * - * @param {number} [x=0] - The x value of this vector. - * @param {number} [y=0] - The y value of this vector. - * @param {number} [z=0] - The z value of this vector. - */ - constructor( x = 0, y = 0, z = 0 ) { - - /** - * The x value of this vector. - * - * @type {number} - */ - this.x = x; - - /** - * The y value of this vector. - * - * @type {number} - */ - this.y = y; - - /** - * The z value of this vector. - * - * @type {number} - */ - this.z = z; - - } - - /** - * Sets the vector components. - * - * @param {number} x - The value of the x component. - * @param {number} y - The value of the y component. - * @param {number} z - The value of the z component. - * @return {Vector3} A reference to this vector. - */ - set( x, y, z ) { - - if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - - this.x = x; - this.y = y; - this.z = z; - - return this; - - } - - /** - * Sets the vector components to the same value. - * - * @param {number} scalar - The value to set for all vector components. - * @return {Vector3} A reference to this vector. - */ - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - - return this; - - } - - /** - * Sets the vector's x component to the given value. - * - * @param {number} x - The value to set. - * @return {Vector3} A reference to this vector. - */ - setX( x ) { - - this.x = x; - - return this; - - } - - /** - * Sets the vector's y component to the given value. - * - * @param {number} y - The value to set. - * @return {Vector3} A reference to this vector. - */ - setY( y ) { - - this.y = y; - - return this; - - } - - /** - * Sets the vector's z component to the given value. - * - * @param {number} z - The value to set. - * @return {Vector3} A reference to this vector. - */ - setZ( z ) { - - this.z = z; - - return this; - - } - - /** - * Allows to set a vector component with an index. - * - * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. - * @param {number} value - The value to set. - * @return {Vector3} A reference to this vector. - */ - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - default: throw new Error( 'THREE.Vector3: index is out of range: ' + index ); - - } - - return this; - - } - - /** - * Returns the value of the vector component which matches the given index. - * - * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. - * @return {number} A vector component value. - */ - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - default: throw new Error( 'THREE.Vector3: index is out of range: ' + index ); - - } - - } - - /** - * Returns a new vector with copied values from this instance. - * - * @return {Vector3} A clone of this instance. - */ - clone() { - - return new this.constructor( this.x, this.y, this.z ); - - } - - /** - * Copies the values of the given vector to this instance. - * - * @param {Vector3} v - The vector to copy. - * @return {Vector3} A reference to this vector. - */ - copy( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - - return this; - - } - - /** - * Adds the given vector to this instance. - * - * @param {Vector3} v - The vector to add. - * @return {Vector3} A reference to this vector. - */ - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; - - return this; - - } - - /** - * Adds the given scalar value to all components of this instance. - * - * @param {number} s - The scalar to add. - * @return {Vector3} A reference to this vector. - */ - addScalar( s ) { - - this.x += s; - this.y += s; - this.z += s; - - return this; - - } - - /** - * Adds the given vectors and stores the result in this instance. - * - * @param {Vector3} a - The first vector. - * @param {Vector3} b - The second vector. - * @return {Vector3} A reference to this vector. - */ - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - - return this; - - } - - /** - * Adds the given vector scaled by the given factor to this instance. - * - * @param {Vector3|Vector4} v - The vector. - * @param {number} s - The factor that scales `v`. - * @return {Vector3} A reference to this vector. - */ - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - - return this; - - } - - /** - * Subtracts the given vector from this instance. - * - * @param {Vector3} v - The vector to subtract. - * @return {Vector3} A reference to this vector. - */ - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - - return this; - - } - - /** - * Subtracts the given scalar value from all components of this instance. - * - * @param {number} s - The scalar to subtract. - * @return {Vector3} A reference to this vector. - */ - subScalar( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - - return this; - - } - - /** - * Subtracts the given vectors and stores the result in this instance. - * - * @param {Vector3} a - The first vector. - * @param {Vector3} b - The second vector. - * @return {Vector3} A reference to this vector. - */ - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - - return this; - - } - - /** - * Multiplies the given vector with this instance. - * - * @param {Vector3} v - The vector to multiply. - * @return {Vector3} A reference to this vector. - */ - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - - return this; - - } - - /** - * Multiplies the given scalar value with all components of this instance. - * - * @param {number} scalar - The scalar to multiply. - * @return {Vector3} A reference to this vector. - */ - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - - return this; - - } - - /** - * Multiplies the given vectors and stores the result in this instance. - * - * @param {Vector3} a - The first vector. - * @param {Vector3} b - The second vector. - * @return {Vector3} A reference to this vector. - */ - multiplyVectors( a, b ) { - - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; - - return this; - - } - - /** - * Applies the given Euler rotation to this vector. - * - * @param {Euler} euler - The Euler angles. - * @return {Vector3} A reference to this vector. - */ - applyEuler( euler ) { - - return this.applyQuaternion( _quaternion$3.setFromEuler( euler ) ); - - } - - /** - * Applies a rotation specified by an axis and an angle to this vector. - * - * @param {Vector3} axis - A normalized vector representing the rotation axis. - * @param {number} angle - The angle in radians. - * @return {Vector3} A reference to this vector. - */ - applyAxisAngle( axis, angle ) { - - return this.applyQuaternion( _quaternion$3.setFromAxisAngle( axis, angle ) ); - - } - - /** - * Multiplies this vector with the given 3x3 matrix. - * - * @param {Matrix3} m - The 3x3 matrix. - * @return {Vector3} A reference to this vector. - */ - applyMatrix3( m ) { - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - - return this; - - } - - /** - * Multiplies this vector by the given normal matrix and normalizes - * the result. - * - * @param {Matrix3} m - The normal matrix. - * @return {Vector3} A reference to this vector. - */ - applyNormalMatrix( m ) { - - return this.applyMatrix3( m ).normalize(); - - } - - /** - * Multiplies this vector (with an implicit 1 in the 4th dimension) by m, and - * divides by perspective. - * - * @param {Matrix4} m - The matrix to apply. - * @return {Vector3} A reference to this vector. - */ - applyMatrix4( m ) { - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; - - return this; - - } - - /** - * Applies the given Quaternion to this vector. - * - * @param {Quaternion} q - The Quaternion. - * @return {Vector3} A reference to this vector. - */ - applyQuaternion( q ) { - - // quaternion q is assumed to have unit length - - const vx = this.x, vy = this.y, vz = this.z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - - // t = 2 * cross( q.xyz, v ); - const tx = 2 * ( qy * vz - qz * vy ); - const ty = 2 * ( qz * vx - qx * vz ); - const tz = 2 * ( qx * vy - qy * vx ); - - // v + q.w * t + cross( q.xyz, t ); - this.x = vx + qw * tx + qy * tz - qz * ty; - this.y = vy + qw * ty + qz * tx - qx * tz; - this.z = vz + qw * tz + qx * ty - qy * tx; - - return this; - - } - - /** - * Projects this vector from world space into the camera's normalized - * device coordinate (NDC) space. - * - * @param {Camera} camera - The camera. - * @return {Vector3} A reference to this vector. - */ - project( camera ) { - - return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); - - } - - /** - * Unprojects this vector from the camera's normalized device coordinate (NDC) - * space into world space. - * - * @param {Camera} camera - The camera. - * @return {Vector3} A reference to this vector. - */ - unproject( camera ) { - - return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); - - } - - /** - * Transforms this vector by the upper left 3x3 sub-matrix of the given 4x4 matrix, - * and normalizes the result. - * - * @param {Matrix4} m - The matrix. - * @return {Vector3} A reference to this vector. - */ - transformDirection( m ) { - - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; - - return this.normalize(); - - } - - /** - * Divides this instance by the given vector. - * - * @param {Vector3} v - The vector to divide. - * @return {Vector3} A reference to this vector. - */ - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; - - return this; - - } - - /** - * Divides this vector by the given scalar. - * - * @param {number} scalar - The scalar to divide. - * @return {Vector3} A reference to this vector. - */ - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - /** - * If this vector's x, y or z value is greater than the given vector's x, y or z - * value, replace that value with the corresponding min value. - * - * @param {Vector3} v - The vector. - * @return {Vector3} A reference to this vector. - */ - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - - return this; - - } - - /** - * If this vector's x, y or z value is less than the given vector's x, y or z - * value, replace that value with the corresponding max value. - * - * @param {Vector3} v - The vector. - * @return {Vector3} A reference to this vector. - */ - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - - return this; - - } - - /** - * If this vector's x, y or z value is greater than the max vector's x, y or z - * value, it is replaced by the corresponding value. - * If this vector's x, y or z value is less than the min vector's x, y or z value, - * it is replaced by the corresponding value. - * - * @param {Vector3} min - The minimum x, y and z values. - * @param {Vector3} max - The maximum x, y and z values in the desired range. - * @return {Vector3} A reference to this vector. - */ - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = clamp( this.x, min.x, max.x ); - this.y = clamp( this.y, min.y, max.y ); - this.z = clamp( this.z, min.z, max.z ); - - return this; - - } - - /** - * If this vector's x, y or z values are greater than the max value, they are - * replaced by the max value. - * If this vector's x, y or z values are less than the min value, they are - * replaced by the min value. - * - * @param {number} minVal - The minimum value the components will be clamped to. - * @param {number} maxVal - The maximum value the components will be clamped to. - * @return {Vector3} A reference to this vector. - */ - clampScalar( minVal, maxVal ) { - - this.x = clamp( this.x, minVal, maxVal ); - this.y = clamp( this.y, minVal, maxVal ); - this.z = clamp( this.z, minVal, maxVal ); - - return this; - - } - - /** - * If this vector's length is greater than the max value, it is replaced by - * the max value. - * If this vector's length is less than the min value, it is replaced by the - * min value. - * - * @param {number} min - The minimum value the vector length will be clamped to. - * @param {number} max - The maximum value the vector length will be clamped to. - * @return {Vector3} A reference to this vector. - */ - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); - - } - - /** - * The components of this vector are rounded down to the nearest integer value. - * - * @return {Vector3} A reference to this vector. - */ - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - - return this; - - } - - /** - * The components of this vector are rounded up to the nearest integer value. - * - * @return {Vector3} A reference to this vector. - */ - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - - return this; - - } - - /** - * The components of this vector are rounded to the nearest integer value - * - * @return {Vector3} A reference to this vector. - */ - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - - return this; - - } - - /** - * The components of this vector are rounded towards zero (up if negative, - * down if positive) to an integer value. - * - * @return {Vector3} A reference to this vector. - */ - roundToZero() { - - this.x = Math.trunc( this.x ); - this.y = Math.trunc( this.y ); - this.z = Math.trunc( this.z ); - - return this; - - } - - /** - * Inverts this vector - i.e. sets x = -x, y = -y and z = -z. - * - * @return {Vector3} A reference to this vector. - */ - negate() { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - - return this; - - } - - /** - * Calculates the dot product of the given vector with this instance. - * - * @param {Vector3} v - The vector to compute the dot product with. - * @return {number} The result of the dot product. - */ - dot( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z; - - } - - /** - * Computes the square of the Euclidean length (straight-line length) from - * (0, 0, 0) to (x, y, z). If you are comparing the lengths of vectors, you should - * compare the length squared instead as it is slightly more efficient to calculate. - * - * @return {number} The square length of this vector. - */ - lengthSq() { - - return this.x * this.x + this.y * this.y + this.z * this.z; - - } - - /** - * Computes the Euclidean length (straight-line length) from (0, 0, 0) to (x, y, z). - * - * @return {number} The length of this vector. - */ - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); - - } - - /** - * Computes the Manhattan length of this vector. - * - * @return {number} The length of this vector. - */ - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - - } - - /** - * Converts this vector to a unit vector - that is, sets it equal to a vector - * with the same direction as this one, but with a vector length of `1`. - * - * @return {Vector3} A reference to this vector. - */ - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - /** - * Sets this vector to a vector with the same direction as this one, but - * with the specified length. - * - * @param {number} length - The new length of this vector. - * @return {Vector3} A reference to this vector. - */ - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - /** - * Linearly interpolates between the given vector and this instance, where - * alpha is the percent distance along the line - alpha = 0 will be this - * vector, and alpha = 1 will be the given one. - * - * @param {Vector3} v - The vector to interpolate towards. - * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. - * @return {Vector3} A reference to this vector. - */ - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - - return this; - - } - - /** - * Linearly interpolates between the given vectors, where alpha is the percent - * distance along the line - alpha = 0 will be first vector, and alpha = 1 will - * be the second one. The result is stored in this instance. - * - * @param {Vector3} v1 - The first vector. - * @param {Vector3} v2 - The second vector. - * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. - * @return {Vector3} A reference to this vector. - */ - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - - return this; - - } - - /** - * Calculates the cross product of the given vector with this instance. - * - * @param {Vector3} v - The vector to compute the cross product with. - * @return {Vector3} The result of the cross product. - */ - cross( v ) { - - return this.crossVectors( this, v ); - - } - - /** - * Calculates the cross product of the given vectors and stores the result - * in this instance. - * - * @param {Vector3} a - The first vector. - * @param {Vector3} b - The second vector. - * @return {Vector3} A reference to this vector. - */ - crossVectors( a, b ) { - - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; - - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; - - return this; - - } - - /** - * Projects this vector onto the given one. - * - * @param {Vector3} v - The vector to project to. - * @return {Vector3} A reference to this vector. - */ - projectOnVector( v ) { - - const denominator = v.lengthSq(); - - if ( denominator === 0 ) return this.set( 0, 0, 0 ); - - const scalar = v.dot( this ) / denominator; - - return this.copy( v ).multiplyScalar( scalar ); - - } - - /** - * Projects this vector onto a plane by subtracting this - * vector projected onto the plane's normal from this vector. - * - * @param {Vector3} planeNormal - The plane normal. - * @return {Vector3} A reference to this vector. - */ - projectOnPlane( planeNormal ) { - - _vector$8.copy( this ).projectOnVector( planeNormal ); - - return this.sub( _vector$8 ); - - } - - /** - * Reflects this vector off a plane orthogonal to the given normal vector. - * - * @param {Vector3} normal - The (normalized) normal vector. - * @return {Vector3} A reference to this vector. - */ - reflect( normal ) { - - return this.sub( _vector$8.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); - - } - /** - * Returns the angle between the given vector and this instance in radians. - * - * @param {Vector3} v - The vector to compute the angle with. - * @return {number} The angle in radians. - */ - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - - if ( denominator === 0 ) return Math.PI / 2; - - const theta = this.dot( v ) / denominator; - - // clamp, to handle numerical problems - - return Math.acos( clamp( theta, -1, 1 ) ); - - } - - /** - * Computes the distance from the given vector to this instance. - * - * @param {Vector3} v - The vector to compute the distance to. - * @return {number} The distance. - */ - distanceTo( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - } - - /** - * Computes the squared distance from the given vector to this instance. - * If you are just comparing the distance with another distance, you should compare - * the distance squared instead as it is slightly more efficient to calculate. - * - * @param {Vector3} v - The vector to compute the squared distance to. - * @return {number} The squared distance. - */ - distanceToSquared( v ) { - - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; - - return dx * dx + dy * dy + dz * dz; - - } - - /** - * Computes the Manhattan distance from the given vector to this instance. - * - * @param {Vector3} v - The vector to compute the Manhattan distance to. - * @return {number} The Manhattan distance. - */ - manhattanDistanceTo( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - - } - - /** - * Sets the vector components from the given spherical coordinates. - * - * @param {Spherical} s - The spherical coordinates. - * @return {Vector3} A reference to this vector. - */ - setFromSpherical( s ) { - - return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); - - } - - /** - * Sets the vector components from the given spherical coordinates. - * - * @param {number} radius - The radius. - * @param {number} phi - The phi angle in radians. - * @param {number} theta - The theta angle in radians. - * @return {Vector3} A reference to this vector. - */ - setFromSphericalCoords( radius, phi, theta ) { - - const sinPhiRadius = Math.sin( phi ) * radius; - - this.x = sinPhiRadius * Math.sin( theta ); - this.y = Math.cos( phi ) * radius; - this.z = sinPhiRadius * Math.cos( theta ); - - return this; - - } - - /** - * Sets the vector components from the given cylindrical coordinates. - * - * @param {Cylindrical} c - The cylindrical coordinates. - * @return {Vector3} A reference to this vector. - */ - setFromCylindrical( c ) { - - return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); - - } - - /** - * Sets the vector components from the given cylindrical coordinates. - * - * @param {number} radius - The radius. - * @param {number} theta - The theta angle in radians. - * @param {number} y - The y value. - * @return {Vector3} A reference to this vector. - */ - setFromCylindricalCoords( radius, theta, y ) { - - this.x = radius * Math.sin( theta ); - this.y = y; - this.z = radius * Math.cos( theta ); - - return this; - - } - - /** - * Sets the vector components to the position elements of the - * given transformation matrix. - * - * @param {Matrix4} m - The 4x4 matrix. - * @return {Vector3} A reference to this vector. - */ - setFromMatrixPosition( m ) { - - const e = m.elements; - - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; - - return this; - - } - - /** - * Sets the vector components to the scale elements of the - * given transformation matrix. - * - * @param {Matrix4} m - The 4x4 matrix. - * @return {Vector3} A reference to this vector. - */ - setFromMatrixScale( m ) { - - const sx = this.setFromMatrixColumn( m, 0 ).length(); - const sy = this.setFromMatrixColumn( m, 1 ).length(); - const sz = this.setFromMatrixColumn( m, 2 ).length(); - - this.x = sx; - this.y = sy; - this.z = sz; - - return this; - - } - - /** - * Sets the vector components from the specified matrix column. - * - * @param {Matrix4} m - The 4x4 matrix. - * @param {number} index - The column index. - * @return {Vector3} A reference to this vector. - */ - setFromMatrixColumn( m, index ) { - - return this.fromArray( m.elements, index * 4 ); - - } - - /** - * Sets the vector components from the specified matrix column. - * - * @param {Matrix3} m - The 3x3 matrix. - * @param {number} index - The column index. - * @return {Vector3} A reference to this vector. - */ - setFromMatrix3Column( m, index ) { - - return this.fromArray( m.elements, index * 3 ); - - } - - /** - * Sets the vector components from the given Euler angles. - * - * @param {Euler} e - The Euler angles to set. - * @return {Vector3} A reference to this vector. - */ - setFromEuler( e ) { - - this.x = e._x; - this.y = e._y; - this.z = e._z; - - return this; - - } - - /** - * Sets the vector components from the RGB components of the - * given color. - * - * @param {Color} c - The color to set. - * @return {Vector3} A reference to this vector. - */ - setFromColor( c ) { - - this.x = c.r; - this.y = c.g; - this.z = c.b; - - return this; - - } - - /** - * Returns `true` if this vector is equal with the given one. - * - * @param {Vector3} v - The vector to test for equality. - * @return {boolean} Whether this vector is equal with the given one. - */ - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); - - } - - /** - * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]` - * and z value to be `array[ offset + 2 ]`. - * - * @param {Array} array - An array holding the vector component values. - * @param {number} [offset=0] - The offset into the array. - * @return {Vector3} A reference to this vector. - */ - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - - return this; - - } - - /** - * Writes the components of this vector to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the vector components. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The vector components. - */ - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - - return array; - - } - - /** - * Sets the components of this vector from the given buffer attribute. - * - * @param {BufferAttribute} attribute - The buffer attribute holding vector data. - * @param {number} index - The index into the attribute. - * @return {Vector3} A reference to this vector. - */ - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - - return this; - - } - - /** - * Sets each component of this vector to a pseudo-random value between `0` and - * `1`, excluding `1`. - * - * @return {Vector3} A reference to this vector. - */ - random() { - - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - - return this; - - } - - /** - * Sets this vector to a uniformly random point on a unit sphere. - * - * @return {Vector3} A reference to this vector. - */ - randomDirection() { - - // https://mathworld.wolfram.com/SpherePointPicking.html - - const theta = Math.random() * Math.PI * 2; - const u = Math.random() * 2 - 1; - const c = Math.sqrt( 1 - u * u ); - - this.x = c * Math.cos( theta ); - this.y = u; - this.z = c * Math.sin( theta ); - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - yield this.z; - - } - -} - -const _vector$8 = /*@__PURE__*/ new Vector3(); -const _quaternion$3 = /*@__PURE__*/ new Quaternion(); - -/** - * Represents a 3x3 matrix. - * - * A Note on Row-Major and Column-Major Ordering: - * - * The constructor and {@link Matrix3#set} method take arguments in - * [row-major](https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order) - * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. - * This means that calling: - * ```js - * const m = new THREE.Matrix(); - * m.set( 11, 12, 13, - * 21, 22, 23, - * 31, 32, 33 ); - * ``` - * will result in the elements array containing: - * ```js - * m.elements = [ 11, 21, 31, - * 12, 22, 32, - * 13, 23, 33 ]; - * ``` - * and internally all calculations are performed using column-major ordering. - * However, as the actual ordering makes no difference mathematically and - * most people are used to thinking about matrices in row-major order, the - * three.js documentation shows matrices in row-major order. Just bear in - * mind that if you are reading the source code, you'll have to take the - * transpose of any matrices outlined here to make sense of the calculations. - */ -class Matrix3 { - - static { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - Matrix3.prototype.isMatrix3 = true; - - } - - /** - * Constructs a new 3x3 matrix. The arguments are supposed to be - * in row-major order. If no arguments are provided, the constructor - * initializes the matrix as an identity matrix. - * - * @param {number} [n11] - 1-1 matrix element. - * @param {number} [n12] - 1-2 matrix element. - * @param {number} [n13] - 1-3 matrix element. - * @param {number} [n21] - 2-1 matrix element. - * @param {number} [n22] - 2-2 matrix element. - * @param {number} [n23] - 2-3 matrix element. - * @param {number} [n31] - 3-1 matrix element. - * @param {number} [n32] - 3-2 matrix element. - * @param {number} [n33] - 3-3 matrix element. - */ - constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - - /** - * A column-major list of matrix values. - * - * @type {Array} - */ - this.elements = [ - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ]; - - if ( n11 !== undefined ) { - - this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); - - } - - } - - /** - * Sets the elements of the matrix.The arguments are supposed to be - * in row-major order. - * - * @param {number} [n11] - 1-1 matrix element. - * @param {number} [n12] - 1-2 matrix element. - * @param {number} [n13] - 1-3 matrix element. - * @param {number} [n21] - 2-1 matrix element. - * @param {number} [n22] - 2-2 matrix element. - * @param {number} [n23] - 2-3 matrix element. - * @param {number} [n31] - 3-1 matrix element. - * @param {number} [n32] - 3-2 matrix element. - * @param {number} [n33] - 3-3 matrix element. - * @return {Matrix3} A reference to this matrix. - */ - set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - - const te = this.elements; - - te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; - te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; - te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; - - return this; - - } - - /** - * Sets this matrix to the 3x3 identity matrix. - * - * @return {Matrix3} A reference to this matrix. - */ - identity() { - - this.set( - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ); - - return this; - - } - - /** - * Copies the values of the given matrix to this instance. - * - * @param {Matrix3} m - The matrix to copy. - * @return {Matrix3} A reference to this matrix. - */ - copy( m ) { - - const te = this.elements; - const me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; - te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; - te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; - - return this; - - } - - /** - * Extracts the basis of this matrix into the three axis vectors provided. - * - * @param {Vector3} xAxis - The basis's x axis. - * @param {Vector3} yAxis - The basis's y axis. - * @param {Vector3} zAxis - The basis's z axis. - * @return {Matrix3} A reference to this matrix. - */ - extractBasis( xAxis, yAxis, zAxis ) { - - xAxis.setFromMatrix3Column( this, 0 ); - yAxis.setFromMatrix3Column( this, 1 ); - zAxis.setFromMatrix3Column( this, 2 ); - - return this; - - } - - /** - * Set this matrix to the upper 3x3 matrix of the given 4x4 matrix. - * - * @param {Matrix4} m - The 4x4 matrix. - * @return {Matrix3} A reference to this matrix. - */ - setFromMatrix4( m ) { - - const me = m.elements; - - this.set( - - me[ 0 ], me[ 4 ], me[ 8 ], - me[ 1 ], me[ 5 ], me[ 9 ], - me[ 2 ], me[ 6 ], me[ 10 ] - - ); - - return this; - - } - - /** - * Post-multiplies this matrix by the given 3x3 matrix. - * - * @param {Matrix3} m - The matrix to multiply with. - * @return {Matrix3} A reference to this matrix. - */ - multiply( m ) { - - return this.multiplyMatrices( this, m ); - - } - - /** - * Pre-multiplies this matrix by the given 3x3 matrix. - * - * @param {Matrix3} m - The matrix to multiply with. - * @return {Matrix3} A reference to this matrix. - */ - premultiply( m ) { - - return this.multiplyMatrices( m, this ); - - } - - /** - * Multiples the given 3x3 matrices and stores the result - * in this matrix. - * - * @param {Matrix3} a - The first matrix. - * @param {Matrix3} b - The second matrix. - * @return {Matrix3} A reference to this matrix. - */ - multiplyMatrices( a, b ) { - - const ae = a.elements; - const be = b.elements; - const te = this.elements; - - const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; - const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; - const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; - - const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; - const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; - const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; - te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; - te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; - te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; - te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; - te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; - te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; - - return this; - - } - - /** - * Multiplies every component of the matrix by the given scalar. - * - * @param {number} s - The scalar. - * @return {Matrix3} A reference to this matrix. - */ - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; - te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; - te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; - - return this; - - } - - /** - * Computes and returns the determinant of this matrix. - * - * @return {number} The determinant. - */ - determinant() { - - const te = this.elements; - - const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], - d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], - g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; - - return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; - - } - - /** - * Inverts this matrix, using the [analytic method](https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution). - * You can not invert with a determinant of zero. If you attempt this, the method produces - * a zero matrix instead. - * - * @return {Matrix3} A reference to this matrix. - */ - invert() { - - const te = this.elements, - - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], - n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], - n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], - - t11 = n33 * n22 - n32 * n23, - t12 = n32 * n13 - n33 * n12, - t13 = n23 * n12 - n22 * n13, - - det = n11 * t11 + n21 * t12 + n31 * t13; - - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - - const detInv = 1 / det; - - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; - te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; - - te[ 3 ] = t12 * detInv; - te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; - te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; - - te[ 6 ] = t13 * detInv; - te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; - te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; - - return this; - - } - - /** - * Transposes this matrix in place. - * - * @return {Matrix3} A reference to this matrix. - */ - transpose() { - - let tmp; - const m = this.elements; - - tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; - tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; - tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; - - return this; - - } - - /** - * Computes the normal matrix which is the inverse transpose of the upper - * left 3x3 portion of the given 4x4 matrix. - * - * @param {Matrix4} matrix4 - The 4x4 matrix. - * @return {Matrix3} A reference to this matrix. - */ - getNormalMatrix( matrix4 ) { - - return this.setFromMatrix4( matrix4 ).invert().transpose(); - - } - - /** - * Transposes this matrix into the supplied array, and returns itself unchanged. - * - * @param {Array} r - An array to store the transposed matrix elements. - * @return {Matrix3} A reference to this matrix. - */ - transposeIntoArray( r ) { - - const m = this.elements; - - r[ 0 ] = m[ 0 ]; - r[ 1 ] = m[ 3 ]; - r[ 2 ] = m[ 6 ]; - r[ 3 ] = m[ 1 ]; - r[ 4 ] = m[ 4 ]; - r[ 5 ] = m[ 7 ]; - r[ 6 ] = m[ 2 ]; - r[ 7 ] = m[ 5 ]; - r[ 8 ] = m[ 8 ]; - - return this; - - } - - /** - * Sets the UV transform matrix from offset, repeat, rotation, and center. - * - * @param {number} tx - Offset x. - * @param {number} ty - Offset y. - * @param {number} sx - Repeat x. - * @param {number} sy - Repeat y. - * @param {number} rotation - Rotation, in radians. Positive values rotate counterclockwise. - * @param {number} cx - Center x of rotation. - * @param {number} cy - Center y of rotation - * @return {Matrix3} A reference to this matrix. - */ - setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { - - const c = Math.cos( rotation ); - const s = Math.sin( rotation ); - - this.set( - sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, - 0, 0, 1 - ); - - return this; - - } - - /** - * Scales this matrix with the given scalar values. - * - * @deprecated - * @param {number} sx - The amount to scale in the X axis. - * @param {number} sy - The amount to scale in the Y axis. - * @return {Matrix3} A reference to this matrix. - */ - scale( sx, sy ) { - - warnOnce( 'Matrix3: .scale() is deprecated. Use .makeScale() instead.' ); // @deprecated r185 - - this.premultiply( _m3.makeScale( sx, sy ) ); - - return this; - - } - - /** - * Rotates this matrix by the given angle. - * - * @deprecated - * @param {number} theta - The rotation in radians. - * @return {Matrix3} A reference to this matrix. - */ - rotate( theta ) { - - warnOnce( 'Matrix3: .rotate() is deprecated. Use .makeRotation() instead.' ); // @deprecated r185 - - this.premultiply( _m3.makeRotation( - theta ) ); - - return this; - - } - - /** - * Translates this matrix by the given scalar values. - * - * @deprecated - * @param {number} tx - The amount to translate in the X axis. - * @param {number} ty - The amount to translate in the Y axis. - * @return {Matrix3} A reference to this matrix. - */ - translate( tx, ty ) { - - warnOnce( 'Matrix3: .translate() is deprecated. Use .makeTranslation() instead.' ); // @deprecated r185 - - this.premultiply( _m3.makeTranslation( tx, ty ) ); - - return this; - - } - - // for 2D Transforms - - /** - * Sets this matrix as a 2D translation transform. - * - * @param {number|Vector2} x - The amount to translate in the X axis or alternatively a translation vector. - * @param {number} y - The amount to translate in the Y axis. - * @return {Matrix3} A reference to this matrix. - */ - makeTranslation( x, y ) { - - if ( x.isVector2 ) { - - this.set( - - 1, 0, x.x, - 0, 1, x.y, - 0, 0, 1 - - ); - - } else { - - this.set( - - 1, 0, x, - 0, 1, y, - 0, 0, 1 - - ); - - } - - return this; - - } - - /** - * Sets this matrix as a 2D rotational transformation. - * - * @param {number} theta - The rotation in radians. - * @return {Matrix3} A reference to this matrix. - */ - makeRotation( theta ) { - - // counterclockwise - - const c = Math.cos( theta ); - const s = Math.sin( theta ); - - this.set( - - c, - s, 0, - s, c, 0, - 0, 0, 1 - - ); - - return this; - - } - - /** - * Sets this matrix as a 2D scale transform. - * - * @param {number} x - The amount to scale in the X axis. - * @param {number} y - The amount to scale in the Y axis. - * @return {Matrix3} A reference to this matrix. - */ - makeScale( x, y ) { - - this.set( - - x, 0, 0, - 0, y, 0, - 0, 0, 1 - - ); - - return this; - - } - - /** - * Returns `true` if this matrix is equal with the given one. - * - * @param {Matrix3} matrix - The matrix to test for equality. - * @return {boolean} Whether this matrix is equal with the given one. - */ - equals( matrix ) { - - const te = this.elements; - const me = matrix.elements; - - for ( let i = 0; i < 9; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - } - - /** - * Sets the elements of the matrix from the given array. - * - * @param {Array} array - The matrix elements in column-major order. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Matrix3} A reference to this matrix. - */ - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - } - - /** - * Writes the elements of this matrix to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The matrix elements in column-major order. - */ - toArray( array = [], offset = 0 ) { - - const te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - - array[ offset + 3 ] = te[ 3 ]; - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - array[ offset + 8 ] = te[ 8 ]; - - return array; - - } - - /** - * Returns a matrix with copied values from this instance. - * - * @return {Matrix3} A clone of this instance. - */ - clone() { - - return new this.constructor().fromArray( this.elements ); - - } - -} - -const _m3 = /*@__PURE__*/ new Matrix3(); - -const LINEAR_REC709_TO_XYZ = /*@__PURE__*/ new Matrix3().set( - 0.4123908, 0.3575843, 0.1804808, - 0.2126390, 0.7151687, 0.0721923, - 0.0193308, 0.1191948, 0.9505322 -); - -const XYZ_TO_LINEAR_REC709 = /*@__PURE__*/ new Matrix3().set( - 3.2409699, -1.5373832, -0.4986108, - -0.9692436, 1.8759675, 0.0415551, - 0.0556301, -0.203977, 1.0569715 -); - -function createColorManagement() { - - const ColorManagement = { - - enabled: true, - - workingColorSpace: LinearSRGBColorSpace, - - /** - * Implementations of supported color spaces. - * - * Required: - * - primaries: chromaticity coordinates [ rx ry gx gy bx by ] - * - whitePoint: reference white [ x y ] - * - transfer: transfer function (pre-defined) - * - toXYZ: Matrix3 RGB to XYZ transform - * - fromXYZ: Matrix3 XYZ to RGB transform - * - luminanceCoefficients: RGB luminance coefficients - * - * Optional: - * - outputColorSpaceConfig: { drawingBufferColorSpace: ColorSpace, toneMappingMode: 'extended' | 'standard' } - * - workingColorSpaceConfig: { unpackColorSpace: ColorSpace } - * - * Reference: - * - https://www.russellcottrell.com/photo/matrixCalculator.htm - */ - spaces: {}, - - convert: function ( color, sourceColorSpace, targetColorSpace ) { - - if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { - - return color; - - } - - if ( this.spaces[ sourceColorSpace ].transfer === SRGBTransfer ) { - - color.r = SRGBToLinear( color.r ); - color.g = SRGBToLinear( color.g ); - color.b = SRGBToLinear( color.b ); - - } - - if ( this.spaces[ sourceColorSpace ].primaries !== this.spaces[ targetColorSpace ].primaries ) { - - color.applyMatrix3( this.spaces[ sourceColorSpace ].toXYZ ); - color.applyMatrix3( this.spaces[ targetColorSpace ].fromXYZ ); - - } - - if ( this.spaces[ targetColorSpace ].transfer === SRGBTransfer ) { - - color.r = LinearToSRGB( color.r ); - color.g = LinearToSRGB( color.g ); - color.b = LinearToSRGB( color.b ); - - } - - return color; - - }, - - workingToColorSpace: function ( color, targetColorSpace ) { - - return this.convert( color, this.workingColorSpace, targetColorSpace ); - - }, - - colorSpaceToWorking: function ( color, sourceColorSpace ) { - - return this.convert( color, sourceColorSpace, this.workingColorSpace ); - - }, - - getPrimaries: function ( colorSpace ) { - - return this.spaces[ colorSpace ].primaries; - - }, - - getTransfer: function ( colorSpace ) { - - if ( colorSpace === NoColorSpace ) return LinearTransfer; - - return this.spaces[ colorSpace ].transfer; - - }, - - getToneMappingMode: function ( colorSpace ) { - - return this.spaces[ colorSpace ].outputColorSpaceConfig.toneMappingMode || 'standard'; - - }, - - getLuminanceCoefficients: function ( target, colorSpace = this.workingColorSpace ) { - - return target.fromArray( this.spaces[ colorSpace ].luminanceCoefficients ); - - }, - - define: function ( colorSpaces ) { - - Object.assign( this.spaces, colorSpaces ); - - }, - - // Internal APIs - - _getMatrix: function ( targetMatrix, sourceColorSpace, targetColorSpace ) { - - return targetMatrix - .copy( this.spaces[ sourceColorSpace ].toXYZ ) - .multiply( this.spaces[ targetColorSpace ].fromXYZ ); - - }, - - _getDrawingBufferColorSpace: function ( colorSpace ) { - - return this.spaces[ colorSpace ].outputColorSpaceConfig.drawingBufferColorSpace; - - }, - - _getUnpackColorSpace: function ( colorSpace = this.workingColorSpace ) { - - return this.spaces[ colorSpace ].workingColorSpaceConfig.unpackColorSpace; - - }, - - // Deprecated - - fromWorkingColorSpace: function ( color, targetColorSpace ) { - - warnOnce( 'ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace().' ); // @deprecated, r177 - - return ColorManagement.workingToColorSpace( color, targetColorSpace ); - - }, - - toWorkingColorSpace: function ( color, sourceColorSpace ) { - - warnOnce( 'ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking().' ); // @deprecated, r177 - - return ColorManagement.colorSpaceToWorking( color, sourceColorSpace ); - - }, - - }; - - /****************************************************************************** - * sRGB definitions - */ - - const REC709_PRIMARIES = [ 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 ]; - const REC709_LUMINANCE_COEFFICIENTS = [ 0.2126, 0.7152, 0.0722 ]; - const D65 = [ 0.3127, 0.3290 ]; - - ColorManagement.define( { - - [ LinearSRGBColorSpace ]: { - primaries: REC709_PRIMARIES, - whitePoint: D65, - transfer: LinearTransfer, - toXYZ: LINEAR_REC709_TO_XYZ, - fromXYZ: XYZ_TO_LINEAR_REC709, - luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, - workingColorSpaceConfig: { unpackColorSpace: SRGBColorSpace }, - outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } - }, - - [ SRGBColorSpace ]: { - primaries: REC709_PRIMARIES, - whitePoint: D65, - transfer: SRGBTransfer, - toXYZ: LINEAR_REC709_TO_XYZ, - fromXYZ: XYZ_TO_LINEAR_REC709, - luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, - outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } - }, - - } ); - - return ColorManagement; - -} - -const ColorManagement = /*@__PURE__*/ createColorManagement(); - -function SRGBToLinear( c ) { - - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - -} - -function LinearToSRGB( c ) { - - return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; - -} - -let _canvas; - -/** - * A class containing utility functions for images. - * - * @hideconstructor - */ -class ImageUtils { - - /** - * Returns a data URI containing a representation of the given image. - * - * @param {(HTMLImageElement|HTMLCanvasElement)} image - The image object. - * @param {string} [type='image/png'] - Indicates the image format. - * @return {string} The data URI. - */ - static getDataURL( image, type = 'image/png' ) { - - if ( /^data:/i.test( image.src ) ) { - - return image.src; - - } - - if ( typeof HTMLCanvasElement === 'undefined' ) { - - return image.src; - - } - - let canvas; - - if ( image instanceof HTMLCanvasElement ) { - - canvas = image; - - } else { - - if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); - - _canvas.width = image.width; - _canvas.height = image.height; - - const context = _canvas.getContext( '2d' ); - - if ( image instanceof ImageData ) { - - context.putImageData( image, 0, 0 ); - - } else { - - context.drawImage( image, 0, 0, image.width, image.height ); - - } - - canvas = _canvas; - - } - - return canvas.toDataURL( type ); - - } - - /** - * Converts the given sRGB image data to linear color space. - * - * @param {(HTMLImageElement|HTMLCanvasElement|ImageBitmap|Object)} image - The image object. - * @return {HTMLCanvasElement|Object} The converted image. - */ - static sRGBToLinear( image ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - const canvas = createElementNS( 'canvas' ); - - canvas.width = image.width; - canvas.height = image.height; - - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, image.width, image.height ); - - const imageData = context.getImageData( 0, 0, image.width, image.height ); - const data = imageData.data; - - for ( let i = 0; i < data.length; i ++ ) { - - data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; - - } - - context.putImageData( imageData, 0, 0 ); - - return canvas; - - } else if ( image.data ) { - - const data = image.data.slice( 0 ); - - for ( let i = 0; i < data.length; i ++ ) { - - if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { - - data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); - - } else { - - // assuming float - - data[ i ] = SRGBToLinear( data[ i ] ); - - } - - } - - return { - data: data, - width: image.width, - height: image.height - }; - - } else { - - warn( 'ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); - return image; - - } - - } - -} - -let _sourceId = 0; - -/** - * Represents the data source of a texture. - * - * The main purpose of this class is to decouple the data definition from the texture - * definition so the same data can be used with multiple texture instances. - */ -class TextureSource { - - /** - * Constructs a new texture source. - * - * @param {any} [data=null] - The data definition of a texture. - */ - constructor( data = null ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isTextureSource = true; - - /** - * The ID of the source. - * - * @name TextureSource#id - * @type {number} - * @readonly - */ - Object.defineProperty( this, 'id', { value: _sourceId ++ } ); - - /** - * The UUID of the source. - * - * @type {string} - * @readonly - */ - this.uuid = generateUUID(); - - /** - * The data definition of a texture. - * - * @type {any} - */ - this.data = data; - - /** - * This property is only relevant when {@link TextureSource#needsUpdate} is set to `true` and - * provides more control on how texture data should be processed. When `dataReady` is set - * to `false`, the engine performs the memory allocation (if necessary) but does not transfer - * the data into the GPU memory. - * - * @type {boolean} - * @default true - */ - this.dataReady = true; - - /** - * This starts at `0` and counts how many times {@link TextureSource#needsUpdate} is set to `true`. - * - * @type {number} - * @readonly - * @default 0 - */ - this.version = 0; - - } - - /** - * Returns the dimensions of the source into the given target vector. - * - * @param {(Vector2|Vector3)} target - The target object the result is written into. - * @return {(Vector2|Vector3)} The dimensions of the source. - */ - getSize( target ) { - - const data = this.data; - - if ( ( typeof HTMLVideoElement !== 'undefined' ) && ( data instanceof HTMLVideoElement ) ) { - - target.set( data.videoWidth, data.videoHeight, 0 ); - - } else if ( ( typeof VideoFrame !== 'undefined' ) && ( data instanceof VideoFrame ) ) { - - target.set( data.displayWidth, data.displayHeight, 0 ); - - } else if ( data !== null ) { - - target.set( data.width, data.height, data.depth || 0 ); - - } else { - - target.set( 0, 0, 0 ); - - } - - return target; - - } - - /** - * When the property is set to `true`, the engine allocates the memory - * for the texture (if necessary) and triggers the actual texture upload - * to the GPU next time the source is used. - * - * @type {boolean} - * @default false - * @param {boolean} value - */ - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - /** - * Serializes the source into JSON. - * - * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. - * @return {Object} A JSON object representing the serialized source. - * @see {@link ObjectLoader#parse} - */ - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { - - return meta.images[ this.uuid ]; - - } - - const output = { - uuid: this.uuid, - url: '' - }; - - const data = this.data; - - if ( data !== null ) { - - let url; - - if ( Array.isArray( data ) ) { - - // cube texture - - url = []; - - for ( let i = 0, l = data.length; i < l; i ++ ) { - - if ( data[ i ].isDataTexture ) { - - url.push( serializeImage( data[ i ].image ) ); - - } else { - - url.push( serializeImage( data[ i ] ) ); - - } - - } - - } else { - - // texture - - url = serializeImage( data ); - - } - - output.url = url; - - } - - if ( ! isRootObject ) { - - meta.images[ this.uuid ] = output; - - } - - return output; - - } - -} - -function serializeImage( image ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - // default images - - return ImageUtils.getDataURL( image ); - - } else { - - if ( image.data ) { - - // images of DataTexture - - return { - data: Array.from( image.data ), - width: image.width, - height: image.height, - type: image.data.constructor.name - }; - - } else { - - warn( 'Texture: Unable to serialize Texture.' ); - return {}; - - } - - } - -} - -let _textureId = 0; - -const _tempVec3 = /*@__PURE__*/ new Vector3(); - -/** - * Base class for all textures. - * - * Note: After the initial use of a texture, its dimensions, format, and type - * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one. - * - * @augments EventDispatcher - */ -class Texture extends EventDispatcher { - - /** - * Constructs a new texture. - * - * @param {?Object} [image=Texture.DEFAULT_IMAGE] - The image holding the texture data. - * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. - * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. - * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. - * @param {number} [magFilter=LinearFilter] - The mag filter value. - * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. - * @param {number} [format=RGBAFormat] - The texture format. - * @param {number} [type=UnsignedByteType] - The texture type. - * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. - * @param {string} [colorSpace=NoColorSpace] - The color space. - */ - constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isTexture = true; - - /** - * The ID of the texture. - * - * @name Texture#id - * @type {number} - * @readonly - */ - Object.defineProperty( this, 'id', { value: _textureId ++ } ); - - /** - * The UUID of the texture. - * - * @type {string} - * @readonly - */ - this.uuid = generateUUID(); - - /** - * The name of the texture. - * - * @type {string} - */ - this.name = ''; - - /** - * The data definition of a texture. A reference to the data source can be - * shared across textures. This is often useful in context of spritesheets - * where multiple textures render the same data but with different texture - * transformations. - * - * @type {TextureSource} - */ - this.source = new TextureSource( image ); - - /** - * An array holding user-defined mipmaps. - * - * @type {Array} - */ - this.mipmaps = []; - - /** - * How the texture is applied to the object. The value `UVMapping` - * is the default, where texture or uv coordinates are used to apply the map. - * - * @type {(UVMapping|CubeReflectionMapping|CubeRefractionMapping|EquirectangularReflectionMapping|EquirectangularRefractionMapping|CubeUVReflectionMapping)} - * @default UVMapping - */ - this.mapping = mapping; - - /** - * Lets you select the uv attribute to map the texture to. `0` for `uv`, - * `1` for `uv1`, `2` for `uv2` and `3` for `uv3`. - * - * @type {number} - * @default 0 - */ - this.channel = 0; - - /** - * This defines how the texture is wrapped horizontally and corresponds to - * *U* in UV mapping. - * - * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} - * @default ClampToEdgeWrapping - */ - this.wrapS = wrapS; - - /** - * This defines how the texture is wrapped horizontally and corresponds to - * *V* in UV mapping. - * - * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} - * @default ClampToEdgeWrapping - */ - this.wrapT = wrapT; - - /** - * How the texture is sampled when a texel covers more than one pixel. - * - * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} - * @default LinearFilter - */ - this.magFilter = magFilter; - - /** - * How the texture is sampled when a texel covers less than one pixel. - * - * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} - * @default LinearMipmapLinearFilter - */ - this.minFilter = minFilter; - - /** - * The number of samples taken along the axis through the pixel that has the - * highest density of texels. By default, this value is `1`. A higher value - * gives a less blurry result than a basic mipmap, at the cost of more - * texture samples being used. - * - * @type {number} - * @default Texture.DEFAULT_ANISOTROPY - */ - this.anisotropy = anisotropy; - - /** - * The format of the texture. - * - * @type {number} - * @default RGBAFormat - */ - this.format = format; - - /** - * The default internal format is derived from {@link Texture#format} and {@link Texture#type} and - * defines how the texture data is going to be stored on the GPU. - * - * This property allows to overwrite the default format. - * - * @type {?string} - * @default null - */ - this.internalFormat = null; - - /** - * The data type of the texture. - * - * @type {number} - * @default UnsignedByteType - */ - this.type = type; - - /** - * How much a single repetition of the texture is offset from the beginning, - * in each direction U and V. Typical range is `0.0` to `1.0`. - * - * @type {Vector2} - * @default (0,0) - */ - this.offset = new Vector2( 0, 0 ); - - /** - * How many times the texture is repeated across the surface, in each - * direction U and V. If repeat is set greater than `1` in either direction, - * the corresponding wrap parameter should also be set to `RepeatWrapping` - * or `MirroredRepeatWrapping` to achieve the desired tiling effect. - * - * @type {Vector2} - * @default (1,1) - */ - this.repeat = new Vector2( 1, 1 ); - - /** - * The point around which rotation occurs. A value of `(0.5, 0.5)` corresponds - * to the center of the texture. Default is `(0, 0)`, the lower left. - * - * @type {Vector2} - * @default (0,0) - */ - this.center = new Vector2( 0, 0 ); - - /** - * How much the texture is rotated around the center point, in radians. - * Positive values are counter-clockwise. - * - * @type {number} - * @default 0 - */ - this.rotation = 0; - - /** - * Whether to update the texture's uv-transformation {@link Texture#matrix} - * from the properties {@link Texture#offset}, {@link Texture#repeat}, - * {@link Texture#rotation}, and {@link Texture#center}. - * - * Set this to `false` if you are specifying the uv-transform matrix directly. - * - * @type {boolean} - * @default true - */ - this.matrixAutoUpdate = true; - - /** - * The uv-transformation matrix of the texture. - * - * @type {Matrix3} - */ - this.matrix = new Matrix3(); - - /** - * Whether to generate mipmaps (if possible) for a texture. - * - * Set this to `false` if you are creating mipmaps manually. - * - * @type {boolean} - * @default true - */ - this.generateMipmaps = true; - - /** - * If set to `true`, the alpha channel, if present, is multiplied into the - * color channels when the texture is uploaded to the GPU. - * - * Note that this property has no effect when using `ImageBitmap`. You need to - * configure premultiply alpha on bitmap creation instead. - * - * @type {boolean} - * @default false - */ - this.premultiplyAlpha = false; - - /** - * If set to `true`, the texture is flipped along the vertical axis when - * uploaded to the GPU. - * - * Note that this property has no effect when using `ImageBitmap`. You need to - * configure the flip on bitmap creation instead. - * - * @type {boolean} - * @default true - */ - this.flipY = true; - - /** - * Specifies the alignment requirements for the start of each pixel row in memory. - * The allowable values are `1` (byte-alignment), `2` (rows aligned to even-numbered bytes), - * `4` (word-alignment), and `8` (rows start on double-word boundaries). - * - * @type {number} - * @default 4 - */ - this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) - - /** - * Textures containing color data should be annotated with `SRGBColorSpace` or `LinearSRGBColorSpace`. - * - * @type {string} - * @default NoColorSpace - */ - this.colorSpace = colorSpace; - - /** - * An object that can be used to store custom data about the texture. It - * should not hold references to functions as these will not be cloned. - * - * @type {Object} - */ - this.userData = {}; - - /** - * This can be used to only update a subregion or specific rows of the texture (for example, just the - * first 3 rows). Use the `addUpdateRange()` function to add ranges to this array. - * - * @type {Array} - */ - this.updateRanges = []; - - /** - * This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`. - * - * @type {number} - * @readonly - * @default 0 - */ - this.version = 0; - - /** - * A callback function, called when the texture is updated (e.g., when - * {@link Texture#needsUpdate} has been set to true and then the texture is used). - * - * @type {?Function} - * @default null - */ - this.onUpdate = null; - - /** - * An optional back reference to the textures render target. - * - * @type {?(RenderTarget|WebGLRenderTarget)} - * @default null - */ - this.renderTarget = null; - - /** - * Indicates whether a texture belongs to a render target or not. - * - * @type {boolean} - * @readonly - * @default false - */ - this.isRenderTargetTexture = false; - - /** - * Indicates if a texture should be handled like a texture array. - * - * @type {boolean} - * @readonly - * @default false - */ - this.isArrayTexture = image && image.depth && image.depth > 1 ? true : false; - - /** - * Indicates whether this texture should be processed by `PMREMGenerator` or not - * (only relevant for render target textures). - * - * @type {number} - * @readonly - * @default 0 - */ - this.pmremVersion = 0; - - /** - * Whether the texture should use one of the 16 bit integer formats which are normalized - * to [0, 1] or [-1, 1] (depending on signed/unsigned) when sampled. - * - * @type {boolean} - * @default false - */ - this.normalized = false; - - } - - /** - * The width of the texture in pixels. - */ - get width() { - - return this.source.getSize( _tempVec3 ).x; - - } - - /** - * The height of the texture in pixels. - */ - get height() { - - return this.source.getSize( _tempVec3 ).y; - - } - - /** - * The depth of the texture in pixels. - */ - get depth() { - - return this.source.getSize( _tempVec3 ).z; - - } - - /** - * The image object holding the texture data. - * - * @type {?Object} - */ - get image() { - - return this.source.data; - - } - - set image( value ) { - - this.source.data = value; - - } - - /** - * Updates the texture transformation matrix from the properties {@link Texture#offset}, - * {@link Texture#repeat}, {@link Texture#rotation}, and {@link Texture#center}. - */ - updateMatrix() { - - this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); - - } - - /** - * Adds a range of data in the data texture to be updated on the GPU. - * - * @param {number} start - Position at which to start update. - * @param {number} count - The number of components to update. - */ - addUpdateRange( start, count ) { - - this.updateRanges.push( { start, count } ); - - } - - /** - * Clears the update ranges. - */ - clearUpdateRanges() { - - this.updateRanges.length = 0; - - } - - /** - * Returns a new texture with copied values from this instance. - * - * @return {Texture} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the values of the given texture to this instance. - * - * @param {Texture} source - The texture to copy. - * @return {Texture} A reference to this instance. - */ - copy( source ) { - - this.name = source.name; - - this.source = source.source; - this.mipmaps = source.mipmaps.slice( 0 ); - - this.mapping = source.mapping; - this.channel = source.channel; - - this.wrapS = source.wrapS; - this.wrapT = source.wrapT; - - this.magFilter = source.magFilter; - this.minFilter = source.minFilter; - - this.anisotropy = source.anisotropy; - - this.format = source.format; - this.internalFormat = source.internalFormat; - this.type = source.type; - this.normalized = source.normalized; - - this.offset.copy( source.offset ); - this.repeat.copy( source.repeat ); - this.center.copy( source.center ); - this.rotation = source.rotation; - - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrix.copy( source.matrix ); - - this.generateMipmaps = source.generateMipmaps; - this.premultiplyAlpha = source.premultiplyAlpha; - this.flipY = source.flipY; - this.unpackAlignment = source.unpackAlignment; - this.colorSpace = source.colorSpace; - - this.renderTarget = source.renderTarget; - this.isRenderTargetTexture = source.isRenderTargetTexture; - this.isArrayTexture = source.isArrayTexture; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - this.needsUpdate = true; - - return this; - - } - - /** - * Sets this texture's properties based on `values`. - * @param {Object} values - A container with texture parameters. - */ - setValues( values ) { - - for ( const key in values ) { - - const newValue = values[ key ]; - - if ( newValue === undefined ) { - - warn( `Texture.setValues(): parameter '${ key }' has value of undefined.` ); - continue; - - } - - const currentValue = this[ key ]; - - if ( currentValue === undefined ) { - - warn( `Texture.setValues(): property '${ key }' does not exist.` ); - continue; - - } - - if ( ( currentValue && newValue ) && ( currentValue.isVector2 && newValue.isVector2 ) ) { - - currentValue.copy( newValue ); - - } else if ( ( currentValue && newValue ) && ( currentValue.isVector3 && newValue.isVector3 ) ) { - - currentValue.copy( newValue ); - - } else if ( ( currentValue && newValue ) && ( currentValue.isMatrix3 && newValue.isMatrix3 ) ) { - - currentValue.copy( newValue ); - - } else { - - this[ key ] = newValue; - - } - - } - - } - - /** - * Serializes the texture into JSON. - * - * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. - * @return {Object} A JSON object representing the serialized texture. - * @see {@link ObjectLoader#parse} - */ - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { - - return meta.textures[ this.uuid ]; - - } - - const output = { - - metadata: { - version: 4.7, - type: 'Texture', - generator: 'Texture.toJSON' - }, - - uuid: this.uuid, - name: this.name, - - image: this.source.toJSON( meta ).uuid, - - mapping: this.mapping, - channel: this.channel, - - repeat: [ this.repeat.x, this.repeat.y ], - offset: [ this.offset.x, this.offset.y ], - center: [ this.center.x, this.center.y ], - rotation: this.rotation, - - wrap: [ this.wrapS, this.wrapT ], - - format: this.format, - internalFormat: this.internalFormat, - type: this.type, - normalized: this.normalized, - colorSpace: this.colorSpace, - - minFilter: this.minFilter, - magFilter: this.magFilter, - anisotropy: this.anisotropy, - - flipY: this.flipY, - - generateMipmaps: this.generateMipmaps, - premultiplyAlpha: this.premultiplyAlpha, - unpackAlignment: this.unpackAlignment - - }; - - if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; - - if ( ! isRootObject ) { - - meta.textures[ this.uuid ] = output; - - } - - return output; - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - * - * @fires Texture#dispose - */ - dispose() { - - /** - * Fires when the texture has been disposed of. - * - * @event Texture#dispose - * @type {Object} - */ - this.dispatchEvent( { type: 'dispose' } ); - - } - - /** - * Transforms the given uv vector with the textures uv transformation matrix. - * - * @param {Vector2} uv - The uv vector. - * @return {Vector2} The transformed uv vector. - */ - transformUv( uv ) { - - if ( this.mapping !== UVMapping ) return uv; - - uv.applyMatrix3( this.matrix ); - - if ( uv.x < 0 || uv.x > 1 ) { - - switch ( this.wrapS ) { - - case RepeatWrapping: - - uv.x = uv.x - Math.floor( uv.x ); - break; - - case ClampToEdgeWrapping: - - uv.x = uv.x < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - - uv.x = Math.ceil( uv.x ) - uv.x; - - } else { - - uv.x = uv.x - Math.floor( uv.x ); - - } - - break; - - } - - } - - if ( uv.y < 0 || uv.y > 1 ) { - - switch ( this.wrapT ) { - - case RepeatWrapping: - - uv.y = uv.y - Math.floor( uv.y ); - break; - - case ClampToEdgeWrapping: - - uv.y = uv.y < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { - - uv.y = Math.ceil( uv.y ) - uv.y; - - } else { - - uv.y = uv.y - Math.floor( uv.y ); - - } - - break; - - } - - } - - if ( this.flipY ) { - - uv.y = 1 - uv.y; - - } - - return uv; - - } - - /** - * Setting this property to `true` indicates the engine the texture - * must be updated in the next render. This triggers a texture upload - * to the GPU and ensures correct texture parameter configuration. - * - * @type {boolean} - * @default false - * @param {boolean} value - */ - set needsUpdate( value ) { - - if ( value === true ) { - - this.version ++; - this.source.needsUpdate = true; - - } - - } - - /** - * Setting this property to `true` indicates the engine the PMREM - * must be regenerated. - * - * @type {boolean} - * @default false - * @param {boolean} value - */ - set needsPMREMUpdate( value ) { - - if ( value === true ) { - - this.pmremVersion ++; - - } - - } - -} - -/** - * The default image for all textures. - * - * @static - * @type {?Image} - * @default null - */ -Texture.DEFAULT_IMAGE = null; - -/** - * The default mapping for all textures. - * - * @static - * @type {number} - * @default UVMapping - */ -Texture.DEFAULT_MAPPING = UVMapping; - -/** - * The default anisotropy value for all textures. - * - * @static - * @type {number} - * @default 1 - */ -Texture.DEFAULT_ANISOTROPY = 1; - -/** - * Class representing a 4D vector. A 4D vector is an ordered quadruplet of numbers - * (labeled x, y, z and w), which can be used to represent a number of things, such as: - * - * - A point in 4D space. - * - A direction and length in 4D space. In three.js the length will - * always be the Euclidean distance(straight-line distance) from `(0, 0, 0, 0)` to `(x, y, z, w)` - * and the direction is also measured from `(0, 0, 0, 0)` towards `(x, y, z, w)`. - * - Any arbitrary ordered quadruplet of numbers. - * - * There are other things a 4D vector can be used to represent, however these - * are the most common uses in *three.js*. - * - * Iterating through a vector instance will yield its components `(x, y, z, w)` in - * the corresponding order. - * ```js - * const a = new THREE.Vector4( 0, 1, 0, 0 ); - * - * //no arguments; will be initialised to (0, 0, 0, 1) - * const b = new THREE.Vector4( ); - * - * const d = a.dot( b ); - * ``` - */ -class Vector4 { - - static { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - Vector4.prototype.isVector4 = true; - - } - - /** - * Constructs a new 4D vector. - * - * @param {number} [x=0] - The x value of this vector. - * @param {number} [y=0] - The y value of this vector. - * @param {number} [z=0] - The z value of this vector. - * @param {number} [w=1] - The w value of this vector. - */ - constructor( x = 0, y = 0, z = 0, w = 1 ) { - - /** - * The x value of this vector. - * - * @type {number} - */ - this.x = x; - - /** - * The y value of this vector. - * - * @type {number} - */ - this.y = y; - - /** - * The z value of this vector. - * - * @type {number} - */ - this.z = z; - - /** - * The w value of this vector. - * - * @type {number} - */ - this.w = w; - - } - - /** - * Alias for {@link Vector4#z}. - * - * @type {number} - */ - get width() { - - return this.z; - - } - - set width( value ) { - - this.z = value; - - } - - /** - * Alias for {@link Vector4#w}. - * - * @type {number} - */ - get height() { - - return this.w; - - } - - set height( value ) { - - this.w = value; - - } - - /** - * Sets the vector components. - * - * @param {number} x - The value of the x component. - * @param {number} y - The value of the y component. - * @param {number} z - The value of the z component. - * @param {number} w - The value of the w component. - * @return {Vector4} A reference to this vector. - */ - set( x, y, z, w ) { - - this.x = x; - this.y = y; - this.z = z; - this.w = w; - - return this; - - } - - /** - * Sets the vector components to the same value. - * - * @param {number} scalar - The value to set for all vector components. - * @return {Vector4} A reference to this vector. - */ - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - this.w = scalar; - - return this; - - } - - /** - * Sets the vector's x component to the given value - * - * @param {number} x - The value to set. - * @return {Vector4} A reference to this vector. - */ - setX( x ) { - - this.x = x; - - return this; - - } - - /** - * Sets the vector's y component to the given value - * - * @param {number} y - The value to set. - * @return {Vector4} A reference to this vector. - */ - setY( y ) { - - this.y = y; - - return this; - - } - - /** - * Sets the vector's z component to the given value - * - * @param {number} z - The value to set. - * @return {Vector4} A reference to this vector. - */ - setZ( z ) { - - this.z = z; - - return this; - - } - - /** - * Sets the vector's w component to the given value - * - * @param {number} w - The value to set. - * @return {Vector4} A reference to this vector. - */ - setW( w ) { - - this.w = w; - - return this; - - } - - /** - * Allows to set a vector component with an index. - * - * @param {number} index - The component index. `0` equals to x, `1` equals to y, - * `2` equals to z, `3` equals to w. - * @param {number} value - The value to set. - * @return {Vector4} A reference to this vector. - */ - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - case 3: this.w = value; break; - default: throw new Error( 'THREE.Vector4: index is out of range: ' + index ); - - } - - return this; - - } - - /** - * Returns the value of the vector component which matches the given index. - * - * @param {number} index - The component index. `0` equals to x, `1` equals to y, - * `2` equals to z, `3` equals to w. - * @return {number} A vector component value. - */ - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - case 3: return this.w; - default: throw new Error( 'THREE.Vector4: index is out of range: ' + index ); - - } - - } - - /** - * Returns a new vector with copied values from this instance. - * - * @return {Vector4} A clone of this instance. - */ - clone() { - - return new this.constructor( this.x, this.y, this.z, this.w ); - - } - - /** - * Copies the values of the given vector to this instance. - * - * @param {Vector3|Vector4} v - The vector to copy. - * @return {Vector4} A reference to this vector. - */ - copy( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; - - return this; - - } - - /** - * Adds the given vector to this instance. - * - * @param {Vector4} v - The vector to add. - * @return {Vector4} A reference to this vector. - */ - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; - - return this; - - } - - /** - * Adds the given scalar value to all components of this instance. - * - * @param {number} s - The scalar to add. - * @return {Vector4} A reference to this vector. - */ - addScalar( s ) { - - this.x += s; - this.y += s; - this.z += s; - this.w += s; - - return this; - - } - - /** - * Adds the given vectors and stores the result in this instance. - * - * @param {Vector4} a - The first vector. - * @param {Vector4} b - The second vector. - * @return {Vector4} A reference to this vector. - */ - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - this.w = a.w + b.w; - - return this; - - } - - /** - * Adds the given vector scaled by the given factor to this instance. - * - * @param {Vector4} v - The vector. - * @param {number} s - The factor that scales `v`. - * @return {Vector4} A reference to this vector. - */ - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - this.w += v.w * s; - - return this; - - } - - /** - * Subtracts the given vector from this instance. - * - * @param {Vector4} v - The vector to subtract. - * @return {Vector4} A reference to this vector. - */ - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; - - return this; - - } - - /** - * Subtracts the given scalar value from all components of this instance. - * - * @param {number} s - The scalar to subtract. - * @return {Vector4} A reference to this vector. - */ - subScalar( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; - - return this; - - } - - /** - * Subtracts the given vectors and stores the result in this instance. - * - * @param {Vector4} a - The first vector. - * @param {Vector4} b - The second vector. - * @return {Vector4} A reference to this vector. - */ - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - this.w = a.w - b.w; - - return this; - - } - - /** - * Multiplies the given vector with this instance. - * - * @param {Vector4} v - The vector to multiply. - * @return {Vector4} A reference to this vector. - */ - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - this.w *= v.w; - - return this; - - } - - /** - * Multiplies the given scalar value with all components of this instance. - * - * @param {number} scalar - The scalar to multiply. - * @return {Vector4} A reference to this vector. - */ - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; - - return this; - - } - - /** - * Multiplies this vector with the given 4x4 matrix. - * - * @param {Matrix4} m - The 4x4 matrix. - * @return {Vector4} A reference to this vector. - */ - applyMatrix4( m ) { - - const x = this.x, y = this.y, z = this.z, w = this.w; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; - this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; - - return this; - - } - - /** - * Divides this instance by the given vector. - * - * @param {Vector4} v - The vector to divide. - * @return {Vector4} A reference to this vector. - */ - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; - this.w /= v.w; - - return this; - - } - - /** - * Divides this vector by the given scalar. - * - * @param {number} scalar - The scalar to divide. - * @return {Vector4} A reference to this vector. - */ - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - /** - * Sets the x, y and z components of this - * vector to the quaternion's axis and w to the angle. - * - * @param {Quaternion} q - The Quaternion to set. - * @return {Vector4} A reference to this vector. - */ - setAxisAngleFromQuaternion( q ) { - - // q is assumed to be normalized - - this.w = 2 * Math.acos( q.w ); - - const s = Math.sqrt( 1 - q.w * q.w ); - - if ( s < 0.0001 ) { - - this.x = 1; - this.y = 0; - this.z = 0; - - } else { - - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; - - } - - return this; - - } - - /** - * Sets the x, y and z components of this - * vector to the axis of rotation and w to the angle. - * - * @param {Matrix4} m - A 4x4 matrix of which the upper left 3x3 matrix is a pure rotation matrix. - * @return {Vector4} A reference to this vector. - */ - setAxisAngleFromRotationMatrix( m ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - let angle, x, y, z; // variables for result - const epsilon = 0.01, // margin to allow for rounding errors - epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees - - te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - if ( ( Math.abs( m12 - m21 ) < epsilon ) && - ( Math.abs( m13 - m31 ) < epsilon ) && - ( Math.abs( m23 - m32 ) < epsilon ) ) { - - // singularity found - // first check for identity matrix which must have +1 for all terms - // in leading diagonal and zero in other terms - - if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && - ( Math.abs( m13 + m31 ) < epsilon2 ) && - ( Math.abs( m23 + m32 ) < epsilon2 ) && - ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - - // this singularity is identity matrix so angle = 0 - - this.set( 1, 0, 0, 0 ); - - return this; // zero angle, arbitrary axis - - } - - // otherwise this singularity is angle = 180 - - angle = Math.PI; - - const xx = ( m11 + 1 ) / 2; - const yy = ( m22 + 1 ) / 2; - const zz = ( m33 + 1 ) / 2; - const xy = ( m12 + m21 ) / 4; - const xz = ( m13 + m31 ) / 4; - const yz = ( m23 + m32 ) / 4; - - if ( ( xx > yy ) && ( xx > zz ) ) { - - // m11 is the largest diagonal term - - if ( xx < epsilon ) { - - x = 0; - y = 0.707106781; - z = 0.707106781; - - } else { - - x = Math.sqrt( xx ); - y = xy / x; - z = xz / x; - - } - - } else if ( yy > zz ) { - - // m22 is the largest diagonal term - - if ( yy < epsilon ) { - - x = 0.707106781; - y = 0; - z = 0.707106781; - - } else { - - y = Math.sqrt( yy ); - x = xy / y; - z = yz / y; - - } - - } else { - - // m33 is the largest diagonal term so base result on this - - if ( zz < epsilon ) { - - x = 0.707106781; - y = 0.707106781; - z = 0; - - } else { - - z = Math.sqrt( zz ); - x = xz / z; - y = yz / z; - - } - - } - - this.set( x, y, z, angle ); - - return this; // return 180 deg rotation - - } - - // as we have reached here there are no singularities so we can handle normally - - let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + - ( m13 - m31 ) * ( m13 - m31 ) + - ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize - - if ( Math.abs( s ) < 0.001 ) s = 1; - - // prevent divide by zero, should not happen if matrix is orthogonal and should be - // caught by singularity test above, but I've left it in just in case - - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); - - return this; - - } - - /** - * Sets the vector components to the position elements of the - * given transformation matrix. - * - * @param {Matrix4} m - The 4x4 matrix. - * @return {Vector4} A reference to this vector. - */ - setFromMatrixPosition( m ) { - - const e = m.elements; - - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; - this.w = e[ 15 ]; - - return this; - - } - - /** - * If this vector's x, y, z or w value is greater than the given vector's x, y, z or w - * value, replace that value with the corresponding min value. - * - * @param {Vector4} v - The vector. - * @return {Vector4} A reference to this vector. - */ - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - this.w = Math.min( this.w, v.w ); - - return this; - - } - - /** - * If this vector's x, y, z or w value is less than the given vector's x, y, z or w - * value, replace that value with the corresponding max value. - * - * @param {Vector4} v - The vector. - * @return {Vector4} A reference to this vector. - */ - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - this.w = Math.max( this.w, v.w ); - - return this; - - } - - /** - * If this vector's x, y, z or w value is greater than the max vector's x, y, z or w - * value, it is replaced by the corresponding value. - * If this vector's x, y, z or w value is less than the min vector's x, y, z or w value, - * it is replaced by the corresponding value. - * - * @param {Vector4} min - The minimum x, y and z values. - * @param {Vector4} max - The maximum x, y and z values in the desired range. - * @return {Vector4} A reference to this vector. - */ - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = clamp( this.x, min.x, max.x ); - this.y = clamp( this.y, min.y, max.y ); - this.z = clamp( this.z, min.z, max.z ); - this.w = clamp( this.w, min.w, max.w ); - - return this; - - } - - /** - * If this vector's x, y, z or w values are greater than the max value, they are - * replaced by the max value. - * If this vector's x, y, z or w values are less than the min value, they are - * replaced by the min value. - * - * @param {number} minVal - The minimum value the components will be clamped to. - * @param {number} maxVal - The maximum value the components will be clamped to. - * @return {Vector4} A reference to this vector. - */ - clampScalar( minVal, maxVal ) { - - this.x = clamp( this.x, minVal, maxVal ); - this.y = clamp( this.y, minVal, maxVal ); - this.z = clamp( this.z, minVal, maxVal ); - this.w = clamp( this.w, minVal, maxVal ); - - return this; - - } - - /** - * If this vector's length is greater than the max value, it is replaced by - * the max value. - * If this vector's length is less than the min value, it is replaced by the - * min value. - * - * @param {number} min - The minimum value the vector length will be clamped to. - * @param {number} max - The maximum value the vector length will be clamped to. - * @return {Vector4} A reference to this vector. - */ - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); - - } - - /** - * The components of this vector are rounded down to the nearest integer value. - * - * @return {Vector4} A reference to this vector. - */ - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - this.w = Math.floor( this.w ); - - return this; - - } - - /** - * The components of this vector are rounded up to the nearest integer value. - * - * @return {Vector4} A reference to this vector. - */ - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - this.w = Math.ceil( this.w ); - - return this; - - } - - /** - * The components of this vector are rounded to the nearest integer value - * - * @return {Vector4} A reference to this vector. - */ - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - this.w = Math.round( this.w ); - - return this; - - } - - /** - * The components of this vector are rounded towards zero (up if negative, - * down if positive) to an integer value. - * - * @return {Vector4} A reference to this vector. - */ - roundToZero() { - - this.x = Math.trunc( this.x ); - this.y = Math.trunc( this.y ); - this.z = Math.trunc( this.z ); - this.w = Math.trunc( this.w ); - - return this; - - } - - /** - * Inverts this vector - i.e. sets x = -x, y = -y, z = -z, w = -w. - * - * @return {Vector4} A reference to this vector. - */ - negate() { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - this.w = - this.w; - - return this; - - } - - /** - * Calculates the dot product of the given vector with this instance. - * - * @param {Vector4} v - The vector to compute the dot product with. - * @return {number} The result of the dot product. - */ - dot( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; - - } - - /** - * Computes the square of the Euclidean length (straight-line length) from - * (0, 0, 0, 0) to (x, y, z, w). If you are comparing the lengths of vectors, you should - * compare the length squared instead as it is slightly more efficient to calculate. - * - * @return {number} The square length of this vector. - */ - lengthSq() { - - return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; - - } - - /** - * Computes the Euclidean length (straight-line length) from (0, 0, 0, 0) to (x, y, z, w). - * - * @return {number} The length of this vector. - */ - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); - - } - - /** - * Computes the Manhattan length of this vector. - * - * @return {number} The length of this vector. - */ - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); - - } - - /** - * Converts this vector to a unit vector - that is, sets it equal to a vector - * with the same direction as this one, but with a vector length of `1`. - * - * @return {Vector4} A reference to this vector. - */ - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - /** - * Sets this vector to a vector with the same direction as this one, but - * with the specified length. - * - * @param {number} length - The new length of this vector. - * @return {Vector4} A reference to this vector. - */ - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - /** - * Linearly interpolates between the given vector and this instance, where - * alpha is the percent distance along the line - alpha = 0 will be this - * vector, and alpha = 1 will be the given one. - * - * @param {Vector4} v - The vector to interpolate towards. - * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. - * @return {Vector4} A reference to this vector. - */ - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - this.w += ( v.w - this.w ) * alpha; - - return this; - - } - - /** - * Linearly interpolates between the given vectors, where alpha is the percent - * distance along the line - alpha = 0 will be first vector, and alpha = 1 will - * be the second one. The result is stored in this instance. - * - * @param {Vector4} v1 - The first vector. - * @param {Vector4} v2 - The second vector. - * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. - * @return {Vector4} A reference to this vector. - */ - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - this.w = v1.w + ( v2.w - v1.w ) * alpha; - - return this; - - } - - /** - * Returns `true` if this vector is equal with the given one. - * - * @param {Vector4} v - The vector to test for equality. - * @return {boolean} Whether this vector is equal with the given one. - */ - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); - - } - - /** - * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]`, - * z value to be `array[ offset + 2 ]`, w value to be `array[ offset + 3 ]`. - * - * @param {Array} array - An array holding the vector component values. - * @param {number} [offset=0] - The offset into the array. - * @return {Vector4} A reference to this vector. - */ - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - this.w = array[ offset + 3 ]; - - return this; - - } - - /** - * Writes the components of this vector to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the vector components. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The vector components. - */ - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - array[ offset + 3 ] = this.w; - - return array; - - } - - /** - * Sets the components of this vector from the given buffer attribute. - * - * @param {BufferAttribute} attribute - The buffer attribute holding vector data. - * @param {number} index - The index into the attribute. - * @return {Vector4} A reference to this vector. - */ - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - this.w = attribute.getW( index ); - - return this; - - } - - /** - * Sets each component of this vector to a pseudo-random value between `0` and - * `1`, excluding `1`. - * - * @return {Vector4} A reference to this vector. - */ - random() { - - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - this.w = Math.random(); - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - yield this.z; - yield this.w; - - } - -} - -/** - * A render target is a buffer where the video card draws pixels for a scene - * that is being rendered in the background. It is used in different effects, - * such as applying postprocessing to a rendered image before displaying it - * on the screen. - * - * @augments EventDispatcher - */ -class RenderTarget extends EventDispatcher { - - /** - * Render target options. - * - * @typedef {Object} RenderTarget~Options - * @property {boolean} [generateMipmaps=false] - Whether to generate mipmaps or not. - * @property {number} [magFilter=LinearFilter] - The mag filter. - * @property {number} [minFilter=LinearFilter] - The min filter. - * @property {number} [format=RGBAFormat] - The texture format. - * @property {number} [type=UnsignedByteType] - The texture type. - * @property {?string} [internalFormat=null] - The texture's internal format. - * @property {number} [wrapS=ClampToEdgeWrapping] - The texture's uv wrapping mode. - * @property {number} [wrapT=ClampToEdgeWrapping] - The texture's uv wrapping mode. - * @property {number} [anisotropy=1] - The texture's anisotropy value. - * @property {string} [colorSpace=NoColorSpace] - The texture's color space. - * @property {boolean} [depthBuffer=true] - Whether to allocate a depth buffer or not. - * @property {boolean} [stencilBuffer=false] - Whether to allocate a stencil buffer or not. - * @property {boolean} [resolveColorBuffer=true] - Whether to resolve the color buffer or not. Only relevant for multisampled render targets. - * @property {boolean} [resolveDepthBuffer=true] - Whether to resolve the depth buffer or not. Only relevant for multisampled render targets. - * @property {boolean} [resolveStencilBuffer=true] - Whether to resolve the stencil buffer or not. Only relevant for multisampled render targets. - * @property {boolean} [storeMultisampledColorBuffer=true] - Whether to store the multisampled color buffer or not. Setting to `false` saves memory bandwidth when the multisampled data are not needed after a render pass. - * @property {boolean} [storeMultisampledDepthBuffer=true] - Whether to store the multisampled depth buffer or not. Setting to `false` saves memory bandwidth when the multisampled data are not needed after a render pass. - * @property {boolean} [storeMultisampledStencilBuffer=true] - Whether to store the multisampled stencil buffer or not. Setting to `false` saves memory bandwidth when the multisampled data are not needed after a render pass. - * @property {?Texture} [depthTexture=null] - Reference to a depth texture. - * @property {number} [samples=0] - The MSAA samples count. - * @property {number} [count=1] - Defines the number of color attachments . Must be at least `1`. - * @property {number} [depth=1] - The texture depth. - * @property {boolean} [multiview=false] - Whether this target is used for multiview rendering (WebGL OVR_multiview2 extension). - * @property {boolean} [useArrayDepthTexture=false] - Whether to create the depth texture as an array texture for per-layer depth testing. This is separate from multiview so layered render targets can use array depth without the multiview extension. - */ - - /** - * Constructs a new render target. - * - * @param {number} [width=1] - The width of the render target. - * @param {number} [height=1] - The height of the render target. - * @param {RenderTarget~Options} [options] - The configuration object. - */ - constructor( width = 1, height = 1, options = {} ) { - - super(); - - options = Object.assign( { - generateMipmaps: false, - internalFormat: null, - minFilter: LinearFilter, - depthBuffer: true, - stencilBuffer: false, - resolveColorBuffer: true, - resolveDepthBuffer: true, - resolveStencilBuffer: true, - storeMultisampledColorBuffer: true, - storeMultisampledDepthBuffer: true, - storeMultisampledStencilBuffer: true, - depthTexture: null, - samples: 0, - count: 1, - depth: 1, - multiview: false, - useArrayDepthTexture: false - }, options ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isRenderTarget = true; - - /** - * The width of the render target. - * - * @type {number} - * @default 1 - */ - this.width = width; - - /** - * The height of the render target. - * - * @type {number} - * @default 1 - */ - this.height = height; - - /** - * The depth of the render target. - * - * @type {number} - * @default 1 - */ - this.depth = options.depth; - - /** - * A rectangular area inside the render target's viewport. Fragments that are - * outside the area will be discarded. - * - * @type {Vector4} - * @default (0,0,width,height) - */ - this.scissor = new Vector4( 0, 0, width, height ); - - /** - * Indicates whether the scissor test should be enabled when rendering into - * this render target or not. - * - * @type {boolean} - * @default false - */ - this.scissorTest = false; - - /** - * A rectangular area representing the render target's viewport. - * - * @type {Vector4} - * @default (0,0,width,height) - */ - this.viewport = new Vector4( 0, 0, width, height ); - - /** - * An array of textures. Each color attachment is represented as a separate texture. - * Has at least a single entry for the default color attachment. - * - * @type {Array} - */ - this.textures = []; - - const image = { width: width, height: height, depth: options.depth }; - const texture = new Texture( image ); - - const count = options.count; - for ( let i = 0; i < count; i ++ ) { - - this.textures[ i ] = texture.clone(); - this.textures[ i ].isRenderTargetTexture = true; - this.textures[ i ].renderTarget = this; - - } - - this._setTextureOptions( options ); - - /** - * Whether to allocate a depth buffer or not. - * - * @type {boolean} - * @default true - */ - this.depthBuffer = options.depthBuffer; - - /** - * Whether to allocate a stencil buffer or not. - * - * @type {boolean} - * @default false - */ - this.stencilBuffer = options.stencilBuffer; - - /** - * Whether to resolve the color buffer or not. When set to `false`, the color - * attachments do not receive the resolved (single-sampled) output of a render - * pass and the render target's textures are left untouched. The rendered - * content is then only accessible within the render pass itself. - * - * Only relevant for multisampled render targets. - * - * @type {boolean} - * @default true - */ - this.resolveColorBuffer = options.resolveColorBuffer; - - /** - * Whether to resolve the depth buffer or not. When set to `false`, the depth - * texture does not receive the resolved depth output of a render pass which - * saves memory bandwidth. Use this setting when the depth data of a render - * pass are not required afterwards. - * - * Only relevant for multisampled render targets in WebGL. WebGPU does not - * support depth resolves; sampling the depth texture of a multisampled render - * target accesses the multisampled data directly, see - * {@link RenderTarget#storeMultisampledDepthBuffer}. - * - * @type {boolean} - * @default true - */ - this.resolveDepthBuffer = options.resolveDepthBuffer; - - /** - * Whether to resolve the stencil buffer or not. Analogous to - * {@link RenderTarget#resolveDepthBuffer} but for the stencil aspect. - * - * @type {boolean} - * @default true - */ - this.resolveStencilBuffer = options.resolveStencilBuffer; - - /** - * Whether to store the multisampled color buffer or not. When set to `false`, - * the multisampled data are discarded at the end of a render pass, right after - * they have been resolved. This saves memory bandwidth, especially on tile-based - * GPUs, and is the recommended setting for render targets that are fully redrawn - * each frame and whose output is only accessed via the resolved textures (e.g. - * scene passes in post-processing chains). - * - * Must be kept `true` when the multisampled data are needed after the render - * pass ends, e.g. when rendering into the target without clearing or when the - * scene contains transmissive objects which require a mid-pass framebuffer copy. - * - * @type {boolean} - * @default true - */ - this.storeMultisampledColorBuffer = options.storeMultisampledColorBuffer; - - /** - * Whether to store the multisampled depth buffer or not. When set to `false`, - * the multisampled depth data are discarded at the end of a render pass which - * saves memory bandwidth. - * - * Must be kept `true` in WebGPU when the depth texture of a multisampled render - * target is sampled (e.g. by depth-based post-processing effects) since depth - * is read directly from the multisampled data. - * - * @type {boolean} - * @default true - */ - this.storeMultisampledDepthBuffer = options.storeMultisampledDepthBuffer; - - /** - * Whether to store the multisampled stencil buffer or not. Analogous to - * {@link RenderTarget#storeMultisampledDepthBuffer} but for the stencil aspect. - * - * @type {boolean} - * @default true - */ - this.storeMultisampledStencilBuffer = options.storeMultisampledStencilBuffer; - - this._depthTexture = null; - this.depthTexture = options.depthTexture; - - /** - * The number of MSAA samples. - * - * A value of `0` disables MSAA. - * - * @type {number} - * @default 0 - */ - this.samples = options.samples; - - /** - * Whether to this target is used in multiview rendering. - * - * @type {boolean} - * @default false - */ - this.multiview = options.multiview; - - /** - * Whether to create the depth texture as an array texture for per-layer depth testing. - * This is separate from multiview so layered render targets can use array depth without - * the multiview extension. - * - * @type {boolean} - * @default false - */ - this.useArrayDepthTexture = options.useArrayDepthTexture; - - } - - _setTextureOptions( options = {} ) { - - const values = { - minFilter: LinearFilter, - generateMipmaps: false, - flipY: false, - internalFormat: null - }; - - if ( options.mapping !== undefined ) values.mapping = options.mapping; - if ( options.wrapS !== undefined ) values.wrapS = options.wrapS; - if ( options.wrapT !== undefined ) values.wrapT = options.wrapT; - if ( options.wrapR !== undefined ) values.wrapR = options.wrapR; - if ( options.magFilter !== undefined ) values.magFilter = options.magFilter; - if ( options.minFilter !== undefined ) values.minFilter = options.minFilter; - if ( options.format !== undefined ) values.format = options.format; - if ( options.type !== undefined ) values.type = options.type; - if ( options.anisotropy !== undefined ) values.anisotropy = options.anisotropy; - if ( options.colorSpace !== undefined ) values.colorSpace = options.colorSpace; - if ( options.flipY !== undefined ) values.flipY = options.flipY; - if ( options.generateMipmaps !== undefined ) values.generateMipmaps = options.generateMipmaps; - if ( options.internalFormat !== undefined ) values.internalFormat = options.internalFormat; - - for ( let i = 0; i < this.textures.length; i ++ ) { - - const texture = this.textures[ i ]; - texture.setValues( values ); - - } - - } - - /** - * The texture representing the default color attachment. - * - * @type {Texture} - */ - get texture() { - - return this.textures[ 0 ]; - - } - - set texture( value ) { - - this.textures[ 0 ] = value; - - } - - set depthTexture( current ) { - - if ( this._depthTexture !== null && this._depthTexture.renderTarget === this ) this._depthTexture.renderTarget = null; - if ( current !== null && current.renderTarget === null ) current.renderTarget = this; - - this._depthTexture = current; - - } - - /** - * Instead of saving the depth in a renderbuffer, a texture - * can be used instead which is useful for further processing - * e.g. in context of post-processing. - * - * @type {?DepthTexture} - * @default null - */ - get depthTexture() { - - return this._depthTexture; - - } - - /** - * Sets the size of this render target. - * - * @param {number} width - The width. - * @param {number} height - The height. - * @param {number} [depth=1] - The depth. - */ - setSize( width, height, depth = 1 ) { - - if ( this.width !== width || this.height !== height || this.depth !== depth ) { - - this.width = width; - this.height = height; - this.depth = depth; - - for ( let i = 0, il = this.textures.length; i < il; i ++ ) { - - this.textures[ i ].image.width = width; - this.textures[ i ].image.height = height; - this.textures[ i ].image.depth = depth; - - if ( this.textures[ i ].isData3DTexture !== true ) { // Fix for #31693 - - // TODO: Reconsider setting isArrayTexture flag here and in the ctor of Texture. - // Maybe a method `isArrayTexture()` or just a getter could replace a flag since - // both are evaluated on each call? - - this.textures[ i ].isArrayTexture = this.textures[ i ].image.depth > 1; - - } - - } - - this.dispose(); - - } - - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); - - } - - /** - * Returns a new render target with copied values from this instance. - * - * @return {RenderTarget} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the settings of the given render target. This is a structural copy so - * no resources are shared between render targets after the copy. That includes - * all MRT textures and the depth texture. - * - * @param {RenderTarget} source - The render target to copy. - * @return {RenderTarget} A reference to this instance. - */ - copy( source ) { - - this.width = source.width; - this.height = source.height; - this.depth = source.depth; - - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; - - this.viewport.copy( source.viewport ); - - this.textures.length = 0; - - for ( let i = 0, il = source.textures.length; i < il; i ++ ) { - - this.textures[ i ] = source.textures[ i ].clone(); - this.textures[ i ].isRenderTargetTexture = true; - this.textures[ i ].renderTarget = this; - - // ensure image object is not shared, see #20328 - - const image = Object.assign( {}, source.textures[ i ].image ); - this.textures[ i ].source = new TextureSource( image ); - - } - - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; - - this.resolveColorBuffer = source.resolveColorBuffer; - this.resolveDepthBuffer = source.resolveDepthBuffer; - this.resolveStencilBuffer = source.resolveStencilBuffer; - - this.storeMultisampledColorBuffer = source.storeMultisampledColorBuffer; - this.storeMultisampledDepthBuffer = source.storeMultisampledDepthBuffer; - this.storeMultisampledStencilBuffer = source.storeMultisampledStencilBuffer; - - if ( source.depthTexture !== null ) { - - if ( source.depthTexture.renderTarget === source ) { - - const depthTexture = source.depthTexture.clone(); - depthTexture.renderTarget = null; - - this.depthTexture = depthTexture; - - } else { - - this.depthTexture = source.depthTexture; - - } - - } - - this.samples = source.samples; - this.multiview = source.multiview; - this.useArrayDepthTexture = source.useArrayDepthTexture; - - return this; - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - * - * @fires RenderTarget#dispose - */ - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} - -/** - * A render target used in context of {@link WebGLRenderer}. - * - * @augments RenderTarget - */ -class WebGLRenderTarget extends RenderTarget { - - /** - * Constructs a new 3D render target. - * - * @param {number} [width=1] - The width of the render target. - * @param {number} [height=1] - The height of the render target. - * @param {RenderTarget~Options} [options] - The configuration object. - */ - constructor( width = 1, height = 1, options = {} ) { - - super( width, height, options ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isWebGLRenderTarget = true; - - } - -} - -const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, - 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, - 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, - 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, - 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, - 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, - 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, - 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, - 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, - 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, - 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, - 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, - 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, - 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, - 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, - 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, - 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, - 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, - 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, - 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, - 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, - 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, - 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, - 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; - -const _hslA = { h: 0, s: 0, l: 0 }; -const _hslB = { h: 0, s: 0, l: 0 }; - -function hue2rgb( p, q, t ) { - - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; - if ( t < 1 / 2 ) return q; - if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); - return p; - -} - -/** - * A Color instance is represented by RGB components in the linear working - * color space, which defaults to `LinearSRGBColorSpace`. Inputs - * conventionally using `SRGBColorSpace` (such as hexadecimals and CSS - * strings) are converted to the working color space automatically. - * - * ```js - * // converted automatically from SRGBColorSpace to LinearSRGBColorSpace - * const color = new THREE.Color().setHex( 0x112233 ); - * ``` - * Source color spaces may be specified explicitly, to ensure correct conversions. - * ```js - * // assumed already LinearSRGBColorSpace; no conversion - * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5 ); - * - * // converted explicitly from SRGBColorSpace to LinearSRGBColorSpace - * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5, SRGBColorSpace ); - * ``` - * If THREE.ColorManagement is disabled, no conversions occur. For details, - * see Color management. Iterating through a Color instance will yield - * its components (r, g, b) in the corresponding order. A Color can be initialised - * in any of the following ways: - * ```js - * //empty constructor - will default white - * const color1 = new THREE.Color(); - * - * //Hexadecimal color (recommended) - * const color2 = new THREE.Color( 0xff0000 ); - * - * //RGB string - * const color3 = new THREE.Color("rgb(255, 0, 0)"); - * const color4 = new THREE.Color("rgb(100%, 0%, 0%)"); - * - * //X11 color name - all 140 color names are supported. - * //Note the lack of CamelCase in the name - * const color5 = new THREE.Color( 'skyblue' ); - * //HSL string - * const color6 = new THREE.Color("hsl(0, 100%, 50%)"); - * - * //Separate RGB values between 0 and 1 - * const color7 = new THREE.Color( 1, 0, 0 ); - * ``` - */ -class Color { - - /** - * Constructs a new color. - * - * Note that standard method of specifying color in three.js is with a hexadecimal triplet, - * and that method is used throughout the rest of the documentation. - * - * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are - * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. - * @param {number} [g] - The green component. - * @param {number} [b] - The blue component. - */ - constructor( r, g, b ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isColor = true; - - /** - * The red component. - * - * @type {number} - * @default 1 - */ - this.r = 1; - - /** - * The green component. - * - * @type {number} - * @default 1 - */ - this.g = 1; - - /** - * The blue component. - * - * @type {number} - * @default 1 - */ - this.b = 1; - - return this.set( r, g, b ); - - } - - /** - * Sets the colors's components from the given values. - * - * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are - * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. - * @param {number} [g] - The green component. - * @param {number} [b] - The blue component. - * @return {Color} A reference to this color. - */ - set( r, g, b ) { - - if ( g === undefined && b === undefined ) { - - // r is THREE.Color, hex or string - - const value = r; - - if ( value && value.isColor ) { - - this.copy( value ); - - } else if ( typeof value === 'number' ) { - - this.setHex( value ); - - } else if ( typeof value === 'string' ) { - - this.setStyle( value ); - - } - - } else { - - this.setRGB( r, g, b ); - - } - - return this; - - } - - /** - * Sets the colors's components to the given scalar value. - * - * @param {number} scalar - The scalar value. - * @return {Color} A reference to this color. - */ - setScalar( scalar ) { - - this.r = scalar; - this.g = scalar; - this.b = scalar; - - return this; - - } - - /** - * Sets this color from a hexadecimal value. - * - * @param {number} hex - The hexadecimal value. - * @param {string} [colorSpace=SRGBColorSpace] - The color space. - * @return {Color} A reference to this color. - */ - setHex( hex, colorSpace = SRGBColorSpace ) { - - hex = Math.floor( hex ); - - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; - - ColorManagement.colorSpaceToWorking( this, colorSpace ); - - return this; - - } - - /** - * Sets this color from RGB values. - * - * @param {number} r - Red channel value between `0.0` and `1.0`. - * @param {number} g - Green channel value between `0.0` and `1.0`. - * @param {number} b - Blue channel value between `0.0` and `1.0`. - * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. - * @return {Color} A reference to this color. - */ - setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - - this.r = r; - this.g = g; - this.b = b; - - ColorManagement.colorSpaceToWorking( this, colorSpace ); - - return this; - - } - - /** - * Sets this color from RGB values. - * - * @param {number} h - Hue value between `0.0` and `1.0`. - * @param {number} s - Saturation value between `0.0` and `1.0`. - * @param {number} l - Lightness value between `0.0` and `1.0`. - * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. - * @return {Color} A reference to this color. - */ - setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - h = euclideanModulo( h, 1 ); - s = clamp( s, 0, 1 ); - l = clamp( l, 0, 1 ); - - if ( s === 0 ) { - - this.r = this.g = this.b = l; - - } else { - - const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - const q = ( 2 * l ) - p; - - this.r = hue2rgb( q, p, h + 1 / 3 ); - this.g = hue2rgb( q, p, h ); - this.b = hue2rgb( q, p, h - 1 / 3 ); - - } - - ColorManagement.colorSpaceToWorking( this, colorSpace ); - - return this; - - } - - /** - * Sets this color from a CSS-style string. For example, `rgb(250, 0,0)`, - * `rgb(100%, 0%, 0%)`, `hsl(0, 100%, 50%)`, `#ff0000`, `#f00`, or `red` ( or - * any [X11 color name](https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart) - - * all 140 color names are supported). - * - * @param {string} style - Color as a CSS-style string. - * @param {string} [colorSpace=SRGBColorSpace] - The color space. - * @return {Color} A reference to this color. - */ - setStyle( style, colorSpace = SRGBColorSpace ) { - - function handleAlpha( string ) { - - if ( string === undefined ) return; - - if ( parseFloat( string ) < 1 ) { - - warn( 'Color: Alpha component of ' + style + ' will be ignored.' ); - - } - - } - - - let m; - - if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { - - // rgb / hsl - - let color; - const name = m[ 1 ]; - const components = m[ 2 ]; - - switch ( name ) { - - case 'rgb': - case 'rgba': - - if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(255,0,0) rgba(255,0,0,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, - colorSpace - ); - - } - - if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, - colorSpace - ); - - } - - break; - - case 'hsl': - case 'hsla': - - if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setHSL( - parseFloat( color[ 1 ] ) / 360, - parseFloat( color[ 2 ] ) / 100, - parseFloat( color[ 3 ] ) / 100, - colorSpace - ); - - } - - break; - - default: - - warn( 'Color: Unknown color model ' + style ); - - } - - } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - - // hex color - - const hex = m[ 1 ]; - const size = hex.length; - - if ( size === 3 ) { - - // #ff0 - return this.setRGB( - parseInt( hex.charAt( 0 ), 16 ) / 15, - parseInt( hex.charAt( 1 ), 16 ) / 15, - parseInt( hex.charAt( 2 ), 16 ) / 15, - colorSpace - ); - - } else if ( size === 6 ) { - - // #ff0000 - return this.setHex( parseInt( hex, 16 ), colorSpace ); - - } else { - - warn( 'Color: Invalid hex color ' + style ); - - } - - } else if ( style && style.length > 0 ) { - - return this.setColorName( style, colorSpace ); - - } - - return this; - - } - - /** - * Sets this color from a color name. Faster than {@link Color#setStyle} if - * you don't need the other CSS-style formats. - * - * For convenience, the list of names is exposed in `Color.NAMES` as a hash. - * ```js - * Color.NAMES.aliceblue // returns 0xF0F8FF - * ``` - * - * @param {string} style - The color name. - * @param {string} [colorSpace=SRGBColorSpace] - The color space. - * @return {Color} A reference to this color. - */ - setColorName( style, colorSpace = SRGBColorSpace ) { - - // color keywords - const hex = _colorKeywords[ style.toLowerCase() ]; - - if ( hex !== undefined ) { - - // red - this.setHex( hex, colorSpace ); - - } else { - - // unknown color - warn( 'Color: Unknown color ' + style ); - - } - - return this; - - } - - /** - * Returns a new color with copied values from this instance. - * - * @return {Color} A clone of this instance. - */ - clone() { - - return new this.constructor( this.r, this.g, this.b ); - - } - - /** - * Copies the values of the given color to this instance. - * - * @param {Color} color - The color to copy. - * @return {Color} A reference to this color. - */ - copy( color ) { - - this.r = color.r; - this.g = color.g; - this.b = color.b; - - return this; - - } - - /** - * Copies the given color into this color, and then converts this color from - * `SRGBColorSpace` to `LinearSRGBColorSpace`. - * - * @param {Color} color - The color to copy/convert. - * @return {Color} A reference to this color. - */ - copySRGBToLinear( color ) { - - this.r = SRGBToLinear( color.r ); - this.g = SRGBToLinear( color.g ); - this.b = SRGBToLinear( color.b ); - - return this; - - } - - /** - * Copies the given color into this color, and then converts this color from - * `LinearSRGBColorSpace` to `SRGBColorSpace`. - * - * @param {Color} color - The color to copy/convert. - * @return {Color} A reference to this color. - */ - copyLinearToSRGB( color ) { - - this.r = LinearToSRGB( color.r ); - this.g = LinearToSRGB( color.g ); - this.b = LinearToSRGB( color.b ); - - return this; - - } - - /** - * Converts this color from `SRGBColorSpace` to `LinearSRGBColorSpace`. - * - * @return {Color} A reference to this color. - */ - convertSRGBToLinear() { - - this.copySRGBToLinear( this ); - - return this; - - } - - /** - * Converts this color from `LinearSRGBColorSpace` to `SRGBColorSpace`. - * - * @return {Color} A reference to this color. - */ - convertLinearToSRGB() { - - this.copyLinearToSRGB( this ); - - return this; - - } - - /** - * Returns the hexadecimal value of this color. - * - * @param {string} [colorSpace=SRGBColorSpace] - The color space. - * @return {number} The hexadecimal value. - */ - getHex( colorSpace = SRGBColorSpace ) { - - ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - - return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); - - } - - /** - * Returns the hexadecimal value of this color as a string (for example, 'FFFFFF'). - * - * @param {string} [colorSpace=SRGBColorSpace] - The color space. - * @return {string} The hexadecimal value as a string. - */ - getHexString( colorSpace = SRGBColorSpace ) { - - return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( -6 ); - - } - - /** - * Converts the colors RGB values into the HSL format and stores them into the - * given target object. - * - * @param {{h:number,s:number,l:number}} target - The target object that is used to store the method's result. - * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. - * @return {{h:number,s:number,l:number}} The HSL representation of this color. - */ - getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - - ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - - const r = _color.r, g = _color.g, b = _color.b; - - const max = Math.max( r, g, b ); - const min = Math.min( r, g, b ); - - let hue, saturation; - const lightness = ( min + max ) / 2.0; - - if ( min === max ) { - - hue = 0; - saturation = 0; - - } else { - - const delta = max - min; - - saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); - - switch ( max ) { - - case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; - case g: hue = ( b - r ) / delta + 2; break; - case b: hue = ( r - g ) / delta + 4; break; - - } - - hue /= 6; - - } - - target.h = hue; - target.s = saturation; - target.l = lightness; - - return target; - - } - - /** - * Returns the RGB values of this color and stores them into the given target object. - * - * @param {Color} target - The target color that is used to store the method's result. - * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. - * @return {Color} The RGB representation of this color. - */ - getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { - - ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - - target.r = _color.r; - target.g = _color.g; - target.b = _color.b; - - return target; - - } - - /** - * Returns the value of this color as a CSS style string. Example: `rgb(255,0,0)`. - * - * @param {string} [colorSpace=SRGBColorSpace] - The color space. - * @return {string} The CSS representation of this color. - */ - getStyle( colorSpace = SRGBColorSpace ) { - - ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); - - const r = _color.r, g = _color.g, b = _color.b; - - if ( colorSpace !== SRGBColorSpace ) { - - // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). - return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; - - } - - return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; - - } - - /** - * Adds the given HSL values to this color's values. - * Internally, this converts the color's RGB values to HSL, adds HSL - * and then converts the color back to RGB. - * - * @param {number} h - Hue value between `0.0` and `1.0`. - * @param {number} s - Saturation value between `0.0` and `1.0`. - * @param {number} l - Lightness value between `0.0` and `1.0`. - * @return {Color} A reference to this color. - */ - offsetHSL( h, s, l ) { - - this.getHSL( _hslA ); - - return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); - - } - - /** - * Adds the RGB values of the given color to the RGB values of this color. - * - * @param {Color} color - The color to add. - * @return {Color} A reference to this color. - */ - add( color ) { - - this.r += color.r; - this.g += color.g; - this.b += color.b; - - return this; - - } - - /** - * Adds the RGB values of the given colors and stores the result in this instance. - * - * @param {Color} color1 - The first color. - * @param {Color} color2 - The second color. - * @return {Color} A reference to this color. - */ - addColors( color1, color2 ) { - - this.r = color1.r + color2.r; - this.g = color1.g + color2.g; - this.b = color1.b + color2.b; - - return this; - - } - - /** - * Adds the given scalar value to the RGB values of this color. - * - * @param {number} s - The scalar to add. - * @return {Color} A reference to this color. - */ - addScalar( s ) { - - this.r += s; - this.g += s; - this.b += s; - - return this; - - } - - /** - * Subtracts the RGB values of the given color from the RGB values of this color. - * - * @param {Color} color - The color to subtract. - * @return {Color} A reference to this color. - */ - sub( color ) { - - this.r = Math.max( 0, this.r - color.r ); - this.g = Math.max( 0, this.g - color.g ); - this.b = Math.max( 0, this.b - color.b ); - - return this; - - } - - /** - * Multiplies the RGB values of the given color with the RGB values of this color. - * - * @param {Color} color - The color to multiply. - * @return {Color} A reference to this color. - */ - multiply( color ) { - - this.r *= color.r; - this.g *= color.g; - this.b *= color.b; - - return this; - - } - - /** - * Multiplies the given scalar value with the RGB values of this color. - * - * @param {number} s - The scalar to multiply. - * @return {Color} A reference to this color. - */ - multiplyScalar( s ) { - - this.r *= s; - this.g *= s; - this.b *= s; - - return this; - - } - - /** - * Linearly interpolates this color's RGB values toward the RGB values of the - * given color. The alpha argument can be thought of as the ratio between - * the two colors, where `0.0` is this color and `1.0` is the first argument. - * - * @param {Color} color - The color to converge on. - * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. - * @return {Color} A reference to this color. - */ - lerp( color, alpha ) { - - this.r += ( color.r - this.r ) * alpha; - this.g += ( color.g - this.g ) * alpha; - this.b += ( color.b - this.b ) * alpha; - - return this; - - } - - /** - * Linearly interpolates between the given colors and stores the result in this instance. - * The alpha argument can be thought of as the ratio between the two colors, where `0.0` - * is the first and `1.0` is the second color. - * - * @param {Color} color1 - The first color. - * @param {Color} color2 - The second color. - * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. - * @return {Color} A reference to this color. - */ - lerpColors( color1, color2, alpha ) { - - this.r = color1.r + ( color2.r - color1.r ) * alpha; - this.g = color1.g + ( color2.g - color1.g ) * alpha; - this.b = color1.b + ( color2.b - color1.b ) * alpha; - - return this; - - } - - /** - * Linearly interpolates this color's HSL values toward the HSL values of the - * given color. It differs from {@link Color#lerp} by not interpolating straight - * from one color to the other, but instead going through all the hues in between - * those two colors. The alpha argument can be thought of as the ratio between - * the two colors, where 0.0 is this color and 1.0 is the first argument. - * - * @param {Color} color - The color to converge on. - * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. - * @return {Color} A reference to this color. - */ - lerpHSL( color, alpha ) { - - this.getHSL( _hslA ); - color.getHSL( _hslB ); - - const h = lerp( _hslA.h, _hslB.h, alpha ); - const s = lerp( _hslA.s, _hslB.s, alpha ); - const l = lerp( _hslA.l, _hslB.l, alpha ); - - this.setHSL( h, s, l ); - - return this; - - } - - /** - * Sets the color's RGB components from the given 3D vector. - * - * @param {Vector3} v - The vector to set. - * @return {Color} A reference to this color. - */ - setFromVector3( v ) { - - this.r = v.x; - this.g = v.y; - this.b = v.z; - - return this; - - } - - /** - * Transforms this color with the given 3x3 matrix. - * - * @param {Matrix3} m - The matrix. - * @return {Color} A reference to this color. - */ - applyMatrix3( m ) { - - const r = this.r, g = this.g, b = this.b; - const e = m.elements; - - this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; - this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; - this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; - - return this; - - } - - /** - * Returns `true` if this color is equal with the given one. - * - * @param {Color} c - The color to test for equality. - * @return {boolean} Whether this bounding color is equal with the given one. - */ - equals( c ) { - - return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); - - } - - /** - * Sets this color's RGB components from the given array. - * - * @param {Array} array - An array holding the RGB values. - * @param {number} [offset=0] - The offset into the array. - * @return {Color} A reference to this color. - */ - fromArray( array, offset = 0 ) { - - this.r = array[ offset ]; - this.g = array[ offset + 1 ]; - this.b = array[ offset + 2 ]; - - return this; - - } - - /** - * Writes the RGB components of this color to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the color components. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The color components. - */ - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.r; - array[ offset + 1 ] = this.g; - array[ offset + 2 ] = this.b; - - return array; - - } - - /** - * Sets the components of this color from the given buffer attribute. - * - * @param {BufferAttribute} attribute - The buffer attribute holding color data. - * @param {number} index - The index into the attribute. - * @return {Color} A reference to this color. - */ - fromBufferAttribute( attribute, index ) { - - this.r = attribute.getX( index ); - this.g = attribute.getY( index ); - this.b = attribute.getZ( index ); - - return this; - - } - - /** - * This methods defines the serialization result of this class. Returns the color - * as a hexadecimal value. - * - * @return {number} The hexadecimal value. - */ - toJSON() { - - return this.getHex(); - - } - - *[ Symbol.iterator ]() { - - yield this.r; - yield this.g; - yield this.b; - - } - -} - -const _color = /*@__PURE__*/ new Color(); - -/** - * A dictionary with X11 color names. - * - * Note that multiple words such as Dark Orange become the string 'darkorange'. - * - * @static - * @type {Object} - */ -Color.NAMES = _colorKeywords; - -/** - * Represents an axis-aligned bounding box (AABB) in 3D space. - */ -class Box3 { - - /** - * Constructs a new bounding box. - * - * @param {Vector3} [min=(Infinity,Infinity,Infinity)] - A vector representing the lower boundary of the box. - * @param {Vector3} [max=(-Infinity,-Infinity,-Infinity)] - A vector representing the upper boundary of the box. - */ - constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isBox3 = true; - - /** - * The lower boundary of the box. - * - * @type {Vector3} - */ - this.min = min; - - /** - * The upper boundary of the box. - * - * @type {Vector3} - */ - this.max = max; - - } - - /** - * Sets the lower and upper boundaries of this box. - * Please note that this method only copies the values from the given objects. - * - * @param {Vector3} min - The lower boundary of the box. - * @param {Vector3} max - The upper boundary of the box. - * @return {Box3} A reference to this bounding box. - */ - set( min, max ) { - - this.min.copy( min ); - this.max.copy( max ); - - return this; - - } - - /** - * Sets the upper and lower bounds of this box so it encloses the position data - * in the given array. - * - * @param {Array} array - An array holding 3D position data. - * @return {Box3} A reference to this bounding box. - */ - setFromArray( array ) { - - this.makeEmpty(); - - for ( let i = 0, il = array.length; i < il; i += 3 ) { - - this.expandByPoint( _vector$7.fromArray( array, i ) ); - - } - - return this; - - } - - /** - * Sets the upper and lower bounds of this box so it encloses the position data - * in the given buffer attribute. - * - * @param {BufferAttribute} attribute - A buffer attribute holding 3D position data. - * @return {Box3} A reference to this bounding box. - */ - setFromBufferAttribute( attribute ) { - - this.makeEmpty(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - this.expandByPoint( _vector$7.fromBufferAttribute( attribute, i ) ); - - } - - return this; - - } - - /** - * Sets the upper and lower bounds of this box so it encloses the position data - * in the given array. - * - * @param {Array} points - An array holding 3D position data as instances of {@link Vector3}. - * @return {Box3} A reference to this bounding box. - */ - setFromPoints( points ) { - - this.makeEmpty(); - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - this.expandByPoint( points[ i ] ); - - } - - return this; - - } - - /** - * Centers this box on the given center vector and sets this box's width, height and - * depth to the given size values. - * - * @param {Vector3} center - The center of the box. - * @param {Vector3} size - The x, y and z dimensions of the box. - * @return {Box3} A reference to this bounding box. - */ - setFromCenterAndSize( center, size ) { - - const halfSize = _vector$7.copy( size ).multiplyScalar( 0.5 ); - - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); - - return this; - - } - - /** - * Computes the world-axis-aligned bounding box for the given 3D object - * (including its children), accounting for the object's, and children's, - * world transforms. The function may result in a larger box than strictly necessary. - * - * Note: To compute the correct bounding box, make sure the given 3D object - * has an up-to-date world matrix that reflects the current transformation of its - * ancestor nodes. Call `object.updateWorldMatrix( true, false )` beforehand if - * you're unsure. - * - * @param {Object3D} object - The 3D object to compute the bounding box for. - * @param {boolean} [precise=false] - If set to `true`, the method computes the smallest - * world-axis-aligned bounding box at the expense of more computation. - * @return {Box3} A reference to this bounding box. - */ - setFromObject( object, precise = false ) { - - this.makeEmpty(); - - return this.expandByObject( object, precise ); - - } - - /** - * Returns a new box with copied values from this instance. - * - * @return {Box3} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the values of the given box to this instance. - * - * @param {Box3} box - The box to copy. - * @return {Box3} A reference to this bounding box. - */ - copy( box ) { - - this.min.copy( box.min ); - this.max.copy( box.max ); - - return this; - - } - - /** - * Makes this box empty which means in encloses a zero space in 3D. - * - * @return {Box3} A reference to this bounding box. - */ - makeEmpty() { - - this.min.x = this.min.y = this.min.z = + Infinity; - this.max.x = this.max.y = this.max.z = - Infinity; - - return this; - - } - - /** - * Returns true if this box includes zero points within its bounds. - * Note that a box with equal lower and upper bounds still includes one - * point, the one both bounds share. - * - * @return {boolean} Whether this box is empty or not. - */ - isEmpty() { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); - - } - - /** - * Returns the center point of this box. - * - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The center point. - */ - getCenter( target ) { - - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - - } - - /** - * Returns the dimensions of this box. - * - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The size. - */ - getSize( target ) { - - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); - - } - - /** - * Expands the boundaries of this box to include the given point. - * - * @param {Vector3} point - The point that should be included by the bounding box. - * @return {Box3} A reference to this bounding box. - */ - expandByPoint( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; - - } - - /** - * Expands this box equilaterally by the given vector. The width of this - * box will be expanded by the x component of the vector in both - * directions. The height of this box will be expanded by the y component of - * the vector in both directions. The depth of this box will be - * expanded by the z component of the vector in both directions. - * - * @param {Vector3} vector - The vector that should expand the bounding box. - * @return {Box3} A reference to this bounding box. - */ - expandByVector( vector ) { - - this.min.sub( vector ); - this.max.add( vector ); - - return this; - - } - - /** - * Expands each dimension of the box by the given scalar. If negative, the - * dimensions of the box will be contracted. - * - * @param {number} scalar - The scalar value that should expand the bounding box. - * @return {Box3} A reference to this bounding box. - */ - expandByScalar( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); - - return this; - - } - - /** - * Expands the boundaries of this box to include the given 3D object and - * its children, accounting for the object's, and children's, world - * transforms. The function may result in a larger box than strictly - * necessary (unless the precise parameter is set to true). - * - * @param {Object3D} object - The 3D object that should expand the bounding box. - * @param {boolean} precise - If set to `true`, the method expands the bounding box - * as little as necessary at the expense of more computation. - * @return {Box3} A reference to this bounding box. - */ - expandByObject( object, precise = false ) { - - // Computes the world-axis-aligned bounding box of an object (including its children), - // accounting for both the object's, and children's, world transforms - - object.updateWorldMatrix( false, false ); - - const geometry = object.geometry; - - if ( geometry !== undefined ) { - - const positionAttribute = geometry.getAttribute( 'position' ); - - // precise AABB computation based on vertex data requires at least a position attribute. - // instancing isn't supported so far and uses the normal (conservative) code path. - - if ( precise === true && positionAttribute !== undefined && object.isInstancedMesh !== true ) { - - for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) { - - if ( object.isMesh === true ) { - - object.getVertexPosition( i, _vector$7 ); - - } else { - - _vector$7.fromBufferAttribute( positionAttribute, i ); - - } - - _vector$7.applyMatrix4( object.matrixWorld ); - this.expandByPoint( _vector$7 ); - - } - - } else { - - if ( object.boundingBox !== undefined ) { - - // object-level bounding box - - if ( object.boundingBox === null ) { - - object.computeBoundingBox(); - - } - - _box$3.copy( object.boundingBox ); - - - } else { - - // geometry-level bounding box - - if ( geometry.boundingBox === null ) { - - geometry.computeBoundingBox(); - - } - - _box$3.copy( geometry.boundingBox ); - - } - - _box$3.applyMatrix4( object.matrixWorld ); - - this.union( _box$3 ); - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - this.expandByObject( children[ i ], precise ); - - } - - return this; - - } - - /** - * Returns `true` if the given point lies within or on the boundaries of this box. - * - * @param {Vector3} point - The point to test. - * @return {boolean} Whether the bounding box contains the given point or not. - */ - containsPoint( point ) { - - return point.x >= this.min.x && point.x <= this.max.x && - point.y >= this.min.y && point.y <= this.max.y && - point.z >= this.min.z && point.z <= this.max.z; - - } - - /** - * Returns `true` if this bounding box includes the entirety of the given bounding box. - * If this box and the given one are identical, this function also returns `true`. - * - * @param {Box3} box - The bounding box to test. - * @return {boolean} Whether the bounding box contains the given bounding box or not. - */ - containsBox( box ) { - - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y && - this.min.z <= box.min.z && box.max.z <= this.max.z; - - } - - /** - * Returns a point as a proportion of this box's width, height and depth. - * - * @param {Vector3} point - A point in 3D space. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} A point as a proportion of this box's width, height and depth. - */ - getParameter( point, target ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ), - ( point.z - this.min.z ) / ( this.max.z - this.min.z ) - ); - - } - - /** - * Returns `true` if the given bounding box intersects with this bounding box. - * - * @param {Box3} box - The bounding box to test. - * @return {boolean} Whether the given bounding box intersects with this bounding box. - */ - intersectsBox( box ) { - - // using 6 splitting planes to rule out intersections. - return box.max.x >= this.min.x && box.min.x <= this.max.x && - box.max.y >= this.min.y && box.min.y <= this.max.y && - box.max.z >= this.min.z && box.min.z <= this.max.z; - - } - - /** - * Returns `true` if the given bounding sphere intersects with this bounding box. - * - * @param {Sphere} sphere - The bounding sphere to test. - * @return {boolean} Whether the given bounding sphere intersects with this bounding box. - */ - intersectsSphere( sphere ) { - - // Find the point on the AABB closest to the sphere center. - this.clampPoint( sphere.center, _vector$7 ); - - // If that point is inside the sphere, the AABB and sphere intersect. - return _vector$7.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); - - } - - /** - * Returns `true` if the given plane intersects with this bounding box. - * - * @param {Plane} plane - The plane to test. - * @return {boolean} Whether the given plane intersects with this bounding box. - */ - intersectsPlane( plane ) { - - // We compute the minimum and maximum dot product values. If those values - // are on the same side (back or front) of the plane, then there is no intersection. - - let min, max; - - if ( plane.normal.x > 0 ) { - - min = plane.normal.x * this.min.x; - max = plane.normal.x * this.max.x; - - } else { - - min = plane.normal.x * this.max.x; - max = plane.normal.x * this.min.x; - - } - - if ( plane.normal.y > 0 ) { - - min += plane.normal.y * this.min.y; - max += plane.normal.y * this.max.y; - - } else { - - min += plane.normal.y * this.max.y; - max += plane.normal.y * this.min.y; - - } - - if ( plane.normal.z > 0 ) { - - min += plane.normal.z * this.min.z; - max += plane.normal.z * this.max.z; - - } else { - - min += plane.normal.z * this.max.z; - max += plane.normal.z * this.min.z; - - } - - return ( min <= - plane.constant && max >= - plane.constant ); - - } - - /** - * Returns `true` if the given triangle intersects with this bounding box. - * - * @param {Triangle} triangle - The triangle to test. - * @return {boolean} Whether the given triangle intersects with this bounding box. - */ - intersectsTriangle( triangle ) { - - if ( this.isEmpty() ) { - - return false; - - } - - // compute box center and extents - this.getCenter( _center ); - _extents.subVectors( this.max, _center ); - - // translate triangle to aabb origin - _v0$3.subVectors( triangle.a, _center ); - _v1$6.subVectors( triangle.b, _center ); - _v2$3.subVectors( triangle.c, _center ); - - // compute edge vectors for triangle - _f0.subVectors( _v1$6, _v0$3 ); - _f1.subVectors( _v2$3, _v1$6 ); - _f2.subVectors( _v0$3, _v2$3 ); - - // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb - // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation - // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) - let axes = [ - 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y, - _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, - - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 - ]; - if ( ! satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ) ) { - - return false; - - } - - // test 3 face normals from the aabb - axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; - if ( ! satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ) ) { - - return false; - - } - - // finally testing the face normal of the triangle - // use already existing triangle edge vectors here - _triangleNormal.crossVectors( _f0, _f1 ); - axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; - - return satForAxes( axes, _v0$3, _v1$6, _v2$3, _extents ); - - } - - /** - * Clamps the given point within the bounds of this box. - * - * @param {Vector3} point - The point to clamp. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The clamped point. - */ - clampPoint( point, target ) { - - return target.copy( point ).clamp( this.min, this.max ); - - } - - /** - * Returns the euclidean distance from any edge of this box to the specified point. If - * the given point lies inside of this box, the distance will be `0`. - * - * @param {Vector3} point - The point to compute the distance to. - * @return {number} The euclidean distance. - */ - distanceToPoint( point ) { - - return this.clampPoint( point, _vector$7 ).distanceTo( point ); - - } - - /** - * Returns a bounding sphere that encloses this bounding box. - * - * @param {Sphere} target - The target sphere that is used to store the method's result. - * @return {Sphere} The bounding sphere that encloses this bounding box. - */ - getBoundingSphere( target ) { - - if ( this.isEmpty() ) { - - target.makeEmpty(); - - } else { - - this.getCenter( target.center ); - - target.radius = this.getSize( _vector$7 ).length() * 0.5; - - } - - return target; - - } - - /** - * Computes the intersection of this bounding box and the given one, setting the upper - * bound of this box to the lesser of the two boxes' upper bounds and the - * lower bound of this box to the greater of the two boxes' lower bounds. If - * there's no overlap, makes this box empty. - * - * @param {Box3} box - The bounding box to intersect with. - * @return {Box3} A reference to this bounding box. - */ - intersect( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); - - // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. - if ( this.isEmpty() ) this.makeEmpty(); - - return this; - - } - - /** - * Computes the union of this box and another and the given one, setting the upper - * bound of this box to the greater of the two boxes' upper bounds and the - * lower bound of this box to the lesser of the two boxes' lower bounds. - * - * @param {Box3} box - The bounding box that will be unioned with this instance. - * @return {Box3} A reference to this bounding box. - */ - union( box ) { - - this.min.min( box.min ); - this.max.max( box.max ); - - return this; - - } - - /** - * Transforms this bounding box by the given 4x4 transformation matrix. - * - * @param {Matrix4} matrix - The transformation matrix. - * @return {Box3} A reference to this bounding box. - */ - applyMatrix4( matrix ) { - - // transform of empty box is an empty box. - if ( this.isEmpty() ) return this; - - // NOTE: I am using a binary pattern to specify all 2^3 combinations below - _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 - _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 - _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 - _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 - _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 - _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 - _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 - _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 - - this.setFromPoints( _points ); - - return this; - - } - - /** - * Adds the given offset to both the upper and lower bounds of this bounding box, - * effectively moving it in 3D space. - * - * @param {Vector3} offset - The offset that should be used to translate the bounding box. - * @return {Box3} A reference to this bounding box. - */ - translate( offset ) { - - this.min.add( offset ); - this.max.add( offset ); - - return this; - - } - - /** - * Returns `true` if this bounding box is equal with the given one. - * - * @param {Box3} box - The box to test for equality. - * @return {boolean} Whether this bounding box is equal with the given one. - */ - equals( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - - /** - * Returns a serialized structure of the bounding box. - * - * @return {Object} Serialized structure with fields representing the object state. - */ - toJSON() { - - return { - min: this.min.toArray(), - max: this.max.toArray() - }; - - } - - /** - * Returns a serialized structure of the bounding box. - * - * @param {Object} json - The serialized json to set the box from. - * @return {Box3} A reference to this bounding box. - */ - fromJSON( json ) { - - this.min.fromArray( json.min ); - this.max.fromArray( json.max ); - return this; - - } - -} - -const _points = [ - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3() -]; - -const _vector$7 = /*@__PURE__*/ new Vector3(); - -const _box$3 = /*@__PURE__*/ new Box3(); - -// triangle centered vertices - -const _v0$3 = /*@__PURE__*/ new Vector3(); -const _v1$6 = /*@__PURE__*/ new Vector3(); -const _v2$3 = /*@__PURE__*/ new Vector3(); - -// triangle edge vectors - -const _f0 = /*@__PURE__*/ new Vector3(); -const _f1 = /*@__PURE__*/ new Vector3(); -const _f2 = /*@__PURE__*/ new Vector3(); - -const _center = /*@__PURE__*/ new Vector3(); -const _extents = /*@__PURE__*/ new Vector3(); -const _triangleNormal = /*@__PURE__*/ new Vector3(); -const _testAxis = /*@__PURE__*/ new Vector3(); - -function satForAxes( axes, v0, v1, v2, extents ) { - - for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) { - - _testAxis.fromArray( axes, i ); - // project the aabb onto the separating axis - const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z ); - // project all 3 vertices of the triangle onto the separating axis - const p0 = v0.dot( _testAxis ); - const p1 = v1.dot( _testAxis ); - const p2 = v2.dot( _testAxis ); - // actual test, basically see if either of the most extreme of the triangle points intersects r - if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { - - // points of the projected triangle are outside the projected half-length of the aabb - // the axis is separating and we can exit - return false; - - } - - } - - return true; - -} - -const _box$2 = /*@__PURE__*/ new Box3(); -const _v1$5 = /*@__PURE__*/ new Vector3(); -const _v2$2 = /*@__PURE__*/ new Vector3(); - -/** - * An analytical 3D sphere defined by a center and radius. This class is mainly - * used as a Bounding Sphere for 3D objects. - */ -class Sphere { - - /** - * Constructs a new sphere. - * - * @param {Vector3} [center=(0,0,0)] - The center of the sphere - * @param {number} [radius=-1] - The radius of the sphere. - */ - constructor( center = new Vector3(), radius = -1 ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isSphere = true; - - /** - * The center of the sphere - * - * @type {Vector3} - */ - this.center = center; - - /** - * The radius of the sphere. - * - * @type {number} - */ - this.radius = radius; - - } - - /** - * Sets the sphere's components by copying the given values. - * - * @param {Vector3} center - The center. - * @param {number} radius - The radius. - * @return {Sphere} A reference to this sphere. - */ - set( center, radius ) { - - this.center.copy( center ); - this.radius = radius; - - return this; - - } - - /** - * Computes the minimum bounding sphere for list of points. - * If the optional center point is given, it is used as the sphere's - * center. Otherwise, the center of the axis-aligned bounding box - * encompassing the points is calculated. - * - * @param {Array} points - A list of points in 3D space. - * @param {Vector3} [optionalCenter] - The center of the sphere. - * @return {Sphere} A reference to this sphere. - */ - setFromPoints( points, optionalCenter ) { - - const center = this.center; - - if ( optionalCenter !== undefined ) { - - center.copy( optionalCenter ); - - } else { - - _box$2.setFromPoints( points ).getCenter( center ); - - } - - let maxRadiusSq = 0; - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); - - } - - this.radius = Math.sqrt( maxRadiusSq ); - - return this; - - } - - /** - * Copies the values of the given sphere to this instance. - * - * @param {Sphere} sphere - The sphere to copy. - * @return {Sphere} A reference to this sphere. - */ - copy( sphere ) { - - this.center.copy( sphere.center ); - this.radius = sphere.radius; - - return this; - - } - - /** - * Returns `true` if the sphere is empty (the radius set to a negative number). - * - * Spheres with a radius of `0` contain only their center point and are not - * considered to be empty. - * - * @return {boolean} Whether this sphere is empty or not. - */ - isEmpty() { - - return ( this.radius < 0 ); - - } - - /** - * Makes this sphere empty which means in encloses a zero space in 3D. - * - * @return {Sphere} A reference to this sphere. - */ - makeEmpty() { - - this.center.set( 0, 0, 0 ); - this.radius = -1; - - return this; - - } - - /** - * Returns `true` if this sphere contains the given point inclusive of - * the surface of the sphere. - * - * @param {Vector3} point - The point to check. - * @return {boolean} Whether this sphere contains the given point or not. - */ - containsPoint( point ) { - - return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); - - } - - /** - * Returns the closest distance from the boundary of the sphere to the - * given point. If the sphere contains the point, the distance will - * be negative. - * - * @param {Vector3} point - The point to compute the distance to. - * @return {number} The distance to the point. - */ - distanceToPoint( point ) { - - return ( point.distanceTo( this.center ) - this.radius ); - - } - - /** - * Returns `true` if this sphere intersects with the given one. - * - * @param {Sphere} sphere - The sphere to test. - * @return {boolean} Whether this sphere intersects with the given one or not. - */ - intersectsSphere( sphere ) { - - const radiusSum = this.radius + sphere.radius; - - return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); - - } - - /** - * Returns `true` if this sphere intersects with the given box. - * - * @param {Box3} box - The box to test. - * @return {boolean} Whether this sphere intersects with the given box or not. - */ - intersectsBox( box ) { - - return box.intersectsSphere( this ); - - } - - /** - * Returns `true` if this sphere intersects with the given plane. - * - * @param {Plane} plane - The plane to test. - * @return {boolean} Whether this sphere intersects with the given plane or not. - */ - intersectsPlane( plane ) { - - return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; - - } - - /** - * Clamps a point within the sphere. If the point is outside the sphere, it - * will clamp it to the closest point on the edge of the sphere. Points - * already inside the sphere will not be affected. - * - * @param {Vector3} point - The plane to clamp. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The clamped point. - */ - clampPoint( point, target ) { - - const deltaLengthSq = this.center.distanceToSquared( point ); - - target.copy( point ); - - if ( deltaLengthSq > ( this.radius * this.radius ) ) { - - target.sub( this.center ).normalize(); - target.multiplyScalar( this.radius ).add( this.center ); - - } - - return target; - - } - - /** - * Returns a bounding box that encloses this sphere. - * - * @param {Box3} target - The target box that is used to store the method's result. - * @return {Box3} The bounding box that encloses this sphere. - */ - getBoundingBox( target ) { - - if ( this.isEmpty() ) { - - // Empty sphere produces empty bounding box - target.makeEmpty(); - return target; - - } - - target.set( this.center, this.center ); - target.expandByScalar( this.radius ); - - return target; - - } - - /** - * Transforms this sphere with the given 4x4 transformation matrix. - * - * @param {Matrix4} matrix - The transformation matrix. - * @return {Sphere} A reference to this sphere. - */ - applyMatrix4( matrix ) { - - this.center.applyMatrix4( matrix ); - this.radius = this.radius * matrix.getMaxScaleOnAxis(); - - return this; - - } - - /** - * Translates the sphere's center by the given offset. - * - * @param {Vector3} offset - The offset. - * @return {Sphere} A reference to this sphere. - */ - translate( offset ) { - - this.center.add( offset ); - - return this; - - } - - /** - * Expands the boundaries of this sphere to include the given point. - * - * @param {Vector3} point - The point to include. - * @return {Sphere} A reference to this sphere. - */ - expandByPoint( point ) { - - if ( this.isEmpty() ) { - - this.center.copy( point ); - - this.radius = 0; - - return this; - - } - - _v1$5.subVectors( point, this.center ); - - const lengthSq = _v1$5.lengthSq(); - - if ( lengthSq > ( this.radius * this.radius ) ) { - - // calculate the minimal sphere - - const length = Math.sqrt( lengthSq ); - - const delta = ( length - this.radius ) * 0.5; - - this.center.addScaledVector( _v1$5, delta / length ); - - this.radius += delta; - - } - - return this; - - } - - /** - * Expands this sphere to enclose both the original sphere and the given sphere. - * - * @param {Sphere} sphere - The sphere to include. - * @return {Sphere} A reference to this sphere. - */ - union( sphere ) { - - if ( sphere.isEmpty() ) { - - return this; - - } - - if ( this.isEmpty() ) { - - this.copy( sphere ); - - return this; - - } - - if ( this.center.equals( sphere.center ) === true ) { - - this.radius = Math.max( this.radius, sphere.radius ); - - } else { - - _v2$2.subVectors( sphere.center, this.center ).setLength( sphere.radius ); - - this.expandByPoint( _v1$5.copy( sphere.center ).add( _v2$2 ) ); - - this.expandByPoint( _v1$5.copy( sphere.center ).sub( _v2$2 ) ); - - } - - return this; - - } - - /** - * Returns `true` if this sphere is equal with the given one. - * - * @param {Sphere} sphere - The sphere to test for equality. - * @return {boolean} Whether this bounding sphere is equal with the given one. - */ - equals( sphere ) { - - return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); - - } - - /** - * Returns a new sphere with copied values from this instance. - * - * @return {Sphere} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Returns a serialized structure of the bounding sphere. - * - * @return {Object} Serialized structure with fields representing the object state. - */ - toJSON() { - - return { - radius: this.radius, - center: this.center.toArray() - }; - - } - - /** - * Returns a serialized structure of the bounding sphere. - * - * @param {Object} json - The serialized json to set the sphere from. - * @return {Sphere} A reference to this bounding sphere. - */ - fromJSON( json ) { - - this.radius = json.radius; - this.center.fromArray( json.center ); - return this; - - } - -} - -const _vector1 = /*@__PURE__*/ new Vector3(); -const _vector2$1 = /*@__PURE__*/ new Vector3(); -const _normalMatrix = /*@__PURE__*/ new Matrix3(); - -/** - * A two dimensional surface that extends infinitely in 3D space, represented - * in [Hessian normal form](https://mathworld.wolfram.com/HessianNormalForm.html) - * by a unit length normal vector and a constant. - */ -class Plane { - - /** - * Constructs a new plane. - * - * @param {Vector3} [normal=(1,0,0)] - A unit length vector defining the normal of the plane. - * @param {number} [constant=0] - The signed distance from the origin to the plane. - */ - constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isPlane = true; - - /** - * A unit length vector defining the normal of the plane. - * - * @type {Vector3} - */ - this.normal = normal; - - /** - * The signed distance from the origin to the plane. - * - * @type {number} - * @default 0 - */ - this.constant = constant; - - } - - /** - * Sets the plane components by copying the given values. - * - * @param {Vector3} normal - The normal. - * @param {number} constant - The constant. - * @return {Plane} A reference to this plane. - */ - set( normal, constant ) { - - this.normal.copy( normal ); - this.constant = constant; - - return this; - - } - - /** - * Sets the plane components by defining `x`, `y`, `z` as the - * plane normal and `w` as the constant. - * - * @param {number} x - The value for the normal's x component. - * @param {number} y - The value for the normal's y component. - * @param {number} z - The value for the normal's z component. - * @param {number} w - The constant value. - * @return {Plane} A reference to this plane. - */ - setComponents( x, y, z, w ) { - - this.normal.set( x, y, z ); - this.constant = w; - - return this; - - } - - /** - * Sets the plane from the given normal and coplanar point (that is a point - * that lies onto the plane). - * - * @param {Vector3} normal - The normal. - * @param {Vector3} point - A coplanar point. - * @return {Plane} A reference to this plane. - */ - setFromNormalAndCoplanarPoint( normal, point ) { - - this.normal.copy( normal ); - this.constant = - point.dot( this.normal ); - - return this; - - } - - /** - * Sets the plane from three coplanar points. The winding order is - * assumed to be counter-clockwise, and determines the direction of - * the plane normal. - * - * @param {Vector3} a - The first coplanar point. - * @param {Vector3} b - The second coplanar point. - * @param {Vector3} c - The third coplanar point. - * @return {Plane} A reference to this plane. - */ - setFromCoplanarPoints( a, b, c ) { - - const normal = _vector1.subVectors( c, b ).cross( _vector2$1.subVectors( a, b ) ).normalize(); - - // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? - - this.setFromNormalAndCoplanarPoint( normal, a ); - - return this; - - } - - /** - * Copies the values of the given plane to this instance. - * - * @param {Plane} plane - The plane to copy. - * @return {Plane} A reference to this plane. - */ - copy( plane ) { - - this.normal.copy( plane.normal ); - this.constant = plane.constant; - - return this; - - } - - /** - * Normalizes the plane normal and adjusts the constant accordingly. - * - * @return {Plane} A reference to this plane. - */ - normalize() { - - // Note: will lead to a divide by zero if the plane is invalid. - - const inverseNormalLength = 1.0 / this.normal.length(); - this.normal.multiplyScalar( inverseNormalLength ); - this.constant *= inverseNormalLength; - - return this; - - } - - /** - * Negates both the plane normal and the constant. - * - * @return {Plane} A reference to this plane. - */ - negate() { - - this.constant *= -1; - this.normal.negate(); - - return this; - - } - - /** - * Returns the signed distance from the given point to this plane. - * - * @param {Vector3} point - The point to compute the distance for. - * @return {number} The signed distance. - */ - distanceToPoint( point ) { - - return this.normal.dot( point ) + this.constant; - - } - - /** - * Returns the signed distance from the given sphere to this plane. - * - * @param {Sphere} sphere - The sphere to compute the distance for. - * @return {number} The signed distance. - */ - distanceToSphere( sphere ) { - - return this.distanceToPoint( sphere.center ) - sphere.radius; - - } - - /** - * Projects a the given point onto the plane. - * - * @param {Vector3} point - The point to project. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The projected point on the plane. - */ - projectPoint( point, target ) { - - return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); - - } - - /** - * Returns the intersection point of the passed line and the plane. Returns - * `null` if the line does not intersect. Returns the line's starting point if - * the line is coplanar with the plane. - * - * @param {Line3} line - The line to compute the intersection for. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @param {boolean} [clampToLine=true] - Whether to clamp the intersection to the line segment. - * @return {?Vector3} The intersection point. Returns `null` if no intersection is detected. - */ - intersectLine( line, target, clampToLine = true ) { - - const direction = line.delta( _vector1 ); - - const denominator = this.normal.dot( direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( this.distanceToPoint( line.start ) === 0 ) { - - return target.copy( line.start ); - - } - - // Unsure if this is the correct method to handle this case. - return null; - - } - - const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; - - if ( ( clampToLine === true ) && ( t < 0 || t > 1 ) ) { - - return null; - - } - - return target.copy( line.start ).addScaledVector( direction, t ); - - } - - /** - * Returns `true` if the given line segment intersects with (passes through) the plane. - * - * @param {Line3} line - The line to test. - * @return {boolean} Whether the given line segment intersects with the plane or not. - */ - intersectsLine( line ) { - - // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. - - const startSign = this.distanceToPoint( line.start ); - const endSign = this.distanceToPoint( line.end ); - - return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); - - } - - /** - * Returns `true` if the given bounding box intersects with the plane. - * - * @param {Box3} box - The bounding box to test. - * @return {boolean} Whether the given bounding box intersects with the plane or not. - */ - intersectsBox( box ) { - - return box.intersectsPlane( this ); - - } - - /** - * Returns `true` if the given bounding sphere intersects with the plane. - * - * @param {Sphere} sphere - The bounding sphere to test. - * @return {boolean} Whether the given bounding sphere intersects with the plane or not. - */ - intersectsSphere( sphere ) { - - return sphere.intersectsPlane( this ); - - } - - /** - * Returns a coplanar vector to the plane, by calculating the - * projection of the normal at the origin onto the plane. - * - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The coplanar point. - */ - coplanarPoint( target ) { - - return target.copy( this.normal ).multiplyScalar( - this.constant ); - - } - - /** - * Apply a 4x4 matrix to the plane. The matrix must be an affine, homogeneous transform. - * - * The optional normal matrix can be pre-computed like so: - * ```js - * const optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); - * ``` - * - * @param {Matrix4} matrix - The transformation matrix. - * @param {Matrix4} [optionalNormalMatrix] - A pre-computed normal matrix. - * @return {Plane} A reference to this plane. - */ - applyMatrix4( matrix, optionalNormalMatrix ) { - - const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); - - const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); - - const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); - - this.constant = - referencePoint.dot( normal ); - - return this; - - } - - /** - * Translates the plane by the distance defined by the given offset vector. - * Note that this only affects the plane constant and will not affect the normal vector. - * - * @param {Vector3} offset - The offset vector. - * @return {Plane} A reference to this plane. - */ - translate( offset ) { - - this.constant -= offset.dot( this.normal ); - - return this; - - } - - /** - * Returns `true` if this plane is equal with the given one. - * - * @param {Plane} plane - The plane to test for equality. - * @return {boolean} Whether this plane is equal with the given one. - */ - equals( plane ) { - - return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); - - } - - /** - * Returns a new plane with copied values from this instance. - * - * @return {Plane} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Returns a serialized structure of the plane. - * - * @return {Object} Serialized structure with fields representing the object state. - */ - toJSON() { - - return { - normal: this.normal.toArray(), - constant: this.constant - }; - - } - - /** - * Sets the plane properties from the given JSON. - * - * @param {Object} json - The serialized json to set the plane from. - * @return {Plane} A reference to this plane. - */ - fromJSON( json ) { - - this.normal.fromArray( json.normal ); - this.constant = json.constant; - - return this; - - } - -} - -const _sphere$6 = /*@__PURE__*/ new Sphere(); -const _defaultSpriteCenter = /*@__PURE__*/ new Vector2( 0.5, 0.5 ); -const _vector$6 = /*@__PURE__*/ new Vector3(); - -/** - * Frustums are used to determine what is inside the camera's field of view. - * They help speed up the rendering process - objects which lie outside a camera's - * frustum can safely be excluded from rendering. - * - * This class is mainly intended for use internally by a renderer. - */ -class Frustum { - - /** - * Constructs a new frustum. - * - * @param {Plane} [p0] - The first plane that encloses the frustum. - * @param {Plane} [p1] - The second plane that encloses the frustum. - * @param {Plane} [p2] - The third plane that encloses the frustum. - * @param {Plane} [p3] - The fourth plane that encloses the frustum. - * @param {Plane} [p4] - The fifth plane that encloses the frustum. - * @param {Plane} [p5] - The sixth plane that encloses the frustum. - */ - constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { - - /** - * This array holds the planes that enclose the frustum. - * - * @type {Array} - */ - this.planes = [ p0, p1, p2, p3, p4, p5 ]; - - } - - /** - * Sets the frustum planes by copying the given planes. - * - * @param {Plane} [p0] - The first plane that encloses the frustum. - * @param {Plane} [p1] - The second plane that encloses the frustum. - * @param {Plane} [p2] - The third plane that encloses the frustum. - * @param {Plane} [p3] - The fourth plane that encloses the frustum. - * @param {Plane} [p4] - The fifth plane that encloses the frustum. - * @param {Plane} [p5] - The sixth plane that encloses the frustum. - * @return {Frustum} A reference to this frustum. - */ - set( p0, p1, p2, p3, p4, p5 ) { - - const planes = this.planes; - - planes[ 0 ].copy( p0 ); - planes[ 1 ].copy( p1 ); - planes[ 2 ].copy( p2 ); - planes[ 3 ].copy( p3 ); - planes[ 4 ].copy( p4 ); - planes[ 5 ].copy( p5 ); - - return this; - - } - - /** - * Copies the values of the given frustum to this instance. - * - * @param {Frustum} frustum - The frustum to copy. - * @return {Frustum} A reference to this frustum. - */ - copy( frustum ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - planes[ i ].copy( frustum.planes[ i ] ); - - } - - return this; - - } - - /** - * Sets the frustum planes from the given projection matrix. - * - * @param {Matrix4} m - The projection matrix. - * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} coordinateSystem - The coordinate system. - * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. - * @return {Frustum} A reference to this frustum. - */ - setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { - - const planes = this.planes; - const me = m.elements; - const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; - const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; - const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; - const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; - - planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); - planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); - planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); - planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - - if ( reversedDepth ) { - - planes[ 4 ].setComponents( me2, me6, me10, me14 ).normalize(); // far - planes[ 5 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); // near - - } else { - - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); // far - - if ( coordinateSystem === WebGLCoordinateSystem ) { - - planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); // near - - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - - planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); // near - - } else { - - throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); - - } - - } - - return this; - - } - - /** - * Returns `true` if the 3D object's bounding sphere is intersecting this frustum. - * - * Note that the 3D object must have a geometry so that the bounding sphere can be calculated. - * - * @param {Object3D} object - The 3D object to test. - * @return {boolean} Whether the 3D object's bounding sphere is intersecting this frustum or not. - */ - intersectsObject( object ) { - - if ( object.boundingSphere !== undefined ) { - - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - - _sphere$6.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); - - } else { - - const geometry = object.geometry; - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$6.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); - - } - - return this.intersectsSphere( _sphere$6 ); - - } - - /** - * Returns `true` if the given sprite is intersecting this frustum. - * - * @param {Sprite} sprite - The sprite to test. - * @return {boolean} Whether the sprite is intersecting this frustum or not. - */ - intersectsSprite( sprite ) { - - _sphere$6.center.set( 0, 0, 0 ); - - const offset = _defaultSpriteCenter.distanceTo( sprite.center ); - - _sphere$6.radius = 0.7071067811865476 + offset; - _sphere$6.applyMatrix4( sprite.matrixWorld ); - - return this.intersectsSphere( _sphere$6 ); - - } - - /** - * Returns `true` if the given bounding sphere is intersecting this frustum. - * - * This is a fast, conservative test that favors performance over precision. It can - * report false positives for spheres that lie outside the frustum but are not separated - * by a single frustum plane. It never reports false negatives, so it is safe for culling. - * - * @param {Sphere} sphere - The bounding sphere to test. - * @return {boolean} Whether the bounding sphere is intersecting this frustum or not. - */ - intersectsSphere( sphere ) { - - const planes = this.planes; - const center = sphere.center; - const negRadius = - sphere.radius; - - for ( let i = 0; i < 6; i ++ ) { - - const distance = planes[ i ].distanceToPoint( center ); - - if ( distance < negRadius ) { - - return false; - - } - - } - - return true; - - } - - /** - * Returns `true` if the given bounding box is intersecting this frustum. - * - * This is a fast, conservative test that favors performance over precision. It can - * report false positives for large boxes that lie outside the frustum but are not - * separated by a single frustum plane. It never reports false negatives, so it is - * safe for culling. - * - * @param {Box3} box - The bounding box to test. - * @return {boolean} Whether the bounding box is intersecting this frustum or not. - */ - intersectsBox( box ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - const plane = planes[ i ]; - - // corner at max distance - - _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; - _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; - _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; - - if ( plane.distanceToPoint( _vector$6 ) < 0 ) { - - return false; - - } - - } - - return true; - - } - - /** - * Returns `true` if the given point lies within the frustum. - * - * @param {Vector3} point - The point to test. - * @return {boolean} Whether the point lies within this frustum or not. - */ - containsPoint( point ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - if ( planes[ i ].distanceToPoint( point ) < 0 ) { - - return false; - - } - - } - - return true; - - } - - /** - * Returns a new frustum with copied values from this instance. - * - * @return {Frustum} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - -} - -/** - * Represents a 4x4 matrix. - * - * The most common use of a 4x4 matrix in 3D computer graphics is as a transformation matrix. - * For an introduction to transformation matrices as used in WebGL, check out [this tutorial](https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices) - * - * This allows a 3D vector representing a point in 3D space to undergo - * transformations such as translation, rotation, shear, scale, reflection, - * orthogonal or perspective projection and so on, by being multiplied by the - * matrix. This is known as `applying` the matrix to the vector. - * - * A Note on Row-Major and Column-Major Ordering: - * - * The constructor and {@link Matrix3#set} method take arguments in - * [row-major](https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order) - * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. - * This means that calling: - * ```js - * const m = new THREE.Matrix4(); - * m.set( 11, 12, 13, 14, - * 21, 22, 23, 24, - * 31, 32, 33, 34, - * 41, 42, 43, 44 ); - * ``` - * will result in the elements array containing: - * ```js - * m.elements = [ 11, 21, 31, 41, - * 12, 22, 32, 42, - * 13, 23, 33, 43, - * 14, 24, 34, 44 ]; - * ``` - * and internally all calculations are performed using column-major ordering. - * However, as the actual ordering makes no difference mathematically and - * most people are used to thinking about matrices in row-major order, the - * three.js documentation shows matrices in row-major order. Just bear in - * mind that if you are reading the source code, you'll have to take the - * transpose of any matrices outlined here to make sense of the calculations. - */ -class Matrix4 { - - static { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - Matrix4.prototype.isMatrix4 = true; - - } - - /** - * Constructs a new 4x4 matrix. The arguments are supposed to be - * in row-major order. If no arguments are provided, the constructor - * initializes the matrix as an identity matrix. - * - * @param {number} [n11] - 1-1 matrix element. - * @param {number} [n12] - 1-2 matrix element. - * @param {number} [n13] - 1-3 matrix element. - * @param {number} [n14] - 1-4 matrix element. - * @param {number} [n21] - 2-1 matrix element. - * @param {number} [n22] - 2-2 matrix element. - * @param {number} [n23] - 2-3 matrix element. - * @param {number} [n24] - 2-4 matrix element. - * @param {number} [n31] - 3-1 matrix element. - * @param {number} [n32] - 3-2 matrix element. - * @param {number} [n33] - 3-3 matrix element. - * @param {number} [n34] - 3-4 matrix element. - * @param {number} [n41] - 4-1 matrix element. - * @param {number} [n42] - 4-2 matrix element. - * @param {number} [n43] - 4-3 matrix element. - * @param {number} [n44] - 4-4 matrix element. - */ - constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - - /** - * A column-major list of matrix values. - * - * @type {Array} - */ - this.elements = [ - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ]; - - if ( n11 !== undefined ) { - - this.set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ); - - } - - } - - /** - * Sets the elements of the matrix.The arguments are supposed to be - * in row-major order. - * - * @param {number} [n11] - 1-1 matrix element. - * @param {number} [n12] - 1-2 matrix element. - * @param {number} [n13] - 1-3 matrix element. - * @param {number} [n14] - 1-4 matrix element. - * @param {number} [n21] - 2-1 matrix element. - * @param {number} [n22] - 2-2 matrix element. - * @param {number} [n23] - 2-3 matrix element. - * @param {number} [n24] - 2-4 matrix element. - * @param {number} [n31] - 3-1 matrix element. - * @param {number} [n32] - 3-2 matrix element. - * @param {number} [n33] - 3-3 matrix element. - * @param {number} [n34] - 3-4 matrix element. - * @param {number} [n41] - 4-1 matrix element. - * @param {number} [n42] - 4-2 matrix element. - * @param {number} [n43] - 4-3 matrix element. - * @param {number} [n44] - 4-4 matrix element. - * @return {Matrix4} A reference to this matrix. - */ - set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - - const te = this.elements; - - te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; - te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; - te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; - te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; - - return this; - - } - - /** - * Sets this matrix to the 4x4 identity matrix. - * - * @return {Matrix4} A reference to this matrix. - */ - identity() { - - this.set( - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Returns a matrix with copied values from this instance. - * - * @return {Matrix4} A clone of this instance. - */ - clone() { - - return new Matrix4().fromArray( this.elements ); - - } - - /** - * Copies the values of the given matrix to this instance. - * - * @param {Matrix4} m - The matrix to copy. - * @return {Matrix4} A reference to this matrix. - */ - copy( m ) { - - const te = this.elements; - const me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; - te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; - te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; - te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; - - return this; - - } - - /** - * Copies the translation component of the given matrix - * into this matrix's translation component. - * - * @param {Matrix4} m - The matrix to copy the translation component. - * @return {Matrix4} A reference to this matrix. - */ - copyPosition( m ) { - - const te = this.elements, me = m.elements; - - te[ 12 ] = me[ 12 ]; - te[ 13 ] = me[ 13 ]; - te[ 14 ] = me[ 14 ]; - - return this; - - } - - /** - * Set the upper 3x3 elements of this matrix to the values of given 3x3 matrix. - * - * @param {Matrix3} m - The 3x3 matrix. - * @return {Matrix4} A reference to this matrix. - */ - setFromMatrix3( m ) { - - const me = m.elements; - - this.set( - - me[ 0 ], me[ 3 ], me[ 6 ], 0, - me[ 1 ], me[ 4 ], me[ 7 ], 0, - me[ 2 ], me[ 5 ], me[ 8 ], 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Extracts the basis vectors of this matrix into the three vectors provided. - * - * @param {Vector3} xAxis - The basis's x axis. - * @param {Vector3} yAxis - The basis's y axis. - * @param {Vector3} zAxis - The basis's z axis. - * @return {Matrix4} A reference to this matrix. - */ - extractBasis( xAxis, yAxis, zAxis ) { - - if ( this.determinantAffine() === 0 ) { - - xAxis.set( 1, 0, 0 ); - yAxis.set( 0, 1, 0 ); - zAxis.set( 0, 0, 1 ); - - return this; - - } - - xAxis.setFromMatrixColumn( this, 0 ); - yAxis.setFromMatrixColumn( this, 1 ); - zAxis.setFromMatrixColumn( this, 2 ); - - return this; - - } - - /** - * Sets the given basis vectors to this matrix. - * - * @param {Vector3} xAxis - The basis's x axis. - * @param {Vector3} yAxis - The basis's y axis. - * @param {Vector3} zAxis - The basis's z axis. - * @return {Matrix4} A reference to this matrix. - */ - makeBasis( xAxis, yAxis, zAxis ) { - - this.set( - xAxis.x, yAxis.x, zAxis.x, 0, - xAxis.y, yAxis.y, zAxis.y, 0, - xAxis.z, yAxis.z, zAxis.z, 0, - 0, 0, 0, 1 - ); - - return this; - - } - - /** - * Extracts the rotation component of the given matrix - * into this matrix's rotation component. - * - * Note: This method does not support reflection matrices. - * - * @param {Matrix4} m - The matrix. - * @return {Matrix4} A reference to this matrix. - */ - extractRotation( m ) { - - if ( m.determinantAffine() === 0 ) { - - return this.identity(); - - } - - const te = this.elements; - const me = m.elements; - - const scaleX = 1 / _v1$4.setFromMatrixColumn( m, 0 ).length(); - const scaleY = 1 / _v1$4.setFromMatrixColumn( m, 1 ).length(); - const scaleZ = 1 / _v1$4.setFromMatrixColumn( m, 2 ).length(); - - te[ 0 ] = me[ 0 ] * scaleX; - te[ 1 ] = me[ 1 ] * scaleX; - te[ 2 ] = me[ 2 ] * scaleX; - te[ 3 ] = 0; - - te[ 4 ] = me[ 4 ] * scaleY; - te[ 5 ] = me[ 5 ] * scaleY; - te[ 6 ] = me[ 6 ] * scaleY; - te[ 7 ] = 0; - - te[ 8 ] = me[ 8 ] * scaleZ; - te[ 9 ] = me[ 9 ] * scaleZ; - te[ 10 ] = me[ 10 ] * scaleZ; - te[ 11 ] = 0; - - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - } - - /** - * Sets the rotation component (the upper left 3x3 matrix) of this matrix to - * the rotation specified by the given Euler angles. The rest of - * the matrix is set to the identity. Depending on the {@link Euler#order}, - * there are six possible outcomes. See [this page](https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix) - * for a complete list. - * - * @param {Euler} euler - The Euler angles. - * @return {Matrix4} A reference to this matrix. - */ - makeRotationFromEuler( euler ) { - - const te = this.elements; - - const x = euler.x, y = euler.y, z = euler.z; - const a = Math.cos( x ), b = Math.sin( x ); - const c = Math.cos( y ), d = Math.sin( y ); - const e = Math.cos( z ), f = Math.sin( z ); - - if ( euler.order === 'XYZ' ) { - - const ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = - c * f; - te[ 8 ] = d; - - te[ 1 ] = af + be * d; - te[ 5 ] = ae - bf * d; - te[ 9 ] = - b * c; - - te[ 2 ] = bf - ae * d; - te[ 6 ] = be + af * d; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YXZ' ) { - - const ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce + df * b; - te[ 4 ] = de * b - cf; - te[ 8 ] = a * d; - - te[ 1 ] = a * f; - te[ 5 ] = a * e; - te[ 9 ] = - b; - - te[ 2 ] = cf * b - de; - te[ 6 ] = df + ce * b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZXY' ) { - - const ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce - df * b; - te[ 4 ] = - a * f; - te[ 8 ] = de + cf * b; - - te[ 1 ] = cf + de * b; - te[ 5 ] = a * e; - te[ 9 ] = df - ce * b; - - te[ 2 ] = - a * d; - te[ 6 ] = b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZYX' ) { - - const ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = be * d - af; - te[ 8 ] = ae * d + bf; - - te[ 1 ] = c * f; - te[ 5 ] = bf * d + ae; - te[ 9 ] = af * d - be; - - te[ 2 ] = - d; - te[ 6 ] = b * c; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YZX' ) { - - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = bd - ac * f; - te[ 8 ] = bc * f + ad; - - te[ 1 ] = f; - te[ 5 ] = a * e; - te[ 9 ] = - b * e; - - te[ 2 ] = - d * e; - te[ 6 ] = ad * f + bc; - te[ 10 ] = ac - bd * f; - - } else if ( euler.order === 'XZY' ) { - - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = - f; - te[ 8 ] = d * e; - - te[ 1 ] = ac * f + bd; - te[ 5 ] = a * e; - te[ 9 ] = ad * f - bc; - - te[ 2 ] = bc * f - ad; - te[ 6 ] = b * e; - te[ 10 ] = bd * f + ac; - - } - - // bottom row - te[ 3 ] = 0; - te[ 7 ] = 0; - te[ 11 ] = 0; - - // last column - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - } - - /** - * Sets the rotation component of this matrix to the rotation specified by - * the given Quaternion as outlined [here](https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion) - * The rest of the matrix is set to the identity. - * - * @param {Quaternion} q - The Quaternion. - * @return {Matrix4} A reference to this matrix. - */ - makeRotationFromQuaternion( q ) { - - return this.compose( _zero, q, _one ); - - } - - /** - * Sets the rotation component of the transformation matrix, looking from `eye` towards - * `target`, and oriented by the up-direction. - * - * @param {Vector3} eye - The eye vector. - * @param {Vector3} target - The target vector. - * @param {Vector3} up - The up vector. - * @return {Matrix4} A reference to this matrix. - */ - lookAt( eye, target, up ) { - - const te = this.elements; - - _z.subVectors( eye, target ); - - if ( _z.lengthSq() === 0 ) { - - // eye and target are in the same position - - _z.z = 1; - - } - - _z.normalize(); - _x.crossVectors( up, _z ); - - if ( _x.lengthSq() === 0 ) { - - // up and z are parallel - - if ( Math.abs( up.z ) === 1 ) { - - _z.x += 0.0001; - - } else { - - _z.z += 0.0001; - - } - - _z.normalize(); - _x.crossVectors( up, _z ); - - } - - _x.normalize(); - _y.crossVectors( _z, _x ); - - te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x; - te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y; - te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z; - - return this; - - } - - /** - * Post-multiplies this matrix by the given 4x4 matrix. - * - * @param {Matrix4} m - The matrix to multiply with. - * @return {Matrix4} A reference to this matrix. - */ - multiply( m ) { - - return this.multiplyMatrices( this, m ); - - } - - /** - * Pre-multiplies this matrix by the given 4x4 matrix. - * - * @param {Matrix4} m - The matrix to multiply with. - * @return {Matrix4} A reference to this matrix. - */ - premultiply( m ) { - - return this.multiplyMatrices( m, this ); - - } - - /** - * Multiples the given 4x4 matrices and stores the result - * in this matrix. - * - * @param {Matrix4} a - The first matrix. - * @param {Matrix4} b - The second matrix. - * @return {Matrix4} A reference to this matrix. - */ - multiplyMatrices( a, b ) { - - const ae = a.elements; - const be = b.elements; - const te = this.elements; - - const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; - const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; - const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; - const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; - - const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; - const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; - const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; - const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; - te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; - te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; - te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; - te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; - te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; - te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; - te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; - te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; - te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; - - te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; - te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; - te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; - te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; - - return this; - - } - - /** - * Multiplies every component of the matrix by the given scalar. - * - * @param {number} s - The scalar. - * @return {Matrix4} A reference to this matrix. - */ - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; - te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; - te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; - te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; - - return this; - - } - - /** - * Computes and returns the determinant of this matrix. - * - * @return {number} The determinant. - */ - determinant() { - - const te = this.elements; - - const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; - const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; - const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; - const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; - - const t11 = n23 * n34 - n24 * n33; - const t12 = n22 * n34 - n24 * n32; - const t13 = n22 * n33 - n23 * n32; - - const t21 = n21 * n34 - n24 * n31; - const t22 = n21 * n33 - n23 * n31; - const t23 = n21 * n32 - n22 * n31; - - return n11 * ( n42 * t11 - n43 * t12 + n44 * t13 ) - - n12 * ( n41 * t11 - n43 * t21 + n44 * t22 ) + - n13 * ( n41 * t12 - n42 * t21 + n44 * t23 ) - - n14 * ( n41 * t13 - n42 * t22 + n43 * t23 ); - - } - - /** - * Computes and returns the determinant of the 4x4 matrix, but assumes the - * matrix is affine, saving some computations. - * - * For affine matrices (like an object's world matrix), this value equals the - * full 4x4 {@link Matrix4#determinant} but is cheaper to compute. - * - * Assumes the bottom row is [0, 0, 0, 1]. - * - * @return {number} The determinant of the matrix. - */ - determinantAffine() { - - const te = this.elements; - - const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ]; - const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ]; - const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ]; - - return n11 * ( n22 * n33 - n23 * n32 ) - - n12 * ( n21 * n33 - n23 * n31 ) + - n13 * ( n21 * n32 - n22 * n31 ); - - } - - /** - * Transposes this matrix in place. - * - * @return {Matrix4} A reference to this matrix. - */ - transpose() { - - const te = this.elements; - let tmp; - - tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; - tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; - tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; - - tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; - tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; - tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; - - return this; - - } - - /** - * Sets the position component for this matrix from the given vector, - * without affecting the rest of the matrix. - * - * @param {number|Vector3} x - The x component of the vector or alternatively the vector object. - * @param {number} y - The y component of the vector. - * @param {number} z - The z component of the vector. - * @return {Matrix4} A reference to this matrix. - */ - setPosition( x, y, z ) { - - const te = this.elements; - - if ( x.isVector3 ) { - - te[ 12 ] = x.x; - te[ 13 ] = x.y; - te[ 14 ] = x.z; - - } else { - - te[ 12 ] = x; - te[ 13 ] = y; - te[ 14 ] = z; - - } - - return this; - - } - - /** - * Inverts this matrix, using the [analytic method](https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution). - * You can not invert with a determinant of zero. If you attempt this, the method produces - * a zero matrix instead. - * - * @return {Matrix4} A reference to this matrix. - */ - invert() { - - // based on https://github.com/toji/gl-matrix - const te = this.elements, - - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ], - n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ], - n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ], - n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ], - - t1 = n11 * n22 - n21 * n12, - t2 = n11 * n32 - n31 * n12, - t3 = n11 * n42 - n41 * n12, - t4 = n21 * n32 - n31 * n22, - t5 = n21 * n42 - n41 * n22, - t6 = n31 * n42 - n41 * n32, - t7 = n13 * n24 - n23 * n14, - t8 = n13 * n34 - n33 * n14, - t9 = n13 * n44 - n43 * n14, - t10 = n23 * n34 - n33 * n24, - t11 = n23 * n44 - n43 * n24, - t12 = n33 * n44 - n43 * n34; - - const det = t1 * t12 - t2 * t11 + t3 * t10 + t4 * t9 - t5 * t8 + t6 * t7; - - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - - const detInv = 1 / det; - - te[ 0 ] = ( n22 * t12 - n32 * t11 + n42 * t10 ) * detInv; - te[ 1 ] = ( n31 * t11 - n21 * t12 - n41 * t10 ) * detInv; - te[ 2 ] = ( n24 * t6 - n34 * t5 + n44 * t4 ) * detInv; - te[ 3 ] = ( n33 * t5 - n23 * t6 - n43 * t4 ) * detInv; - - te[ 4 ] = ( n32 * t9 - n12 * t12 - n42 * t8 ) * detInv; - te[ 5 ] = ( n11 * t12 - n31 * t9 + n41 * t8 ) * detInv; - te[ 6 ] = ( n34 * t3 - n14 * t6 - n44 * t2 ) * detInv; - te[ 7 ] = ( n13 * t6 - n33 * t3 + n43 * t2 ) * detInv; - - te[ 8 ] = ( n12 * t11 - n22 * t9 + n42 * t7 ) * detInv; - te[ 9 ] = ( n21 * t9 - n11 * t11 - n41 * t7 ) * detInv; - te[ 10 ] = ( n14 * t5 - n24 * t3 + n44 * t1 ) * detInv; - te[ 11 ] = ( n23 * t3 - n13 * t5 - n43 * t1 ) * detInv; - - te[ 12 ] = ( n22 * t8 - n12 * t10 - n32 * t7 ) * detInv; - te[ 13 ] = ( n11 * t10 - n21 * t8 + n31 * t7 ) * detInv; - te[ 14 ] = ( n24 * t2 - n14 * t4 - n34 * t1 ) * detInv; - te[ 15 ] = ( n13 * t4 - n23 * t2 + n33 * t1 ) * detInv; - - return this; - - } - - /** - * Scales each of the first three columns of this matrix by the corresponding component of the given vector. - * - * @param {Vector3} v - The scale vector. - * @return {Matrix4} A reference to this matrix. - */ - scale( v ) { - - const te = this.elements; - const x = v.x, y = v.y, z = v.z; - - te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; - te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; - te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; - te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; - - return this; - - } - - /** - * Gets the maximum scale value of the three axes. - * - * @return {number} The maximum scale. - */ - getMaxScaleOnAxis() { - - const te = this.elements; - - const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; - const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; - const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; - - return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); - - } - - /** - * Sets this matrix as a translation transform from the given vector. - * - * @param {number|Vector3} x - The amount to translate in the X axis or alternatively a translation vector. - * @param {number} y - The amount to translate in the Y axis. - * @param {number} z - The amount to translate in the z axis. - * @return {Matrix4} A reference to this matrix. - */ - makeTranslation( x, y, z ) { - - if ( x.isVector3 ) { - - this.set( - - 1, 0, 0, x.x, - 0, 1, 0, x.y, - 0, 0, 1, x.z, - 0, 0, 0, 1 - - ); - - } else { - - this.set( - - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 - - ); - - } - - return this; - - } - - /** - * Sets this matrix as a rotational transformation around the X axis by - * the given angle. - * - * @param {number} theta - The rotation in radians. - * @return {Matrix4} A reference to this matrix. - */ - makeRotationX( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - 1, 0, 0, 0, - 0, c, - s, 0, - 0, s, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Sets this matrix as a rotational transformation around the Y axis by - * the given angle. - * - * @param {number} theta - The rotation in radians. - * @return {Matrix4} A reference to this matrix. - */ - makeRotationY( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, 0, s, 0, - 0, 1, 0, 0, - - s, 0, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Sets this matrix as a rotational transformation around the Z axis by - * the given angle. - * - * @param {number} theta - The rotation in radians. - * @return {Matrix4} A reference to this matrix. - */ - makeRotationZ( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, - s, 0, 0, - s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Sets this matrix as a rotational transformation around the given axis by - * the given angle. - * - * @param {Vector3} axis - The normalized rotation axis. - * @param {number} angle - The rotation in radians. - * @return {Matrix4} A reference to this matrix. - */ - makeRotationAxis( axis, angle ) { - - const c = Math.cos( angle ); - const s = Math.sin( angle ); - const t = 1 - c; - const x = axis.x, y = axis.y, z = axis.z; - const tx = t * x, ty = t * y; - - this.set( - - tx * x + c, tx * y - s * z, tx * z + s * y, 0, - tx * y + s * z, ty * y + c, ty * z - s * x, 0, - tx * z - s * y, ty * z + s * x, t * z * z + c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Sets this matrix as a scale transformation. - * - * @param {number} x - The amount to scale in the X axis. - * @param {number} y - The amount to scale in the Y axis. - * @param {number} z - The amount to scale in the Z axis. - * @return {Matrix4} A reference to this matrix. - */ - makeScale( x, y, z ) { - - this.set( - - x, 0, 0, 0, - 0, y, 0, 0, - 0, 0, z, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Sets this matrix as a shear transformation. - * - * @param {number} xy - The amount to shear X by Y. - * @param {number} xz - The amount to shear X by Z. - * @param {number} yx - The amount to shear Y by X. - * @param {number} yz - The amount to shear Y by Z. - * @param {number} zx - The amount to shear Z by X. - * @param {number} zy - The amount to shear Z by Y. - * @return {Matrix4} A reference to this matrix. - */ - makeShear( xy, xz, yx, yz, zx, zy ) { - - this.set( - - 1, yx, zx, 0, - xy, 1, zy, 0, - xz, yz, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - /** - * Sets this matrix to the transformation composed of the given position, - * rotation (Quaternion) and scale. - * - * @param {Vector3} position - The position vector. - * @param {Quaternion} quaternion - The rotation as a Quaternion. - * @param {Vector3} scale - The scale vector. - * @return {Matrix4} A reference to this matrix. - */ - compose( position, quaternion, scale ) { - - const te = this.elements; - - const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; - const x2 = x + x, y2 = y + y, z2 = z + z; - const xx = x * x2, xy = x * y2, xz = x * z2; - const yy = y * y2, yz = y * z2, zz = z * z2; - const wx = w * x2, wy = w * y2, wz = w * z2; - - const sx = scale.x, sy = scale.y, sz = scale.z; - - te[ 0 ] = ( 1 - ( yy + zz ) ) * sx; - te[ 1 ] = ( xy + wz ) * sx; - te[ 2 ] = ( xz - wy ) * sx; - te[ 3 ] = 0; - - te[ 4 ] = ( xy - wz ) * sy; - te[ 5 ] = ( 1 - ( xx + zz ) ) * sy; - te[ 6 ] = ( yz + wx ) * sy; - te[ 7 ] = 0; - - te[ 8 ] = ( xz + wy ) * sz; - te[ 9 ] = ( yz - wx ) * sz; - te[ 10 ] = ( 1 - ( xx + yy ) ) * sz; - te[ 11 ] = 0; - - te[ 12 ] = position.x; - te[ 13 ] = position.y; - te[ 14 ] = position.z; - te[ 15 ] = 1; - - return this; - - } - - /** - * Decomposes this matrix into its position, rotation and scale components - * and provides the result in the given objects. - * - * Note: Not all matrices are decomposable in this way. For example, if an - * object has a non-uniformly scaled parent, then the object's world matrix - * may not be decomposable, and this method may not be appropriate. - * - * @param {Vector3} position - The position vector. - * @param {Quaternion} quaternion - The rotation as a Quaternion. - * @param {Vector3} scale - The scale vector. - * @return {Matrix4} A reference to this matrix. - */ - decompose( position, quaternion, scale ) { - - const te = this.elements; - - position.x = te[ 12 ]; - position.y = te[ 13 ]; - position.z = te[ 14 ]; - - const det = this.determinantAffine(); - - if ( det === 0 ) { - - scale.set( 1, 1, 1 ); - quaternion.identity(); - - return this; - - } - - let sx = _v1$4.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); - const sy = _v1$4.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); - const sz = _v1$4.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); - - // if determinant is negative, we need to invert one scale - if ( det < 0 ) sx = - sx; - - // scale the rotation part - _m1$4.copy( this ); - - const invSX = 1 / sx; - const invSY = 1 / sy; - const invSZ = 1 / sz; - - _m1$4.elements[ 0 ] *= invSX; - _m1$4.elements[ 1 ] *= invSX; - _m1$4.elements[ 2 ] *= invSX; - - _m1$4.elements[ 4 ] *= invSY; - _m1$4.elements[ 5 ] *= invSY; - _m1$4.elements[ 6 ] *= invSY; - - _m1$4.elements[ 8 ] *= invSZ; - _m1$4.elements[ 9 ] *= invSZ; - _m1$4.elements[ 10 ] *= invSZ; - - quaternion.setFromRotationMatrix( _m1$4 ); - - scale.x = sx; - scale.y = sy; - scale.z = sz; - - return this; - - } - - /** - * Creates a perspective projection matrix. This is used internally by - * {@link PerspectiveCamera#updateProjectionMatrix}. - - * @param {number} left - Left boundary of the viewing frustum at the near plane. - * @param {number} right - Right boundary of the viewing frustum at the near plane. - * @param {number} top - Top boundary of the viewing frustum at the near plane. - * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. - * @param {number} near - The distance from the camera to the near plane. - * @param {number} far - The distance from the camera to the far plane. - * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. - * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. - * @return {Matrix4} A reference to this matrix. - */ - makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { - - const te = this.elements; - - const x = 2 * near / ( right - left ); - const y = 2 * near / ( top - bottom ); - - const a = ( right + left ) / ( right - left ); - const b = ( top + bottom ) / ( top - bottom ); - - let c, d; - - if ( reversedDepth ) { - - c = near / ( far - near ); - d = ( far * near ) / ( far - near ); - - } else { - - if ( coordinateSystem === WebGLCoordinateSystem ) { - - c = - ( far + near ) / ( far - near ); - d = ( -2 * far * near ) / ( far - near ); - - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - - c = - far / ( far - near ); - d = ( - far * near ) / ( far - near ); - - } else { - - throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); - - } - - } - - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = -1; te[ 15 ] = 0; - - return this; - - } - - /** - * Creates a orthographic projection matrix. This is used internally by - * {@link OrthographicCamera#updateProjectionMatrix}. - - * @param {number} left - Left boundary of the viewing frustum at the near plane. - * @param {number} right - Right boundary of the viewing frustum at the near plane. - * @param {number} top - Top boundary of the viewing frustum at the near plane. - * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. - * @param {number} near - The distance from the camera to the near plane. - * @param {number} far - The distance from the camera to the far plane. - * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. - * @param {boolean} [reversedDepth=false] - Whether to use a reversed depth. - * @return {Matrix4} A reference to this matrix. - */ - makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem, reversedDepth = false ) { - - const te = this.elements; - - const x = 2 / ( right - left ); - const y = 2 / ( top - bottom ); - - const a = - ( right + left ) / ( right - left ); - const b = - ( top + bottom ) / ( top - bottom ); - - let c, d; - - if ( reversedDepth ) { - - c = 1 / ( far - near ); - d = far / ( far - near ); - - } else { - - if ( coordinateSystem === WebGLCoordinateSystem ) { - - c = -2 / ( far - near ); - d = - ( far + near ) / ( far - near ); - - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - - c = -1 / ( far - near ); - d = - near / ( far - near ); - - } else { - - throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); - - } - - } - - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = a; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = 0; te[ 13 ] = b; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; - - return this; - - } - - /** - * Returns `true` if this matrix is equal with the given one. - * - * @param {Matrix4} matrix - The matrix to test for equality. - * @return {boolean} Whether this matrix is equal with the given one. - */ - equals( matrix ) { - - const te = this.elements; - const me = matrix.elements; - - for ( let i = 0; i < 16; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - } - - /** - * Sets the elements of the matrix from the given array. - * - * @param {Array} array - The matrix elements in column-major order. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Matrix4} A reference to this matrix. - */ - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 16; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - } - - /** - * Writes the elements of this matrix to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The matrix elements in column-major order. - */ - toArray( array = [], offset = 0 ) { - - const te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - array[ offset + 3 ] = te[ 3 ]; - - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - - array[ offset + 8 ] = te[ 8 ]; - array[ offset + 9 ] = te[ 9 ]; - array[ offset + 10 ] = te[ 10 ]; - array[ offset + 11 ] = te[ 11 ]; - - array[ offset + 12 ] = te[ 12 ]; - array[ offset + 13 ] = te[ 13 ]; - array[ offset + 14 ] = te[ 14 ]; - array[ offset + 15 ] = te[ 15 ]; - - return array; - - } - -} - -const _v1$4 = /*@__PURE__*/ new Vector3(); -const _m1$4 = /*@__PURE__*/ new Matrix4(); -const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); -const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); -const _x = /*@__PURE__*/ new Vector3(); -const _y = /*@__PURE__*/ new Vector3(); -const _z = /*@__PURE__*/ new Vector3(); - -function WebGLAnimation() { - - let context = null; - let isAnimating = false; - let animationLoop = null; - let requestId = null; - - function onAnimationFrame( time, frame ) { - - requestId = context.requestAnimationFrame( onAnimationFrame ); - - animationLoop( time, frame ); - - } - - return { - - start: function () { - - if ( isAnimating === true ) return; - if ( animationLoop === null ) return; - if ( context === null ) return; - - requestId = context.requestAnimationFrame( onAnimationFrame ); - - isAnimating = true; - - }, - - stop: function () { - - if ( context !== null ) context.cancelAnimationFrame( requestId ); - - isAnimating = false; - - }, - - setAnimationLoop: function ( callback ) { - - animationLoop = callback; - - }, - - setContext: function ( value ) { - - context = value; - - } - - }; - -} - -function WebGLAttributes( gl ) { - - const buffers = new WeakMap(); - - function createBuffer( attribute, bufferType ) { - - const array = attribute.array; - const usage = attribute.usage; - const size = array.byteLength; - - const buffer = gl.createBuffer(); - - gl.bindBuffer( bufferType, buffer ); - gl.bufferData( bufferType, array, usage ); - - attribute.onUploadCallback(); - - let type; - - if ( array instanceof Float32Array ) { - - type = gl.FLOAT; - - } else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) { - - type = gl.HALF_FLOAT; - - } else if ( array instanceof Uint16Array ) { - - if ( attribute.isFloat16BufferAttribute ) { - - type = gl.HALF_FLOAT; - - } else { - - type = gl.UNSIGNED_SHORT; - - } - - } else if ( array instanceof Int16Array ) { - - type = gl.SHORT; - - } else if ( array instanceof Uint32Array ) { - - type = gl.UNSIGNED_INT; - - } else if ( array instanceof Int32Array ) { - - type = gl.INT; - - } else if ( array instanceof Int8Array ) { - - type = gl.BYTE; - - } else if ( array instanceof Uint8Array ) { - - type = gl.UNSIGNED_BYTE; - - } else if ( array instanceof Uint8ClampedArray ) { - - type = gl.UNSIGNED_BYTE; - - } else { - - throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); - - } - - return { - buffer: buffer, - type: type, - bytesPerElement: array.BYTES_PER_ELEMENT, - version: attribute.version, - size: size - }; - - } - - function updateBuffer( buffer, attribute, bufferType ) { - - const array = attribute.array; - const updateRanges = attribute.updateRanges; - - gl.bindBuffer( bufferType, buffer ); - - if ( updateRanges.length === 0 ) { - - // Not using update ranges - gl.bufferSubData( bufferType, 0, array ); - - } else { - - // Before applying update ranges, we merge any adjacent / overlapping - // ranges to reduce load on `gl.bufferSubData`. Empirically, this has led - // to performance improvements for applications which make heavy use of - // update ranges. Likely due to GPU command overhead. - // - // Note that to reduce garbage collection between frames, we merge the - // update ranges in-place. This is safe because this method will clear the - // update ranges once updated. - - updateRanges.sort( ( a, b ) => a.start - b.start ); - - // To merge the update ranges in-place, we work from left to right in the - // existing updateRanges array, merging ranges. This may result in a final - // array which is smaller than the original. This index tracks the last - // index representing a merged range, any data after this index can be - // trimmed once the merge algorithm is completed. - let mergeIndex = 0; - - for ( let i = 1; i < updateRanges.length; i ++ ) { - - const previousRange = updateRanges[ mergeIndex ]; - const range = updateRanges[ i ]; - - // We add one here to merge adjacent ranges. This is safe because ranges - // operate over positive integers. - if ( range.start <= previousRange.start + previousRange.count + 1 ) { - - previousRange.count = Math.max( - previousRange.count, - range.start + range.count - previousRange.start - ); - - } else { - - ++ mergeIndex; - updateRanges[ mergeIndex ] = range; - - } - - } - - // Trim the array to only contain the merged ranges. - updateRanges.length = mergeIndex + 1; - - for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { - - const range = updateRanges[ i ]; - - gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, - array, range.start, range.count ); - - } - - attribute.clearUpdateRanges(); - - } - - attribute.onUploadCallback(); - - } - - // - - function get( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - return buffers.get( attribute ); - - } - - function remove( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - const data = buffers.get( attribute ); - - if ( data ) { - - gl.deleteBuffer( data.buffer ); - - buffers.delete( attribute ); - - } - - } - - function update( attribute, bufferType ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - if ( attribute.isGLBufferAttribute ) { - - const cached = buffers.get( attribute ); - - if ( ! cached || cached.version < attribute.version ) { - - buffers.set( attribute, { - buffer: attribute.buffer, - type: attribute.type, - bytesPerElement: attribute.elementSize, - version: attribute.version - } ); - - } - - return; - - } - - const data = buffers.get( attribute ); - - if ( data === undefined ) { - - buffers.set( attribute, createBuffer( attribute, bufferType ) ); - - } else if ( data.version < attribute.version ) { - - if ( data.size !== attribute.array.byteLength ) { - - throw new Error( 'THREE.WebGLAttributes: The size of the buffer attribute\'s array buffer does not match the original size. Resizing buffer attributes is not supported.' ); - - } - - updateBuffer( data.buffer, attribute, bufferType ); - - data.version = attribute.version; - - } - - } - - return { - - get: get, - remove: remove, - update: update - - }; - -} - -const _vector$5 = /*@__PURE__*/ new Vector3(); -const _vector2 = /*@__PURE__*/ new Vector2(); - -let _id$2 = 0; - -/** - * This class stores data for an attribute (such as vertex positions, face - * indices, normals, colors, UVs, and any custom attributes ) associated with - * a geometry, which allows for more efficient passing of data to the GPU. - * - * When working with vector-like data, the `fromBufferAttribute( attribute, index )` - * helper methods on vector and color class might be helpful. E.g. {@link Vector3#fromBufferAttribute}. - */ -class BufferAttribute extends EventDispatcher { - - /** - * Constructs a new buffer attribute. - * - * @param {TypedArray} array - The array holding the attribute data. - * @param {number} itemSize - The item size. - * @param {boolean} [normalized=false] - Whether the data are normalized or not. - */ - constructor( array, itemSize, normalized = false ) { - - super(); - - if ( Array.isArray( array ) ) { - - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - - } - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isBufferAttribute = true; - - /** - * The ID of the buffer attribute. - * - * @name BufferAttribute#id - * @type {number} - * @readonly - */ - Object.defineProperty( this, 'id', { value: _id$2 ++ } ); - - /** - * The name of the buffer attribute. - * - * @type {string} - */ - this.name = ''; - - /** - * The array holding the attribute data. It should have `itemSize * numVertices` - * elements, where `numVertices` is the number of vertices in the associated geometry. - * - * @type {TypedArray} - */ - this.array = array; - - /** - * The number of values of the array that should be associated with a particular vertex. - * For instance, if this attribute is storing a 3-component vector (such as a position, - * normal, or color), then the value should be `3`. - * - * @type {number} - */ - this.itemSize = itemSize; - - /** - * Represents the number of items this buffer attribute stores. It is internally computed - * by dividing the `array` length by the `itemSize`. - * - * @type {number} - * @readonly - */ - this.count = array !== undefined ? array.length / itemSize : 0; - - /** - * Applies to integer data only. Indicates how the underlying data in the buffer maps to - * the values in the GLSL code. For instance, if `array` is an instance of `UInt16Array`, - * and `normalized` is `true`, the values `0 - +65535` in the array data will be mapped to - * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted - * to floats unmodified, i.e. `65535` becomes `65535.0f`. - * - * @type {boolean} - */ - this.normalized = normalized; - - /** - * Defines the intended usage pattern of the data store for optimization purposes. - * - * Note: After the initial use of a buffer, its usage cannot be changed. Instead, - * instantiate a new one and set the desired usage before the next render. - * - * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} - * @default StaticDrawUsage - */ - this.usage = StaticDrawUsage; - - /** - * This can be used to only update some components of stored vectors (for example, just the - * component related to color). Use the `addUpdateRange()` function to add ranges to this array. - * - * @type {Array} - */ - this.updateRanges = []; - - /** - * Configures the bound GPU type for use in shaders. - * - * Note: this only has an effect for integer arrays and is not configurable for float arrays. - * For lower precision float types, use `Float16BufferAttribute`. - * - * @type {(FloatType|IntType)} - * @default FloatType - */ - this.gpuType = FloatType; - - /** - * A version number, incremented every time the `needsUpdate` is set to `true`. - * - * @type {number} - */ - this.version = 0; - - } - - /** - * A callback function that is executed after the renderer has transferred the attribute - * array data to the GPU. - */ - onUploadCallback() {} - - /** - * Flag to indicate that this attribute has changed and should be re-sent to - * the GPU. Set this to `true` when you modify the value of the array. - * - * @type {number} - * @default false - * @param {boolean} value - */ - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - /** - * Sets the usage of this buffer attribute. - * - * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. - * @return {BufferAttribute} A reference to this buffer attribute. - */ - setUsage( value ) { - - this.usage = value; - - return this; - - } - - /** - * Adds a range of data in the data array to be updated on the GPU. - * - * @param {number} start - Position at which to start update. - * @param {number} count - The number of components to update. - */ - addUpdateRange( start, count ) { - - this.updateRanges.push( { start, count } ); - - } - - /** - * Clears the update ranges. - */ - clearUpdateRanges() { - - this.updateRanges.length = 0; - - } - - /** - * Copies the values of the given buffer attribute to this instance. - * - * @param {BufferAttribute} source - The buffer attribute to copy. - * @return {BufferAttribute} A reference to this instance. - */ - copy( source ) { - - this.name = source.name; - this.array = new source.array.constructor( source.array ); - this.itemSize = source.itemSize; - this.count = source.count; - this.normalized = source.normalized; - - this.usage = source.usage; - this.gpuType = source.gpuType; - - return this; - - } - - /** - * Copies a vector from the given buffer attribute to this one. The start - * and destination position in the attribute buffers are represented by the - * given indices. - * - * @param {number} index1 - The destination index into this buffer attribute. - * @param {BufferAttribute} attribute - The buffer attribute to copy from. - * @param {number} index2 - The source index into the given buffer attribute. - * @return {BufferAttribute} A reference to this instance. - */ - copyAt( index1, attribute, index2 ) { - - index1 *= this.itemSize; - index2 *= attribute.itemSize; - - for ( let i = 0, l = this.itemSize; i < l; i ++ ) { - - this.array[ index1 + i ] = attribute.array[ index2 + i ]; - - } - - return this; - - } - - /** - * Copies the given array data into this buffer attribute. - * - * @param {(TypedArray|Array)} array - The array to copy. - * @return {BufferAttribute} A reference to this instance. - */ - copyArray( array ) { - - this.array.set( array ); - - return this; - - } - - /** - * Applies the given 3x3 matrix to the given attribute. Works with - * item size `2` and `3`. - * - * @param {Matrix3} m - The matrix to apply. - * @return {BufferAttribute} A reference to this instance. - */ - applyMatrix3( m ) { - - if ( this.itemSize === 2 ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector2.fromBufferAttribute( this, i ); - _vector2.applyMatrix3( m ); - - this.setXY( i, _vector2.x, _vector2.y ); - - } - - } else if ( this.itemSize === 3 ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - _vector$5.applyMatrix3( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - } - - return this; - - } - - /** - * Applies the given 4x4 matrix to the given attribute. Only works with - * item size `3`. - * - * @param {Matrix4} m - The matrix to apply. - * @return {BufferAttribute} A reference to this instance. - */ - applyMatrix4( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.applyMatrix4( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - /** - * Applies the given 3x3 normal matrix to the given attribute. Only works with - * item size `3`. - * - * @param {Matrix3} m - The normal matrix to apply. - * @return {BufferAttribute} A reference to this instance. - */ - applyNormalMatrix( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.applyNormalMatrix( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - /** - * Applies the given 4x4 matrix to the given attribute. Only works with - * item size `3` and with direction vectors. - * - * @param {Matrix4} m - The matrix to apply. - * @return {BufferAttribute} A reference to this instance. - */ - transformDirection( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.transformDirection( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - /** - * Sets the given array data in the buffer attribute. - * - * @param {(TypedArray|Array)} value - The array data to set. - * @param {number} [offset=0] - The offset in this buffer attribute's array. - * @return {BufferAttribute} A reference to this instance. - */ - set( value, offset = 0 ) { - - // Matching BufferAttribute constructor, do not normalize the array. - this.array.set( value, offset ); - - return this; - - } - - /** - * Returns the given component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} component - The component index. - * @return {number} The returned value. - */ - getComponent( index, component ) { - - let value = this.array[ index * this.itemSize + component ]; - - if ( this.normalized ) value = denormalize( value, this.array ); - - return value; - - } - - /** - * Sets the given value to the given component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} component - The component index. - * @param {number} value - The value to set. - * @return {BufferAttribute} A reference to this instance. - */ - setComponent( index, component, value ) { - - if ( this.normalized ) value = normalize$1( value, this.array ); - - this.array[ index * this.itemSize + component ] = value; - - return this; - - } - - /** - * Returns the x component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @return {number} The x component. - */ - getX( index ) { - - let x = this.array[ index * this.itemSize ]; - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; - - } - - /** - * Sets the x component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} x - The value to set. - * @return {BufferAttribute} A reference to this instance. - */ - setX( index, x ) { - - if ( this.normalized ) x = normalize$1( x, this.array ); - - this.array[ index * this.itemSize ] = x; - - return this; - - } - - /** - * Returns the y component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @return {number} The y component. - */ - getY( index ) { - - let y = this.array[ index * this.itemSize + 1 ]; - - if ( this.normalized ) y = denormalize( y, this.array ); - - return y; - - } - - /** - * Sets the y component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} y - The value to set. - * @return {BufferAttribute} A reference to this instance. - */ - setY( index, y ) { - - if ( this.normalized ) y = normalize$1( y, this.array ); - - this.array[ index * this.itemSize + 1 ] = y; - - return this; - - } - - /** - * Returns the z component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @return {number} The z component. - */ - getZ( index ) { - - let z = this.array[ index * this.itemSize + 2 ]; - - if ( this.normalized ) z = denormalize( z, this.array ); - - return z; - - } - - /** - * Sets the z component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} z - The value to set. - * @return {BufferAttribute} A reference to this instance. - */ - setZ( index, z ) { - - if ( this.normalized ) z = normalize$1( z, this.array ); - - this.array[ index * this.itemSize + 2 ] = z; - - return this; - - } - - /** - * Returns the w component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @return {number} The w component. - */ - getW( index ) { - - let w = this.array[ index * this.itemSize + 3 ]; - - if ( this.normalized ) w = denormalize( w, this.array ); - - return w; - - } - - /** - * Sets the w component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} w - The value to set. - * @return {BufferAttribute} A reference to this instance. - */ - setW( index, w ) { - - if ( this.normalized ) w = normalize$1( w, this.array ); - - this.array[ index * this.itemSize + 3 ] = w; - - return this; - - } - - /** - * Sets the x and y component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} x - The value for the x component to set. - * @param {number} y - The value for the y component to set. - * @return {BufferAttribute} A reference to this instance. - */ - setXY( index, x, y ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize$1( x, this.array ); - y = normalize$1( y, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - - return this; - - } - - /** - * Sets the x, y and z component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} x - The value for the x component to set. - * @param {number} y - The value for the y component to set. - * @param {number} z - The value for the z component to set. - * @return {BufferAttribute} A reference to this instance. - */ - setXYZ( index, x, y, z ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize$1( x, this.array ); - y = normalize$1( y, this.array ); - z = normalize$1( z, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - - return this; - - } - - /** - * Sets the x, y, z and w component of the vector at the given index. - * - * @param {number} index - The index into the buffer attribute. - * @param {number} x - The value for the x component to set. - * @param {number} y - The value for the y component to set. - * @param {number} z - The value for the z component to set. - * @param {number} w - The value for the w component to set. - * @return {BufferAttribute} A reference to this instance. - */ - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize$1( x, this.array ); - y = normalize$1( y, this.array ); - z = normalize$1( z, this.array ); - w = normalize$1( w, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - this.array[ index + 3 ] = w; - - return this; - - } - - /** - * Sets the given callback function that is executed after the Renderer has transferred - * the attribute array data to the GPU. Can be used to perform clean-up operations after - * the upload when attribute data are not needed anymore on the CPU side. - * - * @param {Function} callback - The `onUpload()` callback. - * @return {BufferAttribute} A reference to this instance. - */ - onUpload( callback ) { - - this.onUploadCallback = callback; - - return this; - - } - - /** - * Returns a new buffer attribute with copied values from this instance. - * - * @return {BufferAttribute} A clone of this instance. - */ - clone() { - - return new this.constructor( this.array, this.itemSize ).copy( this ); - - } - - /** - * Serializes the buffer attribute into JSON. - * - * @return {Object} A JSON object representing the serialized buffer attribute. - */ - toJSON() { - - const data = { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: Array.from( this.array ), - normalized: this.normalized - }; - - data.name = this.name; - data.usage = this.usage; - data.gpuType = this.gpuType; - - return data; - - } - - /** - * Disposes of the buffer attribute. Available only in {@link WebGPURenderer}. - */ - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} - -/** - * Convenient class that can be used when creating a `UInt16` buffer attribute with - * a plain `Array` instance. - * - * @augments BufferAttribute - */ -class Uint16BufferAttribute extends BufferAttribute { - - /** - * Constructs a new buffer attribute. - * - * @param {(Array|Uint16Array)} array - The array holding the attribute data. - * @param {number} itemSize - The item size. - * @param {boolean} [normalized=false] - Whether the data are normalized or not. - */ - constructor( array, itemSize, normalized ) { - - super( new Uint16Array( array ), itemSize, normalized ); - - } - -} - -/** - * Convenient class that can be used when creating a `UInt32` buffer attribute with - * a plain `Array` instance. - * - * @augments BufferAttribute - */ -class Uint32BufferAttribute extends BufferAttribute { - - /** - * Constructs a new buffer attribute. - * - * @param {(Array|Uint32Array)} array - The array holding the attribute data. - * @param {number} itemSize - The item size. - * @param {boolean} [normalized=false] - Whether the data are normalized or not. - */ - constructor( array, itemSize, normalized ) { - - super( new Uint32Array( array ), itemSize, normalized ); - - } - -} - -/** - * Convenient class that can be used when creating a `Float32` buffer attribute with - * a plain `Array` instance. - * - * @augments BufferAttribute - */ -class Float32BufferAttribute extends BufferAttribute { - - /** - * Constructs a new buffer attribute. - * - * @param {(Array|Float32Array)} array - The array holding the attribute data. - * @param {number} itemSize - The item size. - * @param {boolean} [normalized=false] - Whether the data are normalized or not. - */ - constructor( array, itemSize, normalized ) { - - super( new Float32Array( array ), itemSize, normalized ); - - } - -} - -const _matrix$2 = /*@__PURE__*/ new Matrix4(); -const _quaternion$2 = /*@__PURE__*/ new Quaternion(); - -/** - * A class representing Euler angles. - * - * Euler angles describe a rotational transformation by rotating an object on - * its various axes in specified amounts per axis, and a specified axis - * order. - * - * Iterating through an instance will yield its components (x, y, z, - * order) in the corresponding order. - * - * ```js - * const a = new THREE.Euler( 0, 1, 1.57, 'XYZ' ); - * const b = new THREE.Vector3( 1, 0, 1 ); - * b.applyEuler(a); - * ``` - */ -class Euler { - - /** - * Constructs a new euler instance. - * - * @param {number} [x=0] - The angle of the x axis in radians. - * @param {number} [y=0] - The angle of the y axis in radians. - * @param {number} [z=0] - The angle of the z axis in radians. - * @param {string} [order=Euler.DEFAULT_ORDER] - A string representing the order that the rotations are applied. - */ - constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isEuler = true; - - this._x = x; - this._y = y; - this._z = z; - this._order = order; - - } - - /** - * The angle of the x axis in radians. - * - * @type {number} - * @default 0 - */ - get x() { - - return this._x; - - } - - set x( value ) { - - this._x = value; - this._onChangeCallback(); - - } - - /** - * The angle of the y axis in radians. - * - * @type {number} - * @default 0 - */ - get y() { - - return this._y; - - } - - set y( value ) { - - this._y = value; - this._onChangeCallback(); - - } - - /** - * The angle of the z axis in radians. - * - * @type {number} - * @default 0 - */ - get z() { - - return this._z; - - } - - set z( value ) { - - this._z = value; - this._onChangeCallback(); - - } - - /** - * A string representing the order that the rotations are applied. - * - * @type {string} - * @default 'XYZ' - */ - get order() { - - return this._order; - - } - - set order( value ) { - - this._order = value; - this._onChangeCallback(); - - } - - /** - * Sets the Euler components. - * - * @param {number} x - The angle of the x axis in radians. - * @param {number} y - The angle of the y axis in radians. - * @param {number} z - The angle of the z axis in radians. - * @param {string} [order] - A string representing the order that the rotations are applied. - * @return {Euler} A reference to this Euler instance. - */ - set( x, y, z, order = this._order ) { - - this._x = x; - this._y = y; - this._z = z; - this._order = order; - - this._onChangeCallback(); - - return this; - - } - - /** - * Returns a new Euler instance with copied values from this instance. - * - * @return {Euler} A clone of this instance. - */ - clone() { - - return new this.constructor( this._x, this._y, this._z, this._order ); - - } - - /** - * Copies the values of the given Euler instance to this instance. - * - * @param {Euler} euler - The Euler instance to copy. - * @return {Euler} A reference to this Euler instance. - */ - copy( euler ) { - - this._x = euler._x; - this._y = euler._y; - this._z = euler._z; - this._order = euler._order; - - this._onChangeCallback(); - - return this; - - } - - /** - * Sets the angles of this Euler instance from a pure rotation matrix. - * - * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). - * @param {string} [order] - A string representing the order that the rotations are applied. - * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. - * @return {Euler} A reference to this Euler instance. - */ - setFromRotationMatrix( m, order = this._order, update = true ) { - - const te = m.elements; - const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; - const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; - const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - switch ( order ) { - - case 'XYZ': - - this._y = Math.asin( clamp( m13, -1, 1 ) ); - - if ( Math.abs( m13 ) < 0.9999999 ) { - - this._x = Math.atan2( - m23, m33 ); - this._z = Math.atan2( - m12, m11 ); - - } else { - - this._x = Math.atan2( m32, m22 ); - this._z = 0; - - } - - break; - - case 'YXZ': - - this._x = Math.asin( - clamp( m23, -1, 1 ) ); - - if ( Math.abs( m23 ) < 0.9999999 ) { - - this._y = Math.atan2( m13, m33 ); - this._z = Math.atan2( m21, m22 ); - - } else { - - this._y = Math.atan2( - m31, m11 ); - this._z = 0; - - } - - break; - - case 'ZXY': - - this._x = Math.asin( clamp( m32, -1, 1 ) ); - - if ( Math.abs( m32 ) < 0.9999999 ) { - - this._y = Math.atan2( - m31, m33 ); - this._z = Math.atan2( - m12, m22 ); - - } else { - - this._y = 0; - this._z = Math.atan2( m21, m11 ); - - } - - break; - - case 'ZYX': - - this._y = Math.asin( - clamp( m31, -1, 1 ) ); - - if ( Math.abs( m31 ) < 0.9999999 ) { - - this._x = Math.atan2( m32, m33 ); - this._z = Math.atan2( m21, m11 ); - - } else { - - this._x = 0; - this._z = Math.atan2( - m12, m22 ); - - } - - break; - - case 'YZX': - - this._z = Math.asin( clamp( m21, -1, 1 ) ); - - if ( Math.abs( m21 ) < 0.9999999 ) { - - this._x = Math.atan2( - m23, m22 ); - this._y = Math.atan2( - m31, m11 ); - - } else { - - this._x = 0; - this._y = Math.atan2( m13, m33 ); - - } - - break; - - case 'XZY': - - this._z = Math.asin( - clamp( m12, -1, 1 ) ); - - if ( Math.abs( m12 ) < 0.9999999 ) { - - this._x = Math.atan2( m32, m22 ); - this._y = Math.atan2( m13, m11 ); - - } else { - - this._x = Math.atan2( - m23, m33 ); - this._y = 0; - - } - - break; - - default: - - warn( 'Euler: .setFromRotationMatrix() encountered an unknown order: ' + order ); - - } - - this._order = order; - - if ( update === true ) this._onChangeCallback(); - - return this; - - } - - /** - * Sets the angles of this Euler instance from a normalized quaternion. - * - * @param {Quaternion} q - A normalized Quaternion. - * @param {string} [order] - A string representing the order that the rotations are applied. - * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. - * @return {Euler} A reference to this Euler instance. - */ - setFromQuaternion( q, order, update ) { - - _matrix$2.makeRotationFromQuaternion( q ); - - return this.setFromRotationMatrix( _matrix$2, order, update ); - - } - - /** - * Sets the angles of this Euler instance from the given vector. - * - * @param {Vector3} v - The vector. - * @param {string} [order] - A string representing the order that the rotations are applied. - * @return {Euler} A reference to this Euler instance. - */ - setFromVector3( v, order = this._order ) { - - return this.set( v.x, v.y, v.z, order ); - - } - - /** - * Resets the euler angle with a new order by creating a quaternion from this - * euler angle and then setting this euler angle with the quaternion and the - * new order. - * - * Warning: This discards revolution information. - * - * @param {string} [newOrder] - A string representing the new order that the rotations are applied. - * @return {Euler} A reference to this Euler instance. - */ - reorder( newOrder ) { - - _quaternion$2.setFromEuler( this ); - - return this.setFromQuaternion( _quaternion$2, newOrder ); - - } - - /** - * Returns `true` if this Euler instance is equal with the given one. - * - * @param {Euler} euler - The Euler instance to test for equality. - * @return {boolean} Whether this Euler instance is equal with the given one. - */ - equals( euler ) { - - return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); - - } - - /** - * Sets this Euler instance's components to values from the given array. The first three - * entries of the array are assign to the x,y and z components. An optional fourth entry - * defines the Euler order. - * - * @param {Array} array - An array holding the Euler component values. - * @return {Euler} A reference to this Euler instance. - */ - fromArray( array ) { - - this._x = array[ 0 ]; - this._y = array[ 1 ]; - this._z = array[ 2 ]; - if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; - - this._onChangeCallback(); - - return this; - - } - - /** - * Writes the components of this Euler instance to the given array. If no array is provided, - * the method returns a new instance. - * - * @param {Array} [array=[]] - The target array holding the Euler components. - * @param {number} [offset=0] - Index of the first element in the array. - * @return {Array} The Euler components. - */ - toArray( array = [], offset = 0 ) { - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._order; - - return array; - - } - - _onChange( callback ) { - - this._onChangeCallback = callback; - - return this; - - } - - _onChangeCallback() {} - - *[ Symbol.iterator ]() { - - yield this._x; - yield this._y; - yield this._z; - yield this._order; - - } - -} - -/** - * The default Euler angle order. - * - * @static - * @type {string} - * @default 'XYZ' - */ -Euler.DEFAULT_ORDER = 'XYZ'; - -/** - * A layers object assigns an 3D object to 1 or more of 32 - * layers numbered `0` to `31` - internally the layers are stored as a - * bit mask], and by default all 3D objects are a member of layer `0`. - * - * This can be used to control visibility - an object must share a layer with - * a camera to be visible when that camera's view is - * rendered. - * - * All classes that inherit from {@link Object3D} have an `layers` property which - * is an instance of this class. - */ -class Layers { - - /** - * Constructs a new layers instance, with membership - * initially set to layer `0`. - */ - constructor() { - - /** - * A bit mask storing which of the 32 layers this layers object is currently - * a member of. - * - * @type {number} - */ - this.mask = 1 | 0; - - } - - /** - * Sets membership to the given layer, and remove membership all other layers. - * - * @param {number} layer - The layer to set. - */ - set( layer ) { - - this.mask = ( 1 << layer | 0 ) >>> 0; - - } - - /** - * Adds membership of the given layer. - * - * @param {number} layer - The layer to enable. - */ - enable( layer ) { - - this.mask |= 1 << layer | 0; - - } - - /** - * Adds membership to all layers. - */ - enableAll() { - - this.mask = 0xffffffff | 0; - - } - - /** - * Toggles the membership of the given layer. - * - * @param {number} layer - The layer to toggle. - */ - toggle( layer ) { - - this.mask ^= 1 << layer | 0; - - } - - /** - * Removes membership of the given layer. - * - * @param {number} layer - The layer to enable. - */ - disable( layer ) { - - this.mask &= ~ ( 1 << layer | 0 ); - - } - - /** - * Removes the membership from all layers. - */ - disableAll() { - - this.mask = 0; - - } - - /** - * Returns `true` if this and the given layers object have at least one - * layer in common. - * - * @param {Layers} layers - The layers to test. - * @return {boolean } Whether this and the given layers object have at least one layer in common or not. - */ - test( layers ) { - - return ( this.mask & layers.mask ) !== 0; - - } - - /** - * Returns `true` if the given layer is enabled. - * - * @param {number} layer - The layer to test. - * @return {boolean } Whether the given layer is enabled or not. - */ - isEnabled( layer ) { - - return ( this.mask & ( 1 << layer | 0 ) ) !== 0; - - } - -} - -let _object3DId = 0; - -const _v1$3 = /*@__PURE__*/ new Vector3(); -const _q1 = /*@__PURE__*/ new Quaternion(); -const _m1$3 = /*@__PURE__*/ new Matrix4(); -const _target = /*@__PURE__*/ new Vector3(); - -const _position$2 = /*@__PURE__*/ new Vector3(); -const _scale$1 = /*@__PURE__*/ new Vector3(); -const _quaternion$1 = /*@__PURE__*/ new Quaternion(); - -const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); -const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); -const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); - -/** - * Fires when the object has been added to its parent object. - * - * @event Object3D#added - * @type {Object} - */ -const _addedEvent = { type: 'added' }; - -/** - * Fires when the object has been removed from its parent object. - * - * @event Object3D#removed - * @type {Object} - */ -const _removedEvent = { type: 'removed' }; - -/** - * Fires when a new child object has been added. - * - * @event Object3D#childadded - * @type {Object} - */ -const _childaddedEvent = { type: 'childadded', child: null }; - -/** - * Fires when a child object has been removed. - * - * @event Object3D#childremoved - * @type {Object} - */ -const _childremovedEvent = { type: 'childremoved', child: null }; - -/** - * This is the base class for most objects in three.js and provides a set of - * properties and methods for manipulating objects in 3D space. - * - * @augments EventDispatcher - */ -class Object3D extends EventDispatcher { - - /** - * Constructs a new 3D object. - */ - constructor() { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isObject3D = true; - - /** - * The ID of the 3D object. - * - * @name Object3D#id - * @type {number} - * @readonly - */ - Object.defineProperty( this, 'id', { value: _object3DId ++ } ); - - /** - * The UUID of the 3D object. - * - * @type {string} - * @readonly - */ - this.uuid = generateUUID(); - - /** - * The name of the 3D object. - * - * @type {string} - */ - this.name = ''; - - /** - * The type property is used for detecting the object type - * in context of serialization/deserialization. - * - * @type {string} - * @readonly - */ - this.type = 'Object3D'; - - /** - * A reference to the parent object. - * - * @type {?Object3D} - * @default null - */ - this.parent = null; - - /** - * An array holding the child 3D objects of this instance. - * - * @type {Array} - */ - this.children = []; - - /** - * Defines the `up` direction of the 3D object which influences - * the orientation via methods like {@link Object3D#lookAt}. - * - * The default values for all 3D objects is defined by `Object3D.DEFAULT_UP`. - * - * @type {Vector3} - */ - this.up = Object3D.DEFAULT_UP.clone(); - - const position = new Vector3(); - const rotation = new Euler(); - const quaternion = new Quaternion(); - const scale = new Vector3( 1, 1, 1 ); - - function onRotationChange() { - - quaternion.setFromEuler( rotation, false ); - - } - - function onQuaternionChange() { - - rotation.setFromQuaternion( quaternion, undefined, false ); - - } - - rotation._onChange( onRotationChange ); - quaternion._onChange( onQuaternionChange ); - - Object.defineProperties( this, { - /** - * Represents the object's local position. - * - * @name Object3D#position - * @type {Vector3} - * @default (0,0,0) - */ - position: { - configurable: true, - enumerable: true, - value: position - }, - /** - * Represents the object's local rotation as Euler angles, in radians. - * - * @name Object3D#rotation - * @type {Euler} - * @default (0,0,0) - */ - rotation: { - configurable: true, - enumerable: true, - value: rotation - }, - /** - * Represents the object's local rotation as Quaternions. - * - * @name Object3D#quaternion - * @type {Quaternion} - */ - quaternion: { - configurable: true, - enumerable: true, - value: quaternion - }, - /** - * Represents the object's local scale. - * - * @name Object3D#scale - * @type {Vector3} - * @default (1,1,1) - */ - scale: { - configurable: true, - enumerable: true, - value: scale - }, - /** - * Represents the object's model-view matrix. - * - * @name Object3D#modelViewMatrix - * @type {Matrix4} - */ - modelViewMatrix: { - value: new Matrix4() - }, - /** - * Represents the object's normal matrix. - * - * @name Object3D#normalMatrix - * @type {Matrix3} - */ - normalMatrix: { - value: new Matrix3() - } - } ); - - /** - * Represents the object's transformation matrix in local space. - * - * @type {Matrix4} - */ - this.matrix = new Matrix4(); - - /** - * Represents the object's transformation matrix in world space. - * If the 3D object has no parent, then it's identical to the local transformation matrix - * - * @type {Matrix4} - */ - this.matrixWorld = new Matrix4(); - - /** - * When set to `true`, the engine automatically computes the local matrix from position, - * rotation and scale every frame. If set to `false`, the app is responsible for recomputing - * the local matrix by calling `updateMatrix()`. - * - * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_AUTO_UPDATE`. - * - * @type {boolean} - * @default true - */ - this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; - - /** - * When set to `true`, the engine automatically computes the world matrix from the current local - * matrix and the object's transformation hierarchy. If set to `false`, the app is responsible for - * recomputing the world matrix by directly updating the `matrixWorld` property. - * - * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE`. - * - * @type {boolean} - * @default true - */ - this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer - - /** - * When set to `true`, it calculates the world matrix in that frame and resets this property - * to `false`. - * - * @type {boolean} - * @default false - */ - this.matrixWorldNeedsUpdate = false; - - /** - * The layer membership of the 3D object. The 3D object is only visible if it has - * at least one layer in common with the camera in use. This property can also be - * used to filter out unwanted objects in ray-intersection tests when using {@link Raycaster}. - * - * @type {Layers} - */ - this.layers = new Layers(); - - /** - * When set to `true`, the 3D object gets rendered. - * - * @type {boolean} - * @default true - */ - this.visible = true; - - /** - * When set to `true`, the 3D object gets rendered into shadow maps. - * - * @type {boolean} - * @default false - */ - this.castShadow = false; - - /** - * When set to `true`, the 3D object is affected by shadows in the scene. - * - * @type {boolean} - * @default false - */ - this.receiveShadow = false; - - /** - * When set to `true`, the 3D object is honored by view frustum culling. - * - * @type {boolean} - * @default true - */ - this.frustumCulled = true; - - /** - * This value allows the default rendering order of scene graph objects to be - * overridden although opaque and transparent objects remain sorted independently. - * When this property is set for an instance of {@link Group},all descendants - * objects will be sorted and rendered together. Sorting is from lowest to highest - * render order. - * - * @type {number} - * @default 0 - */ - this.renderOrder = 0; - - /** - * An array holding the animation clips of the 3D object. - * - * @type {Array} - */ - this.animations = []; - - /** - * Custom depth material to be used when rendering to the depth map. Can only be used - * in context of meshes. When shadow-casting with a {@link DirectionalLight} or {@link SpotLight}, - * if you are modifying vertex positions in the vertex shader you must specify a custom depth - * material for proper shadows. - * - * Only relevant in context of {@link WebGLRenderer}. - * - * @type {(Material|undefined)} - * @default undefined - */ - this.customDepthMaterial = undefined; - - /** - * Same as {@link Object3D#customDepthMaterial}, but used with {@link PointLight}. - * - * Only relevant in context of {@link WebGLRenderer}. - * - * @type {(Material|undefined)} - * @default undefined - */ - this.customDistanceMaterial = undefined; - - /** - * Whether the 3D object is supposed to be static or not. If set to `true`, it means - * the 3D object is not going to be changed after the initial renderer. This includes - * geometry and material settings. A static 3D object can be processed by the renderer - * slightly faster since certain state checks can be bypassed. - * - * Only relevant in context of {@link WebGPURenderer}. - * - * @type {boolean} - * @default false - */ - this.static = false; - - /** - * An object that can be used to store custom data about the 3D object. It - * should not hold references to functions as these will not be cloned. - * - * @type {Object} - */ - this.userData = {}; - - /** - * The pivot point for rotation and scale transformations. - * When set, rotation and scale are applied around this point - * instead of the object's origin. - * - * @type {?Vector3} - * @default null - */ - this.pivot = null; - - } - - /** - * A callback that is executed immediately before a 3D object is rendered to a shadow map. - * - * @param {Renderer|WebGLRenderer} renderer - The renderer. - * @param {Object3D} object - The 3D object. - * @param {Camera} camera - The camera that is used to render the scene. - * @param {Camera} shadowCamera - The shadow camera. - * @param {BufferGeometry} geometry - The 3D object's geometry. - * @param {Material} depthMaterial - The depth material. - * @param {Object} group - The geometry group data. - */ - onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} - - /** - * A callback that is executed immediately after a 3D object is rendered to a shadow map. - * - * @param {Renderer|WebGLRenderer} renderer - The renderer. - * @param {Object3D} object - The 3D object. - * @param {Camera} camera - The camera that is used to render the scene. - * @param {Camera} shadowCamera - The shadow camera. - * @param {BufferGeometry} geometry - The 3D object's geometry. - * @param {Material} depthMaterial - The depth material. - * @param {Object} group - The geometry group data. - */ - onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} - - /** - * A callback that is executed immediately before a 3D object is rendered. - * - * @param {Renderer|WebGLRenderer} renderer - The renderer. - * @param {Object3D} object - The 3D object. - * @param {Camera} camera - The camera that is used to render the scene. - * @param {BufferGeometry} geometry - The 3D object's geometry. - * @param {Material} material - The 3D object's material. - * @param {Object} group - The geometry group data. - */ - onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} - - /** - * A callback that is executed immediately after a 3D object is rendered. - * - * @param {Renderer|WebGLRenderer} renderer - The renderer. - * @param {Object3D} object - The 3D object. - * @param {Camera} camera - The camera that is used to render the scene. - * @param {BufferGeometry} geometry - The 3D object's geometry. - * @param {Material} material - The 3D object's material. - * @param {Object} group - The geometry group data. - */ - onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} - - /** - * Applies the given transformation matrix to the object and updates the object's position, - * rotation and scale. - * - * @param {Matrix4} matrix - The transformation matrix. - */ - applyMatrix4( matrix ) { - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - this.matrix.premultiply( matrix ); - - this.matrix.decompose( this.position, this.quaternion, this.scale ); - - } - - /** - * Applies a rotation represented by given the quaternion to the 3D object. - * - * @param {Quaternion} q - The quaternion. - * @return {Object3D} A reference to this instance. - */ - applyQuaternion( q ) { - - this.quaternion.premultiply( q ); - - return this; - - } - - /** - * Sets the given rotation represented as an axis/angle couple to the 3D object. - * - * @param {Vector3} axis - The (normalized) axis vector. - * @param {number} angle - The angle in radians. - */ - setRotationFromAxisAngle( axis, angle ) { - - // assumes axis is normalized - - this.quaternion.setFromAxisAngle( axis, angle ); - - } - - /** - * Sets the given rotation represented as Euler angles to the 3D object. - * - * @param {Euler} euler - The Euler angles. - */ - setRotationFromEuler( euler ) { - - this.quaternion.setFromEuler( euler, true ); - - } - - /** - * Sets the given rotation represented as rotation matrix to the 3D object. - * - * @param {Matrix4} m - Although a 4x4 matrix is expected, the upper 3x3 portion must be - * a pure rotation matrix (i.e, unscaled). - */ - setRotationFromMatrix( m ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - this.quaternion.setFromRotationMatrix( m ); - - } - - /** - * Sets the given rotation represented as a Quaternion to the 3D object. - * - * @param {Quaternion} q - The Quaternion - */ - setRotationFromQuaternion( q ) { - - // assumes q is normalized - - this.quaternion.copy( q ); - - } - - /** - * Rotates the 3D object along an axis in local space. - * - * @param {Vector3} axis - The (normalized) axis vector. - * @param {number} angle - The angle in radians. - * @return {Object3D} A reference to this instance. - */ - rotateOnAxis( axis, angle ) { - - // rotate object on axis in object space - // axis is assumed to be normalized - - _q1.setFromAxisAngle( axis, angle ); - - this.quaternion.multiply( _q1 ); - - return this; - - } - - /** - * Rotates the 3D object along an axis in world space. - * - * @param {Vector3} axis - The (normalized) axis vector. - * @param {number} angle - The angle in radians. - * @return {Object3D} A reference to this instance. - */ - rotateOnWorldAxis( axis, angle ) { - - // rotate object on axis in world space - // axis is assumed to be normalized - // method assumes no rotated parent - - _q1.setFromAxisAngle( axis, angle ); - - this.quaternion.premultiply( _q1 ); - - return this; - - } - - /** - * Rotates the 3D object around its X axis in local space. - * - * @param {number} angle - The angle in radians. - * @return {Object3D} A reference to this instance. - */ - rotateX( angle ) { - - return this.rotateOnAxis( _xAxis, angle ); - - } - - /** - * Rotates the 3D object around its Y axis in local space. - * - * @param {number} angle - The angle in radians. - * @return {Object3D} A reference to this instance. - */ - rotateY( angle ) { - - return this.rotateOnAxis( _yAxis, angle ); - - } - - /** - * Rotates the 3D object around its Z axis in local space. - * - * @param {number} angle - The angle in radians. - * @return {Object3D} A reference to this instance. - */ - rotateZ( angle ) { - - return this.rotateOnAxis( _zAxis, angle ); - - } - - /** - * Translate the 3D object by a distance along the given axis in local space. - * - * @param {Vector3} axis - The (normalized) axis vector. - * @param {number} distance - The distance in world units. - * @return {Object3D} A reference to this instance. - */ - translateOnAxis( axis, distance ) { - - // translate object by distance along axis in object space - // axis is assumed to be normalized - - _v1$3.copy( axis ).applyQuaternion( this.quaternion ); - - this.position.add( _v1$3.multiplyScalar( distance ) ); - - return this; - - } - - /** - * Translate the 3D object by a distance along its X-axis in local space. - * - * @param {number} distance - The distance in world units. - * @return {Object3D} A reference to this instance. - */ - translateX( distance ) { - - return this.translateOnAxis( _xAxis, distance ); - - } - - /** - * Translate the 3D object by a distance along its Y-axis in local space. - * - * @param {number} distance - The distance in world units. - * @return {Object3D} A reference to this instance. - */ - translateY( distance ) { - - return this.translateOnAxis( _yAxis, distance ); - - } - - /** - * Translate the 3D object by a distance along its Z-axis in local space. - * - * @param {number} distance - The distance in world units. - * @return {Object3D} A reference to this instance. - */ - translateZ( distance ) { - - return this.translateOnAxis( _zAxis, distance ); - - } - - /** - * Converts the given vector from this 3D object's local space to world space. - * - * @param {Vector3} vector - The vector to convert. - * @return {Vector3} The converted vector. - */ - localToWorld( vector ) { - - this.updateWorldMatrix( true, false ); - - return vector.applyMatrix4( this.matrixWorld ); - - } - - /** - * Converts the given vector from this 3D object's world space to local space. - * - * @param {Vector3} vector - The vector to convert. - * @return {Vector3} The converted vector. - */ - worldToLocal( vector ) { - - this.updateWorldMatrix( true, false ); - - return vector.applyMatrix4( _m1$3.copy( this.matrixWorld ).invert() ); - - } - - /** - * Rotates the object to face a point in world space. - * - * This method does not support objects having non-uniformly-scaled parent(s). - * - * @param {number|Vector3} x - The x coordinate in world space. Alternatively, a vector representing a position in world space - * @param {number} [y] - The y coordinate in world space. - * @param {number} [z] - The z coordinate in world space. - */ - lookAt( x, y, z ) { - - // This method does not support objects having non-uniformly-scaled parent(s) - - if ( x.isVector3 ) { - - _target.copy( x ); - - } else { - - _target.set( x, y, z ); - - } - - const parent = this.parent; - - this.updateWorldMatrix( true, false ); - - _position$2.setFromMatrixPosition( this.matrixWorld ); - - if ( this.isCamera || this.isLight ) { - - _m1$3.lookAt( _position$2, _target, this.up ); - - } else { - - _m1$3.lookAt( _target, _position$2, this.up ); - - } - - this.quaternion.setFromRotationMatrix( _m1$3 ); - - if ( parent ) { - - _m1$3.extractRotation( parent.matrixWorld ); - _q1.setFromRotationMatrix( _m1$3 ); - this.quaternion.premultiply( _q1.invert() ); - - } - - } - - /** - * Adds the given 3D object as a child to this 3D object. An arbitrary number of - * objects may be added. Any current parent on an object passed in here will be - * removed, since an object can have at most one parent. - * - * @fires Object3D#added - * @fires Object3D#childadded - * @param {Object3D} object - The 3D object to add. - * @return {Object3D} A reference to this instance. - */ - add( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - if ( object === this ) { - - error( 'Object3D.add: object can\'t be added as a child of itself.', object ); - return this; - - } - - if ( object && object.isObject3D ) { - - object.removeFromParent(); - object.parent = this; - this.children.push( object ); - - object.dispatchEvent( _addedEvent ); - - _childaddedEvent.child = object; - this.dispatchEvent( _childaddedEvent ); - _childaddedEvent.child = null; - - } else { - - error( 'Object3D.add: object not an instance of THREE.Object3D.', object ); - - } - - return this; - - } - - /** - * Removes the given 3D object as child from this 3D object. - * An arbitrary number of objects may be removed. - * - * @fires Object3D#removed - * @fires Object3D#childremoved - * @param {Object3D} object - The 3D object to remove. - * @return {Object3D} A reference to this instance. - */ - remove( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.remove( arguments[ i ] ); - - } - - return this; - - } - - const index = this.children.indexOf( object ); - - if ( index !== -1 ) { - - object.parent = null; - this.children.splice( index, 1 ); - - object.dispatchEvent( _removedEvent ); - - _childremovedEvent.child = object; - this.dispatchEvent( _childremovedEvent ); - _childremovedEvent.child = null; - - } - - return this; - - } - - /** - * Removes this 3D object from its current parent. - * - * @fires Object3D#removed - * @fires Object3D#childremoved - * @return {Object3D} A reference to this instance. - */ - removeFromParent() { - - const parent = this.parent; - - if ( parent !== null ) { - - parent.remove( this ); - - } - - return this; - - } - - /** - * Removes all child objects. - * - * @fires Object3D#removed - * @fires Object3D#childremoved - * @return {Object3D} A reference to this instance. - */ - clear() { - - return this.remove( ... this.children ); - - } - - /** - * Adds the given 3D object as a child of this 3D object, while maintaining the object's world - * transform. This method does not support scene graphs having non-uniformly-scaled nodes(s). - * - * @fires Object3D#added - * @fires Object3D#childadded - * @param {Object3D} object - The 3D object to attach. - * @return {Object3D} A reference to this instance. - */ - attach( object ) { - - // adds object as a child of this, while maintaining the object's world transform - - // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) - - this.updateWorldMatrix( true, false ); - - _m1$3.copy( this.matrixWorld ).invert(); - - if ( object.parent !== null ) { - - object.parent.updateWorldMatrix( true, false ); - - _m1$3.multiply( object.parent.matrixWorld ); - - } - - object.applyMatrix4( _m1$3 ); - - object.removeFromParent(); - object.parent = this; - this.children.push( object ); - - object.updateWorldMatrix( false, true ); - - object.dispatchEvent( _addedEvent ); - - _childaddedEvent.child = object; - this.dispatchEvent( _childaddedEvent ); - _childaddedEvent.child = null; - - return this; - - } - - /** - * Searches through the 3D object and its children, starting with the 3D object - * itself, and returns the first with a matching ID. - * - * @param {number} id - The id. - * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. - */ - getObjectById( id ) { - - return this.getObjectByProperty( 'id', id ); - - } - - /** - * Searches through the 3D object and its children, starting with the 3D object - * itself, and returns the first with a matching name. - * - * @param {string} name - The name. - * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. - */ - getObjectByName( name ) { - - return this.getObjectByProperty( 'name', name ); - - } - - /** - * Searches through the 3D object and its children, starting with the 3D object - * itself, and returns the first with a matching property value. - * - * @param {string} name - The name of the property. - * @param {any} value - The value. - * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. - */ - getObjectByProperty( name, value ) { - - if ( this[ name ] === value ) return this; - - for ( let i = 0, l = this.children.length; i < l; i ++ ) { - - const child = this.children[ i ]; - const object = child.getObjectByProperty( name, value ); - - if ( object !== undefined ) { - - return object; - - } - - } - - return undefined; - - } - - /** - * Searches through the 3D object and its children, starting with the 3D object - * itself, and returns all 3D objects with a matching property value. - * - * @param {string} name - The name of the property. - * @param {any} value - The value. - * @param {Array} result - The method stores the result in this array. - * @return {Array} The found 3D objects. - */ - getObjectsByProperty( name, value, result = [] ) { - - if ( this[ name ] === value ) result.push( this ); - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].getObjectsByProperty( name, value, result ); - - } - - return result; - - } - - /** - * Returns a vector representing the position of the 3D object in world space. - * - * @param {Vector3} target - The target vector the result is stored to. - * @return {Vector3} The 3D object's position in world space. - */ - getWorldPosition( target ) { - - this.updateWorldMatrix( true, false ); - - return target.setFromMatrixPosition( this.matrixWorld ); - - } - - /** - * Returns a Quaternion representing the position of the 3D object in world space. - * - * @param {Quaternion} target - The target Quaternion the result is stored to. - * @return {Quaternion} The 3D object's rotation in world space. - */ - getWorldQuaternion( target ) { - - this.updateWorldMatrix( true, false ); - - this.matrixWorld.decompose( _position$2, target, _scale$1 ); - - return target; - - } - - /** - * Returns a vector representing the scale of the 3D object in world space. - * - * @param {Vector3} target - The target vector the result is stored to. - * @return {Vector3} The 3D object's scale in world space. - */ - getWorldScale( target ) { - - this.updateWorldMatrix( true, false ); - - this.matrixWorld.decompose( _position$2, _quaternion$1, target ); - - return target; - - } - - /** - * Returns a vector representing the ("look") direction of the 3D object in world space. - * - * @param {Vector3} target - The target vector the result is stored to. - * @return {Vector3} The 3D object's direction in world space. - */ - getWorldDirection( target ) { - - this.updateWorldMatrix( true, false ); - - const e = this.matrixWorld.elements; - - return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); - - } - - /** - * Abstract method to get intersections between a casted ray and this - * 3D object. Renderable 3D objects such as {@link Mesh}, {@link Line} or {@link Points} - * implement this method in order to use raycasting. - * - * @abstract - * @param {Raycaster} raycaster - The raycaster. - * @param {Array} intersects - An array holding the result of the method. - */ - raycast( /* raycaster, intersects */ ) {} - - /** - * Abstract method to test whether this 3D object intersects the given frustum. - * Renderable 3D objects such as {@link Mesh}, {@link Line} or {@link Points} - * implement this method in order to use frustum culling. - * - * @abstract - * @param {Frustum|FrustumArray} frustum - The frustum to test. - * @return {boolean|undefined} Whether this 3D object intersects the given frustum or not. - */ - intersectsFrustum( /* frustum */ ) {} - - /** - * Executes the callback on this 3D object and all descendants. - * - * Note: Modifying the scene graph inside the callback is discouraged. - * - * @param {Function} callback - A callback function that allows to process the current 3D object. - */ - traverse( callback ) { - - callback( this ); - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverse( callback ); - - } - - } - - /** - * Like {@link Object3D#traverse}, but the callback will only be executed for visible 3D objects. - * Descendants of invisible 3D objects are not traversed. - * - * Note: Modifying the scene graph inside the callback is discouraged. - * - * @param {Function} callback - A callback function that allows to process the current 3D object. - */ - traverseVisible( callback ) { - - if ( this.visible === false ) return; - - callback( this ); - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverseVisible( callback ); - - } - - } - - /** - * Like {@link Object3D#traverse}, but the callback will only be executed for all ancestors. - * - * Note: Modifying the scene graph inside the callback is discouraged. - * - * @param {Function} callback - A callback function that allows to process the current 3D object. - */ - traverseAncestors( callback ) { - - const parent = this.parent; - - if ( parent !== null ) { - - callback( parent ); - - parent.traverseAncestors( callback ); - - } - - } - - /** - * Updates the transformation matrix in local space by computing it from the current - * position, rotation and scale values. - */ - updateMatrix() { - - this.matrix.compose( this.position, this.quaternion, this.scale ); - - const pivot = this.pivot; - - if ( pivot !== null ) { - - const px = pivot.x, py = pivot.y, pz = pivot.z; - const te = this.matrix.elements; - - te[ 12 ] += px - te[ 0 ] * px - te[ 4 ] * py - te[ 8 ] * pz; - te[ 13 ] += py - te[ 1 ] * px - te[ 5 ] * py - te[ 9 ] * pz; - te[ 14 ] += pz - te[ 2 ] * px - te[ 6 ] * py - te[ 10 ] * pz; - - } - - this.matrixWorldNeedsUpdate = true; - - } - - /** - * Updates the transformation matrix in world space of this 3D objects and its descendants. - * - * To ensure correct results, this method also recomputes the 3D object's transformation matrix in - * local space. The computation of the local and world matrix can be controlled with the - * {@link Object3D#matrixAutoUpdate} and {@link Object3D#matrixWorldAutoUpdate} flags which are both - * `true` by default. Set these flags to `false` if you need more control over the update matrix process. - * - * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even - * when {@link Object3D#matrixWorldNeedsUpdate} is `false`. - */ - updateMatrixWorld( force ) { - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - if ( this.matrixWorldNeedsUpdate || force ) { - - if ( this.matrixWorldAutoUpdate === true ) { - - if ( this.parent === null ) { - - this.matrixWorld.copy( this.matrix ); - - } else { - - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - - } - - } - - this.matrixWorldNeedsUpdate = false; - - force = true; - - } - - // make sure descendants are updated if required - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - const child = children[ i ]; - - child.updateMatrixWorld( force ); - - } - - } - - /** - * An alternative version of {@link Object3D#updateMatrixWorld} with more control over the - * update of ancestor and descendant nodes. - * - * @param {boolean} [updateParents=false] Whether ancestor nodes should be updated or not. - * @param {boolean} [updateChildren=false] Whether descendant nodes should be updated or not. - * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even - * when {@link Object3D#matrixWorldNeedsUpdate} is `false`. - */ - updateWorldMatrix( updateParents, updateChildren, force = false ) { - - const parent = this.parent; - - if ( updateParents === true && parent !== null ) { - - parent.updateWorldMatrix( true, false ); - - } - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - if ( this.matrixWorldNeedsUpdate || force ) { - - if ( this.matrixWorldAutoUpdate === true ) { - - if ( this.parent === null ) { - - this.matrixWorld.copy( this.matrix ); - - } else { - - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - - } - - } - - this.matrixWorldNeedsUpdate = false; - - force = true; - - } - - // make sure descendants are updated - - if ( updateChildren === true ) { - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - const child = children[ i ]; - - child.updateWorldMatrix( false, true, force ); - - } - - } - - } - - /** - * Serializes the 3D object into JSON. - * - * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. - * @return {Object} A JSON object representing the serialized 3D object. - * @see {@link ObjectLoader#parse} - */ - toJSON( meta ) { - - // meta is a string when called from JSON.stringify - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - const output = {}; - - // meta is a hash used to collect geometries, materials. - // not providing it implies that this is the root object - // being serialized. - if ( isRootObject ) { - - // initialize meta obj - meta = { - geometries: {}, - materials: {}, - textures: {}, - images: {}, - shapes: {}, - skeletons: {}, - animations: {}, - nodes: {} - }; - - output.metadata = { - version: 4.7, - type: 'Object', - generator: 'Object3D.toJSON' - }; - - } - - // standard Object3D serialization - - const object = {}; - - object.uuid = this.uuid; - object.type = this.type; - - object.name = this.name; - object.castShadow = this.castShadow; - object.receiveShadow = this.receiveShadow; - object.visible = this.visible; - object.frustumCulled = this.frustumCulled; - object.renderOrder = this.renderOrder; - object.static = this.static; - object.matrixAutoUpdate = this.matrixAutoUpdate; - - if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; - - object.layers = this.layers.mask; - object.matrix = this.matrix.toArray(); - object.up = this.up.toArray(); - - if ( this.pivot !== null ) object.pivot = this.pivot.toArray(); - - if ( this.morphTargetDictionary !== undefined ) object.morphTargetDictionary = Object.assign( {}, this.morphTargetDictionary ); - if ( this.morphTargetInfluences !== undefined ) object.morphTargetInfluences = this.morphTargetInfluences.slice(); - - // object specific properties - - if ( this.isInstancedMesh ) { - - object.type = 'InstancedMesh'; - object.count = this.count; - object.instanceMatrix = this.instanceMatrix.toJSON(); - if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); - - } - - if ( this.isBatchedMesh ) { - - object.type = 'BatchedMesh'; - object.perObjectFrustumCulled = this.perObjectFrustumCulled; - object.sortObjects = this.sortObjects; - - object.drawRanges = this._drawRanges; - object.reservedRanges = this._reservedRanges; - - object.geometryInfo = this._geometryInfo.map( info => ( { - ...info, - boundingBox: info.boundingBox ? info.boundingBox.toJSON() : undefined, - boundingSphere: info.boundingSphere ? info.boundingSphere.toJSON() : undefined - } ) ); - object.instanceInfo = this._instanceInfo.map( info => ( { ...info } ) ); - - object.availableInstanceIds = this._availableInstanceIds.slice(); - object.availableGeometryIds = this._availableGeometryIds.slice(); - - object.nextIndexStart = this._nextIndexStart; - object.nextVertexStart = this._nextVertexStart; - object.geometryCount = this._geometryCount; - - object.maxInstanceCount = this._maxInstanceCount; - object.maxVertexCount = this._maxVertexCount; - object.maxIndexCount = this._maxIndexCount; - - object.geometryInitialized = this._geometryInitialized; - - object.matricesTexture = this._matricesTexture.toJSON( meta ); - - object.indirectTexture = this._indirectTexture.toJSON( meta ); - - if ( this._colorsTexture !== null ) { - - object.colorsTexture = this._colorsTexture.toJSON( meta ); - - } - - if ( this.boundingSphere !== null ) { - - object.boundingSphere = this.boundingSphere.toJSON(); - - } - - if ( this.boundingBox !== null ) { - - object.boundingBox = this.boundingBox.toJSON(); - - } - - } - - // - - function serialize( library, element ) { - - if ( library[ element.uuid ] === undefined ) { - - library[ element.uuid ] = element.toJSON( meta ); - - } - - return element.uuid; - - } - - if ( this.isScene ) { - - if ( this.background ) { - - if ( this.background.isColor ) { - - object.background = this.background.toJSON(); - - } else if ( this.background.isTexture ) { - - object.background = this.background.toJSON( meta ).uuid; - - } - - } - - if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { - - object.environment = this.environment.toJSON( meta ).uuid; - - } - - } else if ( this.isMesh || this.isLine || this.isPoints ) { - - object.geometry = serialize( meta.geometries, this.geometry ); - - const parameters = this.geometry.parameters; - - if ( parameters !== undefined && parameters.shapes !== undefined ) { - - const shapes = parameters.shapes; - - if ( Array.isArray( shapes ) ) { - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - - serialize( meta.shapes, shape ); - - } - - } else { - - serialize( meta.shapes, shapes ); - - } - - } - - } - - if ( this.isSkinnedMesh ) { - - object.bindMode = this.bindMode; - object.bindMatrix = this.bindMatrix.toArray(); - - if ( this.skeleton !== undefined ) { - - serialize( meta.skeletons, this.skeleton ); - - object.skeleton = this.skeleton.uuid; - - } - - } - - if ( this.material !== undefined ) { - - if ( Array.isArray( this.material ) ) { - - const uuids = []; - - for ( let i = 0, l = this.material.length; i < l; i ++ ) { - - uuids.push( serialize( meta.materials, this.material[ i ] ) ); - - } - - object.material = uuids; - - } else { - - object.material = serialize( meta.materials, this.material ); - - } - - } - - // - - if ( this.children.length > 0 ) { - - object.children = []; - - for ( let i = 0; i < this.children.length; i ++ ) { - - object.children.push( this.children[ i ].toJSON( meta ).object ); - - } - - } - - // - - if ( this.animations.length > 0 ) { - - object.animations = []; - - for ( let i = 0; i < this.animations.length; i ++ ) { - - const animation = this.animations[ i ]; - - object.animations.push( serialize( meta.animations, animation ) ); - - } - - } - - if ( isRootObject ) { - - const geometries = extractFromCache( meta.geometries ); - const materials = extractFromCache( meta.materials ); - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - const shapes = extractFromCache( meta.shapes ); - const skeletons = extractFromCache( meta.skeletons ); - const animations = extractFromCache( meta.animations ); - const nodes = extractFromCache( meta.nodes ); - - if ( geometries.length > 0 ) output.geometries = geometries; - if ( materials.length > 0 ) output.materials = materials; - if ( textures.length > 0 ) output.textures = textures; - if ( images.length > 0 ) output.images = images; - if ( shapes.length > 0 ) output.shapes = shapes; - if ( skeletons.length > 0 ) output.skeletons = skeletons; - if ( animations.length > 0 ) output.animations = animations; - if ( nodes.length > 0 ) output.nodes = nodes; - - } - - output.object = object; - - return output; - - // extract data from the cache hash - // remove metadata on each item - // and return as array - function extractFromCache( cache ) { - - const values = []; - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - } - - /** - * Returns a new 3D object with copied values from this instance. - * - * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are also cloned. - * @return {Object3D} A clone of this instance. - */ - clone( recursive ) { - - return new this.constructor().copy( this, recursive ); - - } - - /** - * Copies the values of the given 3D object to this instance. - * - * @param {Object3D} source - The 3D object to copy. - * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are cloned. - * @return {Object3D} A reference to this instance. - */ - copy( source, recursive = true ) { - - this.name = source.name; - - this.up.copy( source.up ); - - this.position.copy( source.position ); - this.rotation.order = source.rotation.order; - this.quaternion.copy( source.quaternion ); - this.scale.copy( source.scale ); - - this.pivot = ( source.pivot !== null ) ? source.pivot.clone() : null; - - this.matrix.copy( source.matrix ); - this.matrixWorld.copy( source.matrixWorld ); - - this.matrixAutoUpdate = source.matrixAutoUpdate; - - this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; - this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; - - this.layers.mask = source.layers.mask; - this.visible = source.visible; - - this.castShadow = source.castShadow; - this.receiveShadow = source.receiveShadow; - - this.frustumCulled = source.frustumCulled; - this.renderOrder = source.renderOrder; - - this.static = source.static; - - this.animations = source.animations.slice(); - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - if ( recursive === true ) { - - for ( let i = 0; i < source.children.length; i ++ ) { - - const child = source.children[ i ]; - this.add( child.clone() ); - - } - - } - - return this; - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - * - * Geometries, materials and textures are potentially shared with other - * 3D objects and must be disposed of separately. - * - * @fires Object3D#dispose - */ - dispose() { - - /** - * Fires when the 3D object has been disposed of. - * - * @event Object3D#dispose - * @type {Object} - */ - this.dispatchEvent( { type: 'dispose' } ); - - } - -} - -/** - * The default up direction for objects, also used as the default - * position for {@link DirectionalLight} and {@link HemisphereLight}. - * - * @static - * @type {Vector3} - * @default (0,1,0) - */ -Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); - -/** - * The default setting for {@link Object3D#matrixAutoUpdate} for - * newly created 3D objects. - * - * @static - * @type {boolean} - * @default true - */ -Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; - -/** - * The default setting for {@link Object3D#matrixWorldAutoUpdate} for - * newly created 3D objects. - * - * @static - * @type {boolean} - * @default true - */ -Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; - -let _id$1 = 0; - -const _m1$2 = /*@__PURE__*/ new Matrix4(); -const _obj = /*@__PURE__*/ new Object3D(); -const _offset = /*@__PURE__*/ new Vector3(); -const _box$1 = /*@__PURE__*/ new Box3(); -const _boxMorphTargets = /*@__PURE__*/ new Box3(); -const _vector$4 = /*@__PURE__*/ new Vector3(); - -/** - * A representation of mesh, line, or point geometry. Includes vertex - * positions, face indices, normals, colors, UVs, and custom attributes - * within buffers, reducing the cost of passing all this data to the GPU. - * - * ```js - * const geometry = new THREE.BufferGeometry(); - * // create a simple square shape. We duplicate the top left and bottom right - * // vertices because each vertex needs to appear once per triangle. - * const vertices = new Float32Array( [ - * -1.0, -1.0, 1.0, // v0 - * 1.0, -1.0, 1.0, // v1 - * 1.0, 1.0, 1.0, // v2 - * - * 1.0, 1.0, 1.0, // v3 - * -1.0, 1.0, 1.0, // v4 - * -1.0, -1.0, 1.0 // v5 - * ] ); - * // itemSize = 3 because there are 3 values (components) per vertex - * geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); - * const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); - * const mesh = new THREE.Mesh( geometry, material ); - * ``` - * - * @augments EventDispatcher - */ -class BufferGeometry extends EventDispatcher { - - /** - * Constructs a new geometry. - */ - constructor() { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isBufferGeometry = true; - - /** - * The ID of the geometry. - * - * @name BufferGeometry#id - * @type {number} - * @readonly - */ - Object.defineProperty( this, 'id', { value: _id$1 ++ } ); - - /** - * The UUID of the geometry. - * - * @type {string} - * @readonly - */ - this.uuid = generateUUID(); - - /** - * The name of the geometry. - * - * @type {string} - */ - this.name = ''; - this.type = 'BufferGeometry'; - - /** - * Allows for vertices to be re-used across multiple triangles; this is - * called using "indexed triangles". Each triangle is associated with the - * indices of three vertices. This attribute therefore stores the index of - * each vertex for each triangular face. If this attribute is not set, the - * renderer assumes that each three contiguous positions represent a single triangle. - * - * @type {?BufferAttribute} - * @default null - */ - this.index = null; - - /** - * A (storage) buffer attribute which was generated with a compute shader and - * now defines indirect draw calls. - * - * Can only be used with {@link WebGPURenderer} and a WebGPU backend. - * - * @type {?BufferAttribute} - * @default null - */ - this.indirect = null; - - /** - * The offset, in bytes, into the indirect drawing buffer where the value data begins. If an array is provided, multiple indirect draw calls will be made for each offset. - * - * Can only be used with {@link WebGPURenderer} and a WebGPU backend. - * - * @type {number|Array} - * @default 0 - */ - this.indirectOffset = 0; - - /** - * This dictionary has as id the name of the attribute to be set and as value - * the buffer attribute to set it to. Rather than accessing this property directly, - * use `setAttribute()` and `getAttribute()` to access attributes of this geometry. - * - * @type {Object} - */ - this.attributes = {}; - - /** - * This dictionary holds the morph targets of the geometry. - * - * Note: Once the geometry has been rendered, the morph attribute data cannot - * be changed. You will have to call `dispose()`, and create a new geometry instance. - * - * @type {Object} - */ - this.morphAttributes = {}; - - /** - * Used to control the morph target behavior; when set to `true`, the morph - * target data is treated as relative offsets, rather than as absolute - * positions/normals. - * - * @type {boolean} - * @default false - */ - this.morphTargetsRelative = false; - - /** - * Split the geometry into groups, each of which will be rendered in a - * separate draw call. This allows an array of materials to be used with the geometry. - * - * Use `addGroup()` and `clearGroups()` to edit groups, rather than modifying this array directly. - * - * Every vertex and index must belong to exactly one group — groups must not share vertices or - * indices, and must not leave vertices or indices unused. - * - * @type {Array} - */ - this.groups = []; - - /** - * Bounding box for the geometry which can be calculated with `computeBoundingBox()`. - * - * @type {?Box3} - * @default null - */ - this.boundingBox = null; - - /** - * Bounding sphere for the geometry which can be calculated with `computeBoundingSphere()`. - * - * @type {?Sphere} - * @default null - */ - this.boundingSphere = null; - - /** - * Determines the part of the geometry to render. This should not be set directly, - * instead use `setDrawRange()`. - * - * @type {{start:number,count:number}} - */ - this.drawRange = { start: 0, count: Infinity }; - - /** - * An object that can be used to store custom data about the geometry. - * It should not hold references to functions as these will not be cloned. - * - * @type {Object} - */ - this.userData = {}; - - /** - * `true` when the geometry has been transformed since construction - * (e.g. via {@link BufferGeometry#applyMatrix4}). Only relevant for - * geometry generators (subclasses that populate `parameters`): when set, - * {@link BufferGeometry#toJSON} omits `parameters` since they no longer - * describe the geometry. - * - * @private - * @type {boolean} - * @default false - */ - this._transformed = false; - - } - - /** - * Returns the index of this geometry. - * - * @return {?BufferAttribute} The index. Returns `null` if no index is defined. - */ - getIndex() { - - return this.index; - - } - - /** - * Sets the given index to this geometry. - * - * @param {Array|BufferAttribute} index - The index to set. - * @return {BufferGeometry} A reference to this instance. - */ - setIndex( index ) { - - if ( Array.isArray( index ) ) { - - this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); - - } else { - - this.index = index; - - } - - return this; - - } - - /** - * Sets the given indirect attribute to this geometry. - * - * @param {BufferAttribute} indirect - The attribute holding indirect draw calls. - * @param {number|Array} [indirectOffset=0] - The offset, in bytes, into the indirect drawing buffer where the value data begins. If an array is provided, multiple indirect draw calls will be made for each offset. - * @return {BufferGeometry} A reference to this instance. - */ - setIndirect( indirect, indirectOffset = 0 ) { - - this.indirect = indirect; - this.indirectOffset = indirectOffset; - - return this; - - } - - /** - * Returns the indirect attribute of this geometry. - * - * @return {?BufferAttribute} The indirect attribute. Returns `null` if no indirect attribute is defined. - */ - getIndirect() { - - return this.indirect; - - } - - /** - * Returns the buffer attribute for the given name. - * - * @param {string} name - The attribute name. - * @return {BufferAttribute|InterleavedBufferAttribute|undefined} The buffer attribute. - * Returns `undefined` if not attribute has been found. - */ - getAttribute( name ) { - - return this.attributes[ name ]; - - } - - /** - * Sets the given attribute for the given name. - * - * @param {string} name - The attribute name. - * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute to set. - * @return {BufferGeometry} A reference to this instance. - */ - setAttribute( name, attribute ) { - - this.attributes[ name ] = attribute; - - return this; - - } - - /** - * Deletes the attribute for the given name. - * - * @param {string} name - The attribute name to delete. - * @return {BufferGeometry} A reference to this instance. - */ - deleteAttribute( name ) { - - delete this.attributes[ name ]; - - return this; - - } - - /** - * Returns `true` if this geometry has an attribute for the given name. - * - * @param {string} name - The attribute name. - * @return {boolean} Whether this geometry has an attribute for the given name or not. - */ - hasAttribute( name ) { - - return this.attributes[ name ] !== undefined; - - } - - /** - * Adds a group to this geometry. - * - * @param {number} start - The first element in this draw call. That is the first - * vertex for non-indexed geometry, otherwise the first triangle index. - * @param {number} count - Specifies how many vertices (or indices) are part of this group. - * @param {number} [materialIndex=0] - The material array index to use. - */ - addGroup( start, count, materialIndex = 0 ) { - - this.groups.push( { - - start: start, - count: count, - materialIndex: materialIndex - - } ); - - } - - /** - * Clears all groups. - */ - clearGroups() { - - this.groups = []; - - } - - /** - * Sets the draw range for this geometry. - * - * @param {number} start - The first vertex for non-indexed geometry, otherwise the first triangle index. - * @param {number} count - For non-indexed BufferGeometry, `count` is the number of vertices to render. - * For indexed BufferGeometry, `count` is the number of indices to render. - */ - setDrawRange( start, count ) { - - this.drawRange.start = start; - this.drawRange.count = count; - - } - - /** - * Applies the given 4x4 transformation matrix to the geometry. - * - * @param {Matrix4} matrix - The matrix to apply. - * @return {BufferGeometry} A reference to this instance. - */ - applyMatrix4( matrix ) { - - const position = this.attributes.position; - - if ( position !== undefined ) { - - position.applyMatrix4( matrix ); - - position.needsUpdate = true; - - } - - const normal = this.attributes.normal; - - if ( normal !== undefined ) { - - const normalMatrix = new Matrix3().getNormalMatrix( matrix ); - - normal.applyNormalMatrix( normalMatrix ); - - normal.needsUpdate = true; - - } - - const tangent = this.attributes.tangent; - - if ( tangent !== undefined ) { - - tangent.transformDirection( matrix ); - - tangent.needsUpdate = true; - - } - - if ( this.boundingBox !== null ) { - - this.computeBoundingBox(); - - } - - if ( this.boundingSphere !== null ) { - - this.computeBoundingSphere(); - - } - - this._transformed = true; - - return this; - - } - - /** - * Applies the rotation represented by the Quaternion to the geometry. - * - * @param {Quaternion} q - The Quaternion to apply. - * @return {BufferGeometry} A reference to this instance. - */ - applyQuaternion( q ) { - - _m1$2.makeRotationFromQuaternion( q ); - - this.applyMatrix4( _m1$2 ); - - return this; - - } - - /** - * Rotates the geometry about the X axis. This is typically done as a one time - * operation, and not during a loop. Use {@link Object3D#rotation} for typical - * real-time mesh rotation. - * - * @param {number} angle - The angle in radians. - * @return {BufferGeometry} A reference to this instance. - */ - rotateX( angle ) { - - // rotate geometry around world x-axis - - _m1$2.makeRotationX( angle ); - - this.applyMatrix4( _m1$2 ); - - return this; - - } - - /** - * Rotates the geometry about the Y axis. This is typically done as a one time - * operation, and not during a loop. Use {@link Object3D#rotation} for typical - * real-time mesh rotation. - * - * @param {number} angle - The angle in radians. - * @return {BufferGeometry} A reference to this instance. - */ - rotateY( angle ) { - - // rotate geometry around world y-axis - - _m1$2.makeRotationY( angle ); - - this.applyMatrix4( _m1$2 ); - - return this; - - } - - /** - * Rotates the geometry about the Z axis. This is typically done as a one time - * operation, and not during a loop. Use {@link Object3D#rotation} for typical - * real-time mesh rotation. - * - * @param {number} angle - The angle in radians. - * @return {BufferGeometry} A reference to this instance. - */ - rotateZ( angle ) { - - // rotate geometry around world z-axis - - _m1$2.makeRotationZ( angle ); - - this.applyMatrix4( _m1$2 ); - - return this; - - } - - /** - * Translates the geometry. This is typically done as a one time - * operation, and not during a loop. Use {@link Object3D#position} for typical - * real-time mesh rotation. - * - * @param {number} x - The x offset. - * @param {number} y - The y offset. - * @param {number} z - The z offset. - * @return {BufferGeometry} A reference to this instance. - */ - translate( x, y, z ) { - - // translate geometry - - _m1$2.makeTranslation( x, y, z ); - - this.applyMatrix4( _m1$2 ); - - return this; - - } - - /** - * Scales the geometry. This is typically done as a one time - * operation, and not during a loop. Use {@link Object3D#scale} for typical - * real-time mesh rotation. - * - * @param {number} x - The x scale. - * @param {number} y - The y scale. - * @param {number} z - The z scale. - * @return {BufferGeometry} A reference to this instance. - */ - scale( x, y, z ) { - - // scale geometry - - _m1$2.makeScale( x, y, z ); - - this.applyMatrix4( _m1$2 ); - - return this; - - } - - /** - * Rotates the geometry to face a point in 3D space. This is typically done as a one time - * operation, and not during a loop. Use {@link Object3D#lookAt} for typical - * real-time mesh rotation. - * - * @param {Vector3} vector - The target point. - * @return {BufferGeometry} A reference to this instance. - */ - lookAt( vector ) { - - _obj.lookAt( vector ); - - _obj.updateMatrix(); - - this.applyMatrix4( _obj.matrix ); - - return this; - - } - - /** - * Center the geometry based on its bounding box. - * - * @return {BufferGeometry} A reference to this instance. - */ - center() { - - this.computeBoundingBox(); - - this.boundingBox.getCenter( _offset ).negate(); - - this.translate( _offset.x, _offset.y, _offset.z ); - - return this; - - } - - /** - * Defines a geometry by creating a `position` attribute based on the given array of points. The array - * can hold 2D or 3D vectors. When using two-dimensional data, the `z` coordinate for all vertices is - * set to `0`. - * - * If the method is used with an existing `position` attribute, the vertex data are overwritten with the - * data from the array. The length of the array must match the vertex count. - * - * @param {Array|Array} points - The points. - * @return {BufferGeometry} A reference to this instance. - */ - setFromPoints( points ) { - - const positionAttribute = this.getAttribute( 'position' ); - - if ( positionAttribute === undefined ) { - - const position = []; - - for ( let i = 0, l = points.length; i < l; i ++ ) { - - const point = points[ i ]; - position.push( point.x, point.y, point.z || 0 ); - - } - - this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); - - } else { - - const l = Math.min( points.length, positionAttribute.count ); // make sure data do not exceed buffer size - - for ( let i = 0; i < l; i ++ ) { - - const point = points[ i ]; - positionAttribute.setXYZ( i, point.x, point.y, point.z || 0 ); - - } - - if ( points.length > positionAttribute.count ) { - - warn( 'BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry.' ); - - } - - positionAttribute.needsUpdate = true; - - } - - return this; - - } - - /** - * Computes the bounding box of the geometry, and updates the `boundingBox` member. - * The bounding box is not computed by the engine; it must be computed by your app. - * You may need to recompute the bounding box if the geometry vertices are modified. - */ - computeBoundingBox() { - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; - - if ( position && position.isGLBufferAttribute ) { - - error( 'BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.', this ); - - this.boundingBox.set( - new Vector3( - Infinity, - Infinity, - Infinity ), - new Vector3( + Infinity, + Infinity, + Infinity ) - ); - - return; - - } - - if ( position !== undefined ) { - - this.boundingBox.setFromBufferAttribute( position ); - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - _box$1.setFromBufferAttribute( morphAttribute ); - - if ( this.morphTargetsRelative ) { - - _vector$4.addVectors( this.boundingBox.min, _box$1.min ); - this.boundingBox.expandByPoint( _vector$4 ); - - _vector$4.addVectors( this.boundingBox.max, _box$1.max ); - this.boundingBox.expandByPoint( _vector$4 ); - - } else { - - this.boundingBox.expandByPoint( _box$1.min ); - this.boundingBox.expandByPoint( _box$1.max ); - - } - - } - - } - - } else { - - this.boundingBox.makeEmpty(); - - } - - if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { - - error( 'BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); - - } - - } - - /** - * Computes the bounding sphere of the geometry, and updates the `boundingSphere` member. - * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. - * You may need to recompute the bounding sphere if the geometry vertices are modified. - */ - computeBoundingSphere() { - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; - - if ( position && position.isGLBufferAttribute ) { - - error( 'BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere.', this ); - - this.boundingSphere.set( new Vector3(), Infinity ); - - return; - - } - - if ( position ) { - - // first, find the center of the bounding sphere - - const center = this.boundingSphere.center; - - _box$1.setFromBufferAttribute( position ); - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - _boxMorphTargets.setFromBufferAttribute( morphAttribute ); - - if ( this.morphTargetsRelative ) { - - _vector$4.addVectors( _box$1.min, _boxMorphTargets.min ); - _box$1.expandByPoint( _vector$4 ); - - _vector$4.addVectors( _box$1.max, _boxMorphTargets.max ); - _box$1.expandByPoint( _vector$4 ); - - } else { - - _box$1.expandByPoint( _boxMorphTargets.min ); - _box$1.expandByPoint( _boxMorphTargets.max ); - - } - - } - - } - - _box$1.getCenter( center ); - - // second, try to find a boundingSphere with a radius smaller than the - // boundingSphere of the boundingBox: sqrt(3) smaller in the best case - - let maxRadiusSq = 0; - - for ( let i = 0, il = position.count; i < il; i ++ ) { - - _vector$4.fromBufferAttribute( position, i ); - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) ); - - } - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - const morphTargetsRelative = this.morphTargetsRelative; - - for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { - - _vector$4.fromBufferAttribute( morphAttribute, j ); - - if ( morphTargetsRelative ) { - - _offset.fromBufferAttribute( position, j ); - _vector$4.add( _offset ); - - } - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) ); - - } - - } - - } - - this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); - - if ( isNaN( this.boundingSphere.radius ) ) { - - error( 'BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); - - } - - } - - } - - /** - * Calculates and adds a tangent attribute to this geometry. - * - * The computation is only supported for indexed geometries and if position, normal, and uv attributes - * are defined. When using a tangent space normal map, prefer the MikkTSpace algorithm provided by - * {@link BufferGeometryUtils#computeMikkTSpaceTangents} instead. - */ - computeTangents() { - - const index = this.index; - const attributes = this.attributes; - - // based on http://www.terathon.com/code/tangent.html - // (per vertex tangents) - - if ( index === null || - attributes.position === undefined || - attributes.normal === undefined || - attributes.uv === undefined ) { - - error( 'BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); - return; - - } - - const positionAttribute = attributes.position; - const normalAttribute = attributes.normal; - const uvAttribute = attributes.uv; - - let tangentAttribute = this.getAttribute( 'tangent' ); - - if ( tangentAttribute === undefined || tangentAttribute.count !== positionAttribute.count ) { - - tangentAttribute = new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ); - this.setAttribute( 'tangent', tangentAttribute ); - - } - - const tan1 = [], tan2 = []; - - for ( let i = 0; i < positionAttribute.count; i ++ ) { - - tan1[ i ] = new Vector3(); - tan2[ i ] = new Vector3(); - - } - - const vA = new Vector3(), - vB = new Vector3(), - vC = new Vector3(), - - uvA = new Vector2(), - uvB = new Vector2(), - uvC = new Vector2(), - - sdir = new Vector3(), - tdir = new Vector3(); - - function handleTriangle( a, b, c ) { - - vA.fromBufferAttribute( positionAttribute, a ); - vB.fromBufferAttribute( positionAttribute, b ); - vC.fromBufferAttribute( positionAttribute, c ); - - uvA.fromBufferAttribute( uvAttribute, a ); - uvB.fromBufferAttribute( uvAttribute, b ); - uvC.fromBufferAttribute( uvAttribute, c ); - - vB.sub( vA ); - vC.sub( vA ); - - uvB.sub( uvA ); - uvC.sub( uvA ); - - const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); - - // silently ignore degenerate uv triangles having coincident or colinear vertices - - if ( ! isFinite( r ) ) return; - - sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); - tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); - - tan1[ a ].add( sdir ); - tan1[ b ].add( sdir ); - tan1[ c ].add( sdir ); - - tan2[ a ].add( tdir ); - tan2[ b ].add( tdir ); - tan2[ c ].add( tdir ); - - } - - let groups = this.groups; - - if ( groups.length === 0 ) { - - groups = [ { - start: 0, - count: index.count - } ]; - - } - - for ( let i = 0, il = groups.length; i < il; ++ i ) { - - const group = groups[ i ]; - - const start = group.start; - const count = group.count; - - for ( let j = start, jl = start + count; j < jl; j += 3 ) { - - handleTriangle( - index.getX( j + 0 ), - index.getX( j + 1 ), - index.getX( j + 2 ) - ); - - } - - } - - const tmp = new Vector3(), tmp2 = new Vector3(); - const n = new Vector3(), n2 = new Vector3(); - - function handleVertex( v ) { - - n.fromBufferAttribute( normalAttribute, v ); - n2.copy( n ); - - const t = tan1[ v ]; - - // Gram-Schmidt orthogonalize - - tmp.copy( t ); - tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); - - // Calculate handedness - - tmp2.crossVectors( n2, t ); - const test = tmp2.dot( tan2[ v ] ); - const w = ( test < 0.0 ) ? -1 : 1.0; - - tangentAttribute.setXYZW( v, tmp.x, tmp.y, tmp.z, w ); - - } - - for ( let i = 0, il = groups.length; i < il; ++ i ) { - - const group = groups[ i ]; - - const start = group.start; - const count = group.count; - - for ( let j = start, jl = start + count; j < jl; j += 3 ) { - - handleVertex( index.getX( j + 0 ) ); - handleVertex( index.getX( j + 1 ) ); - handleVertex( index.getX( j + 2 ) ); - - } - - } - - this._transformed = true; - - } - - /** - * Computes vertex normals for the given vertex data. For indexed geometries, the method sets - * each vertex normal to be the average of the face normals of the faces that share that vertex. - * For non-indexed geometries, vertices are not shared, and the method sets each vertex normal - * to be the same as the face normal. - */ - computeVertexNormals() { - - const index = this.index; - const positionAttribute = this.getAttribute( 'position' ); - - if ( positionAttribute !== undefined ) { - - let normalAttribute = this.getAttribute( 'normal' ); - - if ( normalAttribute === undefined || normalAttribute.count !== positionAttribute.count ) { - - normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); - this.setAttribute( 'normal', normalAttribute ); - - } else { - - // reset existing normals to zero - - for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { - - normalAttribute.setXYZ( i, 0, 0, 0 ); - - } - - } - - const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); - const cb = new Vector3(), ab = new Vector3(); - - // indexed elements - - if ( index ) { - - for ( let i = 0, il = index.count; i < il; i += 3 ) { - - const vA = index.getX( i + 0 ); - const vB = index.getX( i + 1 ); - const vC = index.getX( i + 2 ); - - pA.fromBufferAttribute( positionAttribute, vA ); - pB.fromBufferAttribute( positionAttribute, vB ); - pC.fromBufferAttribute( positionAttribute, vC ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - nA.fromBufferAttribute( normalAttribute, vA ); - nB.fromBufferAttribute( normalAttribute, vB ); - nC.fromBufferAttribute( normalAttribute, vC ); - - nA.add( cb ); - nB.add( cb ); - nC.add( cb ); - - normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); - normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); - normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); - - } - - } else { - - // non-indexed elements (unconnected triangle soup) - - for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { - - pA.fromBufferAttribute( positionAttribute, i + 0 ); - pB.fromBufferAttribute( positionAttribute, i + 1 ); - pC.fromBufferAttribute( positionAttribute, i + 2 ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); - - } - - } - - this.normalizeNormals(); - - normalAttribute.needsUpdate = true; - - } - - } - - /** - * Ensures every normal vector in a geometry will have a magnitude of `1`. This will - * correct lighting on the geometry surfaces. - */ - normalizeNormals() { - - const normals = this.attributes.normal; - - for ( let i = 0, il = normals.count; i < il; i ++ ) { - - _vector$4.fromBufferAttribute( normals, i ); - - _vector$4.normalize(); - - normals.setXYZ( i, _vector$4.x, _vector$4.y, _vector$4.z ); - - } - - } - - /** - * Return a new non-index version of this indexed geometry. If the geometry - * is already non-indexed, the method is a NOOP. - * - * @return {BufferGeometry} The non-indexed version of this indexed geometry. - */ - toNonIndexed() { - - function convertBufferAttribute( attribute, indices ) { - - const array = attribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - const array2 = new array.constructor( indices.length * itemSize ); - - let index = 0, index2 = 0; - - for ( let i = 0, l = indices.length; i < l; i ++ ) { - - if ( attribute.isInterleavedBufferAttribute ) { - - index = indices[ i ] * attribute.data.stride + attribute.offset; - - } else { - - index = indices[ i ] * itemSize; - - } - - for ( let j = 0; j < itemSize; j ++ ) { - - array2[ index2 ++ ] = array[ index ++ ]; - - } - - } - - return new BufferAttribute( array2, itemSize, normalized ); - - } - - // - - if ( this.index === null ) { - - warn( 'BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); - return this; - - } - - const geometry2 = new BufferGeometry(); - - const indices = this.index.array; - const attributes = this.attributes; - - // attributes - - for ( const name in attributes ) { - - const attribute = attributes[ name ]; - - const newAttribute = convertBufferAttribute( attribute, indices ); - - geometry2.setAttribute( name, newAttribute ); - - } - - // morph attributes - - const morphAttributes = this.morphAttributes; - - for ( const name in morphAttributes ) { - - const morphArray = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - - for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { - - const attribute = morphAttribute[ i ]; - - const newAttribute = convertBufferAttribute( attribute, indices ); - - morphArray.push( newAttribute ); - - } - - geometry2.morphAttributes[ name ] = morphArray; - - } - - geometry2.morphTargetsRelative = this.morphTargetsRelative; - - // groups - - const groups = this.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - geometry2.addGroup( group.start, group.count, group.materialIndex ); - - } - - return geometry2; - - } - - /** - * Serializes the geometry into JSON. - * - * @return {Object} A JSON object representing the serialized geometry. - */ - toJSON() { - - const data = { - metadata: { - version: 4.7, - type: 'BufferGeometry', - generator: 'BufferGeometry.toJSON' - } - }; - - // standard BufferGeometry serialization - - data.uuid = this.uuid; - data.type = ( this.parameters !== undefined && this._transformed === true ) ? 'BufferGeometry' : this.type; - data.name = this.name; - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - - if ( this.parameters !== undefined && this._transformed !== true ) { - - const parameters = this.parameters; - - for ( const key in parameters ) { - - if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - - } - - return data; - - } - - // for simplicity the code assumes attributes are not shared across geometries, see #15811 - - data.data = { attributes: {} }; - - const index = this.index; - - if ( index !== null ) { - - data.data.index = { - type: index.array.constructor.name, - array: Array.prototype.slice.call( index.array ) - }; - - } - - const attributes = this.attributes; - - for ( const key in attributes ) { - - const attribute = attributes[ key ]; - - data.data.attributes[ key ] = attribute.toJSON( data.data ); - - } - - const morphAttributes = {}; - let hasMorphAttributes = false; - - for ( const key in this.morphAttributes ) { - - const attributeArray = this.morphAttributes[ key ]; - - const array = []; - - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { - - const attribute = attributeArray[ i ]; - - array.push( attribute.toJSON( data.data ) ); - - } - - if ( array.length > 0 ) { - - morphAttributes[ key ] = array; - - hasMorphAttributes = true; - - } - - } - - if ( hasMorphAttributes ) { - - data.data.morphAttributes = morphAttributes; - data.data.morphTargetsRelative = this.morphTargetsRelative; - - } - - const groups = this.groups; - - if ( groups.length > 0 ) { - - data.data.groups = JSON.parse( JSON.stringify( groups ) ); - - } - - const boundingSphere = this.boundingSphere; - - if ( boundingSphere !== null ) { - - data.data.boundingSphere = boundingSphere.toJSON(); - - } - - return data; - - } - - /** - * Returns a new geometry with copied values from this instance. - * - * @return {BufferGeometry} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the values of the given geometry to this instance. - * - * @param {BufferGeometry} source - The geometry to copy. - * @return {BufferGeometry} A reference to this instance. - */ - copy( source ) { - - // reset - - this.index = null; - this.attributes = {}; - this.morphAttributes = {}; - this.groups = []; - this.boundingBox = null; - this.boundingSphere = null; - - // used for storing cloned, shared data - - const data = {}; - - // name - - this.name = source.name; - - // index - - const index = source.index; - - if ( index !== null ) { - - this.setIndex( index.clone() ); - - } - - // attributes - - const attributes = source.attributes; - - for ( const name in attributes ) { - - const attribute = attributes[ name ]; - this.setAttribute( name, attribute.clone( data ) ); - - } - - // morph attributes - - const morphAttributes = source.morphAttributes; - - for ( const name in morphAttributes ) { - - const array = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - - for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { - - array.push( morphAttribute[ i ].clone( data ) ); - - } - - this.morphAttributes[ name ] = array; - - } - - this.morphTargetsRelative = source.morphTargetsRelative; - - // groups - - const groups = source.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - this.addGroup( group.start, group.count, group.materialIndex ); - - } - - // bounding box - - const boundingBox = source.boundingBox; - - if ( boundingBox !== null ) { - - this.boundingBox = boundingBox.clone(); - - } - - // bounding sphere - - const boundingSphere = source.boundingSphere; - - if ( boundingSphere !== null ) { - - this.boundingSphere = boundingSphere.clone(); - - } - - // draw range - - this.drawRange.start = source.drawRange.start; - this.drawRange.count = source.drawRange.count; - - // user data - - this.userData = source.userData; - - // transformed flag - - this._transformed = source._transformed; - - return this; - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - * - * @fires BufferGeometry#dispose - */ - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} - -/** - * A geometry class for a rectangular cuboid with a given width, height, and depth. - * On creation, the cuboid is centred on the origin, with each edge parallel to one - * of the axes. - * - * ```js - * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - * const cube = new THREE.Mesh( geometry, material ); - * scene.add( cube ); - * ``` - * - * @augments BufferGeometry - * @demo scenes/geometry-browser.html#BoxGeometry - */ -class BoxGeometry extends BufferGeometry { - - /** - * Constructs a new box geometry. - * - * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis. - * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis. - * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis. - * @param {number} [widthSegments=1] - Number of segmented rectangular faces along the width of the sides. - * @param {number} [heightSegments=1] - Number of segmented rectangular faces along the height of the sides. - * @param {number} [depthSegments=1] - Number of segmented rectangular faces along the depth of the sides. - */ - constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { - - super(); - - this.type = 'BoxGeometry'; - - /** - * Holds the constructor parameters that have been - * used to generate the geometry. Any modification - * after instantiation does not change the geometry. - * - * @type {Object} - */ - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; - - const scope = this; - - // segments - - widthSegments = Math.floor( widthSegments ); - heightSegments = Math.floor( heightSegments ); - depthSegments = Math.floor( depthSegments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - let numberOfVertices = 0; - let groupStart = 0; - - // build each side of the box geometry - - buildPlane( 'z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0 ); // px - buildPlane( 'z', 'y', 'x', 1, -1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx - buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py - buildPlane( 'x', 'z', 'y', 1, -1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny - buildPlane( 'x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4 ); // pz - buildPlane( 'x', 'y', 'z', -1, -1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { - - const segmentWidth = width / gridX; - const segmentHeight = height / gridY; - - const widthHalf = width / 2; - const heightHalf = height / 2; - const depthHalf = depth / 2; - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - let vertexCounter = 0; - let groupCount = 0; - - const vector = new Vector3(); - - // generate vertices, normals and uvs - - for ( let iy = 0; iy < gridY1; iy ++ ) { - - const y = iy * segmentHeight - heightHalf; - - for ( let ix = 0; ix < gridX1; ix ++ ) { - - const x = ix * segmentWidth - widthHalf; - - // set values to correct vector component - - vector[ u ] = x * udir; - vector[ v ] = y * vdir; - vector[ w ] = depthHalf; - - // now apply vector to vertex buffer - - vertices.push( vector.x, vector.y, vector.z ); - - // set values to correct vector component - - vector[ u ] = 0; - vector[ v ] = 0; - vector[ w ] = depth > 0 ? 1 : -1; - - // now apply vector to normal buffer - - normals.push( vector.x, vector.y, vector.z ); - - // uvs - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - // counters - - vertexCounter += 1; - - } - - } - - // indices - - // 1. you need three indices to draw a single face - // 2. a single segment consists of two faces - // 3. so we need to generate six (2*3) indices per segment - - for ( let iy = 0; iy < gridY; iy ++ ) { - - for ( let ix = 0; ix < gridX; ix ++ ) { - - const a = numberOfVertices + ix + gridX1 * iy; - const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); - const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - // increase counter - - groupCount += 6; - - } - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, materialIndex ); - - // calculate new start value for groups - - groupStart += groupCount; - - // update total number of vertices - - numberOfVertices += vertexCounter; - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - /** - * Factory method for creating an instance of this class from the given - * JSON object. - * - * @param {Object} data - A JSON object representing the serialized geometry. - * @return {BoxGeometry} A new instance. - */ - static fromJSON( data ) { - - return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); - - } - -} - -/** - * A geometry class for representing a plane. - * - * ```js - * const geometry = new THREE.PlaneGeometry( 1, 1 ); - * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); - * const plane = new THREE.Mesh( geometry, material ); - * scene.add( plane ); - * ``` - * - * @augments BufferGeometry - * @demo scenes/geometry-browser.html#PlaneGeometry - */ -class PlaneGeometry extends BufferGeometry { - - /** - * Constructs a new plane geometry. - * - * @param {number} [width=1] - The width along the X axis. - * @param {number} [height=1] - The height along the Y axis - * @param {number} [widthSegments=1] - The number of segments along the X axis. - * @param {number} [heightSegments=1] - The number of segments along the Y axis. - */ - constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { - - super(); - - this.type = 'PlaneGeometry'; - - /** - * Holds the constructor parameters that have been - * used to generate the geometry. Any modification - * after instantiation does not change the geometry. - * - * @type {Object} - */ - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; - - const width_half = width / 2; - const height_half = height / 2; - - const gridX = Math.floor( widthSegments ); - const gridY = Math.floor( heightSegments ); - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - const segment_width = width / gridX; - const segment_height = height / gridY; - - // - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - for ( let iy = 0; iy < gridY1; iy ++ ) { - - const y = iy * segment_height - height_half; - - for ( let ix = 0; ix < gridX1; ix ++ ) { - - const x = ix * segment_width - width_half; - - vertices.push( x, - y, 0 ); - - normals.push( 0, 0, 1 ); - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - } - - } - - for ( let iy = 0; iy < gridY; iy ++ ) { - - for ( let ix = 0; ix < gridX; ix ++ ) { - - const a = ix + gridX1 * iy; - const b = ix + gridX1 * ( iy + 1 ); - const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = ( ix + 1 ) + gridX1 * iy; - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - /** - * Factory method for creating an instance of this class from the given - * JSON object. - * - * @param {Object} data - A JSON object representing the serialized geometry. - * @return {PlaneGeometry} A new instance. - */ - static fromJSON( data ) { - - return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); - - } - -} - -let _materialId = 0; - -/** - * Abstract base class for materials. - * - * Materials define the appearance of renderable 3D objects. - * - * @abstract - * @augments EventDispatcher - */ -class Material extends EventDispatcher { - - /** - * Constructs a new material. - */ - constructor() { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMaterial = true; - - /** - * The ID of the material. - * - * @name Material#id - * @type {number} - * @readonly - */ - Object.defineProperty( this, 'id', { value: _materialId ++ } ); - - /** - * The UUID of the material. - * - * @type {string} - * @readonly - */ - this.uuid = generateUUID(); - - /** - * The name of the material. - * - * @type {string} - */ - this.name = ''; - - /** - * The type property is used for detecting the object type - * in context of serialization/deserialization. - * - * @type {string} - * @readonly - */ - this.type = 'Material'; - - /** - * Defines the blending type of the material. - * - * It must be set to `CustomBlending` if custom blending properties like - * {@link Material#blendSrc}, {@link Material#blendDst} or {@link Material#blendEquation} - * should have any effect. - * - * @type {(NoBlending|NormalBlending|AdditiveBlending|SubtractiveBlending|MultiplyBlending|CustomBlending)} - * @default NormalBlending - */ - this.blending = NormalBlending; - - /** - * Defines which side of faces will be rendered - front, back or both. - * - * @type {(FrontSide|BackSide|DoubleSide)} - * @default FrontSide - */ - this.side = FrontSide; - - /** - * If set to `true`, vertex colors should be used. - * - * The engine supports RGB and RGBA vertex colors depending on whether a three (RGB) or - * four (RGBA) component color buffer attribute is used. - * - * @type {boolean} - * @default false - */ - this.vertexColors = false; - - /** - * Defines how transparent the material is. - * A value of `0.0` indicates fully transparent, `1.0` is fully opaque. - * - * If the {@link Material#transparent} is not set to `true`, - * the material will remain fully opaque and this value will only affect its color. - * - * @type {number} - * @default 1 - */ - this.opacity = 1; - - /** - * Defines whether this material is transparent. This has an effect on - * rendering as transparent objects need special treatment and are rendered - * after non-transparent objects. - * - * When set to true, the extent to which the material is transparent is - * controlled by {@link Material#opacity}. - * - * @type {boolean} - * @default false - */ - this.transparent = false; - - /** - * Enables alpha hashed transparency, an alternative to {@link Material#transparent} or - * {@link Material#alphaTest}. The material will not be rendered if opacity is lower than - * a random threshold. Randomization introduces some grain or noise, but approximates alpha - * blending without the associated problems of sorting. Using TAA can reduce the resulting noise. - * - * @type {boolean} - * @default false - */ - this.alphaHash = false; - - /** - * Defines the blending source factor. - * - * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} - * @default SrcAlphaFactor - */ - this.blendSrc = SrcAlphaFactor; - - /** - * Defines the blending destination factor. - * - * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} - * @default OneMinusSrcAlphaFactor - */ - this.blendDst = OneMinusSrcAlphaFactor; - - /** - * Defines the blending equation. - * - * @type {(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} - * @default AddEquation - */ - this.blendEquation = AddEquation; - - /** - * Defines the blending source alpha factor. - * - * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} - * @default null - */ - this.blendSrcAlpha = null; - - /** - * Defines the blending destination alpha factor. - * - * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} - * @default null - */ - this.blendDstAlpha = null; - - /** - * Defines the blending equation of the alpha channel. - * - * @type {?(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} - * @default null - */ - this.blendEquationAlpha = null; - - /** - * Represents the RGB values of the constant blend color. - * - * This property has only an effect when using custom blending with `ConstantColor` or `OneMinusConstantColor`. - * - * @type {Color} - * @default (0,0,0) - */ - this.blendColor = new Color( 0, 0, 0 ); - - /** - * Represents the alpha value of the constant blend color. - * - * This property has only an effect when using custom blending with `ConstantAlpha` or `OneMinusConstantAlpha`. - * - * @type {number} - * @default 0 - */ - this.blendAlpha = 0; - - /** - * Defines the depth function. - * - * @type {(NeverDepth|AlwaysDepth|LessDepth|LessEqualDepth|EqualDepth|GreaterEqualDepth|GreaterDepth|NotEqualDepth)} - * @default LessEqualDepth - */ - this.depthFunc = LessEqualDepth; - - /** - * Whether to have depth test enabled when rendering this material. - * When the depth test is disabled, the depth write will also be implicitly disabled. - * - * @type {boolean} - * @default true - */ - this.depthTest = true; - - /** - * Whether rendering this material has any effect on the depth buffer. - * - * When drawing 2D overlays it can be useful to disable the depth writing in - * order to layer several things together without creating z-index artifacts. - * - * @type {boolean} - * @default true - */ - this.depthWrite = true; - - /** - * The bit mask to use when writing to the stencil buffer. - * - * @type {number} - * @default 0xff - */ - this.stencilWriteMask = 0xff; - - /** - * The stencil comparison function to use. - * - * @type {NeverStencilFunc|LessStencilFunc|EqualStencilFunc|LessEqualStencilFunc|GreaterStencilFunc|NotEqualStencilFunc|GreaterEqualStencilFunc|AlwaysStencilFunc} - * @default AlwaysStencilFunc - */ - this.stencilFunc = AlwaysStencilFunc; - - /** - * The value to use when performing stencil comparisons or stencil operations. - * - * @type {number} - * @default 0 - */ - this.stencilRef = 0; - - /** - * The bit mask to use when comparing against the stencil buffer. - * - * @type {number} - * @default 0xff - */ - this.stencilFuncMask = 0xff; - - /** - * Which stencil operation to perform when the comparison function returns `false`. - * - * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} - * @default KeepStencilOp - */ - this.stencilFail = KeepStencilOp; - - /** - * Which stencil operation to perform when the comparison function returns - * `true` but the depth test fails. - * - * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} - * @default KeepStencilOp - */ - this.stencilZFail = KeepStencilOp; - - /** - * Which stencil operation to perform when the comparison function returns - * `true` and the depth test passes. - * - * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} - * @default KeepStencilOp - */ - this.stencilZPass = KeepStencilOp; - - /** - * Whether stencil operations are performed against the stencil buffer. In - * order to perform writes or comparisons against the stencil buffer this - * value must be `true`. - * - * @type {boolean} - * @default false - */ - this.stencilWrite = false; - - /** - * User-defined clipping planes specified as THREE.Plane objects in world - * space. These planes apply to the objects this material is attached to. - * Points in space whose signed distance to the plane is negative are clipped - * (not rendered). This requires {@link WebGLRenderer#localClippingEnabled} to - * be `true`. - * - * @type {?Array} - * @default null - */ - this.clippingPlanes = null; - - /** - * Changes the behavior of clipping planes so that only their intersection is - * clipped, rather than their union. - * - * @type {boolean} - * @default false - */ - this.clipIntersection = false; - - /** - * Defines whether to clip shadows according to the clipping planes specified - * on this material. - * - * @type {boolean} - * @default false - */ - this.clipShadows = false; - - /** - * Defines which side of faces cast shadows. If `null`, the side casting shadows - * is determined as follows: - * - * - When {@link Material#side} is set to `FrontSide`, the back side cast shadows. - * - When {@link Material#side} is set to `BackSide`, the front side cast shadows. - * - When {@link Material#side} is set to `DoubleSide`, both sides cast shadows. - * - * @type {?(FrontSide|BackSide|DoubleSide)} - * @default null - */ - this.shadowSide = null; - - /** - * Whether to render the material's color. - * - * This can be used in conjunction with {@link Object3D#renderOder} to create invisible - * objects that occlude other objects. - * - * @type {boolean} - * @default true - */ - this.colorWrite = true; - - /** - * Override the renderer's default precision for this material. - * - * @type {?('highp'|'mediump'|'lowp')} - * @default null - */ - this.precision = null; - - /** - * Whether to use polygon offset or not. When enabled, each fragment's depth value will - * be offset after it is interpolated from the depth values of the appropriate vertices. - * The offset is added before the depth test is performed and before the value is written - * into the depth buffer. - * - * Can be useful for rendering hidden-line images, for applying decals to surfaces, and for - * rendering solids with highlighted edges. - * - * @type {boolean} - * @default false - */ - this.polygonOffset = false; - - /** - * Specifies a scale factor that is used to create a variable depth offset for each polygon. - * - * @type {number} - * @default 0 - */ - this.polygonOffsetFactor = 0; - - /** - * Is multiplied by an implementation-specific value to create a constant depth offset. - * - * @type {number} - * @default 0 - */ - this.polygonOffsetUnits = 0; - - /** - * Whether to apply dithering to the color to remove the appearance of banding. - * - * @type {boolean} - * @default false - */ - this.dithering = false; - - /** - * Whether alpha to coverage should be enabled or not. Can only be used with MSAA-enabled contexts - * (meaning when the renderer was created with *antialias* parameter set to `true`). Enabling this - * will smooth aliasing on clip plane edges and alphaTest-clipped edges. - * - * @type {boolean} - * @default false - */ - this.alphaToCoverage = false; - - /** - * Whether to premultiply the alpha (transparency) value. - * - * @type {boolean} - * @default false - */ - this.premultipliedAlpha = false; - - /** - * Whether double-sided, transparent objects should be rendered with a single pass or not. - * - * The engine renders double-sided, transparent objects with two draw calls (back faces first, - * then front faces) to mitigate transparency artifacts. There are scenarios however where this - * approach produces no quality gains but still doubles draw calls e.g. when rendering flat - * vegetation like grass sprites. In these cases, set the `forceSinglePass` flag to `true` to - * disable the two pass rendering to avoid performance issues. - * - * @type {boolean} - * @default false - */ - this.forceSinglePass = false; - - /** - * Whether it's possible to override the material with {@link Scene#overrideMaterial} or not. - * - * @type {boolean} - * @default true - */ - this.allowOverride = true; - - /** - * Defines whether 3D objects using this material are visible. - * - * @type {boolean} - * @default true - */ - this.visible = true; - - /** - * Defines whether this material is tone mapped according to the renderer's tone mapping setting. - * - * It is ignored when rendering to a render target or using post processing or when using - * `WebGPURenderer`. In all these cases, all materials are honored by tone mapping. - * - * @type {boolean} - * @default true - */ - this.toneMapped = true; - - /** - * An object that can be used to store custom data about the Material. It - * should not hold references to functions as these will not be cloned. - * - * @type {Object} - */ - this.userData = {}; - - /** - * This starts at `0` and counts how many times {@link Material#needsUpdate} is set to `true`. - * - * @type {number} - * @readonly - * @default 0 - */ - this.version = 0; - - this._alphaTest = 0; - - } - - /** - * Sets the alpha value to be used when running an alpha test. The material - * will not be rendered if the opacity is lower than this value. - * - * @type {number} - * @readonly - * @default 0 - */ - get alphaTest() { - - return this._alphaTest; - - } - - set alphaTest( value ) { - - if ( this._alphaTest > 0 !== value > 0 ) { - - this.version ++; - - } - - this._alphaTest = value; - - } - - /** - * An optional callback that is executed immediately before the material is used to render a 3D object. - * - * This method can only be used when rendering with {@link WebGLRenderer}. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {Scene} scene - The scene. - * @param {Camera} camera - The camera that is used to render the scene. - * @param {BufferGeometry} geometry - The 3D object's geometry. - * @param {Object3D} object - The 3D object. - * @param {Object} group - The geometry group data. - */ - onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} - - /** - * An optional callback that is executed immediately before the shader - * program is compiled. This function is called with the shader source code - * as a parameter. Useful for the modification of built-in materials. - * - * This method can only be used when rendering with {@link WebGLRenderer}. The - * recommended approach when customizing materials is to use `WebGPURenderer` with the new - * Node Material system and [TSL](https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language). - * - * @param {{vertexShader:string,fragmentShader:string,uniforms:Object}} shaderobject - The object holds the uniforms and the vertex and fragment shader source. - * @param {WebGLRenderer} renderer - A reference to the renderer. - */ - onBeforeCompile( /* shaderobject, renderer */ ) {} - - /** - * In case {@link Material#onBeforeCompile} is used, this callback can be used to identify - * values of settings used in `onBeforeCompile()`, so three.js can reuse a cached - * shader or recompile the shader for this material as needed. - * - * This method can only be used when rendering with {@link WebGLRenderer}. - * - * @return {string} The custom program cache key. - */ - customProgramCacheKey() { - - return this.onBeforeCompile.toString(); - - } - - /** - * This method can be used to set default values from parameter objects. - * It is a generic implementation so it can be used with different types - * of materials. - * - * @param {Object} [values] - The material values to set. - */ - setValues( values ) { - - if ( values === undefined ) return; - - for ( const key in values ) { - - const newValue = values[ key ]; - - if ( newValue === undefined ) { - - warn( `Material: parameter '${ key }' has value of undefined.` ); - continue; - - } - - const currentValue = this[ key ]; - - if ( currentValue === undefined ) { - - warn( `Material: '${ key }' is not a property of THREE.${ this.type }.` ); - continue; - - } - - if ( currentValue && currentValue.isColor ) { - - currentValue.set( newValue ); - - } else if ( - ( ( currentValue && currentValue.isVector2 ) && ( newValue && newValue.isVector2 ) ) || - ( ( currentValue && currentValue.isEuler ) && ( newValue && newValue.isEuler ) ) || - ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) - ) { - - currentValue.copy( newValue ); - - } else { - - this[ key ] = newValue; - - } - - } - - } - - /** - * Serializes the material into JSON. - * - * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. - * @return {Object} A JSON object representing the serialized material. - * @see {@link ObjectLoader#parse} - */ - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( isRootObject ) { - - meta = { - textures: {}, - images: {} - }; - - } - - const data = { - metadata: { - version: 4.7, - type: 'Material', - generator: 'Material.toJSON' - } - }; - - // standard Material serialization - - data.uuid = this.uuid; - data.type = this.type; - - data.blending = this.blending; - data.side = this.side; - data.shadowSide = this.shadowSide; - data.vertexColors = this.vertexColors; - - data.opacity = this.opacity; - data.transparent = this.transparent; - - data.blendSrc = this.blendSrc; - data.blendDst = this.blendDst; - data.blendEquation = this.blendEquation; - data.blendSrcAlpha = this.blendSrcAlpha; - data.blendDstAlpha = this.blendDstAlpha; - data.blendEquationAlpha = this.blendEquationAlpha; - data.blendColor = this.blendColor.getHex(); - data.blendAlpha = this.blendAlpha; - - data.depthFunc = this.depthFunc; - data.depthTest = this.depthTest; - data.depthWrite = this.depthWrite; - data.colorWrite = this.colorWrite; - - data.clipIntersection = this.clipIntersection; - data.clipShadows = this.clipShadows; - - data.stencilWriteMask = this.stencilWriteMask; - data.stencilFunc = this.stencilFunc; - data.stencilRef = this.stencilRef; - data.stencilFuncMask = this.stencilFuncMask; - data.stencilFail = this.stencilFail; - data.stencilZFail = this.stencilZFail; - data.stencilZPass = this.stencilZPass; - data.stencilWrite = this.stencilWrite; - - data.polygonOffset = this.polygonOffset; - data.polygonOffsetFactor = this.polygonOffsetFactor; - data.polygonOffsetUnits = this.polygonOffsetUnits; - - data.dithering = this.dithering; - - data.alphaTest = this.alphaTest; - data.alphaHash = this.alphaHash; - data.alphaToCoverage = this.alphaToCoverage; - data.premultipliedAlpha = this.premultipliedAlpha; - data.forceSinglePass = this.forceSinglePass; - data.allowOverride = this.allowOverride; - - data.visible = this.visible; - data.toneMapped = this.toneMapped; - - data.name = this.name; - - if ( this.color && this.color.isColor ) data.color = this.color.getHex(); - - if ( this.roughness !== undefined ) data.roughness = this.roughness; - if ( this.metalness !== undefined ) data.metalness = this.metalness; - - if ( this.sheen !== undefined ) data.sheen = this.sheen; - if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); - if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; - if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); - if ( this.emissiveIntensity !== undefined ) data.emissiveIntensity = this.emissiveIntensity; - - if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); - if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; - if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); - if ( this.shininess !== undefined ) data.shininess = this.shininess; - if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; - if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; - - if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { - - data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; - - } - - if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { - - data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; - - } - - if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { - - data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; - data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); - - } - - if ( this.sheenColorMap && this.sheenColorMap.isTexture ) { - - data.sheenColorMap = this.sheenColorMap.toJSON( meta ).uuid; - - } - - if ( this.sheenRoughnessMap && this.sheenRoughnessMap.isTexture ) { - - data.sheenRoughnessMap = this.sheenRoughnessMap.toJSON( meta ).uuid; - - } - - if ( this.dispersion !== undefined ) data.dispersion = this.dispersion; - if ( this.retroreflectivity !== undefined ) data.retroreflectivity = this.retroreflectivity; - - if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; - if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; - if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; - - if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { - - data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; - - } - - if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { - - data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; - - } - - if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; - if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; - - if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { - - data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; - - } - - if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; - if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; - if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; - - if ( this.lightMap && this.lightMap.isTexture ) { - - data.lightMap = this.lightMap.toJSON( meta ).uuid; - data.lightMapIntensity = this.lightMapIntensity; - - } - - if ( this.aoMap && this.aoMap.isTexture ) { - - data.aoMap = this.aoMap.toJSON( meta ).uuid; - data.aoMapIntensity = this.aoMapIntensity; - - } - - if ( this.bumpMap && this.bumpMap.isTexture ) { - - data.bumpMap = this.bumpMap.toJSON( meta ).uuid; - data.bumpScale = this.bumpScale; - - } - - if ( this.normalMap && this.normalMap.isTexture ) { - - data.normalMap = this.normalMap.toJSON( meta ).uuid; - data.normalMapType = this.normalMapType; - data.normalScale = this.normalScale.toArray(); - - } - - if ( this.displacementMap && this.displacementMap.isTexture ) { - - data.displacementMap = this.displacementMap.toJSON( meta ).uuid; - data.displacementScale = this.displacementScale; - data.displacementBias = this.displacementBias; - - } - - if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; - if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; - - if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; - if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; - if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; - if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; - - if ( this.envMap && this.envMap.isTexture ) { - - data.envMap = this.envMap.toJSON( meta ).uuid; - - if ( this.combine !== undefined ) data.combine = this.combine; - - } - - if ( this.envMapRotation !== undefined ) data.envMapRotation = this.envMapRotation.toArray(); - if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; - if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; - if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; - - if ( this.gradientMap && this.gradientMap.isTexture ) { - - data.gradientMap = this.gradientMap.toJSON( meta ).uuid; - - } - - if ( this.transmission !== undefined ) data.transmission = this.transmission; - if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; - if ( this.thickness !== undefined ) data.thickness = this.thickness; - if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; - if ( this.attenuationDistance !== undefined ) data.attenuationDistance = this.attenuationDistance; - if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); - - if ( this.size !== undefined ) data.size = this.size; - if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; - - if ( Array.isArray( this.clippingPlanes ) && this.clippingPlanes.length > 0 ) { - - data.clippingPlanes = this.clippingPlanes.map( plane => plane.toJSON() ); - - } - - // rotation (SpriteMaterial) - if ( this.rotation !== undefined ) data.rotation = this.rotation; - - // depthPacking (MeshDepthMaterial) - if ( this.depthPacking !== undefined ) data.depthPacking = this.depthPacking; - - if ( this.linewidth !== undefined ) data.linewidth = this.linewidth; - if ( this.linecap !== undefined ) data.linecap = this.linecap; - if ( this.linejoin !== undefined ) data.linejoin = this.linejoin; - if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; - if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; - if ( this.scale !== undefined ) data.scale = this.scale; - - if ( this.wireframe !== undefined ) data.wireframe = this.wireframe; - if ( this.wireframeLinewidth !== undefined ) data.wireframeLinewidth = this.wireframeLinewidth; - if ( this.wireframeLinecap !== undefined ) data.wireframeLinecap = this.wireframeLinecap; - if ( this.wireframeLinejoin !== undefined ) data.wireframeLinejoin = this.wireframeLinejoin; - - if ( this.flatShading !== undefined ) data.flatShading = this.flatShading; - - if ( this.fog !== undefined ) data.fog = this.fog; - - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - - // TODO: Copied from Object3D.toJSON - - function extractFromCache( cache ) { - - const values = []; - - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - if ( isRootObject ) { - - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; - - } - - return data; - - } - - /** - * Deserializes the material from the given JSON. - * - * @param {Object} json - The JSON holding the serialized material. - * @param {Object} textures - A dictionary holding textures referenced by the material. - * @return {Material} A reference to this material. - */ - fromJSON( json, textures ) { - - if ( json.uuid !== undefined ) this.uuid = json.uuid; - if ( json.name !== undefined ) this.name = json.name; - if ( json.color !== undefined && this.color !== undefined ) this.color.setHex( json.color ); - if ( json.roughness !== undefined ) this.roughness = json.roughness; - if ( json.metalness !== undefined ) this.metalness = json.metalness; - if ( json.sheen !== undefined ) this.sheen = json.sheen; - if ( json.sheenColor !== undefined ) this.sheenColor = new Color().setHex( json.sheenColor ); - if ( json.sheenRoughness !== undefined ) this.sheenRoughness = json.sheenRoughness; - if ( json.emissive !== undefined && this.emissive !== undefined ) this.emissive.setHex( json.emissive ); - if ( json.specular !== undefined && this.specular !== undefined ) this.specular.setHex( json.specular ); - if ( json.specularIntensity !== undefined ) this.specularIntensity = json.specularIntensity; - if ( json.specularColor !== undefined && this.specularColor !== undefined ) this.specularColor.setHex( json.specularColor ); - if ( json.shininess !== undefined ) this.shininess = json.shininess; - if ( json.clearcoat !== undefined ) this.clearcoat = json.clearcoat; - if ( json.clearcoatRoughness !== undefined ) this.clearcoatRoughness = json.clearcoatRoughness; - if ( json.dispersion !== undefined ) this.dispersion = json.dispersion; - if ( json.retroreflectivity !== undefined ) this.retroreflectivity = json.retroreflectivity; - if ( json.iridescence !== undefined ) this.iridescence = json.iridescence; - if ( json.iridescenceIOR !== undefined ) this.iridescenceIOR = json.iridescenceIOR; - if ( json.iridescenceThicknessRange !== undefined ) this.iridescenceThicknessRange = json.iridescenceThicknessRange; - if ( json.transmission !== undefined ) this.transmission = json.transmission; - if ( json.thickness !== undefined ) this.thickness = json.thickness; - if ( json.attenuationDistance !== undefined ) this.attenuationDistance = json.attenuationDistance; - if ( json.attenuationColor !== undefined && this.attenuationColor !== undefined ) this.attenuationColor.setHex( json.attenuationColor ); - if ( json.anisotropy !== undefined ) this.anisotropy = json.anisotropy; - if ( json.anisotropyRotation !== undefined ) this.anisotropyRotation = json.anisotropyRotation; - if ( json.fog !== undefined ) this.fog = json.fog; - if ( json.flatShading !== undefined ) this.flatShading = json.flatShading; - if ( json.blending !== undefined ) this.blending = json.blending; - if ( json.combine !== undefined ) this.combine = json.combine; - if ( json.side !== undefined ) this.side = json.side; - if ( json.shadowSide !== undefined ) this.shadowSide = json.shadowSide; - if ( json.opacity !== undefined ) this.opacity = json.opacity; - if ( json.transparent !== undefined ) this.transparent = json.transparent; - if ( json.alphaTest !== undefined ) this.alphaTest = json.alphaTest; - if ( json.alphaHash !== undefined ) this.alphaHash = json.alphaHash; - if ( json.depthFunc !== undefined ) this.depthFunc = json.depthFunc; - if ( json.depthTest !== undefined ) this.depthTest = json.depthTest; - if ( json.depthWrite !== undefined ) this.depthWrite = json.depthWrite; - if ( json.colorWrite !== undefined ) this.colorWrite = json.colorWrite; - if ( json.clippingPlanes !== undefined ) this.clippingPlanes = json.clippingPlanes.map( plane => new Plane().fromJSON( plane ) ); - if ( json.clipIntersection !== undefined ) this.clipIntersection = json.clipIntersection; - if ( json.clipShadows !== undefined ) this.clipShadows = json.clipShadows; - if ( json.depthPacking !== undefined ) this.depthPacking = json.depthPacking; - if ( json.blendSrc !== undefined ) this.blendSrc = json.blendSrc; - if ( json.blendDst !== undefined ) this.blendDst = json.blendDst; - if ( json.blendEquation !== undefined ) this.blendEquation = json.blendEquation; - if ( json.blendSrcAlpha !== undefined ) this.blendSrcAlpha = json.blendSrcAlpha; - if ( json.blendDstAlpha !== undefined ) this.blendDstAlpha = json.blendDstAlpha; - if ( json.blendEquationAlpha !== undefined ) this.blendEquationAlpha = json.blendEquationAlpha; - if ( json.blendColor !== undefined && this.blendColor !== undefined ) this.blendColor.setHex( json.blendColor ); - if ( json.blendAlpha !== undefined ) this.blendAlpha = json.blendAlpha; - if ( json.stencilWriteMask !== undefined ) this.stencilWriteMask = json.stencilWriteMask; - if ( json.stencilFunc !== undefined ) this.stencilFunc = json.stencilFunc; - if ( json.stencilRef !== undefined ) this.stencilRef = json.stencilRef; - if ( json.stencilFuncMask !== undefined ) this.stencilFuncMask = json.stencilFuncMask; - if ( json.stencilFail !== undefined ) this.stencilFail = json.stencilFail; - if ( json.stencilZFail !== undefined ) this.stencilZFail = json.stencilZFail; - if ( json.stencilZPass !== undefined ) this.stencilZPass = json.stencilZPass; - if ( json.stencilWrite !== undefined ) this.stencilWrite = json.stencilWrite; - - if ( json.wireframe !== undefined ) this.wireframe = json.wireframe; - if ( json.wireframeLinewidth !== undefined ) this.wireframeLinewidth = json.wireframeLinewidth; - if ( json.wireframeLinecap !== undefined ) this.wireframeLinecap = json.wireframeLinecap; - if ( json.wireframeLinejoin !== undefined ) this.wireframeLinejoin = json.wireframeLinejoin; - - if ( json.rotation !== undefined ) this.rotation = json.rotation; - - if ( json.linewidth !== undefined ) this.linewidth = json.linewidth; - if ( json.linecap !== undefined ) this.linecap = json.linecap; - if ( json.linejoin !== undefined ) this.linejoin = json.linejoin; - if ( json.dashSize !== undefined ) this.dashSize = json.dashSize; - if ( json.gapSize !== undefined ) this.gapSize = json.gapSize; - if ( json.scale !== undefined ) this.scale = json.scale; - - if ( json.polygonOffset !== undefined ) this.polygonOffset = json.polygonOffset; - if ( json.polygonOffsetFactor !== undefined ) this.polygonOffsetFactor = json.polygonOffsetFactor; - if ( json.polygonOffsetUnits !== undefined ) this.polygonOffsetUnits = json.polygonOffsetUnits; - - if ( json.dithering !== undefined ) this.dithering = json.dithering; - - if ( json.alphaToCoverage !== undefined ) this.alphaToCoverage = json.alphaToCoverage; - if ( json.premultipliedAlpha !== undefined ) this.premultipliedAlpha = json.premultipliedAlpha; - if ( json.forceSinglePass !== undefined ) this.forceSinglePass = json.forceSinglePass; - if ( json.allowOverride !== undefined ) this.allowOverride = json.allowOverride; - - if ( json.visible !== undefined ) this.visible = json.visible; - - if ( json.toneMapped !== undefined ) this.toneMapped = json.toneMapped; - - if ( json.userData !== undefined ) this.userData = json.userData; - - if ( json.vertexColors !== undefined ) { - - if ( typeof json.vertexColors === 'number' ) { - - this.vertexColors = json.vertexColors > 0; - - } else { - - this.vertexColors = json.vertexColors; - - } - - } - - // for PointsMaterial - - if ( json.size !== undefined ) this.size = json.size; - if ( json.sizeAttenuation !== undefined ) this.sizeAttenuation = json.sizeAttenuation; - - // maps - - if ( json.map !== undefined ) this.map = textures[ json.map ] || null; - if ( json.matcap !== undefined ) this.matcap = textures[ json.matcap ] || null; - - if ( json.alphaMap !== undefined ) this.alphaMap = textures[ json.alphaMap ] || null; - - if ( json.bumpMap !== undefined ) this.bumpMap = textures[ json.bumpMap ] || null; - if ( json.bumpScale !== undefined ) this.bumpScale = json.bumpScale; - - if ( json.normalMap !== undefined ) this.normalMap = textures[ json.normalMap ] || null; - if ( json.normalMapType !== undefined ) this.normalMapType = json.normalMapType; - if ( json.normalScale !== undefined ) { - - let normalScale = json.normalScale; - - if ( Array.isArray( normalScale ) === false ) { - - // Blender exporter used to export a scalar. See #7459 - - normalScale = [ normalScale, normalScale ]; - - } - - this.normalScale = new Vector2().fromArray( normalScale ); - - } - - if ( json.displacementMap !== undefined ) this.displacementMap = textures[ json.displacementMap ] || null; - if ( json.displacementScale !== undefined ) this.displacementScale = json.displacementScale; - if ( json.displacementBias !== undefined ) this.displacementBias = json.displacementBias; - - if ( json.roughnessMap !== undefined ) this.roughnessMap = textures[ json.roughnessMap ] || null; - if ( json.metalnessMap !== undefined ) this.metalnessMap = textures[ json.metalnessMap ] || null; - - if ( json.emissiveMap !== undefined ) this.emissiveMap = textures[ json.emissiveMap ] || null; - if ( json.emissiveIntensity !== undefined ) this.emissiveIntensity = json.emissiveIntensity; - - if ( json.specularMap !== undefined ) this.specularMap = textures[ json.specularMap ] || null; - if ( json.specularIntensityMap !== undefined ) this.specularIntensityMap = textures[ json.specularIntensityMap ] || null; - if ( json.specularColorMap !== undefined ) this.specularColorMap = textures[ json.specularColorMap ] || null; - - if ( json.envMap !== undefined ) this.envMap = textures[ json.envMap ] || null; - if ( json.envMapRotation !== undefined ) this.envMapRotation.fromArray( json.envMapRotation ); - if ( json.envMapIntensity !== undefined ) this.envMapIntensity = json.envMapIntensity; - - if ( json.reflectivity !== undefined ) this.reflectivity = json.reflectivity; - if ( json.refractionRatio !== undefined ) this.refractionRatio = json.refractionRatio; - - if ( json.lightMap !== undefined ) this.lightMap = textures[ json.lightMap ] || null; - if ( json.lightMapIntensity !== undefined ) this.lightMapIntensity = json.lightMapIntensity; - - if ( json.aoMap !== undefined ) this.aoMap = textures[ json.aoMap ] || null; - if ( json.aoMapIntensity !== undefined ) this.aoMapIntensity = json.aoMapIntensity; - - if ( json.gradientMap !== undefined ) this.gradientMap = textures[ json.gradientMap ] || null; - - if ( json.clearcoatMap !== undefined ) this.clearcoatMap = textures[ json.clearcoatMap ] || null; - if ( json.clearcoatRoughnessMap !== undefined ) this.clearcoatRoughnessMap = textures[ json.clearcoatRoughnessMap ] || null; - if ( json.clearcoatNormalMap !== undefined ) this.clearcoatNormalMap = textures[ json.clearcoatNormalMap ] || null; - if ( json.clearcoatNormalScale !== undefined ) this.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); - - if ( json.iridescenceMap !== undefined ) this.iridescenceMap = textures[ json.iridescenceMap ] || null; - if ( json.iridescenceThicknessMap !== undefined ) this.iridescenceThicknessMap = textures[ json.iridescenceThicknessMap ] || null; - - if ( json.transmissionMap !== undefined ) this.transmissionMap = textures[ json.transmissionMap ] || null; - if ( json.thicknessMap !== undefined ) this.thicknessMap = textures[ json.thicknessMap ] || null; - - if ( json.anisotropyMap !== undefined ) this.anisotropyMap = textures[ json.anisotropyMap ] || null; - - if ( json.sheenColorMap !== undefined ) this.sheenColorMap = textures[ json.sheenColorMap ] || null; - if ( json.sheenRoughnessMap !== undefined ) this.sheenRoughnessMap = textures[ json.sheenRoughnessMap ] || null; - - return this; - - } - - /** - * Returns a new material with copied values from this instance. - * - * @return {Material} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the values of the given material to this instance. - * - * @param {Material} source - The material to copy. - * @return {Material} A reference to this instance. - */ - copy( source ) { - - this.name = source.name; - - this.blending = source.blending; - this.side = source.side; - this.vertexColors = source.vertexColors; - - this.opacity = source.opacity; - this.transparent = source.transparent; - - this.blendSrc = source.blendSrc; - this.blendDst = source.blendDst; - this.blendEquation = source.blendEquation; - this.blendSrcAlpha = source.blendSrcAlpha; - this.blendDstAlpha = source.blendDstAlpha; - this.blendEquationAlpha = source.blendEquationAlpha; - this.blendColor.copy( source.blendColor ); - this.blendAlpha = source.blendAlpha; - - this.depthFunc = source.depthFunc; - this.depthTest = source.depthTest; - this.depthWrite = source.depthWrite; - - this.stencilWriteMask = source.stencilWriteMask; - this.stencilFunc = source.stencilFunc; - this.stencilRef = source.stencilRef; - this.stencilFuncMask = source.stencilFuncMask; - this.stencilFail = source.stencilFail; - this.stencilZFail = source.stencilZFail; - this.stencilZPass = source.stencilZPass; - this.stencilWrite = source.stencilWrite; - - const srcPlanes = source.clippingPlanes; - let dstPlanes = null; - - if ( srcPlanes !== null ) { - - const n = srcPlanes.length; - dstPlanes = new Array( n ); - - for ( let i = 0; i !== n; ++ i ) { - - dstPlanes[ i ] = srcPlanes[ i ].clone(); - - } - - } - - this.clippingPlanes = dstPlanes; - this.clipIntersection = source.clipIntersection; - this.clipShadows = source.clipShadows; - - this.shadowSide = source.shadowSide; - - this.colorWrite = source.colorWrite; - - this.precision = source.precision; - - this.polygonOffset = source.polygonOffset; - this.polygonOffsetFactor = source.polygonOffsetFactor; - this.polygonOffsetUnits = source.polygonOffsetUnits; - - this.dithering = source.dithering; - - this.alphaTest = source.alphaTest; - this.alphaHash = source.alphaHash; - this.alphaToCoverage = source.alphaToCoverage; - this.premultipliedAlpha = source.premultipliedAlpha; - this.forceSinglePass = source.forceSinglePass; - this.allowOverride = source.allowOverride; - - this.visible = source.visible; - - this.toneMapped = source.toneMapped; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - return this; - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - * - * @fires Material#dispose - */ - dispose() { - - /** - * Fires when the material has been disposed of. - * - * @event Material#dispose - * @type {Object} - */ - this.dispatchEvent( { type: 'dispose' } ); - - } - - /** - * Setting this property to `true` indicates the engine the material - * needs to be recompiled. - * - * @type {boolean} - * @default false - * @param {boolean} value - */ - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - -} - -/** - * Provides utility functions for managing uniforms. - * - * @module UniformsUtils - */ - -/** - * Clones the given uniform definitions by performing a deep-copy. That means - * if the value of a uniform refers to an object like a Vector3 or Texture, - * the cloned uniform will refer to a new object reference. - * - * @param {Object} src - An object representing uniform definitions. - * @return {Object} The cloned uniforms. - */ -function cloneUniforms( src ) { - - const dst = {}; - - for ( const u in src ) { - - dst[ u ] = {}; - - for ( const p in src[ u ] ) { - - const property = src[ u ][ p ]; - - if ( isThreeObject( property ) ) { - - if ( property.isRenderTargetTexture ) { - - warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); - dst[ u ][ p ] = null; - - } else { - - dst[ u ][ p ] = property.clone(); - - } - - } else if ( Array.isArray( property ) ) { - - if ( isThreeObject( property[ 0 ] ) ) { - - const clonedProperty = []; - - for ( let i = 0, l = property.length; i < l; i ++ ) { - - clonedProperty[ i ] = property[ i ].clone(); - - } - - dst[ u ][ p ] = clonedProperty; - - } else { - - dst[ u ][ p ] = property.slice(); - - } - - } else { - - dst[ u ][ p ] = property; - - } - - } - - } - - return dst; - -} - -/** - * Merges the given uniform definitions into a single object. Since the - * method internally uses cloneUniforms(), it performs a deep-copy when - * producing the merged uniform definitions. - * - * @param {Array} uniforms - An array of objects containing uniform definitions. - * @return {Object} The merged uniforms. - */ -function mergeUniforms( uniforms ) { - - const merged = {}; - - for ( let u = 0; u < uniforms.length; u ++ ) { - - const tmp = cloneUniforms( uniforms[ u ] ); - - for ( const p in tmp ) { - - merged[ p ] = tmp[ p ]; - - } - - } - - return merged; - -} - -function isThreeObject( property ) { - - return ( property && ( property.isColor || - property.isMatrix3 || property.isMatrix4 || - property.isVector2 || property.isVector3 || property.isVector4 || - property.isTexture || property.isQuaternion ) ); - -} - -function cloneUniformsGroups( src ) { - - const dst = []; - - for ( let u = 0; u < src.length; u ++ ) { - - dst.push( src[ u ].clone() ); - - } - - return dst; - -} - -function getUnlitUniformColorSpace( renderer ) { - - const currentRenderTarget = renderer.getRenderTarget(); - - if ( currentRenderTarget === null ) { - - // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 - return renderer.outputColorSpace; - - } - - // https://github.com/mrdoob/three.js/issues/27868 - if ( currentRenderTarget.isXRRenderTarget === true ) { - - return currentRenderTarget.texture.colorSpace; - - } - - return ColorManagement.workingColorSpace; - -} - -// Legacy - -const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; - -var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; - -var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; - -/** - * A material rendered with custom shaders. A shader is a small program written in GLSL. - * that runs on the GPU. You may want to use a custom shader if you need to implement an - * effect not included with any of the built-in materials. - * - * There are the following notes to bear in mind when using a `ShaderMaterial`: - * - * - `ShaderMaterial` can only be used with {@link WebGLRenderer}. - * - Built in attributes and uniforms are passed to the shaders along with your code. If - * you don't want that, use {@link RawShaderMaterial} instead. - * - You can use the directive `#pragma unroll_loop_start` and `#pragma unroll_loop_end` - * in order to unroll a `for` loop in GLSL by the shader preprocessor. The directive has - * to be placed right above the loop. The loop formatting has to correspond to a defined standard. - * - The loop has to be [normalized](https://en.wikipedia.org/wiki/Normalized_loop). - * - The loop variable has to be *i*. - * - The value `UNROLLED_LOOP_INDEX` will be replaced with the explicitly - * value of *i* for the given iteration and can be used in preprocessor - * statements. - * - * ```js - * const material = new THREE.ShaderMaterial( { - * uniforms: { - * time: { value: 1.0 }, - * resolution: { value: new THREE.Vector2() } - * }, - * vertexShader: document.getElementById( 'vertexShader' ).textContent, - * fragmentShader: document.getElementById( 'fragmentShader' ).textContent - * } ); - * ``` - * - * @augments Material - */ -class ShaderMaterial extends Material { - - /** - * Constructs a new shader material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isShaderMaterial = true; - - this.type = 'ShaderMaterial'; - - /** - * Defines custom constants using `#define` directives within the GLSL code - * for both the vertex shader and the fragment shader; each key/value pair - * yields another directive. - * ```js - * defines: { - * FOO: 15, - * BAR: true - * } - * ``` - * Yields the lines: - * ``` - * #define FOO 15 - * #define BAR true - * ``` - * - * @type {Object} - */ - this.defines = {}; - - /** - * An object of the form: - * ```js - * { - * "uniform1": { value: 1.0 }, - * "uniform2": { value: 2 } - * } - * ``` - * specifying the uniforms to be passed to the shader code; keys are uniform - * names, values are definitions of the form - * ``` - * { - * value: 1.0 - * } - * ``` - * where `value` is the value of the uniform. Names must match the name of - * the uniform, as defined in the GLSL code. Note that uniforms are refreshed - * on every frame, so updating the value of the uniform will immediately - * update the value available to the GLSL code. - * - * @type {Object} - */ - this.uniforms = {}; - - /** - * An array holding uniforms groups for configuring UBOs. - * - * @type {Array} - */ - this.uniformsGroups = []; - - /** - * Vertex shader GLSL code. This is the actual code for the shader. - * - * @type {string} - */ - this.vertexShader = default_vertex; - - /** - * Fragment shader GLSL code. This is the actual code for the shader. - * - * @type {string} - */ - this.fragmentShader = default_fragment; - - /** - * Controls line thickness or lines. - * - * WebGL and WebGPU ignore this setting and always render line primitives with a - * width of one pixel. - * - * @type {number} - * @default 1 - */ - this.linewidth = 1; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * WebGL and WebGPU ignore this property and always render - * 1 pixel wide lines. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Defines whether the material color is affected by global fog settings; `true` - * to pass fog uniforms to the shader. - * - * Setting this property to `true` requires the definition of fog uniforms. It is - * recommended to use `UniformsUtils.merge()` to combine the custom shader uniforms - * with predefined fog uniforms. - * - * ```js - * const material = new ShaderMaterial( { - * uniforms: UniformsUtils.merge( [ UniformsLib[ 'fog' ], shaderUniforms ] ); - * vertexShader: vertexShader, - * fragmentShader: fragmentShader, - * fog: true - * } ); - * ``` - * - * @type {boolean} - * @default false - */ - this.fog = false; - - /** - * Defines whether this material uses lighting; `true` to pass uniform data - * related to lighting to this shader. - * - * @type {boolean} - * @default false - */ - this.lights = false; - - /** - * Defines whether this material supports clipping; `true` to let the renderer - * pass the clippingPlanes uniform. - * - * @type {boolean} - * @default false - */ - this.clipping = false; - - /** - * Overwritten and set to `true` by default. - * - * @type {boolean} - * @default true - */ - this.forceSinglePass = true; - - /** - * This object allows to enable certain WebGL 2 extensions. - * - * - clipCullDistance: set to `true` to use vertex shader clipping - * - multiDraw: set to `true` to use vertex shader multi_draw / enable gl_DrawID - * - * @type {{clipCullDistance:false,multiDraw:false}} - */ - this.extensions = { - clipCullDistance: false, // set to use vertex shader clipping - multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID - }; - - /** - * When the rendered geometry doesn't include these attributes but the - * material does, these default values will be passed to the shaders. This - * avoids errors when buffer data is missing. - * - * - color: [ 1, 1, 1 ] - * - uv: [ 0, 0 ] - * - uv1: [ 0, 0 ] - * - * @type {Object} - */ - this.defaultAttributeValues = { - 'color': [ 1, 1, 1 ], - 'uv': [ 0, 0 ], - 'uv1': [ 0, 0 ] - }; - - /** - * If set, this calls [gl.bindAttribLocation](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindAttribLocation) - * to bind a generic vertex index to an attribute variable. - * - * @type {string|undefined} - * @default undefined - */ - this.index0AttributeName = undefined; - - /** - * Can be used to force a uniform update while changing uniforms in - * {@link Object3D#onBeforeRender}. - * - * @type {boolean} - * @default false - */ - this.uniformsNeedUpdate = false; - - /** - * Defines the GLSL version of custom shader code. - * - * @type {?(GLSL1|GLSL3)} - * @default null - */ - this.glslVersion = null; - - if ( parameters !== undefined ) { - - this.setValues( parameters ); - - } - - } - - copy( source ) { - - super.copy( source ); - - this.fragmentShader = source.fragmentShader; - this.vertexShader = source.vertexShader; - - this.uniforms = cloneUniforms( source.uniforms ); - this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); - - this.defines = Object.assign( {}, source.defines ); - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.fog = source.fog; - this.lights = source.lights; - this.clipping = source.clipping; - - this.extensions = Object.assign( {}, source.extensions ); - - this.glslVersion = source.glslVersion; - - this.defaultAttributeValues = Object.assign( {}, source.defaultAttributeValues ); - - this.index0AttributeName = source.index0AttributeName; - - this.uniformsNeedUpdate = source.uniformsNeedUpdate; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.glslVersion = this.glslVersion; - data.uniforms = {}; - - for ( const name in this.uniforms ) { - - const uniform = this.uniforms[ name ]; - const value = uniform.value; - - if ( value && value.isTexture ) { - - data.uniforms[ name ] = { - type: 't', - value: value.toJSON( meta ).uuid - }; - - } else if ( value && value.isColor ) { - - data.uniforms[ name ] = { - type: 'c', - value: value.getHex() - }; - - } else if ( value && value.isVector2 ) { - - data.uniforms[ name ] = { - type: 'v2', - value: value.toArray() - }; - - } else if ( value && value.isVector3 ) { - - data.uniforms[ name ] = { - type: 'v3', - value: value.toArray() - }; - - } else if ( value && value.isVector4 ) { - - data.uniforms[ name ] = { - type: 'v4', - value: value.toArray() - }; - - } else if ( value && value.isMatrix3 ) { - - data.uniforms[ name ] = { - type: 'm3', - value: value.toArray() - }; - - } else if ( value && value.isMatrix4 ) { - - data.uniforms[ name ] = { - type: 'm4', - value: value.toArray() - }; - - } else { - - data.uniforms[ name ] = { - value: value - }; - - // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far - - } - - } - - if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; - - data.vertexShader = this.vertexShader; - data.fragmentShader = this.fragmentShader; - - data.lights = this.lights; - data.clipping = this.clipping; - - const extensions = {}; - - for ( const key in this.extensions ) { - - if ( this.extensions[ key ] === true ) extensions[ key ] = true; - - } - - if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; - - return data; - - } - - /** - * Deserializes the material from the given JSON. - * - * @param {Object} json - The JSON holding the serialized material. - * @param {Object} textures - A dictionary holding textures referenced by the material. - * @return {ShaderMaterial} A reference to this material. - */ - fromJSON( json, textures ) { - - super.fromJSON( json, textures ); - - if ( json.uniforms !== undefined ) { - - for ( const name in json.uniforms ) { - - const uniform = json.uniforms[ name ]; - - this.uniforms[ name ] = {}; - - switch ( uniform.type ) { - - case 't': - this.uniforms[ name ].value = textures[ uniform.value ] || null; - break; - - case 'c': - this.uniforms[ name ].value = new Color().setHex( uniform.value ); - break; - - case 'v2': - this.uniforms[ name ].value = new Vector2().fromArray( uniform.value ); - break; - - case 'v3': - this.uniforms[ name ].value = new Vector3().fromArray( uniform.value ); - break; - - case 'v4': - this.uniforms[ name ].value = new Vector4().fromArray( uniform.value ); - break; - - case 'm3': - this.uniforms[ name ].value = new Matrix3().fromArray( uniform.value ); - break; - - case 'm4': - this.uniforms[ name ].value = new Matrix4().fromArray( uniform.value ); - break; - - default: - this.uniforms[ name ].value = uniform.value; - - } - - } - - } - - if ( json.defines !== undefined ) this.defines = json.defines; - if ( json.vertexShader !== undefined ) this.vertexShader = json.vertexShader; - if ( json.fragmentShader !== undefined ) this.fragmentShader = json.fragmentShader; - if ( json.glslVersion !== undefined ) this.glslVersion = json.glslVersion; - - if ( json.extensions !== undefined ) { - - for ( const key in json.extensions ) { - - this.extensions[ key ] = json.extensions[ key ]; - - } - - } - - if ( json.lights !== undefined ) this.lights = json.lights; - if ( json.clipping !== undefined ) this.clipping = json.clipping; - - return this; - - } - -} - -const _vector$3 = /*@__PURE__*/ new Vector3(); -const _segCenter = /*@__PURE__*/ new Vector3(); -const _segDir = /*@__PURE__*/ new Vector3(); -const _diff = /*@__PURE__*/ new Vector3(); - -/** - * A ray that emits from an origin in a certain direction. The class is used by - * {@link Raycaster} to assist with raycasting. Raycasting is used for - * mouse picking (working out what objects in the 3D space the mouse is over) - * amongst other things. - */ -class Ray { - - /** - * Constructs a new ray. - * - * @param {Vector3} [origin=(0,0,0)] - The origin of the ray. - * @param {Vector3} [direction=(0,0,-1)] - The (normalized) direction of the ray. - */ - constructor( origin = new Vector3(), direction = new Vector3( 0, 0, -1 ) ) { - - /** - * The origin of the ray. - * - * @type {Vector3} - */ - this.origin = origin; - - /** - * The (normalized) direction of the ray. - * - * @type {Vector3} - */ - this.direction = direction; - - } - - /** - * Sets the ray's components by copying the given values. - * - * @param {Vector3} origin - The origin. - * @param {Vector3} direction - The direction. - * @return {Ray} A reference to this ray. - */ - set( origin, direction ) { - - this.origin.copy( origin ); - this.direction.copy( direction ); - - return this; - - } - - /** - * Copies the values of the given ray to this instance. - * - * @param {Ray} ray - The ray to copy. - * @return {Ray} A reference to this ray. - */ - copy( ray ) { - - this.origin.copy( ray.origin ); - this.direction.copy( ray.direction ); - - return this; - - } - - /** - * Returns a vector that is located at a given distance along this ray. - * - * @param {number} t - The distance along the ray to retrieve a position for. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} A position on the ray. - */ - at( t, target ) { - - return target.copy( this.origin ).addScaledVector( this.direction, t ); - - } - - /** - * Adjusts the direction of the ray to point at the given vector in world space. - * - * @param {Vector3} v - The target position. - * @return {Ray} A reference to this ray. - */ - lookAt( v ) { - - this.direction.copy( v ).sub( this.origin ).normalize(); - - return this; - - } - - /** - * Shift the origin of this ray along its direction by the given distance. - * - * @param {number} t - The distance along the ray to interpolate. - * @return {Ray} A reference to this ray. - */ - recast( t ) { - - this.origin.copy( this.at( t, _vector$3 ) ); - - return this; - - } - - /** - * Returns the point along this ray that is closest to the given point. - * - * @param {Vector3} point - A point in 3D space to get the closet location on the ray for. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The closest point on this ray. - */ - closestPointToPoint( point, target ) { - - target.subVectors( point, this.origin ); - - const directionDistance = target.dot( this.direction ); - - if ( directionDistance < 0 ) { - - return target.copy( this.origin ); - - } - - return target.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - - } - - /** - * Returns the distance of the closest approach between this ray and the given point. - * - * @param {Vector3} point - A point in 3D space to compute the distance to. - * @return {number} The distance. - */ - distanceToPoint( point ) { - - return Math.sqrt( this.distanceSqToPoint( point ) ); - - } - - /** - * Returns the squared distance of the closest approach between this ray and the given point. - * - * @param {Vector3} point - A point in 3D space to compute the distance to. - * @return {number} The squared distance. - */ - distanceSqToPoint( point ) { - - const directionDistance = _vector$3.subVectors( point, this.origin ).dot( this.direction ); - - // point behind the ray - - if ( directionDistance < 0 ) { - - return this.origin.distanceToSquared( point ); - - } - - _vector$3.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - - return _vector$3.distanceToSquared( point ); - - } - - /** - * Returns the squared distance between this ray and the given line segment. - * - * @param {Vector3} v0 - The start point of the line segment. - * @param {Vector3} v1 - The end point of the line segment. - * @param {Vector3} [optionalPointOnRay] - When provided, it receives the point on this ray that is closest to the segment. - * @param {Vector3} [optionalPointOnSegment] - When provided, it receives the point on the line segment that is closest to this ray. - * @return {number} The squared distance. - */ - distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { - - // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h - // It returns the min distance between the ray and the segment - // defined by v0 and v1 - // It can also set two optional targets : - // - The closest point on the ray - // - The closest point on the segment - - _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); - _segDir.copy( v1 ).sub( v0 ).normalize(); - _diff.copy( this.origin ).sub( _segCenter ); - - const segExtent = v0.distanceTo( v1 ) * 0.5; - const a01 = - this.direction.dot( _segDir ); - const b0 = _diff.dot( this.direction ); - const b1 = - _diff.dot( _segDir ); - const c = _diff.lengthSq(); - const det = Math.abs( 1 - a01 * a01 ); - let s0, s1, sqrDist, extDet; - - if ( det > 0 ) { - - // The ray and segment are not parallel. - - s0 = a01 * b1 - b0; - s1 = a01 * b0 - b1; - extDet = segExtent * det; - - if ( s0 >= 0 ) { - - if ( s1 >= - extDet ) { - - if ( s1 <= extDet ) { - - // region 0 - // Minimum at interior points of ray and segment. - - const invDet = 1 / det; - s0 *= invDet; - s1 *= invDet; - sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; - - } else { - - // region 1 - - s1 = segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - // region 5 - - s1 = - segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - if ( s1 <= - extDet ) { - - // region 4 - - s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } else if ( s1 <= extDet ) { - - // region 3 - - s0 = 0; - s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = s1 * ( s1 + 2 * b1 ) + c; - - } else { - - // region 2 - - s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } - - } else { - - // Ray and segment are parallel. - - s1 = ( a01 > 0 ) ? - segExtent : segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - if ( optionalPointOnRay ) { - - optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 ); - - } - - if ( optionalPointOnSegment ) { - - optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 ); - - } - - return sqrDist; - - } - - /** - * Intersects this ray with the given sphere, returning the intersection - * point or `null` if there is no intersection. - * - * @param {Sphere} sphere - The sphere to intersect. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The intersection point. - */ - intersectSphere( sphere, target ) { - - if ( sphere.radius < 0 ) return null; // handle empty spheres, see #31187 - - _vector$3.subVectors( sphere.center, this.origin ); - const tca = _vector$3.dot( this.direction ); - const d2 = _vector$3.dot( _vector$3 ) - tca * tca; - const radius2 = sphere.radius * sphere.radius; - - if ( d2 > radius2 ) return null; - - const thc = Math.sqrt( radius2 - d2 ); - - // t0 = first intersect point - entrance on front of sphere - const t0 = tca - thc; - - // t1 = second intersect point - exit point on back of sphere - const t1 = tca + thc; - - // test to see if t1 is behind the ray - if so, return null - if ( t1 < 0 ) return null; - - // test to see if t0 is behind the ray: - // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, - // in order to always return an intersect point that is in front of the ray. - if ( t0 < 0 ) return this.at( t1, target ); - - // else t0 is in front of the ray, so return the first collision point scaled by t0 - return this.at( t0, target ); - - } - - /** - * Returns `true` if this ray intersects with the given sphere. - * - * @param {Sphere} sphere - The sphere to intersect. - * @return {boolean} Whether this ray intersects with the given sphere or not. - */ - intersectsSphere( sphere ) { - - if ( sphere.radius < 0 ) return false; // handle empty spheres, see #31187 - - return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); - - } - - /** - * Computes the distance from the ray's origin to the given plane. Returns `null` if the ray - * does not intersect with the plane. - * - * @param {Plane} plane - The plane to compute the distance to. - * @return {?number} Whether this ray intersects with the given sphere or not. - */ - distanceToPlane( plane ) { - - const denominator = plane.normal.dot( this.direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( plane.distanceToPoint( this.origin ) === 0 ) { - - return 0; - - } - - // Null is preferable to undefined since undefined means.... it is undefined - - return null; - - } - - const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; - - // Return if the ray never intersects the plane - - return t >= 0 ? t : null; - - } - - /** - * Intersects this ray with the given plane, returning the intersection - * point or `null` if there is no intersection. - * - * @param {Plane} plane - The plane to intersect. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The intersection point. - */ - intersectPlane( plane, target ) { - - const t = this.distanceToPlane( plane ); - - if ( t === null ) { - - return null; - - } - - return this.at( t, target ); - - } - - /** - * Returns `true` if this ray intersects with the given plane. - * - * @param {Plane} plane - The plane to intersect. - * @return {boolean} Whether this ray intersects with the given plane or not. - */ - intersectsPlane( plane ) { - - // check if the ray lies on the plane first - - const distToPoint = plane.distanceToPoint( this.origin ); - - if ( distToPoint === 0 ) { - - return true; - - } - - const denominator = plane.normal.dot( this.direction ); - - if ( denominator * distToPoint < 0 ) { - - return true; - - } - - // ray origin is behind the plane (and is pointing behind it) - - return false; - - } - - /** - * Intersects this ray with the given bounding box, returning the intersection - * point or `null` if there is no intersection. - * - * @param {Box3} box - The box to intersect. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The intersection point. - */ - intersectBox( box, target ) { - - let tmin, tmax, tymin, tymax, tzmin, tzmax; - - const invdirx = 1 / this.direction.x, - invdiry = 1 / this.direction.y, - invdirz = 1 / this.direction.z; - - const origin = this.origin; - - if ( invdirx >= 0 ) { - - tmin = ( box.min.x - origin.x ) * invdirx; - tmax = ( box.max.x - origin.x ) * invdirx; - - } else { - - tmin = ( box.max.x - origin.x ) * invdirx; - tmax = ( box.min.x - origin.x ) * invdirx; - - } - - if ( invdiry >= 0 ) { - - tymin = ( box.min.y - origin.y ) * invdiry; - tymax = ( box.max.y - origin.y ) * invdiry; - - } else { - - tymin = ( box.max.y - origin.y ) * invdiry; - tymax = ( box.min.y - origin.y ) * invdiry; - - } - - if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; - - if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin; - - if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax; - - if ( invdirz >= 0 ) { - - tzmin = ( box.min.z - origin.z ) * invdirz; - tzmax = ( box.max.z - origin.z ) * invdirz; - - } else { - - tzmin = ( box.max.z - origin.z ) * invdirz; - tzmax = ( box.min.z - origin.z ) * invdirz; - - } - - if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; - - if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; - - if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; - - //return point closest to the ray (positive side) - - if ( tmax < 0 ) return null; - - return this.at( tmin >= 0 ? tmin : tmax, target ); - - } - - /** - * Returns `true` if this ray intersects with the given box. - * - * @param {Box3} box - The box to intersect. - * @return {boolean} Whether this ray intersects with the given box or not. - */ - intersectsBox( box ) { - - return this.intersectBox( box, _vector$3 ) !== null; - - } - - /** - * Intersects this ray with the given triangle, returning the intersection - * point or `null` if there is no intersection. - * - * @param {Vector3} a - The first vertex of the triangle. - * @param {Vector3} b - The second vertex of the triangle. - * @param {Vector3} c - The third vertex of the triangle. - * @param {boolean} backfaceCulling - Whether to use backface culling or not. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The intersection point. - */ - intersectTriangle( a, b, c, backfaceCulling, target ) { - - // Watertight ray/triangle intersection. Reference: Woop, Benthin, Wald, - // "Watertight Ray/Triangle Intersection", JCGT vol. 2 no. 1 (2013), Appendix A. - // https://jcgt.org/published/0002/01/05/ - - const origin = this.origin; - const direction = this.direction; - - const dx = direction.x; - const dy = direction.y; - const dz = direction.z; - - // triangle vertices relative to the ray origin - - const aox = a.x - origin.x, aoy = a.y - origin.y, aoz = a.z - origin.z; - const box = b.x - origin.x, boy = b.y - origin.y, boz = b.z - origin.z; - const cox = c.x - origin.x, coy = c.y - origin.y, coz = c.z - origin.z; - - // Use the dimension where the ray direction is maximal as the projection - // axis (kz) and read every component already permuted into (kx, ky, kz). - // kx and ky are swapped when the direction's kz component is negative, to - // preserve the winding order of triangles. - - const adx = Math.abs( dx ), ady = Math.abs( dy ), adz = Math.abs( dz ); - - let dkx, dky, dkz; - let akx, aky, akz, bkx, bky, bkz, ckx, cky, ckz; - - if ( adx >= ady && adx >= adz ) { - - dkz = dx; akz = aox; bkz = box; ckz = cox; - - if ( dx >= 0 ) { - - dkx = dy; dky = dz; - akx = aoy; aky = aoz; bkx = boy; bky = boz; ckx = coy; cky = coz; - - } else { - - dkx = dz; dky = dy; - akx = aoz; aky = aoy; bkx = boz; bky = boy; ckx = coz; cky = coy; - - } - - } else if ( ady >= adz ) { - - dkz = dy; akz = aoy; bkz = boy; ckz = coy; - - if ( dy >= 0 ) { - - dkx = dz; dky = dx; - akx = aoz; aky = aox; bkx = boz; bky = box; ckx = coz; cky = cox; - - } else { - - dkx = dx; dky = dz; - akx = aox; aky = aoz; bkx = box; bky = boz; ckx = cox; cky = coz; - - } - - } else { - - dkz = dz; akz = aoz; bkz = boz; ckz = coz; - - if ( dz >= 0 ) { - - dkx = dx; dky = dy; - akx = aox; aky = aoy; bkx = box; bky = boy; ckx = cox; cky = coy; - - } else { - - dkx = dy; dky = dx; - akx = aoy; aky = aox; bkx = boy; bky = box; ckx = coy; cky = cox; - - } - - } - - // a zero direction has no maximal axis and cannot intersect - - if ( dkz === 0 ) return null; - - // shear constants that align the ray with the +kz axis - - const sx = dkx / dkz, sy = dky / dkz, sz = 1 / dkz; - - // sheared and scaled vertices - - const ax = akx - sx * akz, ay = aky - sy * akz; - const bx = bkx - sx * bkz, by = bky - sy * bkz; - const cx = ckx - sx * ckz, cy = cky - sy * ckz; - - // scaled barycentric coordinates (signed edge functions); the shear makes a - // shared edge evaluate identically for both adjacent triangles, so the ray - // can never fall between them - - const u = cx * by - cy * bx; - const v = ax * cy - ay * cx; - const w = bx * ay - by * ax; - - if ( backfaceCulling ) { - - if ( u < 0 || v < 0 || w < 0 ) return null; - - } else { - - if ( ( u < 0 || v < 0 || w < 0 ) && ( u > 0 || v > 0 || w > 0 ) ) return null; - - } - - const det = u + v + w; - - // ray is co-planar with the triangle - - if ( det === 0 ) return null; - - // scaled hit distance; t = tScaled / det must lie in front of the origin - - const tScaled = sz * ( u * akz + v * bkz + w * ckz ); - - if ( det > 0 ? tScaled < 0 : tScaled > 0 ) return null; - - return this.at( tScaled / det, target ); - - } - - /** - * Transforms this ray with the given 4x4 transformation matrix. - * - * @param {Matrix4} matrix4 - The transformation matrix. - * @return {Ray} A reference to this ray. - */ - applyMatrix4( matrix4 ) { - - this.origin.applyMatrix4( matrix4 ); - this.direction.transformDirection( matrix4 ); - - return this; - - } - - /** - * Returns `true` if this ray is equal with the given one. - * - * @param {Ray} ray - The ray to test for equality. - * @return {boolean} Whether this ray is equal with the given one. - */ - equals( ray ) { - - return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); - - } - - /** - * Returns a new ray with copied values from this instance. - * - * @return {Ray} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - -} - -const _v0$2 = /*@__PURE__*/ new Vector3(); -const _v1$2 = /*@__PURE__*/ new Vector3(); -const _v2$1 = /*@__PURE__*/ new Vector3(); -const _v3$1 = /*@__PURE__*/ new Vector3(); - -const _vab = /*@__PURE__*/ new Vector3(); -const _vac = /*@__PURE__*/ new Vector3(); -const _vbc = /*@__PURE__*/ new Vector3(); -const _vap = /*@__PURE__*/ new Vector3(); -const _vbp = /*@__PURE__*/ new Vector3(); -const _vcp = /*@__PURE__*/ new Vector3(); - -const _v40 = /*@__PURE__*/ new Vector4(); -const _v41 = /*@__PURE__*/ new Vector4(); -const _v42 = /*@__PURE__*/ new Vector4(); - -/** - * A geometric triangle as defined by three vectors representing its three corners. - */ -class Triangle { - - /** - * Constructs a new triangle. - * - * @param {Vector3} [a=(0,0,0)] - The first corner of the triangle. - * @param {Vector3} [b=(0,0,0)] - The second corner of the triangle. - * @param {Vector3} [c=(0,0,0)] - The third corner of the triangle. - */ - constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { - - /** - * The first corner of the triangle. - * - * @type {Vector3} - */ - this.a = a; - - /** - * The second corner of the triangle. - * - * @type {Vector3} - */ - this.b = b; - - /** - * The third corner of the triangle. - * - * @type {Vector3} - */ - this.c = c; - - } - - /** - * Computes the normal vector of a triangle. - * - * @param {Vector3} a - The first corner of the triangle. - * @param {Vector3} b - The second corner of the triangle. - * @param {Vector3} c - The third corner of the triangle. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The triangle's normal. - */ - static getNormal( a, b, c, target ) { - - target.subVectors( c, b ); - _v0$2.subVectors( a, b ); - target.cross( _v0$2 ); - - const targetLengthSq = target.lengthSq(); - if ( targetLengthSq > 0 ) { - - return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); - - } - - return target.set( 0, 0, 0 ); - - } - - /** - * Computes a barycentric coordinates from the given vector. - * Returns `null` if the triangle is degenerate. - * - * @param {Vector3} point - A point in 3D space. - * @param {Vector3} a - The first corner of the triangle. - * @param {Vector3} b - The second corner of the triangle. - * @param {Vector3} c - The third corner of the triangle. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The barycentric coordinates for the given point - */ - static getBarycoord( point, a, b, c, target ) { - - _v0$2.subVectors( c, a ); - _v1$2.subVectors( b, a ); - _v2$1.subVectors( point, a ); - - const dot00 = _v0$2.dot( _v0$2 ); - const dot01 = _v0$2.dot( _v1$2 ); - const dot02 = _v0$2.dot( _v2$1 ); - const dot11 = _v1$2.dot( _v1$2 ); - const dot12 = _v1$2.dot( _v2$1 ); - - const denom = ( dot00 * dot11 - dot01 * dot01 ); - - // collinear or singular triangle - if ( denom === 0 ) { - - target.set( 0, 0, 0 ); - return null; - - } - - const invDenom = 1 / denom; - const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; - const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; - - // barycentric coordinates must always sum to 1 - return target.set( 1 - u - v, v, u ); - - } - - /** - * Returns `true` if the given point, when projected onto the plane of the - * triangle, lies within the triangle. - * - * @param {Vector3} point - The point in 3D space to test. - * @param {Vector3} a - The first corner of the triangle. - * @param {Vector3} b - The second corner of the triangle. - * @param {Vector3} c - The third corner of the triangle. - * @return {boolean} Whether the given point, when projected onto the plane of the - * triangle, lies within the triangle or not. - */ - static containsPoint( point, a, b, c ) { - - // if the triangle is degenerate then we can't contain a point - if ( this.getBarycoord( point, a, b, c, _v3$1 ) === null ) { - - return false; - - } - - return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 ); - - } - - /** - * Computes the value barycentrically interpolated for the given point on the - * triangle. Returns `null` if the triangle is degenerate. - * - * @param {Vector3} point - Position of interpolated point. - * @param {Vector3} p1 - The first corner of the triangle. - * @param {Vector3} p2 - The second corner of the triangle. - * @param {Vector3} p3 - The third corner of the triangle. - * @param {Vector3} v1 - Value to interpolate of first vertex. - * @param {Vector3} v2 - Value to interpolate of second vertex. - * @param {Vector3} v3 - Value to interpolate of third vertex. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The interpolated value. - */ - static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { - - if ( this.getBarycoord( point, p1, p2, p3, _v3$1 ) === null ) { - - target.x = 0; - target.y = 0; - if ( 'z' in target ) target.z = 0; - if ( 'w' in target ) target.w = 0; - return null; - - } - - target.setScalar( 0 ); - target.addScaledVector( v1, _v3$1.x ); - target.addScaledVector( v2, _v3$1.y ); - target.addScaledVector( v3, _v3$1.z ); - - return target; - - } - - /** - * Computes the value barycentrically interpolated for the given attribute and indices. - * - * @param {BufferAttribute} attr - The attribute to interpolate. - * @param {number} i1 - Index of first vertex. - * @param {number} i2 - Index of second vertex. - * @param {number} i3 - Index of third vertex. - * @param {Vector3} barycoord - The barycoordinate value to use to interpolate. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The interpolated attribute value. - */ - static getInterpolatedAttribute( attr, i1, i2, i3, barycoord, target ) { - - _v40.setScalar( 0 ); - _v41.setScalar( 0 ); - _v42.setScalar( 0 ); - - _v40.fromBufferAttribute( attr, i1 ); - _v41.fromBufferAttribute( attr, i2 ); - _v42.fromBufferAttribute( attr, i3 ); - - target.setScalar( 0 ); - target.addScaledVector( _v40, barycoord.x ); - target.addScaledVector( _v41, barycoord.y ); - target.addScaledVector( _v42, barycoord.z ); - - return target; - - } - - /** - * Returns `true` if the triangle is oriented towards the given direction. - * - * @param {Vector3} a - The first corner of the triangle. - * @param {Vector3} b - The second corner of the triangle. - * @param {Vector3} c - The third corner of the triangle. - * @param {Vector3} direction - The (normalized) direction vector. - * @return {boolean} Whether the triangle is oriented towards the given direction or not. - */ - static isFrontFacing( a, b, c, direction ) { - - _v0$2.subVectors( c, b ); - _v1$2.subVectors( a, b ); - - // strictly front facing - return _v0$2.cross( _v1$2 ).dot( direction ) < 0; - - } - - /** - * Sets the triangle's vertices by copying the given values. - * - * @param {Vector3} a - The first corner of the triangle. - * @param {Vector3} b - The second corner of the triangle. - * @param {Vector3} c - The third corner of the triangle. - * @return {Triangle} A reference to this triangle. - */ - set( a, b, c ) { - - this.a.copy( a ); - this.b.copy( b ); - this.c.copy( c ); - - return this; - - } - - /** - * Sets the triangle's vertices by copying the given array values. - * - * @param {Array} points - An array with 3D points. - * @param {number} i0 - The array index representing the first corner of the triangle. - * @param {number} i1 - The array index representing the second corner of the triangle. - * @param {number} i2 - The array index representing the third corner of the triangle. - * @return {Triangle} A reference to this triangle. - */ - setFromPointsAndIndices( points, i0, i1, i2 ) { - - this.a.copy( points[ i0 ] ); - this.b.copy( points[ i1 ] ); - this.c.copy( points[ i2 ] ); - - return this; - - } - - /** - * Sets the triangle's vertices by copying the given attribute values. - * - * @param {BufferAttribute} attribute - A buffer attribute with 3D points data. - * @param {number} i0 - The attribute index representing the first corner of the triangle. - * @param {number} i1 - The attribute index representing the second corner of the triangle. - * @param {number} i2 - The attribute index representing the third corner of the triangle. - * @return {Triangle} A reference to this triangle. - */ - setFromAttributeAndIndices( attribute, i0, i1, i2 ) { - - this.a.fromBufferAttribute( attribute, i0 ); - this.b.fromBufferAttribute( attribute, i1 ); - this.c.fromBufferAttribute( attribute, i2 ); - - return this; - - } - - /** - * Returns a new triangle with copied values from this instance. - * - * @return {Triangle} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the values of the given triangle to this instance. - * - * @param {Triangle} triangle - The triangle to copy. - * @return {Triangle} A reference to this triangle. - */ - copy( triangle ) { - - this.a.copy( triangle.a ); - this.b.copy( triangle.b ); - this.c.copy( triangle.c ); - - return this; - - } - - /** - * Computes the area of the triangle. - * - * @return {number} The triangle's area. - */ - getArea() { - - _v0$2.subVectors( this.c, this.b ); - _v1$2.subVectors( this.a, this.b ); - - return _v0$2.cross( _v1$2 ).length() * 0.5; - - } - - /** - * Computes the midpoint of the triangle. - * - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The triangle's midpoint. - */ - getMidpoint( target ) { - - return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); - - } - - /** - * Computes the normal of the triangle. - * - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The triangle's normal. - */ - getNormal( target ) { - - return Triangle.getNormal( this.a, this.b, this.c, target ); - - } - - /** - * Computes a plane the triangle lies within. - * - * @param {Plane} target - The target vector that is used to store the method's result. - * @return {Plane} The plane the triangle lies within. - */ - getPlane( target ) { - - return target.setFromCoplanarPoints( this.a, this.b, this.c ); - - } - - /** - * Computes a barycentric coordinates from the given vector. - * Returns `null` if the triangle is degenerate. - * - * @param {Vector3} point - A point in 3D space. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The barycentric coordinates for the given point - */ - getBarycoord( point, target ) { - - return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); - - } - - /** - * Computes the value barycentrically interpolated for the given point on the - * triangle. Returns `null` if the triangle is degenerate. - * - * @param {Vector3} point - Position of interpolated point. - * @param {Vector3} v1 - Value to interpolate of first vertex. - * @param {Vector3} v2 - Value to interpolate of second vertex. - * @param {Vector3} v3 - Value to interpolate of third vertex. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {?Vector3} The interpolated value. - */ - getInterpolation( point, v1, v2, v3, target ) { - - return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); - - } - - /** - * Returns `true` if the given point, when projected onto the plane of the - * triangle, lies within the triangle. - * - * @param {Vector3} point - The point in 3D space to test. - * @return {boolean} Whether the given point, when projected onto the plane of the - * triangle, lies within the triangle or not. - */ - containsPoint( point ) { - - return Triangle.containsPoint( point, this.a, this.b, this.c ); - - } - - /** - * Returns `true` if the triangle is oriented towards the given direction. - * - * @param {Vector3} direction - The (normalized) direction vector. - * @return {boolean} Whether the triangle is oriented towards the given direction or not. - */ - isFrontFacing( direction ) { - - return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); - - } - - /** - * Returns `true` if this triangle intersects with the given box. - * - * @param {Box3} box - The box to intersect. - * @return {boolean} Whether this triangle intersects with the given box or not. - */ - intersectsBox( box ) { - - return box.intersectsTriangle( this ); - - } - - /** - * Returns the closest point on the triangle to the given point. - * - * @param {Vector3} p - The point to compute the closest point for. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The closest point on the triangle. - */ - closestPointToPoint( p, target ) { - - const a = this.a, b = this.b, c = this.c; - let v, w; - - // algorithm thanks to Real-Time Collision Detection by Christer Ericson, - // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc., - // under the accompanying license; see chapter 5.1.5 for detailed explanation. - // basically, we're distinguishing which of the voronoi regions of the triangle - // the point lies in with the minimum amount of redundant computation. - - _vab.subVectors( b, a ); - _vac.subVectors( c, a ); - _vap.subVectors( p, a ); - const d1 = _vab.dot( _vap ); - const d2 = _vac.dot( _vap ); - if ( d1 <= 0 && d2 <= 0 ) { - - // vertex region of A; barycentric coords (1, 0, 0) - return target.copy( a ); - - } - - _vbp.subVectors( p, b ); - const d3 = _vab.dot( _vbp ); - const d4 = _vac.dot( _vbp ); - if ( d3 >= 0 && d4 <= d3 ) { - - // vertex region of B; barycentric coords (0, 1, 0) - return target.copy( b ); - - } - - const vc = d1 * d4 - d3 * d2; - if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { - - v = d1 / ( d1 - d3 ); - // edge region of AB; barycentric coords (1-v, v, 0) - return target.copy( a ).addScaledVector( _vab, v ); - - } - - _vcp.subVectors( p, c ); - const d5 = _vab.dot( _vcp ); - const d6 = _vac.dot( _vcp ); - if ( d6 >= 0 && d5 <= d6 ) { - - // vertex region of C; barycentric coords (0, 0, 1) - return target.copy( c ); - - } - - const vb = d5 * d2 - d1 * d6; - if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { - - w = d2 / ( d2 - d6 ); - // edge region of AC; barycentric coords (1-w, 0, w) - return target.copy( a ).addScaledVector( _vac, w ); - - } - - const va = d3 * d6 - d5 * d4; - if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) { - - _vbc.subVectors( c, b ); - w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); - // edge region of BC; barycentric coords (0, 1-w, w) - return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC - - } - - // face region - const denom = 1 / ( va + vb + vc ); - // u = va * denom - v = vb * denom; - w = vc * denom; - - return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w ); - - } - - /** - * Returns `true` if this triangle is equal with the given one. - * - * @param {Triangle} triangle - The triangle to test for equality. - * @return {boolean} Whether this triangle is equal with the given one. - */ - equals( triangle ) { - - return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); - - } - -} - -/** - * A material for drawing geometries in a simple shaded (flat or wireframe) way. - * - * This material is not affected by lights. - * - * @augments Material - * @demo scenes/material-browser.html#MeshBasicMaterial - */ -class MeshBasicMaterial extends Material { - - /** - * Constructs a new mesh basic material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshBasicMaterial = true; - - this.type = 'MeshBasicMaterial'; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); // diffuse - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The light map. Requires a second set of UVs. - * - * `lightMap` represents pre-baked illuminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `lightMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.lightMap = null; - - /** - * Intensity of the baked light. - * - * @type {number} - * @default 1 - */ - this.lightMapIntensity = 1.0; - - /** - * The red channel of this texture is used as the ambient occlusion map. - * Requires a second set of UVs. - * - * `aoMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.aoMap = null; - - /** - * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` - * disables ambient occlusion. Where intensity is `1` and the AO map's - * red channel is also `1`, ambient light is fully occluded on a surface. - * - * @type {number} - * @default 1 - */ - this.aoMapIntensity = 1.0; - - /** - * Specular map used by the material. - * - * `specularMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `specularMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.specularMap = null; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * The environment map. - * - * `envMap` represents luminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `envMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.envMap = null; - - /** - * The rotation of the environment map in radians. - * - * @type {Euler} - * @default (0,0,0) - */ - this.envMapRotation = new Euler(); - - /** - * How to combine the result of the surface's color with the environment map, if any. - * - * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to - * blend between the two colors. - * - * @type {(MultiplyOperation|MixOperation|AddOperation)} - * @default MultiplyOperation - */ - this.combine = MultiplyOperation; - - /** - * How much the environment map affects the surface. - * The valid range is between `0` (no reflections) and `1` (full reflections). - * - * @type {number} - * @default 1 - */ - this.reflectivity = 1; - - /** - * The index of refraction (IOR) of air (approximately 1) divided by the - * index of refraction of the material. It is used with environment mapping - * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. - * The refraction ratio should not exceed `1`. - * - * @type {number} - * @default 0.98 - */ - this.refractionRatio = 0.98; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Defines appearance of wireframe ends. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinecap = 'round'; - - /** - * Defines appearance of wireframe joints. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinejoin = 'round'; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.fog = source.fog; - - return this; - - } - -} - -const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); -const _ray$3 = /*@__PURE__*/ new Ray(); -const _sphere$5 = /*@__PURE__*/ new Sphere(); -const _sphereHitAt = /*@__PURE__*/ new Vector3(); - -const _vA$1 = /*@__PURE__*/ new Vector3(); -const _vB$1 = /*@__PURE__*/ new Vector3(); -const _vC$1 = /*@__PURE__*/ new Vector3(); - -const _tempA = /*@__PURE__*/ new Vector3(); -const _morphA = /*@__PURE__*/ new Vector3(); - -const _intersectionPoint = /*@__PURE__*/ new Vector3(); -const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); - -/** - * Class representing triangular polygon mesh based objects. - * - * ```js - * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); - * const mesh = new THREE.Mesh( geometry, material ); - * scene.add( mesh ); - * ``` - * - * @augments Object3D - */ -class Mesh extends Object3D { - - /** - * Constructs a new mesh. - * - * @param {BufferGeometry} [geometry] - The mesh geometry. - * @param {Material|Array} [material] - The mesh material. - */ - constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMesh = true; - - this.type = 'Mesh'; - - /** - * The mesh geometry. - * - * @type {BufferGeometry} - */ - this.geometry = geometry; - - /** - * The mesh material. - * - * @type {Material|Array} - * @default MeshBasicMaterial - */ - this.material = material; - - /** - * A dictionary representing the morph targets in the geometry. The key is the - * morph targets name, the value its attribute index. This member is `undefined` - * by default and only set when morph targets are detected in the geometry. - * - * @type {Object|undefined} - * @default undefined - */ - this.morphTargetDictionary = undefined; - - /** - * An array of weights typically in the range `[0,1]` that specify how much of the morph - * is applied. This member is `undefined` by default and only set when morph targets are - * detected in the geometry. - * - * @type {Array|undefined} - * @default undefined - */ - this.morphTargetInfluences = undefined; - - /** - * The number of instances of this mesh. - * Can only be used with {@link WebGPURenderer}. - * - * @type {number} - * @default 1 - */ - this.count = 1; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.morphTargetInfluences !== undefined ) { - - this.morphTargetInfluences = source.morphTargetInfluences.slice(); - - } - - if ( source.morphTargetDictionary !== undefined ) { - - this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); - - } - - this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; - this.geometry = source.geometry; - - return this; - - } - - /** - * Sets the values of {@link Mesh#morphTargetDictionary} and {@link Mesh#morphTargetInfluences} - * to make sure existing morph targets can influence this 3D object. - */ - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - - /** - * Returns the local-space position of the vertex at the given index, taking into - * account the current animation state of both morph targets and skinning. - * - * @param {number} index - The vertex index. - * @param {Vector3} target - The target object that is used to store the method's result. - * @return {Vector3} The vertex position in local space. - */ - getVertexPosition( index, target ) { - - const geometry = this.geometry; - const position = geometry.attributes.position; - const morphPosition = geometry.morphAttributes.position; - const morphTargetsRelative = geometry.morphTargetsRelative; - - target.fromBufferAttribute( position, index ); - - const morphInfluences = this.morphTargetInfluences; - - if ( morphPosition && morphInfluences ) { - - _morphA.set( 0, 0, 0 ); - - for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { - - const influence = morphInfluences[ i ]; - const morphAttribute = morphPosition[ i ]; - - if ( influence === 0 ) continue; - - _tempA.fromBufferAttribute( morphAttribute, index ); - - if ( morphTargetsRelative ) { - - _morphA.addScaledVector( _tempA, influence ); - - } else { - - _morphA.addScaledVector( _tempA.sub( target ), influence ); - - } - - } - - target.add( _morphA ); - - } - - return target; - - } - - /** - * Returns `true` if this mesh intersects the given frustum. - * - * @param {Frustum|FrustumArray} frustum - The frustum to test. - * @return {boolean} Whether this mesh intersects the given frustum or not. - */ - intersectsFrustum( frustum ) { - - return frustum.intersectsObject( this ); - - } - - /** - * Computes intersection points between a casted ray and this line. - * - * @param {Raycaster} raycaster - The raycaster. - * @param {Array} intersects - The target array that holds the intersection points. - */ - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const material = this.material; - const matrixWorld = this.matrixWorld; - - if ( material === undefined ) return; - - // test with bounding sphere in world space - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$5.copy( geometry.boundingSphere ); - _sphere$5.applyMatrix4( matrixWorld ); - - // check distance from ray origin to bounding sphere - - _ray$3.copy( raycaster.ray ).recast( raycaster.near ); - - if ( _sphere$5.containsPoint( _ray$3.origin ) === false ) { - - if ( _ray$3.intersectSphere( _sphere$5, _sphereHitAt ) === null ) return; - - if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; - - } - - // convert ray to local space of mesh - - _inverseMatrix$3.copy( matrixWorld ).invert(); - _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); - - // test with bounding box in local space - - if ( geometry.boundingBox !== null ) { - - if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; - - } - - // test for intersections with geometry - - this._computeIntersections( raycaster, intersects, _ray$3 ); - - } - - _computeIntersections( raycaster, intersects, rayLocalSpace ) { - - let intersection; - - const geometry = this.geometry; - const material = this.material; - - const index = geometry.index; - const position = geometry.attributes.position; - const uv = geometry.attributes.uv; - const uv1 = geometry.attributes.uv1; - const normal = geometry.attributes.normal; - const groups = geometry.groups; - const drawRange = geometry.drawRange; - - if ( index !== null ) { - - // indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = index.getX( j ); - const b = index.getX( j + 1 ); - const c = index.getX( j + 2 ); - - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); - - } - - } - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = index.getX( i ); - const b = index.getX( i + 1 ); - const c = index.getX( i + 2 ); - - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics - intersects.push( intersection ); - - } - - } - - } - - } else if ( position !== undefined ) { - - // non-indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = j; - const b = j + 1; - const c = j + 2; - - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); - - } - - } - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = i; - const b = i + 1; - const c = i + 2; - - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics - intersects.push( intersection ); - - } - - } - - } - - } - - } - -} - -function checkIntersection$1( object, material, raycaster, ray, pA, pB, pC, point ) { - - let intersect; - - if ( material.side === BackSide ) { - - intersect = ray.intersectTriangle( pC, pB, pA, true, point ); - - } else { - - intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); - - } - - if ( intersect === null ) return null; - - _intersectionPointWorld.copy( point ); - _intersectionPointWorld.applyMatrix4( object.matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); - - if ( distance < raycaster.near || distance > raycaster.far ) return null; - - return { - distance: distance, - point: _intersectionPointWorld.clone(), - object: object - }; - -} - -function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { - - object.getVertexPosition( a, _vA$1 ); - object.getVertexPosition( b, _vB$1 ); - object.getVertexPosition( c, _vC$1 ); - - const intersection = checkIntersection$1( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); - - if ( intersection ) { - - const barycoord = new Vector3(); - Triangle.getBarycoord( _intersectionPoint, _vA$1, _vB$1, _vC$1, barycoord ); - - if ( uv ) { - - intersection.uv = Triangle.getInterpolatedAttribute( uv, a, b, c, barycoord, new Vector2() ); - - } - - if ( uv1 ) { - - intersection.uv1 = Triangle.getInterpolatedAttribute( uv1, a, b, c, barycoord, new Vector2() ); - - } - - if ( normal ) { - - intersection.normal = Triangle.getInterpolatedAttribute( normal, a, b, c, barycoord, new Vector3() ); - - if ( intersection.normal.dot( ray.direction ) > 0 ) { - - intersection.normal.multiplyScalar( -1 ); - - } - - } - - const face = { - a: a, - b: b, - c: c, - normal: new Vector3(), - materialIndex: 0 - }; - - Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); - - intersection.face = face; - intersection.barycoord = barycoord; - - } - - return intersection; - -} - -var alphahash_fragment = "#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif"; - -var alphahash_pars_fragment = "#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif"; - -var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; - -var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - -var alphatest_fragment = "#ifdef USE_ALPHATEST\n\t#ifdef ALPHA_TO_COVERAGE\n\tdiffuseColor.a = smoothstep( alphaTest, alphaTest + fwidth( diffuseColor.a ), diffuseColor.a );\n\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\tif ( diffuseColor.a < alphaTest ) discard;\n\t#endif\n#endif"; - -var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; - -var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; - -var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; - -var batching_pars_vertex = "#ifdef USE_BATCHING\n\t#if ! defined( GL_ANGLE_multi_draw )\n\t#define gl_DrawID _gl_DrawID\n\tuniform int _gl_DrawID;\n\t#endif\n\tuniform highp sampler2D batchingTexture;\n\tuniform highp usampler2D batchingIdTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n\tfloat getIndirectIndex( const in int i ) {\n\t\tint size = textureSize( batchingIdTexture, 0 ).x;\n\t\tint x = i % size;\n\t\tint y = i / size;\n\t\treturn float( texelFetch( batchingIdTexture, ivec2( x, y ), 0 ).r );\n\t}\n#endif\n#ifdef USE_BATCHING_COLOR\n\tuniform sampler2D batchingColorTexture;\n\tvec4 getBatchingColor( const in float i ) {\n\t\tint size = textureSize( batchingColorTexture, 0 ).x;\n\t\tint j = int( i );\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\treturn texelFetch( batchingColorTexture, ivec2( x, y ), 0 );\n\t}\n#endif"; - -var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( getIndirectIndex( gl_DrawID ) );\n#endif"; - -var begin_vertex = "vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif"; - -var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; - -var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; - -var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; - -var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; - -var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif"; - -var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; - -var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; - -var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; - -var color_fragment = "#if defined( USE_COLOR ) || defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#endif"; - -var color_pars_fragment = "#if defined( USE_COLOR ) || defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#endif"; - -var color_pars_vertex = "#if defined( USE_COLOR ) || defined( USE_COLOR_ALPHA ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec4 vColor;\n#endif"; - -var color_vertex = "#if defined( USE_COLOR ) || defined( USE_COLOR_ALPHA ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec4( 1.0 );\n#endif\n#ifdef USE_COLOR_ALPHA\n\tvColor *= color;\n#elif defined( USE_COLOR )\n\tvColor.rgb *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.rgb *= instanceColor.rgb;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvColor *= getBatchingColor( getIndirectIndex( gl_DrawID ) );\n#endif"; - -var common$1 = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\n#define inverseTransformDirection transformDirectionByInverseViewMatrix\nvec3 transformNormalByInverseViewMatrix( in vec3 normal, in mat4 viewMatrix ) {\n\treturn normalize( ( vec4( normal, 0.0 ) * viewMatrix ).xyz );\n}\nvec3 transformDirectionByInverseViewMatrix( in vec3 dir, in mat4 viewMatrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * viewMatrix ).xyz );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; - -var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; - -var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n#endif"; - -var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; - -var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; - -var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; - -var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; - -var colorspace_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; - -var colorspace_pars_fragment = "vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; - -var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = transformNormalByInverseViewMatrix( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * reflectVec );\n\t\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t\t#endif\n\t#endif\n#endif"; - -var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n#endif"; - -var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; - -var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; - -var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = transformNormalByInverseViewMatrix( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; - -var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; - -var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; - -var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; - -var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; - -var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; - -var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; - -var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; - -var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; - -var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = transformNormalByInverseViewMatrix( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_SUN_LIGHTS > 0\n\tstruct SunLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform SunLight sunLights[ NUM_SUN_LIGHTS ];\n\tvoid getSunLightInfo( const in SunLight sunLight, out IncidentLight light ) {\n\t\tlight.color = sunLight.color;\n\t\tlight.direction = sunLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif\n#include "; - -var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = transformNormalByInverseViewMatrix( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, pow4( roughness ) ) );\n\t\t\treflectVec = transformDirectionByInverseViewMatrix( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_RETROREFLECTION\n\t\tvec3 getIBLRetroRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 retroVec = normalize( mix( viewDir, normal, pow4( roughness ) ) );\n\t\t\t\tretroVec = transformDirectionByInverseViewMatrix( retroVec, viewMatrix );\n\t\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * retroVec, roughness );\n\t\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t\t#ifdef USE_RETROREFLECTION\n\t\t\tvec3 getIBLAnisotropyRetroRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\t\treturn getIBLRetroRadiance( viewDir, bentNormal, roughness );\n\t\t\t\t#else\n\t\t\t\t\treturn vec3( 0.0 );\n\t\t\t\t#endif\n\t\t\t}\n\t\t#endif\n\t#endif\n#endif"; - -var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; - -var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; - -var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; - -var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; - -var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.diffuseContribution = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nmaterial.metalness = metalnessFactor;\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor;\n\tmaterial.specularColorBlended = mix( material.specularColor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = vec3( 0.04 );\n\tmaterial.specularColorBlended = mix( material.specularColor, diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_RETROREFLECTION\n\tmaterial.retroreflectivity = retroreflectivity;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.0001, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; - -var lights_physical_pars_fragment = "uniform sampler2D dfgLUT;\nstruct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tvec3 diffuseContribution;\n\tvec3 specularColor;\n\tvec3 specularColorBlended;\n\tfloat roughness;\n\tfloat metalness;\n\tfloat specularF90;\n\tfloat dispersion;\n\tvec2 dfg;\n\tvec3 multiScatteringCompensation;\n\t#ifdef USE_RETROREFLECTION\n\t\tfloat retroreflectivity;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0Dielectric;\n\t\tvec3 iridescenceF0Metallic;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\treturn 0.5 / max( gv + gl, EPSILON );\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColorBlended;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transpose( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat rInv = 1.0 / ( roughness + 0.1 );\n\tfloat a = -1.9362 + 1.0678 * roughness + 0.4573 * r2 - 0.8469 * rInv;\n\tfloat b = -0.6014 + 0.5538 * roughness - 0.4670 * r2 - 0.1255 * rInv;\n\tfloat DG = exp( a * dotNV + b );\n\treturn saturate( DG );\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tvec2 fab = texture2D( dfgLUT, vec2( roughness, dotNV ) ).rg;\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec2 fab, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec2 fab, const in vec3 specularColor, const in float specularF90, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColorBlended * t2.x + ( material.specularF90 - material.specularColorBlended ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseContribution * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t\t#ifdef USE_CLEARCOAT\n\t\t\tvec3 Ncc = geometryClearcoatNormal;\n\t\t\tvec2 uvClearcoat = LTC_Uv( Ncc, viewDir, material.clearcoatRoughness );\n\t\t\tvec4 t1Clearcoat = texture2D( ltc_1, uvClearcoat );\n\t\t\tvec4 t2Clearcoat = texture2D( ltc_2, uvClearcoat );\n\t\t\tmat3 mInvClearcoat = mat3(\n\t\t\t\tvec3( t1Clearcoat.x, 0, t1Clearcoat.y ),\n\t\t\t\tvec3( 0, 1, 0 ),\n\t\t\t\tvec3( t1Clearcoat.z, 0, t1Clearcoat.w )\n\t\t\t);\n\t\t\tvec3 fresnelClearcoat = material.clearcoatF0 * t2Clearcoat.x + ( material.clearcoatF90 - material.clearcoatF0 ) * t2Clearcoat.y;\n\t\t\tclearcoatSpecularDirect += lightColor * fresnelClearcoat * LTC_Evaluate( Ncc, viewDir, position, mInvClearcoat, rectCoords );\n\t\t#endif\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n \n \t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n \n \t\tfloat sheenAlbedoV = IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n \t\tfloat sheenAlbedoL = IBLSheenBRDF( geometryNormal, directLight.direction, material.sheenRoughness );\n \n \t\tfloat sheenEnergyComp = 1.0 - max3( material.sheenColor ) * max( sheenAlbedoV, sheenAlbedoL );\n \n \t\tirradiance *= sheenEnergyComp;\n \n \t#endif\n\tvec3 specularBRDF = BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\t#ifdef USE_RETROREFLECTION\n\t\tvec3 retroViewDir = reflect( - geometryViewDir, geometryNormal );\n\t\tvec3 retroSpecularBRDF = BRDF_GGX( directLight.direction, retroViewDir, geometryNormal, material );\n\t\tspecularBRDF = mix( specularBRDF, retroSpecularBRDF, saturate( material.retroreflectivity ) );\n\t#endif\n\treflectedLight.directSpecular += irradiance * specularBRDF * material.multiScatteringCompensation;\n\tvec3 halfDir = normalize( directLight.direction + geometryViewDir );\n\tfloat dotVH = saturate( dot( geometryViewDir, halfDir ) );\n\tvec3 F = F_Schlick( material.specularColor, material.specularF90, dotVH );\n\t#ifdef USE_RETROREFLECTION\n\t\tvec3 retroHalfDir = normalize( directLight.direction + retroViewDir );\n\t\tfloat dotRetroVH = saturate( dot( retroViewDir, retroHalfDir ) );\n\t\tvec3 retroF = F_Schlick( material.specularColor, material.specularF90, dotRetroVH );\n\t\tF = mix( F, retroF, saturate( material.retroreflectivity ) );\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseContribution ) * ( 1.0 - F );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( material.dfg, material.specularColor, material.specularF90, material.iridescence, material.iridescenceF0Dielectric, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( material.dfg, material.specularColor, material.specularF90, singleScattering, multiScattering );\n\t#endif\n\tvec3 diffuse = irradiance * BRDF_Lambert( material.diffuseContribution ) * ( 1.0 - singleScattering - multiScattering );\n\t#ifdef USE_SHEEN\n\t\tfloat sheenAlbedo = IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * sheenAlbedo * RECIPROCAL_PI;\n\t\tfloat sheenEnergyComp = 1.0 - max3( material.sheenColor ) * sheenAlbedo;\n\t\tdiffuse *= sheenEnergyComp;\n\t#endif\n\treflectedLight.indirectDiffuse += diffuse;\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness ) * RECIPROCAL_PI;\n \t#endif\n\tvec3 singleScatteringDielectric = vec3( 0.0 );\n\tvec3 multiScatteringDielectric = vec3( 0.0 );\n\tvec3 singleScatteringMetallic = vec3( 0.0 );\n\tvec3 multiScatteringMetallic = vec3( 0.0 );\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( material.dfg, material.specularColor, material.specularF90, material.iridescence, material.iridescenceF0Dielectric, singleScatteringDielectric, multiScatteringDielectric );\n\t\tcomputeMultiscatteringIridescence( material.dfg, material.diffuseColor, material.specularF90, material.iridescence, material.iridescenceF0Metallic, singleScatteringMetallic, multiScatteringMetallic );\n\t#else\n\t\tcomputeMultiscattering( material.dfg, material.specularColor, material.specularF90, singleScatteringDielectric, multiScatteringDielectric );\n\t\tcomputeMultiscattering( material.dfg, material.diffuseColor, material.specularF90, singleScatteringMetallic, multiScatteringMetallic );\n\t#endif\n\tvec3 singleScattering = mix( singleScatteringDielectric, singleScatteringMetallic, material.metalness );\n\tvec3 multiScattering = mix( multiScatteringDielectric, multiScatteringMetallic, material.metalness );\n\tvec3 totalScatteringDielectric = singleScatteringDielectric + multiScatteringDielectric;\n\tvec3 diffuse = material.diffuseContribution * ( 1.0 - totalScatteringDielectric );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tvec3 indirectSpecular = radiance * singleScattering;\n\tindirectSpecular += multiScattering * cosineWeightedIrradiance;\n\tvec3 indirectDiffuse = diffuse * cosineWeightedIrradiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenAlbedo = IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t\tfloat sheenEnergyComp = 1.0 - max3( material.sheenColor ) * sheenAlbedo;\n\t\tindirectSpecular *= sheenEnergyComp;\n\t\tindirectDiffuse *= sheenEnergyComp;\n\t#endif\n\treflectedLight.indirectSpecular += indirectSpecular;\n\treflectedLight.indirectDiffuse += indirectDiffuse;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; - -var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tvec3 iridescenceFresnelDielectric = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tvec3 iridescenceFresnelMetallic = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.diffuseColor );\n\t\tmaterial.iridescenceFresnel = mix( iridescenceFresnelDielectric, iridescenceFresnelMetallic, material.metalness );\n\t\tmaterial.iridescenceF0Dielectric = Schlick_to_F0( iridescenceFresnelDielectric, 1.0, dotNVi );\n\t\tmaterial.iridescenceF0Metallic = Schlick_to_F0( iridescenceFresnelMetallic, 1.0, dotNVi );\n\t}\n#endif\n#ifdef STANDARD\n\tfloat dotNVms = saturate( dot( geometryNormal, geometryViewDir ) );\n\tmaterial.dfg = texture2D( dfgLUT, vec2( material.roughness, dotNVms ) ).rg;\n\t#if ( NUM_SUN_LIGHTS > 0 || NUM_DIR_LIGHTS > 0 || NUM_POINT_LIGHTS > 0 || NUM_SPOT_LIGHTS > 0 )\n\t\tfloat EssMs = material.dfg.x + material.dfg.y;\n\t\tmaterial.multiScatteringCompensation = 1.0 + material.specularColorBlended * ( 1.0 / EssMs - 1.0 );\n\t#endif\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS ) && ( defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_BASIC ) )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SUN_LIGHTS > 0 ) && defined( RE_Direct )\n\tSunLight sunLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_SUN_LIGHT_SHADOWS > 0\n\tSunLightShadow sunLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SUN_LIGHTS; i ++ ) {\n\t\tsunLight = sunLights[ i ];\n\t\tgetSunLightInfo( sunLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SUN_LIGHT_SHADOWS )\n\t\tsunLightShadow = sunLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getSunShadow( sunShadowMap[ i ], sunLightShadow, UNROLLED_LOOP_INDEX ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#ifdef USE_LIGHT_PROBES_GRID\n\t\tvec3 probeWorldPos = ( ( vec4( geometryPosition, 1.0 ) - viewMatrix[ 3 ] ) * viewMatrix ).xyz;\n\t\tvec3 probeWorldNormal = transformNormalByInverseViewMatrix( geometryNormal, viewMatrix );\n\t\tirradiance += getLightProbeGridIrradiance( probeWorldPos, probeWorldNormal );\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; - -var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\t#if defined( STANDARD ) || defined( LAMBERT ) || defined( PHONG )\n\t\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t\t#endif\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 iblRadiance = getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tvec3 iblRadiance = getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_RETROREFLECTION\n\t\t#ifdef USE_ANISOTROPY\n\t\t\tvec3 retroIBLRadiance = getIBLAnisotropyRetroRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t\t#else\n\t\t\tvec3 retroIBLRadiance = getIBLRetroRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t\t#endif\n\t\tiblRadiance = mix( iblRadiance, retroIBLRadiance, saturate( material.retroreflectivity ) );\n\t#endif\n\tradiance += iblRadiance;\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; - -var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\t#if defined( LAMBERT ) || defined( PHONG )\n\t\tirradiance += iblIrradiance;\n\t#endif\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif"; - -var lightprobes_pars_fragment = "#ifdef USE_LIGHT_PROBES_GRID\nuniform highp sampler3D probesSH;\nuniform vec3 probesMin;\nuniform vec3 probesMax;\nuniform vec3 probesResolution;\nvec3 getLightProbeGridIrradiance( vec3 worldPos, vec3 worldNormal ) {\n\tvec3 res = probesResolution;\n\tvec3 gridRange = probesMax - probesMin;\n\tvec3 resMinusOne = res - 1.0;\n\tvec3 probeSpacing = gridRange / resMinusOne;\n\tvec3 samplePos = worldPos + worldNormal * probeSpacing * 0.5;\n\tvec3 uvw = clamp( ( samplePos - probesMin ) / gridRange, 0.0, 1.0 );\n\tuvw = uvw * resMinusOne / res + 0.5 / res;\n\tfloat nz = res.z;\n\tfloat paddedSlices = nz + 2.0;\n\tfloat atlasDepth = 7.0 * paddedSlices;\n\tfloat uvZBase = uvw.z * nz + 1.0;\n\tvec4 s0 = texture( probesSH, vec3( uvw.xy, ( uvZBase ) / atlasDepth ) );\n\tvec4 s1 = texture( probesSH, vec3( uvw.xy, ( uvZBase + paddedSlices ) / atlasDepth ) );\n\tvec4 s2 = texture( probesSH, vec3( uvw.xy, ( uvZBase + 2.0 * paddedSlices ) / atlasDepth ) );\n\tvec4 s3 = texture( probesSH, vec3( uvw.xy, ( uvZBase + 3.0 * paddedSlices ) / atlasDepth ) );\n\tvec4 s4 = texture( probesSH, vec3( uvw.xy, ( uvZBase + 4.0 * paddedSlices ) / atlasDepth ) );\n\tvec4 s5 = texture( probesSH, vec3( uvw.xy, ( uvZBase + 5.0 * paddedSlices ) / atlasDepth ) );\n\tvec4 s6 = texture( probesSH, vec3( uvw.xy, ( uvZBase + 6.0 * paddedSlices ) / atlasDepth ) );\n\tvec3 c0 = s0.xyz;\n\tvec3 c1 = vec3( s0.w, s1.xy );\n\tvec3 c2 = vec3( s1.zw, s2.x );\n\tvec3 c3 = s2.yzw;\n\tvec3 c4 = s3.xyz;\n\tvec3 c5 = vec3( s3.w, s4.xy );\n\tvec3 c6 = vec3( s4.zw, s5.x );\n\tvec3 c7 = s5.yzw;\n\tvec3 c8 = s6.xyz;\n\tfloat x = worldNormal.x, y = worldNormal.y, z = worldNormal.z;\n\tvec3 result = c0 * 0.886227;\n\tresult += c1 * 2.0 * 0.511664 * y;\n\tresult += c2 * 2.0 * 0.511664 * z;\n\tresult += c3 * 2.0 * 0.511664 * x;\n\tresult += c4 * 2.0 * 0.429043 * x * y;\n\tresult += c5 * 2.0 * 0.429043 * y * z;\n\tresult += c6 * ( 0.743125 * z * z - 0.247708 );\n\tresult += c7 * 2.0 * 0.429043 * x * z;\n\tresult += c8 * 0.429043 * ( x * x - y * y );\n\treturn max( result, vec3( 0.0 ) );\n}\n#endif"; - -var logdepthbuf_fragment = "#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; - -var logdepthbuf_pars_fragment = "#if defined( USE_LOGARITHMIC_DEPTH_BUFFER )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - -var logdepthbuf_pars_vertex = "#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - -var logdepthbuf_vertex = "#ifdef USE_LOGARITHMIC_DEPTH_BUFFER\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif"; - -var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; - -var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; - -var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; - -var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - -var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; - -var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; - -var morphinstance_vertex = "#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif"; - -var morphcolor_vertex = "#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; - -var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - -var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif"; - -var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - -var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;"; - -var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#if defined( USE_PACKED_NORMALMAP )\n\t\tmapN = vec3( mapN.xy, sqrt( saturate( 1.0 - dot( mapN.xy, mapN.xy ) ) ) );\n\t#endif\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; - -var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - -var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - -var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t\t#ifdef FLIP_SIDED\n\t\t\tvBitangent = - vBitangent;\n\t\t#endif\n\t#endif\n#endif"; - -var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; - -var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif"; - -var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; - -var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; - -var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; - -var opaque_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; - -var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\n\t\treturn depth * ( far - near ) - far;\n\t#else\n\t\treturn depth * ( near - far ) - near;\n\t#endif\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\t\n\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\treturn ( near * far ) / ( ( near - far ) * depth - near );\n\t#else\n\t\treturn ( near * far ) / ( ( far - near ) * depth - far );\n\t#endif\n}"; - -var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; - -var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; - -var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; - -var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; - -var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; - -var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; - -var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_SUN_LIGHT_SHADOWS > 0\n\t\t#define SUN_LIGHT_CASCADES 2\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tuniform sampler2DShadow sunShadowMap[ NUM_SUN_LIGHT_SHADOWS ];\n\t\t#else\n\t\t\tuniform sampler2D sunShadowMap[ NUM_SUN_LIGHT_SHADOWS ];\n\t\t#endif\n\t\tuniform mat4 sunShadowMatrix[ NUM_SUN_LIGHT_SHADOWS * SUN_LIGHT_CASCADES ];\n\t\tuniform vec4 sunShadowCascade[ NUM_SUN_LIGHT_SHADOWS * SUN_LIGHT_CASCADES ];\n\t\tvarying vec4 vSunShadowWorldPosition;\n\t\tvarying vec3 vSunShadowWorldNormal;\n\t\tstruct SunLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SunLightShadow sunLightShadows[ NUM_SUN_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tuniform sampler2DShadow directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\t#else\n\t\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\t#endif\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tuniform sampler2DShadow spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\t#else\n\t\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\t#endif\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tuniform samplerCubeShadow pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\t#elif defined( SHADOWMAP_TYPE_BASIC )\n\t\t\tuniform samplerCube pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\t#endif\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\tfloat interleavedGradientNoise( vec2 position ) {\n\t\t\treturn fract( 52.9829189 * fract( dot( position, vec2( 0.06711056, 0.00583715 ) ) ) );\n\t\t}\n\t\tvec2 vogelDiskSample( int sampleIndex, int samplesCount, float phi ) {\n\t\t\tconst float goldenAngle = 2.399963229728653;\n\t\t\tfloat r = sqrt( ( float( sampleIndex ) + 0.5 ) / float( samplesCount ) );\n\t\t\tfloat theta = float( sampleIndex ) * goldenAngle + phi;\n\t\t\treturn vec2( cos( theta ), sin( theta ) ) * r;\n\t\t}\n\t#endif\n\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\tfloat getShadow( sampler2DShadow shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\t\tfloat shadow = 1.0;\n\t\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\t\tshadowCoord.z += shadowBias;\n\t\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\t\tif ( frustumTest ) {\n\t\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\t\tfloat radius = shadowRadius * texelSize.x;\n\t\t\t\tfloat phi = interleavedGradientNoise( gl_FragCoord.xy ) * PI2;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture( shadowMap, vec3( shadowCoord.xy + vogelDiskSample( 0, 5, phi ) * radius, shadowCoord.z ) ) +\n\t\t\t\t\ttexture( shadowMap, vec3( shadowCoord.xy + vogelDiskSample( 1, 5, phi ) * radius, shadowCoord.z ) ) +\n\t\t\t\t\ttexture( shadowMap, vec3( shadowCoord.xy + vogelDiskSample( 2, 5, phi ) * radius, shadowCoord.z ) ) +\n\t\t\t\t\ttexture( shadowMap, vec3( shadowCoord.xy + vogelDiskSample( 3, 5, phi ) * radius, shadowCoord.z ) ) +\n\t\t\t\t\ttexture( shadowMap, vec3( shadowCoord.xy + vogelDiskSample( 4, 5, phi ) * radius, shadowCoord.z ) )\n\t\t\t\t) * 0.2;\n\t\t\t}\n\t\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t\t}\n\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\t\tfloat shadow = 1.0;\n\t\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\t\tshadowCoord.z -= shadowBias;\n\t\t\t#else\n\t\t\t\tshadowCoord.z += shadowBias;\n\t\t\t#endif\n\t\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\t\tif ( frustumTest ) {\n\t\t\t\tvec2 distribution = texture2D( shadowMap, shadowCoord.xy ).rg;\n\t\t\t\tfloat mean = distribution.x;\n\t\t\t\tfloat variance = distribution.y * distribution.y;\n\t\t\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\t\t\tfloat hard_shadow = step( mean, shadowCoord.z );\n\t\t\t\t#else\n\t\t\t\t\tfloat hard_shadow = step( shadowCoord.z, mean );\n\t\t\t\t#endif\n\t\t\t\t\n\t\t\t\tif ( hard_shadow == 1.0 ) {\n\t\t\t\t\tshadow = 1.0;\n\t\t\t\t} else {\n\t\t\t\t\tvariance = max( variance, 0.0000001 );\n\t\t\t\t\tfloat d = shadowCoord.z - mean;\n\t\t\t\t\tfloat p_max = variance / ( variance + d * d );\n\t\t\t\t\tp_max = clamp( ( p_max - 0.3 ) / 0.65, 0.0, 1.0 );\n\t\t\t\t\tshadow = max( hard_shadow, p_max );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t\t}\n\t#else\n\t\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\t\tfloat shadow = 1.0;\n\t\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\t\tshadowCoord.z -= shadowBias;\n\t\t\t#else\n\t\t\t\tshadowCoord.z += shadowBias;\n\t\t\t#endif\n\t\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\t\tif ( frustumTest ) {\n\t\t\t\tfloat depth = texture2D( shadowMap, shadowCoord.xy ).r;\n\t\t\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\t\t\tshadow = step( depth, shadowCoord.z );\n\t\t\t\t#else\n\t\t\t\t\tshadow = step( shadowCoord.z, depth );\n\t\t\t\t#endif\n\t\t\t}\n\t\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t\t}\n\t#endif\n\t#if NUM_SUN_LIGHT_SHADOWS > 0\n\t\tfloat getSunShadow(\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\t\tsampler2DShadow shadowMap,\n\t\t\t#else\n\t\t\t\tsampler2D shadowMap,\n\t\t\t#endif\n\t\t\tSunLightShadow sunLightShadow,\n\t\t\tint shadowIndex\n\t\t) {\n\t\t\tvec4 shadowWorldPosition = vec4( vSunShadowWorldPosition.xyz + vSunShadowWorldNormal * sunLightShadow.shadowNormalBias, 1.0 );\n\t\t\tfloat viewDepth = vSunShadowWorldPosition.w;\n\t\t\tint cascadeOffset = shadowIndex * SUN_LIGHT_CASCADES;\n\t\t\tfloat shadow = 1.0;\n\t\t\tfor ( int i = SUN_LIGHT_CASCADES - 1; i >= 0; i -- ) {\n\t\t\t\tvec4 cascade = sunShadowCascade[ cascadeOffset + i ];\n\t\t\t\tif ( viewDepth >= cascade.x && viewDepth < cascade.y ) {\n\t\t\t\t\tfloat cascadeShadow = getShadow(\n\t\t\t\t\t\tshadowMap,\n\t\t\t\t\t\tsunLightShadow.shadowMapSize,\n\t\t\t\t\t\tsunLightShadow.shadowIntensity,\n\t\t\t\t\t\tsunLightShadow.shadowBias,\n\t\t\t\t\t\tsunLightShadow.shadowRadius,\n\t\t\t\t\t\tsunShadowMatrix[ cascadeOffset + i ] * shadowWorldPosition\n\t\t\t\t\t);\n\t\t\t\t\tshadow = mix( cascadeShadow, shadow, smoothstep( cascade.z, cascade.y, viewDepth ) );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn shadow;\n\t\t}\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#if defined( SHADOWMAP_TYPE_PCF )\n\tfloat getPointShadow( samplerCubeShadow shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\tvec3 absVec = abs( lightToPosition );\n\t\tfloat viewSpaceZ = max( max( absVec.x, absVec.y ), absVec.z );\n\t\tif ( viewSpaceZ - shadowCameraFar <= 0.0 && viewSpaceZ - shadowCameraNear >= 0.0 ) {\n\t\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\t\tfloat dp = ( shadowCameraNear * ( shadowCameraFar - viewSpaceZ ) ) / ( viewSpaceZ * ( shadowCameraFar - shadowCameraNear ) );\n\t\t\t\tdp -= shadowBias;\n\t\t\t#else\n\t\t\t\tfloat dp = ( shadowCameraFar * ( viewSpaceZ - shadowCameraNear ) ) / ( viewSpaceZ * ( shadowCameraFar - shadowCameraNear ) );\n\t\t\t\tdp += shadowBias;\n\t\t\t#endif\n\t\t\tfloat texelSize = shadowRadius / shadowMapSize.x;\n\t\t\tvec3 absDir = abs( bd3D );\n\t\t\tvec3 tangent = absDir.x > absDir.z ? vec3( 0.0, 1.0, 0.0 ) : vec3( 1.0, 0.0, 0.0 );\n\t\t\ttangent = normalize( cross( bd3D, tangent ) );\n\t\t\tvec3 bitangent = cross( bd3D, tangent );\n\t\t\tfloat phi = interleavedGradientNoise( gl_FragCoord.xy ) * PI2;\n\t\t\tvec2 sample0 = vogelDiskSample( 0, 5, phi );\n\t\t\tvec2 sample1 = vogelDiskSample( 1, 5, phi );\n\t\t\tvec2 sample2 = vogelDiskSample( 2, 5, phi );\n\t\t\tvec2 sample3 = vogelDiskSample( 3, 5, phi );\n\t\t\tvec2 sample4 = vogelDiskSample( 4, 5, phi );\n\t\t\tshadow = (\n\t\t\t\ttexture( shadowMap, vec4( bd3D + ( tangent * sample0.x + bitangent * sample0.y ) * texelSize, dp ) ) +\n\t\t\t\ttexture( shadowMap, vec4( bd3D + ( tangent * sample1.x + bitangent * sample1.y ) * texelSize, dp ) ) +\n\t\t\t\ttexture( shadowMap, vec4( bd3D + ( tangent * sample2.x + bitangent * sample2.y ) * texelSize, dp ) ) +\n\t\t\t\ttexture( shadowMap, vec4( bd3D + ( tangent * sample3.x + bitangent * sample3.y ) * texelSize, dp ) ) +\n\t\t\t\ttexture( shadowMap, vec4( bd3D + ( tangent * sample4.x + bitangent * sample4.y ) * texelSize, dp ) )\n\t\t\t) * 0.2;\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\t#elif defined( SHADOWMAP_TYPE_BASIC )\n\tfloat getPointShadow( samplerCube shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tvec3 absVec = abs( lightToPosition );\n\t\tfloat viewSpaceZ = max( max( absVec.x, absVec.y ), absVec.z );\n\t\tif ( viewSpaceZ - shadowCameraFar <= 0.0 && viewSpaceZ - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( shadowCameraFar * ( viewSpaceZ - shadowCameraNear ) ) / ( viewSpaceZ * ( shadowCameraFar - shadowCameraNear ) );\n\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tfloat depth = textureCube( shadowMap, bd3D ).r;\n\t\t\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\t\t\tdepth = 1.0 - depth;\n\t\t\t#endif\n\t\t\tshadow = step( dp, depth );\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\t#endif\n\t#endif\n#endif"; - -var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_SUN_LIGHT_SHADOWS > 0\n\t\tvarying vec4 vSunShadowWorldPosition;\n\t\tvarying vec3 vSunShadowWorldNormal;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; - -var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SUN_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\t#ifdef HAS_NORMAL\n\t\tvec3 shadowWorldNormal = transformNormalByInverseViewMatrix( transformedNormal, viewMatrix );\n\t#else\n\t\tvec3 shadowWorldNormal = vec3( 0.0 );\n\t#endif\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_SUN_LIGHT_SHADOWS > 0\n\t\tvSunShadowWorldPosition = vec4( worldPosition.xyz, - mvPosition.z );\n\t\tvSunShadowWorldNormal = shadowWorldNormal;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; - -var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_SUN_LIGHT_SHADOWS > 0\n\tSunLightShadow sunLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SUN_LIGHT_SHADOWS; i ++ ) {\n\t\tsunLight = sunLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getSunShadow( sunShadowMap[ i ], sunLight, UNROLLED_LOOP_INDEX ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0 && ( defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_BASIC ) )\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; - -var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; - -var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; - -var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; - -var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; - -var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; - -var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; - -var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; - -var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; - -var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = transformNormalByInverseViewMatrix( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseContribution, material.specularColorBlended, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; - -var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; - -var uv_pars_fragment = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - -var uv_pars_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - -var uv_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; - -var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; - -const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; - -const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - -const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - -const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vWorldDirection );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - -const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - -const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; - -const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; - -const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_REVERSED_DEPTH_BUFFER\n\t\tfloat fragCoordZ = vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ];\n\t#else\n\t\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[ 0 ] / vHighPrecisionZW[ 1 ] + 0.5;\n\t#endif\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}"; - -const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; - -const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = vec4( dist, 0.0, 0.0, 1.0 );\n}"; - -const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; - -const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; - -const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; - -const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; - -const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( normalize( normal ) * 0.5 + 0.5, diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; - -const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; - -const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_RETROREFLECTION\n\tuniform float retroreflectivity;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n \n\t\toutgoingLight = outgoingLight + sheenSpecularDirect + sheenSpecularIndirect;\n \n \t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; - -const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; - -const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const ShaderChunk = { - alphahash_fragment: alphahash_fragment, - alphahash_pars_fragment: alphahash_pars_fragment, - alphamap_fragment: alphamap_fragment, - alphamap_pars_fragment: alphamap_pars_fragment, - alphatest_fragment: alphatest_fragment, - alphatest_pars_fragment: alphatest_pars_fragment, - aomap_fragment: aomap_fragment, - aomap_pars_fragment: aomap_pars_fragment, - batching_pars_vertex: batching_pars_vertex, - batching_vertex: batching_vertex, - begin_vertex: begin_vertex, - beginnormal_vertex: beginnormal_vertex, - bsdfs: bsdfs, - iridescence_fragment: iridescence_fragment, - bumpmap_pars_fragment: bumpmap_pars_fragment, - clipping_planes_fragment: clipping_planes_fragment, - clipping_planes_pars_fragment: clipping_planes_pars_fragment, - clipping_planes_pars_vertex: clipping_planes_pars_vertex, - clipping_planes_vertex: clipping_planes_vertex, - color_fragment: color_fragment, - color_pars_fragment: color_pars_fragment, - color_pars_vertex: color_pars_vertex, - color_vertex: color_vertex, - common: common$1, - cube_uv_reflection_fragment: cube_uv_reflection_fragment, - defaultnormal_vertex: defaultnormal_vertex, - displacementmap_pars_vertex: displacementmap_pars_vertex, - displacementmap_vertex: displacementmap_vertex, - emissivemap_fragment: emissivemap_fragment, - emissivemap_pars_fragment: emissivemap_pars_fragment, - colorspace_fragment: colorspace_fragment, - colorspace_pars_fragment: colorspace_pars_fragment, - envmap_fragment: envmap_fragment, - envmap_common_pars_fragment: envmap_common_pars_fragment, - envmap_pars_fragment: envmap_pars_fragment, - envmap_pars_vertex: envmap_pars_vertex, - envmap_physical_pars_fragment: envmap_physical_pars_fragment, - envmap_vertex: envmap_vertex, - fog_vertex: fog_vertex, - fog_pars_vertex: fog_pars_vertex, - fog_fragment: fog_fragment, - fog_pars_fragment: fog_pars_fragment, - gradientmap_pars_fragment: gradientmap_pars_fragment, - lightmap_pars_fragment: lightmap_pars_fragment, - lights_lambert_fragment: lights_lambert_fragment, - lights_lambert_pars_fragment: lights_lambert_pars_fragment, - lights_pars_begin: lights_pars_begin, - lights_toon_fragment: lights_toon_fragment, - lights_toon_pars_fragment: lights_toon_pars_fragment, - lights_phong_fragment: lights_phong_fragment, - lights_phong_pars_fragment: lights_phong_pars_fragment, - lights_physical_fragment: lights_physical_fragment, - lights_physical_pars_fragment: lights_physical_pars_fragment, - lights_fragment_begin: lights_fragment_begin, - lights_fragment_maps: lights_fragment_maps, - lights_fragment_end: lights_fragment_end, - lightprobes_pars_fragment: lightprobes_pars_fragment, - logdepthbuf_fragment: logdepthbuf_fragment, - logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, - logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, - logdepthbuf_vertex: logdepthbuf_vertex, - map_fragment: map_fragment, - map_pars_fragment: map_pars_fragment, - map_particle_fragment: map_particle_fragment, - map_particle_pars_fragment: map_particle_pars_fragment, - metalnessmap_fragment: metalnessmap_fragment, - metalnessmap_pars_fragment: metalnessmap_pars_fragment, - morphinstance_vertex: morphinstance_vertex, - morphcolor_vertex: morphcolor_vertex, - morphnormal_vertex: morphnormal_vertex, - morphtarget_pars_vertex: morphtarget_pars_vertex, - morphtarget_vertex: morphtarget_vertex, - normal_fragment_begin: normal_fragment_begin, - normal_fragment_maps: normal_fragment_maps, - normal_pars_fragment: normal_pars_fragment, - normal_pars_vertex: normal_pars_vertex, - normal_vertex: normal_vertex, - normalmap_pars_fragment: normalmap_pars_fragment, - clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, - clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, - clearcoat_pars_fragment: clearcoat_pars_fragment, - iridescence_pars_fragment: iridescence_pars_fragment, - opaque_fragment: opaque_fragment, - packing: packing, - premultiplied_alpha_fragment: premultiplied_alpha_fragment, - project_vertex: project_vertex, - dithering_fragment: dithering_fragment, - dithering_pars_fragment: dithering_pars_fragment, - roughnessmap_fragment: roughnessmap_fragment, - roughnessmap_pars_fragment: roughnessmap_pars_fragment, - shadowmap_pars_fragment: shadowmap_pars_fragment, - shadowmap_pars_vertex: shadowmap_pars_vertex, - shadowmap_vertex: shadowmap_vertex, - shadowmask_pars_fragment: shadowmask_pars_fragment, - skinbase_vertex: skinbase_vertex, - skinning_pars_vertex: skinning_pars_vertex, - skinning_vertex: skinning_vertex, - skinnormal_vertex: skinnormal_vertex, - specularmap_fragment: specularmap_fragment, - specularmap_pars_fragment: specularmap_pars_fragment, - tonemapping_fragment: tonemapping_fragment, - tonemapping_pars_fragment: tonemapping_pars_fragment, - transmission_fragment: transmission_fragment, - transmission_pars_fragment: transmission_pars_fragment, - uv_pars_fragment: uv_pars_fragment, - uv_pars_vertex: uv_pars_vertex, - uv_vertex: uv_vertex, - worldpos_vertex: worldpos_vertex, - - background_vert: vertex$h, - background_frag: fragment$h, - backgroundCube_vert: vertex$g, - backgroundCube_frag: fragment$g, - cube_vert: vertex$f, - cube_frag: fragment$f, - depth_vert: vertex$e, - depth_frag: fragment$e, - distance_vert: vertex$d, - distance_frag: fragment$d, - equirect_vert: vertex$c, - equirect_frag: fragment$c, - linedashed_vert: vertex$b, - linedashed_frag: fragment$b, - meshbasic_vert: vertex$a, - meshbasic_frag: fragment$a, - meshlambert_vert: vertex$9, - meshlambert_frag: fragment$9, - meshmatcap_vert: vertex$8, - meshmatcap_frag: fragment$8, - meshnormal_vert: vertex$7, - meshnormal_frag: fragment$7, - meshphong_vert: vertex$6, - meshphong_frag: fragment$6, - meshphysical_vert: vertex$5, - meshphysical_frag: fragment$5, - meshtoon_vert: vertex$4, - meshtoon_frag: fragment$4, - points_vert: vertex$3, - points_frag: fragment$3, - shadow_vert: vertex$2, - shadow_frag: fragment$2, - sprite_vert: vertex$1, - sprite_frag: fragment$1 -}; - -// Uniforms library for shared webgl shaders -const UniformsLib = { - - common: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - - alphaTest: { value: 0 } - - }, - - specularmap: { - - specularMap: { value: null }, - specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - envmap: { - - envMap: { value: null }, - envMapRotation: { value: /*@__PURE__*/ new Matrix3() }, - reflectivity: { value: 1.0 }, // basic, lambert, phong - ior: { value: 1.5 }, // physical - refractionRatio: { value: 0.98 }, // basic, lambert, phong - dfgLUT: { value: null } // DFG LUT for physically-based rendering - - }, - - aomap: { - - aoMap: { value: null }, - aoMapIntensity: { value: 1 }, - aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - lightmap: { - - lightMap: { value: null }, - lightMapIntensity: { value: 1 }, - lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - bumpmap: { - - bumpMap: { value: null }, - bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - bumpScale: { value: 1 } - - }, - - normalmap: { - - normalMap: { value: null }, - normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } - - }, - - displacementmap: { - - displacementMap: { value: null }, - displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - displacementScale: { value: 1 }, - displacementBias: { value: 0 } - - }, - - emissivemap: { - - emissiveMap: { value: null }, - emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - metalnessmap: { - - metalnessMap: { value: null }, - metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - roughnessmap: { - - roughnessMap: { value: null }, - roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - gradientmap: { - - gradientMap: { value: null } - - }, - - fog: { - - fogDensity: { value: 0.00025 }, - fogNear: { value: 1 }, - fogFar: { value: 2000 }, - fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } - - }, - - lights: { - - ambientLightColor: { value: [] }, - - lightProbe: { value: [] }, - - sunLights: { value: [], properties: { - direction: {}, - color: {} - } }, - - sunLightShadows: { value: [], properties: { - shadowIntensity: 1, - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - sunShadowMatrix: { value: [] }, - sunShadowCascade: { value: [] }, - - directionalLights: { value: [], properties: { - direction: {}, - color: {} - } }, - - directionalLightShadows: { value: [], properties: { - shadowIntensity: 1, - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - directionalShadowMatrix: { value: [] }, - - spotLights: { value: [], properties: { - color: {}, - position: {}, - direction: {}, - distance: {}, - coneCos: {}, - penumbraCos: {}, - decay: {} - } }, - - spotLightShadows: { value: [], properties: { - shadowIntensity: 1, - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - spotLightMap: { value: [] }, - spotLightMatrix: { value: [] }, - - pointLights: { value: [], properties: { - color: {}, - position: {}, - decay: {}, - distance: {} - } }, - - pointLightShadows: { value: [], properties: { - shadowIntensity: 1, - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {}, - shadowCameraNear: {}, - shadowCameraFar: {} - } }, - - pointShadowMatrix: { value: [] }, - - hemisphereLights: { value: [], properties: { - direction: {}, - skyColor: {}, - groundColor: {} - } }, - - // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src - rectAreaLights: { value: [], properties: { - color: {}, - position: {}, - width: {}, - height: {} - } }, - - ltc_1: { value: null }, - ltc_2: { value: null }, - - probesSH: { value: null }, - probesMin: { value: /*@__PURE__*/ new Vector3() }, - probesMax: { value: /*@__PURE__*/ new Vector3() }, - probesResolution: { value: /*@__PURE__*/ new Vector3() } - - }, - - points: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - size: { value: 1.0 }, - scale: { value: 1.0 }, - map: { value: null }, - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaTest: { value: 0 }, - uvTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - sprite: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, - rotation: { value: 0.0 }, - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaTest: { value: 0 } - - } - -}; - -const ShaderLib = { - - basic: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.meshbasic_vert, - fragmentShader: ShaderChunk.meshbasic_frag - - }, - - lambert: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - envMapIntensity: { value: 1 } - } - ] ), - - vertexShader: ShaderChunk.meshlambert_vert, - fragmentShader: ShaderChunk.meshlambert_frag - - }, - - phong: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, - shininess: { value: 30 }, - envMapIntensity: { value: 1 } - } - ] ), - - vertexShader: ShaderChunk.meshphong_vert, - fragmentShader: ShaderChunk.meshphong_frag - - }, - - standard: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.roughnessmap, - UniformsLib.metalnessmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - roughness: { value: 1.0 }, - metalness: { value: 0.0 }, - envMapIntensity: { value: 1 } - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - - }, - - toon: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.gradientmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } - } - ] ), - - vertexShader: ShaderChunk.meshtoon_vert, - fragmentShader: ShaderChunk.meshtoon_frag - - }, - - matcap: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - { - matcap: { value: null } - } - ] ), - - vertexShader: ShaderChunk.meshmatcap_vert, - fragmentShader: ShaderChunk.meshmatcap_frag - - }, - - points: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.points, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.points_vert, - fragmentShader: ShaderChunk.points_frag - - }, - - dashed: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.fog, - { - scale: { value: 1 }, - dashSize: { value: 1 }, - totalSize: { value: 2 } - } - ] ), - - vertexShader: ShaderChunk.linedashed_vert, - fragmentShader: ShaderChunk.linedashed_frag - - }, - - depth: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap - ] ), - - vertexShader: ShaderChunk.depth_vert, - fragmentShader: ShaderChunk.depth_frag - - }, - - normal: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - { - opacity: { value: 1.0 } - } - ] ), - - vertexShader: ShaderChunk.meshnormal_vert, - fragmentShader: ShaderChunk.meshnormal_frag - - }, - - sprite: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.sprite, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.sprite_vert, - fragmentShader: ShaderChunk.sprite_frag - - }, - - background: { - - uniforms: { - uvTransform: { value: /*@__PURE__*/ new Matrix3() }, - t2D: { value: null }, - backgroundIntensity: { value: 1 } - }, - - vertexShader: ShaderChunk.background_vert, - fragmentShader: ShaderChunk.background_frag - - }, - - backgroundCube: { - - uniforms: { - envMap: { value: null }, - backgroundBlurriness: { value: 0 }, - backgroundIntensity: { value: 1 }, - backgroundRotation: { value: /*@__PURE__*/ new Matrix3() } - }, - - vertexShader: ShaderChunk.backgroundCube_vert, - fragmentShader: ShaderChunk.backgroundCube_frag - - }, - - cube: { - - uniforms: { - tCube: { value: null }, - tFlip: { value: -1 }, - opacity: { value: 1.0 } - }, - - vertexShader: ShaderChunk.cube_vert, - fragmentShader: ShaderChunk.cube_frag - - }, - - equirect: { - - uniforms: { - tEquirect: { value: null }, - }, - - vertexShader: ShaderChunk.equirect_vert, - fragmentShader: ShaderChunk.equirect_frag - - }, - - distance: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap, - { - referencePosition: { value: /*@__PURE__*/ new Vector3() }, - nearDistance: { value: 1 }, - farDistance: { value: 1000 } - } - ] ), - - vertexShader: ShaderChunk.distance_vert, - fragmentShader: ShaderChunk.distance_frag - - }, - - shadow: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.lights, - UniformsLib.fog, - { - color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, - opacity: { value: 1.0 } - }, - ] ), - - vertexShader: ShaderChunk.shadow_vert, - fragmentShader: ShaderChunk.shadow_frag - - } - -}; - -ShaderLib.physical = { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - ShaderLib.standard.uniforms, - { - clearcoat: { value: 0 }, - clearcoatMap: { value: null }, - clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalMap: { value: null }, - clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, - clearcoatRoughness: { value: 0 }, - clearcoatRoughnessMap: { value: null }, - clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - dispersion: { value: 0 }, - retroreflectivity: { value: 0 }, - iridescence: { value: 0 }, - iridescenceMap: { value: null }, - iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescenceIOR: { value: 1.3 }, - iridescenceThicknessMinimum: { value: 100 }, - iridescenceThicknessMaximum: { value: 400 }, - iridescenceThicknessMap: { value: null }, - iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheen: { value: 0 }, - sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - sheenColorMap: { value: null }, - sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheenRoughness: { value: 1 }, - sheenRoughnessMap: { value: null }, - sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmission: { value: 0 }, - transmissionMap: { value: null }, - transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, - transmissionSamplerMap: { value: null }, - thickness: { value: 0 }, - thicknessMap: { value: null }, - thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - attenuationDistance: { value: 0 }, - attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, - specularColorMap: { value: null }, - specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - specularIntensity: { value: 1 }, - specularIntensityMap: { value: null }, - specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, - anisotropyMap: { value: null }, - anisotropyMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - -}; - -const _rgb = { r: 0, b: 0, g: 0 }; -const _m1$1 = /*@__PURE__*/ new Matrix4(); -const _m$1 = /*@__PURE__*/ new Matrix3(); - -_m$1.set( -1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ); - -function WebGLBackground( renderer, environments, state, objects, alpha, premultipliedAlpha ) { - - const clearColor = new Color( 0x000000 ); - let clearAlpha = alpha === true ? 0 : 1; - - let planeMesh; - let boxMesh; - - let currentBackground = null; - let currentBackgroundVersion = 0; - let currentTonemapping = null; - - function getBackground( scene ) { - - let background = scene.isScene === true ? scene.background : null; - - if ( background && background.isTexture ) { - - const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background - background = environments.get( background, usePMREM ); - - } - - return background; - - } - - function render( scene ) { - - let forceClear = false; - const background = getBackground( scene ); - - if ( background === null ) { - - setClear( clearColor, clearAlpha ); - - } else if ( background && background.isColor ) { - - setClear( background, 1 ); - forceClear = true; - - } - - const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); - - if ( environmentBlendMode === 'additive' ) { - - state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); - - } else if ( environmentBlendMode === 'alpha-blend' ) { - - state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); - - } - - if ( renderer.autoClear || forceClear ) { - - // buffers might not be writable which is required to ensure a correct clear - - state.buffers.depth.setTest( true ); - state.buffers.depth.setMask( true ); - state.buffers.color.setMask( true ); - - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - - } - - } - - function addToRenderList( renderList, scene ) { - - const background = getBackground( scene ); - - if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { - - if ( boxMesh === undefined ) { - - boxMesh = new Mesh( - new BoxGeometry( 1, 1, 1 ), - new ShaderMaterial( { - name: 'BackgroundCubeMaterial', - uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), - vertexShader: ShaderLib.backgroundCube.vertexShader, - fragmentShader: ShaderLib.backgroundCube.fragmentShader, - side: BackSide, - depthTest: false, - depthWrite: false, - fog: false, - allowOverride: false - } ) - ); - - boxMesh.geometry.deleteAttribute( 'normal' ); - boxMesh.geometry.deleteAttribute( 'uv' ); - - boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - - this.matrixWorld.copyPosition( camera.matrixWorld ); - - }; - - // add "envMap" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( boxMesh.material, 'envMap', { - - get: function () { - - return this.uniforms.envMap.value; - - } - - } ); - - objects.update( boxMesh ); - - } - - - boxMesh.material.uniforms.envMap.value = background; - boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; - boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - - - // note: since the matrix is orthonormal, we can use the more-efficient transpose() in lieu of invert() - boxMesh.material.uniforms.backgroundRotation.value.setFromMatrix4( _m1$1.makeRotationFromEuler( scene.backgroundRotation ) ).transpose(); - - if ( background.isCubeTexture && background.isRenderTargetTexture === false ) { - - boxMesh.material.uniforms.backgroundRotation.value.premultiply( _m$1 ); - - } - - - boxMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; - - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { - - boxMesh.material.needsUpdate = true; - - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; - - } - - boxMesh.layers.enableAll(); - - // push to the pre-sorted opaque render list - renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); - - } else if ( background && background.isTexture ) { - - if ( planeMesh === undefined ) { - - planeMesh = new Mesh( - new PlaneGeometry( 2, 2 ), - new ShaderMaterial( { - name: 'BackgroundMaterial', - uniforms: cloneUniforms( ShaderLib.background.uniforms ), - vertexShader: ShaderLib.background.vertexShader, - fragmentShader: ShaderLib.background.fragmentShader, - side: FrontSide, - depthTest: false, - depthWrite: false, - fog: false, - allowOverride: false - } ) - ); - - planeMesh.geometry.deleteAttribute( 'normal' ); - - // add "map" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( planeMesh.material, 'map', { - - get: function () { - - return this.uniforms.t2D.value; - - } - - } ); - - objects.update( planeMesh ); - - } - - planeMesh.material.uniforms.t2D.value = background; - planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - planeMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; - - if ( background.matrixAutoUpdate === true ) { - - background.updateMatrix(); - - } - - planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); - - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { - - planeMesh.material.needsUpdate = true; - - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; - - } - - planeMesh.layers.enableAll(); - - // push to the pre-sorted opaque render list - renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); - - } - - } - - function setClear( color, alpha ) { - - color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); - - state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); - - } - - function dispose() { - - if ( boxMesh !== undefined ) { - - boxMesh.geometry.dispose(); - boxMesh.material.dispose(); - - boxMesh = undefined; - - } - - if ( planeMesh !== undefined ) { - - planeMesh.geometry.dispose(); - planeMesh.material.dispose(); - - planeMesh = undefined; - - } - - } - - return { - - getClearColor: function () { - - return clearColor; - - }, - setClearColor: function ( color, alpha = 1 ) { - - clearColor.set( color ); - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); - - }, - getClearAlpha: function () { - - return clearAlpha; - - }, - setClearAlpha: function ( alpha ) { - - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); - - }, - render: render, - addToRenderList: addToRenderList, - dispose: dispose - - }; - -} - -function WebGLBindingStates( gl, attributes ) { - - const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - - const bindingStates = {}; - - const defaultState = createBindingState( null ); - let currentState = defaultState; - let forceUpdate = false; - - function setup( object, material, program, geometry, index ) { - - let updateBuffers = false; - - const state = getBindingState( object, geometry, program, material ); - - if ( currentState !== state ) { - - currentState = state; - bindVertexArrayObject( currentState.object ); - - } - - updateBuffers = needsUpdate( object, geometry, program, index ); - - if ( updateBuffers ) saveCache( object, geometry, program, index ); - - if ( index !== null ) { - - attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); - - } - - if ( updateBuffers || forceUpdate ) { - - forceUpdate = false; - - setupVertexAttributes( object, material, program, geometry ); - - if ( index !== null ) { - - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); - - } - - } - - } - - function createVertexArrayObject() { - - return gl.createVertexArray(); - - } - - function bindVertexArrayObject( vao ) { - - return gl.bindVertexArray( vao ); - - } - - function deleteVertexArrayObject( vao ) { - - return gl.deleteVertexArray( vao ); - - } - - function getBindingState( object, geometry, program, material ) { - - const wireframe = ( material.wireframe === true ); - - let objectMap = bindingStates[ geometry.id ]; - - if ( objectMap === undefined ) { - - objectMap = {}; - bindingStates[ geometry.id ] = objectMap; - - } - - // Each InstancedMesh requires unique binding states because it contains instanced attributes. - const objectId = ( object.isInstancedMesh === true ) ? object.id : 0; - - let programMap = objectMap[ objectId ]; - - if ( programMap === undefined ) { - - programMap = {}; - objectMap[ objectId ] = programMap; - - } - - let stateMap = programMap[ program.id ]; - - if ( stateMap === undefined ) { - - stateMap = {}; - programMap[ program.id ] = stateMap; - - } - - let state = stateMap[ wireframe ]; - - if ( state === undefined ) { - - state = createBindingState( createVertexArrayObject() ); - stateMap[ wireframe ] = state; - - } - - return state; - - } - - function createBindingState( vao ) { - - const newAttributes = []; - const enabledAttributes = []; - const attributeDivisors = []; - - for ( let i = 0; i < maxVertexAttributes; i ++ ) { - - newAttributes[ i ] = 0; - enabledAttributes[ i ] = 0; - attributeDivisors[ i ] = 0; - - } - - return { - - // for backward compatibility on non-VAO support browser - geometry: null, - program: null, - wireframe: false, - - newAttributes: newAttributes, - enabledAttributes: enabledAttributes, - attributeDivisors: attributeDivisors, - object: vao, - attributes: {}, - index: null - - }; - - } - - function needsUpdate( object, geometry, program, index ) { - - const cachedAttributes = currentState.attributes; - const geometryAttributes = geometry.attributes; - - let attributesNum = 0; - - const programAttributes = program.getAttributes(); - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - const cachedAttribute = cachedAttributes[ name ]; - let geometryAttribute = geometryAttributes[ name ]; - - if ( geometryAttribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - - } - - if ( cachedAttribute === undefined ) return true; - - if ( cachedAttribute.attribute !== geometryAttribute ) return true; - - if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; - - attributesNum ++; - - } - - } - - if ( currentState.attributesNum !== attributesNum ) return true; - - if ( currentState.index !== index ) return true; - - return false; - - } - - function saveCache( object, geometry, program, index ) { - - const cache = {}; - const attributes = geometry.attributes; - let attributesNum = 0; - - const programAttributes = program.getAttributes(); - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - let attribute = attributes[ name ]; - - if ( attribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; - - } - - const data = {}; - data.attribute = attribute; - - if ( attribute && attribute.data ) { - - data.data = attribute.data; - - } - - cache[ name ] = data; - - attributesNum ++; - - } - - } - - currentState.attributes = cache; - currentState.attributesNum = attributesNum; - - currentState.index = index; - - } - - function initAttributes() { - - const newAttributes = currentState.newAttributes; - - for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { - - newAttributes[ i ] = 0; - - } - - } - - function enableAttribute( attribute ) { - - enableAttributeAndDivisor( attribute, 0 ); - - } - - function enableAttributeAndDivisor( attribute, meshPerAttribute ) { - - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - const attributeDivisors = currentState.attributeDivisors; - - newAttributes[ attribute ] = 1; - - if ( enabledAttributes[ attribute ] === 0 ) { - - gl.enableVertexAttribArray( attribute ); - enabledAttributes[ attribute ] = 1; - - } - - if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { - - gl.vertexAttribDivisor( attribute, meshPerAttribute ); - attributeDivisors[ attribute ] = meshPerAttribute; - - } - - } - - function disableUnusedAttributes() { - - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - - for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { - - if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { - - gl.disableVertexAttribArray( i ); - enabledAttributes[ i ] = 0; - - } - - } - - } - - function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { - - if ( integer === true ) { - - gl.vertexAttribIPointer( index, size, type, stride, offset ); - - } else { - - gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); - - } - - } - - function setupVertexAttributes( object, material, program, geometry ) { - - initAttributes(); - - const geometryAttributes = geometry.attributes; - - const programAttributes = program.getAttributes(); - - const materialDefaultAttributeValues = material.defaultAttributeValues; - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - let geometryAttribute = geometryAttributes[ name ]; - - if ( geometryAttribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - - } - - if ( geometryAttribute !== undefined ) { - - const normalized = geometryAttribute.normalized; - const size = geometryAttribute.itemSize; - - const attribute = attributes.get( geometryAttribute ); - - // TODO Attribute may not be available on context restore - - if ( attribute === undefined ) continue; - - const buffer = attribute.buffer; - const type = attribute.type; - const bytesPerElement = attribute.bytesPerElement; - - // check for integer attributes - - const integer = ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ); - - if ( geometryAttribute.isInterleavedBufferAttribute ) { - - const data = geometryAttribute.data; - const stride = data.stride; - const offset = geometryAttribute.offset; - - if ( data.isInstancedInterleavedBuffer ) { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); - - } - - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - - geometry._maxInstanceCount = data.meshPerAttribute * data.count; - - } - - } else { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttribute( programAttribute.location + i ); - - } - - } - - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - stride * bytesPerElement, - ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, - integer - ); - - } - - } else { - - if ( geometryAttribute.isInstancedBufferAttribute ) { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); - - } - - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - - geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - - } - - } else { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttribute( programAttribute.location + i ); - - } - - } - - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - size * bytesPerElement, - ( size / programAttribute.locationSize ) * i * bytesPerElement, - integer - ); - - } - - } - - } else if ( materialDefaultAttributeValues !== undefined ) { - - const value = materialDefaultAttributeValues[ name ]; - - if ( value !== undefined ) { - - switch ( value.length ) { - - case 2: - gl.vertexAttrib2fv( programAttribute.location, value ); - break; - - case 3: - gl.vertexAttrib3fv( programAttribute.location, value ); - break; - - case 4: - gl.vertexAttrib4fv( programAttribute.location, value ); - break; - - default: - gl.vertexAttrib1fv( programAttribute.location, value ); - - } - - } - - } - - } - - } - - disableUnusedAttributes(); - - } - - function dispose() { - - reset(); - - for ( const geometryId in bindingStates ) { - - const objectMap = bindingStates[ geometryId ]; - - for ( const objectId in objectMap ) { - - const programMap = objectMap[ objectId ]; - - for ( const programId in programMap ) { - - const stateMap = programMap[ programId ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ programId ]; - - } - - } - - delete bindingStates[ geometryId ]; - - } - - } - - function releaseStatesOfGeometry( geometry ) { - - if ( bindingStates[ geometry.id ] === undefined ) return; - - const objectMap = bindingStates[ geometry.id ]; - - for ( const objectId in objectMap ) { - - const programMap = objectMap[ objectId ]; - - for ( const programId in programMap ) { - - const stateMap = programMap[ programId ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ programId ]; - - } - - } - - delete bindingStates[ geometry.id ]; - - } - - function releaseStatesOfProgram( program ) { - - for ( const geometryId in bindingStates ) { - - const objectMap = bindingStates[ geometryId ]; - - for ( const objectId in objectMap ) { - - const programMap = objectMap[ objectId ]; - - if ( programMap[ program.id ] === undefined ) continue; - - const stateMap = programMap[ program.id ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ program.id ]; - - } - - } - - } - - function releaseStatesOfObject( object ) { - - for ( const geometryId in bindingStates ) { - - const objectMap = bindingStates[ geometryId ]; - - const objectId = ( object.isInstancedMesh === true ) ? object.id : 0; - - const programMap = objectMap[ objectId ]; - - if ( programMap === undefined ) continue; - - for ( const programId in programMap ) { - - const stateMap = programMap[ programId ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ programId ]; - - } - - delete objectMap[ objectId ]; - - if ( Object.keys( objectMap ).length === 0 ) { - - delete bindingStates[ geometryId ]; - - } - - } - - } - - - function reset() { - - resetDefaultState(); - forceUpdate = true; - - if ( currentState === defaultState ) return; - - currentState = defaultState; - bindVertexArrayObject( currentState.object ); - - } - - // for backward-compatibility - - function resetDefaultState() { - - defaultState.geometry = null; - defaultState.program = null; - defaultState.wireframe = false; - - } - - return { - - setup: setup, - reset: reset, - resetDefaultState: resetDefaultState, - dispose: dispose, - releaseStatesOfGeometry: releaseStatesOfGeometry, - releaseStatesOfObject: releaseStatesOfObject, - releaseStatesOfProgram: releaseStatesOfProgram, - - initAttributes: initAttributes, - enableAttribute: enableAttribute, - disableUnusedAttributes: disableUnusedAttributes - - }; - -} - -function WebGLBufferRenderer( gl, extensions, info ) { - - let mode; - - function setMode( value ) { - - mode = value; - - } - - function render( start, count ) { - - gl.drawArrays( mode, start, count ); - - info.update( count, mode, 1 ); - - } - - function renderInstances( start, count, primcount ) { - - if ( primcount === 0 ) return; - - gl.drawArraysInstanced( mode, start, count, primcount ); - - info.update( count, mode, primcount ); - - } - - function renderMultiDraw( starts, counts, drawCount ) { - - if ( drawCount === 0 ) return; - - const extension = extensions.get( 'WEBGL_multi_draw' ); - extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); - - let elementCount = 0; - for ( let i = 0; i < drawCount; i ++ ) { - - elementCount += counts[ i ]; - - } - - info.update( elementCount, mode, 1 ); - - } - - // - - this.setMode = setMode; - this.render = render; - this.renderInstances = renderInstances; - this.renderMultiDraw = renderMultiDraw; - -} - -function WebGLCapabilities( gl, extensions, parameters, utils ) { - - let maxAnisotropy; - - function getMaxAnisotropy() { - - if ( maxAnisotropy !== undefined ) return maxAnisotropy; - - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - - maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - - } else { - - maxAnisotropy = 0; - - } - - return maxAnisotropy; - - } - - function textureFormatReadable( textureFormat ) { - - if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - - return false; - - } - - return true; - - } - - function textureTypeReadable( textureType ) { - - const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ); - - if ( textureType !== UnsignedByteType && textureType !== FloatType && ! halfFloatSupportedByExt && - utils.convert( textureType ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ) ) { // Edge and Chrome Mac < 52 (#9513) - - return false; - - } - - return true; - - } - - function getMaxPrecision( precision ) { - - if ( precision === 'highp' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - - return 'highp'; - - } - - precision = 'mediump'; - - } - - if ( precision === 'mediump' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - - return 'mediump'; - - } - - } - - return 'lowp'; - - } - - let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; - const maxPrecision = getMaxPrecision( precision ); - - if ( maxPrecision !== precision ) { - - warn( 'WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); - precision = maxPrecision; - - } - - const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; - const reversedDepthBuffer = parameters.reversedDepthBuffer === true && extensions.has( 'EXT_clip_control' ); - - if ( parameters.reversedDepthBuffer === true && reversedDepthBuffer === false ) { - - warn( 'WebGLRenderer: Unable to use reversed depth buffer due to missing EXT_clip_control extension. Fallback to default depth buffer.' ); - - } - - const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); - const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); - const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); - const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - - const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); - const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); - const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); - - const maxSamples = gl.getParameter( gl.MAX_SAMPLES ); - const samples = gl.getParameter( gl.SAMPLES ); - - return { - - isWebGL2: true, // keeping this for backwards compatibility - - getMaxAnisotropy: getMaxAnisotropy, - getMaxPrecision: getMaxPrecision, - - textureFormatReadable: textureFormatReadable, - textureTypeReadable: textureTypeReadable, - - precision: precision, - logarithmicDepthBuffer: logarithmicDepthBuffer, - reversedDepthBuffer: reversedDepthBuffer, - - maxTextures: maxTextures, - maxVertexTextures: maxVertexTextures, - maxTextureSize: maxTextureSize, - maxCubemapSize: maxCubemapSize, - - maxAttributes: maxAttributes, - maxVertexUniforms: maxVertexUniforms, - maxVaryings: maxVaryings, - maxFragmentUniforms: maxFragmentUniforms, - - maxSamples: maxSamples, - - samples: samples - - }; - -} - -function WebGLClipping( properties ) { - - const scope = this; - - let globalState = null, - numGlobalPlanes = 0, - localClippingEnabled = false, - renderingShadows = false; - - const plane = new Plane(), - viewNormalMatrix = new Matrix3(), - - uniform = { value: null, needsUpdate: false }; - - this.uniform = uniform; - this.numPlanes = 0; - this.numIntersection = 0; - - this.init = function ( planes, enableLocalClipping ) { - - const enabled = - planes.length !== 0 || - enableLocalClipping || - // enable state of previous frame - the clipping code has to - // run another frame in order to reset the state: - numGlobalPlanes !== 0 || - localClippingEnabled; - - localClippingEnabled = enableLocalClipping; - - numGlobalPlanes = planes.length; - - return enabled; - - }; - - this.beginShadows = function () { - - renderingShadows = true; - projectPlanes( null ); - - }; - - this.endShadows = function () { - - renderingShadows = false; - - }; - - this.setGlobalState = function ( planes, camera ) { - - globalState = projectPlanes( planes, camera, 0 ); - - }; - - this.setState = function ( material, camera, useCache ) { - - const planes = material.clippingPlanes, - clipIntersection = material.clipIntersection, - clipShadows = material.clipShadows; - - const materialProperties = properties.get( material ); - - if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { - - // there's no local clipping - - if ( renderingShadows ) { - - // there's no global clipping - - projectPlanes( null ); - - } else { - - resetGlobalState(); - - } - - } else { - - const nGlobal = renderingShadows ? 0 : numGlobalPlanes, - lGlobal = nGlobal * 4; - - let dstArray = materialProperties.clippingState || null; - - uniform.value = dstArray; // ensure unique state - - dstArray = projectPlanes( planes, camera, lGlobal, useCache ); - - for ( let i = 0; i !== lGlobal; ++ i ) { - - dstArray[ i ] = globalState[ i ]; - - } - - materialProperties.clippingState = dstArray; - this.numIntersection = clipIntersection ? this.numPlanes : 0; - this.numPlanes += nGlobal; - - } - - - }; - - function resetGlobalState() { - - if ( uniform.value !== globalState ) { - - uniform.value = globalState; - uniform.needsUpdate = numGlobalPlanes > 0; - - } - - scope.numPlanes = numGlobalPlanes; - scope.numIntersection = 0; - - } - - function projectPlanes( planes, camera, dstOffset, skipTransform ) { - - const nPlanes = planes !== null ? planes.length : 0; - let dstArray = null; - - if ( nPlanes !== 0 ) { - - dstArray = uniform.value; - - if ( skipTransform !== true || dstArray === null ) { - - const flatSize = dstOffset + nPlanes * 4, - viewMatrix = camera.matrixWorldInverse; - - viewNormalMatrix.getNormalMatrix( viewMatrix ); - - if ( dstArray === null || dstArray.length < flatSize ) { - - dstArray = new Float32Array( flatSize ); - - } - - for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { - - plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); - - plane.normal.toArray( dstArray, i4 ); - dstArray[ i4 + 3 ] = plane.constant; - - } - - } - - uniform.value = dstArray; - uniform.needsUpdate = true; - - } - - scope.numPlanes = nPlanes; - scope.numIntersection = 0; - - return dstArray; - - } - -} - -const _position$1 = /*@__PURE__*/ new Vector3(); -const _quaternion = /*@__PURE__*/ new Quaternion(); -const _scale = /*@__PURE__*/ new Vector3(); - -/** - * Abstract base class for cameras. This class should always be inherited - * when you build a new camera. - * - * @abstract - * @augments Object3D - */ -class Camera extends Object3D { - - /** - * Constructs a new camera. - */ - constructor() { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCamera = true; - - this.type = 'Camera'; - - /** - * The inverse of the camera's world matrix. - * - * @type {Matrix4} - */ - this.matrixWorldInverse = new Matrix4(); - - /** - * The camera's projection matrix. - * - * @type {Matrix4} - */ - this.projectionMatrix = new Matrix4(); - - /** - * The inverse of the camera's projection matrix. - * - * @type {Matrix4} - */ - this.projectionMatrixInverse = new Matrix4(); - - /** - * The coordinate system in which the camera is used. - * - * @type {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} - */ - this.coordinateSystem = WebGLCoordinateSystem; - - this._reversedDepth = false; - - } - - /** - * The flag that indicates whether the camera uses a reversed depth buffer. - * - * @type {boolean} - * @default false - */ - get reversedDepth() { - - return this._reversedDepth; - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.matrixWorldInverse.copy( source.matrixWorldInverse ); - - this.projectionMatrix.copy( source.projectionMatrix ); - this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); - - this.coordinateSystem = source.coordinateSystem; - - return this; - - } - - /** - * Returns a vector representing the ("look") direction of the 3D object in world space. - * - * This method is overwritten since cameras have a different forward vector compared to other - * 3D objects. A camera looks down its local, negative z-axis by default. - * - * @param {Vector3} target - The target vector the result is stored to. - * @return {Vector3} The 3D object's direction in world space. - */ - getWorldDirection( target ) { - - return super.getWorldDirection( target ).negate(); - - } - - updateMatrixWorld( force ) { - - super.updateMatrixWorld( force ); - - // exclude scale from view matrix to be glTF conform - - this.matrixWorld.decompose( _position$1, _quaternion, _scale ); - - if ( _scale.x === 1 && _scale.y === 1 && _scale.z === 1 ) { - - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - - } else { - - this.matrixWorldInverse.compose( _position$1, _quaternion, _scale.set( 1, 1, 1 ) ).invert(); - - } - - } - - updateWorldMatrix( updateParents, updateChildren, force = false ) { - - super.updateWorldMatrix( updateParents, updateChildren, force ); - - // exclude scale from view matrix to be glTF conform - - this.matrixWorld.decompose( _position$1, _quaternion, _scale ); - - if ( _scale.x === 1 && _scale.y === 1 && _scale.z === 1 ) { - - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - - } else { - - this.matrixWorldInverse.compose( _position$1, _quaternion, _scale.set( 1, 1, 1 ) ).invert(); - - } - - } - - clone() { - - return new this.constructor().copy( this ); - - } - -} - -/** - * Camera that uses [orthographic projection](https://en.wikipedia.org/wiki/Orthographic_projection). - * - * In this projection mode, an object's size in the rendered image stays - * constant regardless of its distance from the camera. This can be useful - * for rendering 2D scenes and UI elements, amongst other things. - * - * ```js - * const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 ); - * scene.add( camera ); - * ``` - * - * @augments Camera - */ -class OrthographicCamera extends Camera { - - /** - * Constructs a new orthographic camera. - * - * @param {number} [left=-1] - The left plane of the camera's frustum. - * @param {number} [right=1] - The right plane of the camera's frustum. - * @param {number} [top=1] - The top plane of the camera's frustum. - * @param {number} [bottom=-1] - The bottom plane of the camera's frustum. - * @param {number} [near=0.1] - The camera's near plane. - * @param {number} [far=2000] - The camera's far plane. - */ - constructor( left = -1, right = 1, top = 1, bottom = -1, near = 0.1, far = 2000 ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isOrthographicCamera = true; - - this.type = 'OrthographicCamera'; - - /** - * The zoom factor of the camera. - * - * @type {number} - * @default 1 - */ - this.zoom = 1; - - /** - * Represents the frustum window specification. This property should not be edited - * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. - * - * @type {?Object} - * @default null - */ - this.view = null; - - /** - * The left plane of the camera's frustum. - * - * @type {number} - * @default -1 - */ - this.left = left; - - /** - * The right plane of the camera's frustum. - * - * @type {number} - * @default 1 - */ - this.right = right; - - /** - * The top plane of the camera's frustum. - * - * @type {number} - * @default 1 - */ - this.top = top; - - /** - * The bottom plane of the camera's frustum. - * - * @type {number} - * @default -1 - */ - this.bottom = bottom; - - /** - * The camera's near plane. The valid range is greater than `0` - * and less than the current value of {@link OrthographicCamera#far}. - * - * Note that, unlike for the {@link PerspectiveCamera}, `0` is a - * valid value for an orthographic camera's near plane. - * - * @type {number} - * @default 0.1 - */ - this.near = near; - - /** - * The camera's far plane. Must be greater than the - * current value of {@link OrthographicCamera#near}. - * - * @type {number} - * @default 2000 - */ - this.far = far; - - this.updateProjectionMatrix(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.left = source.left; - this.right = source.right; - this.top = source.top; - this.bottom = source.bottom; - this.near = source.near; - this.far = source.far; - - this.zoom = source.zoom; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - return this; - - } - - /** - * Sets an offset in a larger frustum. This is useful for multi-window or - * multi-monitor/multi-machine setups. - * - * @param {number} fullWidth - The full width of multiview setup. - * @param {number} fullHeight - The full height of multiview setup. - * @param {number} x - The horizontal offset of the subcamera. - * @param {number} y - The vertical offset of the subcamera. - * @param {number} width - The width of subcamera. - * @param {number} height - The height of subcamera. - * @see {@link PerspectiveCamera#setViewOffset} - */ - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - } - - /** - * Removes the view offset from the projection matrix. - */ - clearViewOffset() { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - } - - /** - * Updates the camera's projection matrix. Must be called after any change of - * camera properties. - */ - updateProjectionMatrix() { - - const dx = ( this.right - this.left ) / ( 2 * this.zoom ); - const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); - const cx = ( this.right + this.left ) / 2; - const cy = ( this.top + this.bottom ) / 2; - - let left = cx - dx; - let right = cx + dx; - let top = cy + dy; - let bottom = cy - dy; - - if ( this.view !== null && this.view.enabled ) { - - const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; - const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; - - left += scaleW * this.view.offsetX; - right = left + scaleW * this.view.width; - top -= scaleH * this.view.offsetY; - bottom = top - scaleH * this.view.height; - - } - - this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem, this.reversedDepth ); - - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.zoom = this.zoom; - data.object.left = this.left; - data.object.right = this.right; - data.object.top = this.top; - data.object.bottom = this.bottom; - data.object.near = this.near; - data.object.far = this.far; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - return data; - - } - -} - -const _v3 = /*@__PURE__*/ new Vector3(); -const _minTarget = /*@__PURE__*/ new Vector2(); -const _maxTarget = /*@__PURE__*/ new Vector2(); - -/** - * Camera that uses [perspective projection](https://en.wikipedia.org/wiki/Perspective_(graphical)). - * - * This projection mode is designed to mimic the way the human eye sees. It - * is the most common projection mode used for rendering a 3D scene. - * - * ```js - * const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 ); - * scene.add( camera ); - * ``` - * - * @augments Camera - */ -class PerspectiveCamera extends Camera { - - /** - * Constructs a new perspective camera. - * - * @param {number} [fov=50] - The vertical field of view. - * @param {number} [aspect=1] - The aspect ratio. - * @param {number} [near=0.1] - The camera's near plane. - * @param {number} [far=2000] - The camera's far plane. - */ - constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isPerspectiveCamera = true; - - this.type = 'PerspectiveCamera'; - - /** - * The vertical field of view, from bottom to top of view, - * in degrees. - * - * @type {number} - * @default 50 - */ - this.fov = fov; - - /** - * The zoom factor of the camera. - * - * @type {number} - * @default 1 - */ - this.zoom = 1; - - /** - * The camera's near plane. The valid range is greater than `0` - * and less than the current value of {@link PerspectiveCamera#far}. - * - * Note that, unlike for the {@link OrthographicCamera}, `0` is not a - * valid value for a perspective camera's near plane. - * - * @type {number} - * @default 0.1 - */ - this.near = near; - - /** - * The camera's far plane. Must be greater than the - * current value of {@link PerspectiveCamera#near}. - * - * @type {number} - * @default 2000 - */ - this.far = far; - - /** - * Object distance used for stereoscopy and depth-of-field effects. This - * parameter does not influence the projection matrix unless a - * {@link StereoCamera} is being used. - * - * @type {number} - * @default 10 - */ - this.focus = 10; - - /** - * The aspect ratio, usually the canvas width / canvas height. - * - * @type {number} - * @default 1 - */ - this.aspect = aspect; - - /** - * Represents the frustum window specification. This property should not be edited - * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. - * - * @type {?Object} - * @default null - */ - this.view = null; - - /** - * Film size used for the larger axis. Default is `35` (millimeters). This - * parameter does not influence the projection matrix unless {@link PerspectiveCamera#filmOffset} - * is set to a nonzero value. - * - * @type {number} - * @default 35 - */ - this.filmGauge = 35; - - /** - * Horizontal off-center offset in the same unit as {@link PerspectiveCamera#filmGauge}. - * - * @type {number} - * @default 0 - */ - this.filmOffset = 0; - - this.updateProjectionMatrix(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.fov = source.fov; - this.zoom = source.zoom; - - this.near = source.near; - this.far = source.far; - this.focus = source.focus; - - this.aspect = source.aspect; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - this.filmGauge = source.filmGauge; - this.filmOffset = source.filmOffset; - - return this; - - } - - /** - * Sets the FOV by focal length in respect to the current {@link PerspectiveCamera#filmGauge}. - * - * The default film gauge is 35, so that the focal length can be specified for - * a 35mm (full frame) camera. - * - * @param {number} focalLength - Values for focal length and film gauge must have the same unit. - */ - setFocalLength( focalLength ) { - - /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ - const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; - - this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); - this.updateProjectionMatrix(); - - } - - /** - * Returns the focal length from the current {@link PerspectiveCamera#fov} and - * {@link PerspectiveCamera#filmGauge}. - * - * @return {number} The computed focal length. - */ - getFocalLength() { - - const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); - - return 0.5 * this.getFilmHeight() / vExtentSlope; - - } - - /** - * Returns the current vertical field of view angle in degrees considering {@link PerspectiveCamera#zoom}. - * - * @return {number} The effective FOV. - */ - getEffectiveFOV() { - - return RAD2DEG * 2 * Math.atan( - Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); - - } - - /** - * Returns the width of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or - * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. - * - * @return {number} The film width. - */ - getFilmWidth() { - - // film not completely covered in portrait format (aspect < 1) - return this.filmGauge * Math.min( this.aspect, 1 ); - - } - - /** - * Returns the height of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or - * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. - * - * @return {number} The film width. - */ - getFilmHeight() { - - // film not completely covered in landscape format (aspect > 1) - return this.filmGauge / Math.max( this.aspect, 1 ); - - } - - /** - * Computes the 2D bounds of the camera's viewable rectangle at a given distance along the viewing direction. - * Sets `minTarget` and `maxTarget` to the coordinates of the lower-left and upper-right corners of the view rectangle. - * - * @param {number} distance - The viewing distance. - * @param {Vector2} minTarget - The lower-left corner of the view rectangle is written into this vector. - * @param {Vector2} maxTarget - The upper-right corner of the view rectangle is written into this vector. - */ - getViewBounds( distance, minTarget, maxTarget ) { - - _v3.set( -1, -1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); - - minTarget.set( _v3.x, _v3.y ).multiplyScalar( - distance / _v3.z ); - - _v3.set( 1, 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); - - maxTarget.set( _v3.x, _v3.y ).multiplyScalar( - distance / _v3.z ); - - } - - /** - * Computes the width and height of the camera's viewable rectangle at a given distance along the viewing direction. - * - * @param {number} distance - The viewing distance. - * @param {Vector2} target - The target vector that is used to store result where x is width and y is height. - * @returns {Vector2} The view size. - */ - getViewSize( distance, target ) { - - this.getViewBounds( distance, _minTarget, _maxTarget ); - - return target.subVectors( _maxTarget, _minTarget ); - - } - - /** - * Sets an offset in a larger frustum. This is useful for multi-window or - * multi-monitor/multi-machine setups. - * - * For example, if you have 3x2 monitors and each monitor is 1920x1080 and - * the monitors are in grid like this - *``` - * +---+---+---+ - * | A | B | C | - * +---+---+---+ - * | D | E | F | - * +---+---+---+ - *``` - * then for each monitor you would call it like this: - *```js - * const w = 1920; - * const h = 1080; - * const fullWidth = w * 3; - * const fullHeight = h * 2; - * - * // --A-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); - * // --B-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); - * // --C-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); - * // --D-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); - * // --E-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); - * // --F-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); - * ``` - * - * Note there is no reason monitors have to be the same size or in a grid. - * - * @param {number} fullWidth - The full width of multiview setup. - * @param {number} fullHeight - The full height of multiview setup. - * @param {number} x - The horizontal offset of the subcamera. - * @param {number} y - The vertical offset of the subcamera. - * @param {number} width - The width of subcamera. - * @param {number} height - The height of subcamera. - */ - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - - this.aspect = fullWidth / fullHeight; - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - } - - /** - * Removes the view offset from the projection matrix. - */ - clearViewOffset() { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - } - - /** - * Updates the camera's projection matrix. Must be called after any change of - * camera properties. - */ - updateProjectionMatrix() { - - const near = this.near; - let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; - let height = 2 * top; - let width = this.aspect * height; - let left = -0.5 * width; - const view = this.view; - - if ( this.view !== null && this.view.enabled ) { - - const fullWidth = view.fullWidth, - fullHeight = view.fullHeight; - - left += view.offsetX * width / fullWidth; - top -= view.offsetY * height / fullHeight; - width *= view.width / fullWidth; - height *= view.height / fullHeight; - - } - - const skew = this.filmOffset; - if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - - this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem, this.reversedDepth ); - - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.fov = this.fov; - data.object.zoom = this.zoom; - - data.object.near = this.near; - data.object.far = this.far; - data.object.focus = this.focus; - - data.object.aspect = this.aspect; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - data.object.filmGauge = this.filmGauge; - data.object.filmOffset = this.filmOffset; - - return data; - - } - -} - -const LOD_MIN = 4; - -// The number of extra mips. -// Used for scene blur in fromScene() method. -const EXTRA_LODS = 6; - -// The number of spiral samples per blur pass. -// Used for scene blur in fromScene() method. -const BLUR_SAMPLES = 20; - -// GGX VNDF importance sampling configuration -const GGX_SAMPLES = 256; - -const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); -const _clearColor = /*@__PURE__*/ new Color(); -let _oldTarget = null; -let _oldActiveCubeFace = 0; -let _oldActiveMipmapLevel = 0; -let _oldXrEnabled = false; - -const _origin = /*@__PURE__*/ new Vector3(); -const _direction = /*@__PURE__*/ new Vector3(); - -/** - * This class generates a Prefiltered, Mipmapped Radiance Environment Map - * (PMREM) from a cubeMap environment texture. This allows different levels of - * blur to be quickly accessed based on material roughness. It is packed into a - * special CubeUV format that allows us to perform custom interpolation so that - * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap - * chain, it only goes down to the LOD_MIN level (above), and then creates extra - * even more filtered 'mips' at the same LOD_MIN resolution, associated with - * higher roughness levels. In this way we maintain resolution to smoothly - * interpolate diffuse lighting while limiting sampling computation. - * - * The prefiltering uses GGX VNDF (Visible Normal Distribution Function) - * importance sampling based on "Sampling the GGX Distribution of Visible Normals" - * (Heitz, 2018) to generate environment maps that accurately match the GGX BRDF - * used in material rendering for physically-based image-based lighting. - */ -class PMREMGenerator { - - /** - * Constructs a new PMREM generator. - * - * @param {WebGLRenderer} renderer - The renderer. - */ - constructor( renderer ) { - - this._renderer = renderer; - this._pingPongRenderTarget = null; - - this._lodMax = 0; - this._cubeSize = 0; - this._sizeLods = []; - this._lodMeshes = []; - - this._backgroundBox = null; - - this._cubemapMaterial = null; - this._equirectMaterial = null; - - this._blurMaterial = null; - this._ggxMaterial = null; - - } - - /** - * Generates a PMREM from a supplied Scene, which can be faster than using an - * image if networking bandwidth is low. Optional sigma specifies a blur radius - * in radians to be applied to the scene before PMREM generation. Optional near - * and far planes ensure the scene is rendered in its entirety. - * - * @param {Scene} scene - The scene to be captured. - * @param {number} [sigma=0] - The blur radius in radians. - * @param {number} [near=0.1] - The near plane distance. - * @param {number} [far=100] - The far plane distance. - * @param {Object} [options={}] - The configuration options. - * @param {number} [options.size=256] - The texture size of the PMREM. - * @param {Vector3} [options.position=origin] - The position of the internal cube camera that renders the scene. - * @return {WebGLRenderTarget} The resulting PMREM. - */ - fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { - - const { - size = 256, - position = _origin, - } = options; - - _oldTarget = this._renderer.getRenderTarget(); - _oldActiveCubeFace = this._renderer.getActiveCubeFace(); - _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); - _oldXrEnabled = this._renderer.xr.enabled; - - this._renderer.xr.enabled = false; - - this._setSize( size ); - - const cubeUVRenderTarget = this._allocateTargets(); - cubeUVRenderTarget.depthBuffer = true; - - this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ); - - if ( sigma > 0 ) { - - this._blur( cubeUVRenderTarget, 0, 0, sigma ); - - } - - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); - - return cubeUVRenderTarget; - - } - - /** - * Generates a PMREM from an equirectangular texture, which can be either LDR - * or HDR. The ideal input image size is 1k (1024 x 512), as this matches best - * with the 256 x 256 cubemap output. The minimum supported input image size - * is 64 x 32. - * - * @param {Texture} equirectangular - The equirectangular texture to be converted. - * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. - * @return {WebGLRenderTarget} The resulting PMREM. - */ - fromEquirectangular( equirectangular, renderTarget = null ) { - - return this._fromTexture( equirectangular, renderTarget ); - - } - - /** - * Generates a PMREM from an cubemap texture, which can be either LDR - * or HDR. The ideal input cube size is 256 x 256, as this matches best - * with the 256 x 256 cubemap output. The minimum supported input cube - * size is 16 x 16 per face. - * - * @param {Texture} cubemap - The cubemap texture to be converted. - * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. - * @return {WebGLRenderTarget} The resulting PMREM. - */ - fromCubemap( cubemap, renderTarget = null ) { - - return this._fromTexture( cubemap, renderTarget ); - - } - - /** - * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileCubemapShader() { - - if ( this._cubemapMaterial === null ) { - - this._cubemapMaterial = _getCubemapMaterial(); - this._compileMaterial( this._cubemapMaterial ); - - } - - } - - /** - * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileEquirectangularShader() { - - if ( this._equirectMaterial === null ) { - - this._equirectMaterial = _getEquirectMaterial(); - this._compileMaterial( this._equirectMaterial ); - - } - - } - - /** - * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, - * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on - * one of them will cause any others to also become unusable. - */ - dispose() { - - this._dispose(); - - if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); - if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); - - if ( this._backgroundBox !== null ) { - - this._backgroundBox.geometry.dispose(); - this._backgroundBox.material.dispose(); - - } - - } - - // private interface - - _setSize( cubeSize ) { - - this._lodMax = Math.floor( Math.log2( cubeSize ) ); - this._cubeSize = Math.pow( 2, this._lodMax ); - - } - - _dispose() { - - if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); - if ( this._ggxMaterial !== null ) this._ggxMaterial.dispose(); - - if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); - - for ( let i = 0; i < this._lodMeshes.length; i ++ ) { - - this._lodMeshes[ i ].geometry.dispose(); - - } - - } - - _cleanup( outputTarget ) { - - this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); - this._renderer.xr.enabled = _oldXrEnabled; - - outputTarget.scissorTest = false; - _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); - - } - - _fromTexture( texture, renderTarget ) { - - if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { - - this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); - - } else { // Equirectangular - - this._setSize( texture.image.width / 4 ); - - } - - _oldTarget = this._renderer.getRenderTarget(); - _oldActiveCubeFace = this._renderer.getActiveCubeFace(); - _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); - _oldXrEnabled = this._renderer.xr.enabled; - - this._renderer.xr.enabled = false; - - const cubeUVRenderTarget = renderTarget || this._allocateTargets(); - this._textureToCubeUV( texture, cubeUVRenderTarget ); - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); - - return cubeUVRenderTarget; - - } - - _allocateTargets() { - - const width = 3 * Math.max( this._cubeSize, 16 * 7 ); - const height = 4 * this._cubeSize; - - const params = { - magFilter: LinearFilter, - minFilter: LinearFilter, - generateMipmaps: false, - type: HalfFloatType, - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - depthBuffer: false - }; - - const cubeUVRenderTarget = _createRenderTarget( width, height, params ); - - if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { - - if ( this._pingPongRenderTarget !== null ) { - - this._dispose(); - - } - - this._pingPongRenderTarget = _createRenderTarget( width, height, params ); - - const { _lodMax } = this; - ( { lodMeshes: this._lodMeshes, sizeLods: this._sizeLods } = _createPlanes( _lodMax ) ); - - this._blurMaterial = _getBlurShader( _lodMax, width, height ); - this._ggxMaterial = _getGGXShader( _lodMax, width, height ); - - } - - return cubeUVRenderTarget; - - } - - _compileMaterial( material ) { - - const mesh = new Mesh( new BufferGeometry(), material ); - this._renderer.compile( mesh, _flatCamera ); - - } - - _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) { - - const fov = 90; - const aspect = 1; - const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); - const upSign = [ 1, -1, 1, 1, 1, 1 ]; - const forwardSign = [ 1, 1, 1, -1, -1, -1 ]; - const renderer = this._renderer; - - const originalAutoClear = renderer.autoClear; - const toneMapping = renderer.toneMapping; - renderer.getClearColor( _clearColor ); - - renderer.toneMapping = NoToneMapping; - renderer.autoClear = false; - - // https://github.com/mrdoob/three.js/issues/31413#issuecomment-3095966812 - const reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); - - if ( reversedDepthBuffer ) { - - renderer.setRenderTarget( cubeUVRenderTarget ); - renderer.clearDepth(); - renderer.setRenderTarget( null ); - - } - - if ( this._backgroundBox === null ) { - - this._backgroundBox = new Mesh( - new BoxGeometry(), - new MeshBasicMaterial( { - name: 'PMREM.Background', - side: BackSide, - depthWrite: false, - depthTest: false, - } ) - ); - - } - - const backgroundBox = this._backgroundBox; - const backgroundMaterial = backgroundBox.material; - - let useSolidColor = false; - - const background = scene.background; - - if ( background ) { - - if ( background.isColor ) { - - backgroundMaterial.color.copy( background ); - scene.background = null; - useSolidColor = true; - - } - - } else { - - backgroundMaterial.color.copy( _clearColor ); - useSolidColor = true; - - } - - for ( let i = 0; i < 6; i ++ ) { - - const col = i % 3; - - if ( col === 0 ) { - - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.position.set( position.x, position.y, position.z ); - cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z ); - - } else if ( col === 1 ) { - - cubeCamera.up.set( 0, 0, upSign[ i ] ); - cubeCamera.position.set( position.x, position.y, position.z ); - cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z ); - - - } else { - - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.position.set( position.x, position.y, position.z ); - cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] ); - - } - - const size = this._cubeSize; - - _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); - - renderer.setRenderTarget( cubeUVRenderTarget ); - - if ( useSolidColor ) { - - renderer.render( backgroundBox, cubeCamera ); - - } - - renderer.render( scene, cubeCamera ); - - } - - renderer.toneMapping = toneMapping; - renderer.autoClear = originalAutoClear; - scene.background = background; - - } - - _textureToCubeUV( texture, cubeUVRenderTarget ) { - - const renderer = this._renderer; - - const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); - - if ( isCubeTexture ) { - - if ( this._cubemapMaterial === null ) { - - this._cubemapMaterial = _getCubemapMaterial(); - - } - - this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? -1 : 1; - - } else { - - if ( this._equirectMaterial === null ) { - - this._equirectMaterial = _getEquirectMaterial(); - - } - - } - - const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; - - const mesh = this._lodMeshes[ 0 ]; - mesh.material = material; - - const uniforms = material.uniforms; - - uniforms[ 'envMap' ].value = texture; - - const size = this._cubeSize; - - _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); - - renderer.setRenderTarget( cubeUVRenderTarget ); - renderer.render( mesh, _flatCamera ); - - } - - _applyPMREM( cubeUVRenderTarget ) { - - const renderer = this._renderer; - const autoClear = renderer.autoClear; - renderer.autoClear = false; - - const n = this._lodMeshes.length; - - // Use GGX VNDF importance sampling - for ( let i = 1; i < n; i ++ ) { - - this._applyGGXFilter( cubeUVRenderTarget, i - 1, i ); - - } - - renderer.autoClear = autoClear; - - } - - /** - * Applies GGX VNDF importance sampling filter to generate a prefiltered environment map. - * Uses Monte Carlo integration with VNDF importance sampling to accurately represent the - * GGX BRDF for physically-based rendering. Reads from the previous LOD level and - * applies incremental roughness filtering to avoid over-blurring. - * - * @private - * @param {WebGLRenderTarget} cubeUVRenderTarget - * @param {number} lodIn - Source LOD level to read from - * @param {number} lodOut - Target LOD level to write to - */ - _applyGGXFilter( cubeUVRenderTarget, lodIn, lodOut ) { - - const renderer = this._renderer; - const pingPongRenderTarget = this._pingPongRenderTarget; - - const ggxMaterial = this._ggxMaterial; - const ggxMesh = this._lodMeshes[ lodOut ]; - ggxMesh.material = ggxMaterial; - - const ggxUniforms = ggxMaterial.uniforms; - - // Calculate incremental roughness between LOD levels - const targetRoughness = lodOut / ( this._lodMeshes.length - 1 ); - const sourceRoughness = lodIn / ( this._lodMeshes.length - 1 ); - const incrementalRoughness = Math.sqrt( targetRoughness * targetRoughness - sourceRoughness * sourceRoughness ); - - // Apply blur strength mapping for better quality across the roughness range - const blurStrength = targetRoughness * 1.25; - const adjustedRoughness = incrementalRoughness * blurStrength; - - // Calculate viewport position based on output LOD level - const { _lodMax } = this; - const outputSize = this._sizeLods[ lodOut ]; - const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); - const y = 4 * ( this._cubeSize - outputSize ); - - // Read from previous LOD with incremental roughness - ggxUniforms[ 'envMap' ].value = cubeUVRenderTarget.texture; - ggxUniforms[ 'roughness' ].value = adjustedRoughness; - ggxUniforms[ 'mipInt' ].value = _lodMax - lodIn; // Sample from input LOD - - _setViewport( pingPongRenderTarget, x, y, 3 * outputSize, 2 * outputSize ); - renderer.setRenderTarget( pingPongRenderTarget ); - renderer.render( ggxMesh, _flatCamera ); - - // Copy from pingPong back to cubeUV (simple direct copy) - ggxUniforms[ 'envMap' ].value = pingPongRenderTarget.texture; - ggxUniforms[ 'roughness' ].value = 0.0; // Direct copy - ggxUniforms[ 'mipInt' ].value = _lodMax - lodOut; // Read from the level we just wrote - - _setViewport( cubeUVRenderTarget, x, y, 3 * outputSize, 2 * outputSize ); - renderer.setRenderTarget( cubeUVRenderTarget ); - renderer.render( ggxMesh, _flatCamera ); - - } - - /** - * This is a two-pass Gaussian blur for a cubemap. Each pass importance-samples - * the Gaussian along a spiral kernel (Golden Angle), which distributes samples - * isotropically on the sphere (no pole artifacts). - * - * Used for initial scene blur in fromScene() method when sigma > 0. - * - * @private - * @param {WebGLRenderTarget} cubeUVRenderTarget - * @param {number} lodIn - * @param {number} lodOut - * @param {number} sigma - */ - _blur( cubeUVRenderTarget, lodIn, lodOut, sigma ) { - - const pingPongRenderTarget = this._pingPongRenderTarget; - - // Two passes of sigma / sqrt( 2 ) compose to a blur of sigma while squaring - // the effective sample count. Sigmas beyond PI are visually indistinguishable - // from a uniform blur, so clamp to keep the shader math finite. - const blurSigma = Math.min( sigma, Math.PI ) / Math.SQRT2; - - this._blurPass( cubeUVRenderTarget, pingPongRenderTarget, lodIn, lodOut, blurSigma ); - this._blurPass( pingPongRenderTarget, cubeUVRenderTarget, lodOut, lodOut, blurSigma ); - - } - - _blurPass( targetIn, targetOut, lodIn, lodOut, sigmaRadians ) { - - const renderer = this._renderer; - const blurMaterial = this._blurMaterial; - - const blurMesh = this._lodMeshes[ lodOut ]; - blurMesh.material = blurMaterial; - - const blurUniforms = blurMaterial.uniforms; - - blurUniforms[ 'envMap' ].value = targetIn.texture; - blurUniforms[ 'sigma' ].value = sigmaRadians; - blurUniforms[ 'mipInt' ].value = this._lodMax - lodIn; - - const outputSize = this._sizeLods[ lodOut ]; - const x = 3 * outputSize * ( lodOut > this._lodMax - LOD_MIN ? lodOut - this._lodMax + LOD_MIN : 0 ); - const y = 4 * ( this._cubeSize - outputSize ); - - _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); - renderer.setRenderTarget( targetOut ); - renderer.render( blurMesh, _flatCamera ); - - } - -} - - - -function _createPlanes( lodMax ) { - - const sizeLods = []; - const lodMeshes = []; - - let lod = lodMax; - - const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LODS; - - for ( let i = 0; i < totalLods; i ++ ) { - - const sizeLod = Math.pow( 2, lod ); - sizeLods.push( sizeLod ); - - // UVs overshoot the face by one texel to bake the CubeUV border into the directions. - const texelSize = 1.0 / ( sizeLod - 2 ); - const min = - texelSize; - const max = 1 + texelSize; - const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; - - const cubeFaces = 6; - const vertices = 6; - const positionSize = 3; - - const position = new Float32Array( positionSize * vertices * cubeFaces ); - const outputDirection = new Float32Array( positionSize * vertices * cubeFaces ); - - for ( let face = 0; face < cubeFaces; face ++ ) { - - const x = ( face % 3 ) * 2 / 3 - 1; - const y = face > 2 ? 0 : -1; - const coordinates = [ - x, y, 0, - x + 2 / 3, y, 0, - x + 2 / 3, y + 1, 0, - x, y, 0, - x + 2 / 3, y + 1, 0, - x, y + 1, 0 - ]; - position.set( coordinates, positionSize * vertices * face ); - - for ( let vertex = 0; vertex < vertices; vertex ++ ) { - - const u = uv1[ vertex * 2 ] * 2 - 1; - const v = uv1[ vertex * 2 + 1 ] * 2 - 1; - - // RH coordinate system; PMREM face-indexing convention - if ( face === 0 ) { - - _direction.set( 1, v, u ); // pos x - - } else if ( face === 1 ) { - - _direction.set( - u, 1, - v ); // pos y - - } else if ( face === 2 ) { - - _direction.set( - u, v, 1 ); // pos z - - } else if ( face === 3 ) { - - _direction.set( -1, v, - u ); // neg x - - } else if ( face === 4 ) { - - _direction.set( - u, -1, v ); // neg y - - } else { - - _direction.set( u, v, -1 ); // neg z - - } - - _direction.toArray( outputDirection, ( face * vertices + vertex ) * positionSize ); - - } - - } - - const planes = new BufferGeometry(); - planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); - planes.setAttribute( 'outputDirection', new BufferAttribute( outputDirection, positionSize ) ); - lodMeshes.push( new Mesh( planes, null ) ); - - if ( lod > LOD_MIN ) { - - lod --; - - } - - } - - return { lodMeshes, sizeLods }; - -} - -function _createRenderTarget( width, height, params ) { - - const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); - cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; - cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; - cubeUVRenderTarget.scissorTest = true; - return cubeUVRenderTarget; - -} - -function _setViewport( target, x, y, width, height ) { - - target.viewport.set( x, y, width, height ); - target.scissor.set( x, y, width, height ); - -} - -function _getGGXShader( lodMax, width, height ) { - - const shaderMaterial = new ShaderMaterial( { - - name: 'PMREMGGXConvolution', - - defines: { - 'GGX_SAMPLES': GGX_SAMPLES, - 'CUBEUV_TEXEL_WIDTH': 1.0 / width, - 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, - 'CUBEUV_MAX_MIP': `${lodMax}.0`, - }, - - uniforms: { - 'envMap': { value: null }, - 'roughness': { value: 0.0 }, - 'mipInt': { value: 0 } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision highp float; - precision highp int; - - varying vec3 vOutputDirection; - - uniform sampler2D envMap; - uniform float roughness; - uniform float mipInt; - - #define ENVMAP_TYPE_CUBE_UV - #include - - #define PI 3.14159265359 - - // Van der Corput radical inverse - float radicalInverse_VdC(uint bits) { - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 - } - - // Hammersley sequence - vec2 hammersley(uint i, uint N) { - return vec2(float(i) / float(N), radicalInverse_VdC(i)); - } - - // GGX VNDF importance sampling (Eric Heitz 2018) - // "Sampling the GGX Distribution of Visible Normals" - // https://jcgt.org/published/0007/04/01/ - vec3 importanceSampleGGX_VNDF(vec2 Xi, vec3 V, float roughness) { - float alpha = roughness * roughness; - - // Section 4.1: Orthonormal basis - vec3 T1 = vec3(1.0, 0.0, 0.0); - vec3 T2 = cross(V, T1); - - // Section 4.2: Parameterization of projected area - float r = sqrt(Xi.x); - float phi = 2.0 * PI * Xi.y; - float t1 = r * cos(phi); - float t2 = r * sin(phi); - float s = 0.5 * (1.0 + V.z); - t2 = (1.0 - s) * sqrt(1.0 - t1 * t1) + s * t2; - - // Section 4.3: Reprojection onto hemisphere - vec3 Nh = t1 * T1 + t2 * T2 + sqrt(max(0.0, 1.0 - t1 * t1 - t2 * t2)) * V; - - // Section 3.4: Transform back to ellipsoid configuration - return normalize(vec3(alpha * Nh.x, alpha * Nh.y, max(0.0, Nh.z))); - } - - void main() { - vec3 N = normalize(vOutputDirection); - vec3 V = N; // Assume view direction equals normal for pre-filtering - - vec3 prefilteredColor = vec3(0.0); - float totalWeight = 0.0; - - // For very low roughness, just sample the environment directly - if (roughness < 0.001) { - gl_FragColor = vec4(bilinearCubeUV(envMap, N, mipInt), 1.0); - return; - } - - // Tangent space basis for VNDF sampling - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); - - for(uint i = 0u; i < uint(GGX_SAMPLES); i++) { - vec2 Xi = hammersley(i, uint(GGX_SAMPLES)); - - // For PMREM, V = N, so in tangent space V is always (0, 0, 1) - vec3 H_tangent = importanceSampleGGX_VNDF(Xi, vec3(0.0, 0.0, 1.0), roughness); - - // Transform H back to world space - vec3 H = normalize(tangent * H_tangent.x + bitangent * H_tangent.y + N * H_tangent.z); - vec3 L = normalize(2.0 * dot(V, H) * H - V); - - float NdotL = max(dot(N, L), 0.0); - - if(NdotL > 0.0) { - // Sample environment at fixed mip level - // VNDF importance sampling handles the distribution filtering - vec3 sampleColor = bilinearCubeUV(envMap, L, mipInt); - - // Weight by NdotL for the split-sum approximation - // VNDF PDF naturally accounts for the visible microfacet distribution - prefilteredColor += sampleColor * NdotL; - totalWeight += NdotL; - } - } - - if (totalWeight > 0.0) { - prefilteredColor = prefilteredColor / totalWeight; - } - - gl_FragColor = vec4(prefilteredColor, 1.0); - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - - return shaderMaterial; - -} - -function _getBlurShader( lodMax, width, height ) { - - const shaderMaterial = new ShaderMaterial( { - - name: 'SphericalGaussianBlur', - - defines: { - 'SAMPLES': BLUR_SAMPLES, - 'CUBEUV_TEXEL_WIDTH': 1.0 / width, - 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, - 'CUBEUV_MAX_MIP': `${lodMax}.0`, - }, - - uniforms: { - 'envMap': { value: null }, - 'sigma': { value: 0 }, - 'mipInt': { value: 0 }, - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision highp float; - precision highp int; - - varying vec3 vOutputDirection; - - uniform sampler2D envMap; - uniform float sigma; - uniform float mipInt; - - #define ENVMAP_TYPE_CUBE_UV - #include - - #define PI 3.14159265359 - #define GOLDEN_ANGLE 2.39996322973 - - void main() { - - if ( sigma == 0.0 ) { - - gl_FragColor = vec4( bilinearCubeUV( envMap, vOutputDirection, mipInt ), 1.0 ); - return; - - } - - vec3 outputDirection = normalize( vOutputDirection ); - - vec3 up = abs( outputDirection.z ) < 0.999 ? vec3( 0.0, 0.0, 1.0 ) : vec3( 1.0, 0.0, 0.0 ); - vec3 tangent = normalize( cross( up, outputDirection ) ); - vec3 bitangent = cross( outputDirection, tangent ); - - // Truncate the kernel at three standard deviations or at the antipode. - float thetaMax = min( 3.0 * sigma, PI ); - float truncation = 1.0 - exp( - 0.5 * thetaMax * thetaMax / ( sigma * sigma ) ); - - vec3 accumColor = vec3( 0.0 ); - float accumWeight = 0.0; - - for ( int i = 0; i < SAMPLES; i ++ ) { - - // Stratified inverse-CDF sampling of the Gaussian, placed on a golden-angle spiral. - float stratum = ( float( i ) + 0.5 ) / float( SAMPLES ); - float theta = sigma * sqrt( - 2.0 * log( 1.0 - stratum * truncation ) ); - float phi = float( i ) * GOLDEN_ANGLE; - - vec3 offset = cos( phi ) * tangent + sin( phi ) * bitangent; - vec3 sampleDirection = cos( theta ) * outputDirection + sin( theta ) * offset; - - // Correct the planar sample density to solid angle. - float weight = sin( theta ) / theta; - - accumColor += weight * bilinearCubeUV( envMap, sampleDirection, mipInt ); - accumWeight += weight; - - } - - gl_FragColor = vec4( accumColor / accumWeight, 1.0 ); - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - - return shaderMaterial; - -} - -function _getEquirectMaterial() { - - return new ShaderMaterial( { - - name: 'EquirectangularToCubeUV', - - uniforms: { - 'envMap': { value: null } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - varying vec3 vOutputDirection; - - uniform sampler2D envMap; - - #include - - void main() { - - vec3 outputDirection = normalize( vOutputDirection ); - vec2 uv = equirectUv( outputDirection ); - - gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - -} - -function _getCubemapMaterial() { - - return new ShaderMaterial( { - - name: 'CubemapToCubeUV', - - uniforms: { - 'envMap': { value: null }, - 'flipEnvMap': { value: -1 } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - uniform float flipEnvMap; - - varying vec3 vOutputDirection; - - uniform samplerCube envMap; - - void main() { - - gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - -} - -function _getCommonVertexShader() { - - return /* glsl */` - - precision mediump float; - precision mediump int; - - attribute vec3 outputDirection; - - varying vec3 vOutputDirection; - - void main() { - - vOutputDirection = outputDirection; - gl_Position = vec4( position, 1.0 ); - - } - `; - -} - -const fov = -90; // negative fov is not an error -const aspect = 1; - -/** - * A special type of camera that is positioned in 3D space to render its surroundings into a - * cube render target. The render target can then be used as an environment map for rendering - * realtime reflections in your scene. - * - * ```js - * // Create cube render target - * const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } ); - * - * // Create cube camera - * const cubeCamera = new THREE.CubeCamera( 1, 100000, cubeRenderTarget ); - * scene.add( cubeCamera ); - * - * // Create car - * const chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } ); - * const car = new THREE.Mesh( carGeometry, chromeMaterial ); - * scene.add( car ); - * - * // Update the render target cube - * car.visible = false; - * cubeCamera.position.copy( car.position ); - * cubeCamera.update( renderer, scene ); - * - * // Render the scene - * car.visible = true; - * renderer.render( scene, camera ); - * ``` - * - * @augments Object3D - */ -class CubeCamera extends Object3D { - - /** - * Constructs a new cube camera. - * - * @param {number} near - The camera's near plane. - * @param {number} far - The camera's far plane. - * @param {WebGLCubeRenderTarget} renderTarget - The cube render target. - */ - constructor( near, far, renderTarget ) { - - super(); - - this.type = 'CubeCamera'; - - /** - * A reference to the cube render target. - * - * @type {WebGLCubeRenderTarget} - */ - this.renderTarget = renderTarget; - - /** - * The current active coordinate system. - * - * @type {?(WebGLCoordinateSystem|WebGPUCoordinateSystem)} - * @default null - */ - this.coordinateSystem = null; - - /** - * The current active mipmap level - * - * @type {number} - * @default 0 - */ - this.activeMipmapLevel = 0; - - const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); - cameraPX.layers = this.layers; - this.add( cameraPX ); - - const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); - cameraNX.layers = this.layers; - this.add( cameraNX ); - - const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); - cameraPY.layers = this.layers; - this.add( cameraPY ); - - const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); - cameraNY.layers = this.layers; - this.add( cameraNY ); - - const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraPZ.layers = this.layers; - this.add( cameraPZ ); - - const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraNZ.layers = this.layers; - this.add( cameraNZ ); - - } - - /** - * Must be called when the coordinate system of the cube camera is changed. - */ - updateCoordinateSystem() { - - const coordinateSystem = this.coordinateSystem; - - const cameras = this.children.concat(); - - const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = cameras; - - for ( const camera of cameras ) this.remove( camera ); - - if ( coordinateSystem === WebGLCoordinateSystem ) { - - cameraPX.up.set( 0, 1, 0 ); - cameraPX.lookAt( 1, 0, 0 ); - - cameraNX.up.set( 0, 1, 0 ); - cameraNX.lookAt( -1, 0, 0 ); - - cameraPY.up.set( 0, 0, -1 ); - cameraPY.lookAt( 0, 1, 0 ); - - cameraNY.up.set( 0, 0, 1 ); - cameraNY.lookAt( 0, -1, 0 ); - - cameraPZ.up.set( 0, 1, 0 ); - cameraPZ.lookAt( 0, 0, 1 ); - - cameraNZ.up.set( 0, 1, 0 ); - cameraNZ.lookAt( 0, 0, -1 ); - - } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - - cameraPX.up.set( 0, -1, 0 ); - cameraPX.lookAt( -1, 0, 0 ); - - cameraNX.up.set( 0, -1, 0 ); - cameraNX.lookAt( 1, 0, 0 ); - - cameraPY.up.set( 0, 0, 1 ); - cameraPY.lookAt( 0, 1, 0 ); - - cameraNY.up.set( 0, 0, -1 ); - cameraNY.lookAt( 0, -1, 0 ); - - cameraPZ.up.set( 0, -1, 0 ); - cameraPZ.lookAt( 0, 0, 1 ); - - cameraNZ.up.set( 0, -1, 0 ); - cameraNZ.lookAt( 0, 0, -1 ); - - } else { - - throw new Error( 'THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: ' + coordinateSystem ); - - } - - for ( const camera of cameras ) { - - this.add( camera ); - - camera.updateMatrixWorld(); - - } - - } - - /** - * Calling this method will render the given scene with the given renderer - * into the cube render target of the camera. - * - * @param {(Renderer|WebGLRenderer)} renderer - The renderer. - * @param {Scene} scene - The scene to render. - */ - update( renderer, scene ) { - - if ( this.parent === null ) this.updateMatrixWorld(); - - const { renderTarget, activeMipmapLevel } = this; - - if ( this.coordinateSystem !== renderer.coordinateSystem ) { - - this.coordinateSystem = renderer.coordinateSystem; - - this.updateCoordinateSystem(); - - } - - const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; - - const currentRenderTarget = renderer.getRenderTarget(); - const currentActiveCubeFace = renderer.getActiveCubeFace(); - const currentActiveMipmapLevel = renderer.getActiveMipmapLevel(); - - const currentXrEnabled = renderer.xr.enabled; - - renderer.xr.enabled = false; - - const generateMipmaps = renderTarget.texture.generateMipmaps; - - renderTarget.texture.generateMipmaps = false; - - // https://github.com/mrdoob/three.js/issues/31413#issuecomment-3095966812 - - let reversedDepthBuffer = false; - - if ( renderer.isWebGLRenderer === true ) { - - reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); - - } else { - - reversedDepthBuffer = renderer.reversedDepthBuffer; - - } - - renderer.setRenderTarget( renderTarget, 0, activeMipmapLevel ); - if ( reversedDepthBuffer && renderer.autoClear === false ) renderer.clearDepth(); - renderer.render( scene, cameraPX ); - - renderer.setRenderTarget( renderTarget, 1, activeMipmapLevel ); - if ( reversedDepthBuffer && renderer.autoClear === false ) renderer.clearDepth(); - renderer.render( scene, cameraNX ); - - renderer.setRenderTarget( renderTarget, 2, activeMipmapLevel ); - if ( reversedDepthBuffer && renderer.autoClear === false ) renderer.clearDepth(); - renderer.render( scene, cameraPY ); - - renderer.setRenderTarget( renderTarget, 3, activeMipmapLevel ); - if ( reversedDepthBuffer && renderer.autoClear === false ) renderer.clearDepth(); - renderer.render( scene, cameraNY ); - - renderer.setRenderTarget( renderTarget, 4, activeMipmapLevel ); - if ( reversedDepthBuffer && renderer.autoClear === false ) renderer.clearDepth(); - renderer.render( scene, cameraPZ ); - - // mipmaps are generated during the last call of render() - // at this point, all sides of the cube render target are defined - - renderTarget.texture.generateMipmaps = generateMipmaps; - - renderer.setRenderTarget( renderTarget, 5, activeMipmapLevel ); - if ( reversedDepthBuffer && renderer.autoClear === false ) renderer.clearDepth(); - renderer.render( scene, cameraNZ ); - - renderer.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); - - renderer.xr.enabled = currentXrEnabled; - - renderTarget.texture.needsPMREMUpdate = true; - - } - -} - -/** - * Creates a cube texture made up of six images. - * - * ```js - * const loader = new THREE.CubeTextureLoader(); - * loader.setPath( 'textures/cube/pisa/' ); - * - * const textureCube = loader.load( [ - * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' - * ] ); - * - * const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } ); - * ``` - * - * @augments Texture - */ -class CubeTexture extends Texture { - - /** - * Constructs a new cube texture. - * - * @param {Array} [images=[]] - An array holding a image for each side of a cube. - * @param {number} [mapping=CubeReflectionMapping] - The texture mapping. - * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. - * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. - * @param {number} [magFilter=LinearFilter] - The mag filter value. - * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. - * @param {number} [format=RGBAFormat] - The texture format. - * @param {number} [type=UnsignedByteType] - The texture type. - * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. - * @param {string} [colorSpace=NoColorSpace] - The color space value. - */ - constructor( images = [], mapping = CubeReflectionMapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { - - super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCubeTexture = true; - - /** - * If set to `true`, the texture is flipped along the vertical axis when - * uploaded to the GPU. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.flipY = false; - - } - - /** - * Alias for {@link CubeTexture#image}. - * - * @type {Array} - */ - get images() { - - return this.image; - - } - - set images( value ) { - - this.image = value; - - } - -} - -/** - * A cube render target used in context of {@link WebGLRenderer}. - * - * @augments WebGLRenderTarget - */ -class WebGLCubeRenderTarget extends WebGLRenderTarget { - - /** - * Constructs a new cube render target. - * - * @param {number} [size=1] - The size of the render target. - * @param {RenderTarget~Options} [options] - The configuration object. - */ - constructor( size = 1, options = {} ) { - - super( size, size, options ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isWebGLCubeRenderTarget = true; - - const image = { width: size, height: size, depth: 1 }; - const images = [ image, image, image, image, image, image ]; - - /** - * Overwritten with a different texture type. - * - * @type {DataArrayTexture} - */ - this.texture = new CubeTexture( images ); - this._setTextureOptions( options ); - - // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) - // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, - // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. - - // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped - // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture - // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). - - this.texture.isRenderTargetTexture = true; - - } - - /** - * Converts the given equirectangular texture to a cube map. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {Texture} texture - The equirectangular texture. - * @return {WebGLCubeRenderTarget} A reference to this cube render target. - */ - fromEquirectangularTexture( renderer, texture ) { - - this.texture.type = texture.type; - this.texture.colorSpace = texture.colorSpace; - - this.texture.generateMipmaps = texture.generateMipmaps; - this.texture.minFilter = texture.minFilter; - this.texture.magFilter = texture.magFilter; - - const shader = { - - uniforms: { - tEquirect: { value: null }, - }, - - vertexShader: /* glsl */` - - varying vec3 vWorldDirection; - - vec3 transformDirection( in vec3 dir, in mat4 matrix ) { - - return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); - - } - - void main() { - - vWorldDirection = transformDirection( position, modelMatrix ); - - #include - #include - - } - `, - - fragmentShader: /* glsl */` - - uniform sampler2D tEquirect; - - varying vec3 vWorldDirection; - - #include - - void main() { - - vec3 direction = normalize( vWorldDirection ); - - vec2 sampleUV = equirectUv( direction ); - - gl_FragColor = texture2D( tEquirect, sampleUV ); - - } - ` - }; - - const geometry = new BoxGeometry( 5, 5, 5 ); - - const material = new ShaderMaterial( { - - name: 'CubemapFromEquirect', - - uniforms: cloneUniforms( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: BackSide, - blending: NoBlending - - } ); - - material.uniforms.tEquirect.value = texture; - - const mesh = new Mesh( geometry, material ); - - const currentMinFilter = texture.minFilter; - - // Avoid blurred poles - if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; - - const camera = new CubeCamera( 1, 10, this ); - camera.update( renderer, mesh ); - - texture.minFilter = currentMinFilter; - - mesh.geometry.dispose(); - mesh.material.dispose(); - - return this; - - } - - /** - * Clears this cube render target. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. - * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. - * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. - */ - clear( renderer, color = true, depth = true, stencil = true ) { - - const currentRenderTarget = renderer.getRenderTarget(); - - for ( let i = 0; i < 6; i ++ ) { - - renderer.setRenderTarget( this, i ); - - renderer.clear( color, depth, stencil ); - - } - - renderer.setRenderTarget( currentRenderTarget ); - - } - -} - -function WebGLEnvironments( renderer ) { - - let cubeMaps = new WeakMap(); - let pmremMaps = new WeakMap(); - - let pmremGenerator = null; - - function get( texture, usePMREM = false ) { - - if ( texture === null || texture === undefined ) return null; - - if ( usePMREM ) { - - return getPMREM( texture ); - - } - - return getCube( texture ); - - } - - function getCube( texture ) { - - if ( texture && texture.isTexture ) { - - const mapping = texture.mapping; - - if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { - - if ( cubeMaps.has( texture ) ) { - - const cubemap = cubeMaps.get( texture ).texture; - return mapTextureMapping( cubemap, texture.mapping ); - - } else { - - const image = texture.image; - - if ( image && image.height > 0 ) { - - const renderTarget = new WebGLCubeRenderTarget( image.height ); - renderTarget.fromEquirectangularTexture( renderer, texture ); - cubeMaps.set( texture, renderTarget ); - - texture.addEventListener( 'dispose', onCubemapDispose ); - - return mapTextureMapping( renderTarget.texture, texture.mapping ); - - } else { - - // image not yet ready. try the conversion next frame - - return null; - - } - - } - - } - - } - - return texture; - - } - - function getPMREM( texture ) { - - if ( texture && texture.isTexture ) { - - const mapping = texture.mapping; - - const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); - const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); - - // equirect/cube map to cubeUV conversion - - if ( isEquirectMap || isCubeMap ) { - - let renderTarget = pmremMaps.get( texture ); - - const currentPMREMVersion = renderTarget !== undefined ? renderTarget.texture.pmremVersion : 0; - - if ( texture.isRenderTargetTexture && texture.pmremVersion !== currentPMREMVersion ) { - - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - - renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); - renderTarget.texture.pmremVersion = texture.pmremVersion; - - pmremMaps.set( texture, renderTarget ); - - return renderTarget.texture; - - } else { - - if ( renderTarget !== undefined ) { - - return renderTarget.texture; - - } else { - - const image = texture.image; - - if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { - - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - - renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); - renderTarget.texture.pmremVersion = texture.pmremVersion; - - pmremMaps.set( texture, renderTarget ); - - texture.addEventListener( 'dispose', onPMREMDispose ); - - return renderTarget.texture; - - } else { - - // image not yet ready. try the conversion next frame - - return null; - - } - - } - - } - - } - - } - - return texture; - - } - - function mapTextureMapping( texture, mapping ) { - - if ( mapping === EquirectangularReflectionMapping ) { - - texture.mapping = CubeReflectionMapping; - - } else if ( mapping === EquirectangularRefractionMapping ) { - - texture.mapping = CubeRefractionMapping; - - } - - return texture; - - } - - function isCubeTextureComplete( image ) { - - let count = 0; - const length = 6; - - for ( let i = 0; i < length; i ++ ) { - - if ( image[ i ] !== undefined ) count ++; - - } - - return count === length; - - } - - function onCubemapDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onCubemapDispose ); - - const cubemap = cubeMaps.get( texture ); - - if ( cubemap !== undefined ) { - - cubeMaps.delete( texture ); - cubemap.dispose(); - - } - - } - - function onPMREMDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onPMREMDispose ); - - const pmrem = pmremMaps.get( texture ); - - if ( pmrem !== undefined ) { - - pmremMaps.delete( texture ); - pmrem.dispose(); - - } - - } - - function dispose() { - - cubeMaps = new WeakMap(); - pmremMaps = new WeakMap(); - - if ( pmremGenerator !== null ) { - - pmremGenerator.dispose(); - pmremGenerator = null; - - } - - } - - return { - get: get, - dispose: dispose - }; - -} - -function WebGLExtensions( gl ) { - - const extensions = {}; - - function getExtension( name ) { - - if ( extensions[ name ] !== undefined ) { - - return extensions[ name ]; - - } - - const extension = gl.getExtension( name ); - - extensions[ name ] = extension; - - return extension; - - } - - return { - - has: function ( name ) { - - return getExtension( name ) !== null; - - }, - - init: function () { - - getExtension( 'EXT_color_buffer_float' ); - getExtension( 'WEBGL_clip_cull_distance' ); - getExtension( 'OES_texture_float_linear' ); - getExtension( 'EXT_color_buffer_half_float' ); - getExtension( 'WEBGL_multisampled_render_to_texture' ); - getExtension( 'WEBGL_render_shared_exponent' ); - - }, - - get: function ( name ) { - - const extension = getExtension( name ); - - if ( extension === null ) { - - warnOnce( 'WebGLRenderer: ' + name + ' extension not supported.' ); - - } - - return extension; - - } - - }; - -} - -function WebGLGeometries( gl, attributes, info, bindingStates ) { - - const geometries = {}; - const wireframeAttributes = new WeakMap(); - - function onGeometryDispose( event ) { - - const geometry = event.target; - - if ( geometry.index !== null ) { - - attributes.remove( geometry.index ); - - } - - for ( const name in geometry.attributes ) { - - attributes.remove( geometry.attributes[ name ] ); - - } - - geometry.removeEventListener( 'dispose', onGeometryDispose ); - - delete geometries[ geometry.id ]; - - const attribute = wireframeAttributes.get( geometry ); - - if ( attribute ) { - - attributes.remove( attribute ); - wireframeAttributes.delete( geometry ); - - } - - bindingStates.releaseStatesOfGeometry( geometry ); - - if ( geometry.isInstancedBufferGeometry === true ) { - - delete geometry._maxInstanceCount; - - } - - // - - info.memory.geometries --; - - } - - function get( object, geometry ) { - - if ( geometries[ geometry.id ] === true ) return geometry; - - geometry.addEventListener( 'dispose', onGeometryDispose ); - - geometries[ geometry.id ] = true; - - info.memory.geometries ++; - - return geometry; - - } - - function update( geometry ) { - - const geometryAttributes = geometry.attributes; - - // Updating index buffer in VAO now. See WebGLBindingStates. - - for ( const name in geometryAttributes ) { - - attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); - - } - - } - - function updateWireframeAttribute( geometry ) { - - const indices = []; - - const geometryIndex = geometry.index; - const geometryPosition = geometry.attributes.position; - let version = 0; - - if ( geometryPosition === undefined ) { - - return; - - } - - if ( geometryIndex !== null ) { - - const array = geometryIndex.array; - version = geometryIndex.version; - - for ( let i = 0, l = array.length; i < l; i += 3 ) { - - const a = array[ i + 0 ]; - const b = array[ i + 1 ]; - const c = array[ i + 2 ]; - - indices.push( a, b, b, c, c, a ); - - } - - } else { - - const array = geometryPosition.array; - version = geometryPosition.version; - - for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - - const a = i + 0; - const b = i + 1; - const c = i + 2; - - indices.push( a, b, b, c, c, a ); - - } - - } - - // check whether a 32 bit or 16 bit buffer is required to store the indices - // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 - const attribute = new ( geometryPosition.count >= 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); - attribute.version = version; - - // Updating index buffer in VAO now. See WebGLBindingStates - - // - - const previousAttribute = wireframeAttributes.get( geometry ); - - if ( previousAttribute ) attributes.remove( previousAttribute ); - - // - - wireframeAttributes.set( geometry, attribute ); - - } - - function getWireframeAttribute( geometry ) { - - const currentAttribute = wireframeAttributes.get( geometry ); - - if ( currentAttribute ) { - - const geometryIndex = geometry.index; - - if ( geometryIndex !== null ) { - - // if the attribute is obsolete, create a new one - - if ( currentAttribute.version < geometryIndex.version ) { - - updateWireframeAttribute( geometry ); - - } - - } - - } else { - - updateWireframeAttribute( geometry ); - - } - - return wireframeAttributes.get( geometry ); - - } - - return { - - get: get, - update: update, - - getWireframeAttribute: getWireframeAttribute - - }; - -} - -function WebGLIndexedBufferRenderer( gl, extensions, info ) { - - let mode; - - function setMode( value ) { - - mode = value; - - } - - let type, bytesPerElement; - - function setIndex( value ) { - - type = value.type; - bytesPerElement = value.bytesPerElement; - - } - - function render( start, count ) { - - gl.drawElements( mode, count, type, start * bytesPerElement ); - - info.update( count, mode, 1 ); - - } - - function renderInstances( start, count, primcount ) { - - if ( primcount === 0 ) return; - - gl.drawElementsInstanced( mode, count, type, start * bytesPerElement, primcount ); - - info.update( count, mode, primcount ); - - } - - function renderMultiDraw( starts, counts, drawCount ) { - - if ( drawCount === 0 ) return; - - const extension = extensions.get( 'WEBGL_multi_draw' ); - extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); - - let elementCount = 0; - for ( let i = 0; i < drawCount; i ++ ) { - - elementCount += counts[ i ]; - - } - - info.update( elementCount, mode, 1 ); - - - } - - // - - this.setMode = setMode; - this.setIndex = setIndex; - this.render = render; - this.renderInstances = renderInstances; - this.renderMultiDraw = renderMultiDraw; - -} - -function WebGLInfo( gl ) { - - const memory = { - geometries: 0, - textures: 0 - }; - - const render = { - frame: 0, - calls: 0, - triangles: 0, - points: 0, - lines: 0 - }; - - function update( count, mode, instanceCount ) { - - render.calls ++; - - switch ( mode ) { - - case gl.TRIANGLES: - render.triangles += instanceCount * ( count / 3 ); - break; - - case gl.LINES: - render.lines += instanceCount * ( count / 2 ); - break; - - case gl.LINE_STRIP: - render.lines += instanceCount * ( count - 1 ); - break; - - case gl.LINE_LOOP: - render.lines += instanceCount * count; - break; - - case gl.POINTS: - render.points += instanceCount * count; - break; - - default: - error( 'WebGLInfo: Unknown draw mode:', mode ); - break; - - } - - } - - function reset() { - - render.calls = 0; - render.triangles = 0; - render.points = 0; - render.lines = 0; - - } - - return { - memory: memory, - render: render, - programs: null, - autoReset: true, - reset: reset, - update: update - }; - -} - -/** - * Creates an array of textures directly from raw buffer data. - * - * @augments Texture - */ -class DataArrayTexture extends Texture { - - /** - * Constructs a new data array texture. - * - * @param {?TypedArray} [data=null] - The buffer data. - * @param {number} [width=1] - The width of the texture. - * @param {number} [height=1] - The height of the texture. - * @param {number} [depth=1] - The depth of the texture. - */ - constructor( data = null, width = 1, height = 1, depth = 1 ) { - - super( null ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isDataArrayTexture = true; - - /** - * The image definition of a data texture. - * - * @type {{data:TypedArray,width:number,height:number,depth:number}} - */ - this.image = { data, width, height, depth }; - - /** - * How the texture is sampled when a texel covers more than one pixel. - * - * Overwritten and set to `NearestFilter` by default. - * - * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} - * @default NearestFilter - */ - this.magFilter = NearestFilter; - - /** - * How the texture is sampled when a texel covers less than one pixel. - * - * Overwritten and set to `NearestFilter` by default. - * - * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} - * @default NearestFilter - */ - this.minFilter = NearestFilter; - - /** - * This defines how the texture is wrapped in the depth and corresponds to - * *W* in UVW mapping. - * - * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} - * @default ClampToEdgeWrapping - */ - this.wrapR = ClampToEdgeWrapping; - - /** - * Whether to generate mipmaps (if possible) for a texture. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.generateMipmaps = false; - - /** - * If set to `true`, the texture is flipped along the vertical axis when - * uploaded to the GPU. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.flipY = false; - - /** - * Specifies the alignment requirements for the start of each pixel row in memory. - * - * Overwritten and set to `1` by default. - * - * @type {boolean} - * @default 1 - */ - this.unpackAlignment = 1; - - /** - * A set of all layers which need to be updated in the texture. - * - * @type {Set} - */ - this.layerUpdates = new Set(); - - } - - /** - * Copies the values of the given texture to this instance. - * - * @param {DataArrayTexture} source - The texture to copy. - * @return {DataArrayTexture} A reference to this instance. - */ - copy( source ) { - - super.copy( source ); - - this.wrapR = source.wrapR; - - return this; - - } - - /** - * Describes that a specific layer of the texture needs to be updated. - * Normally when {@link Texture#needsUpdate} is set to `true`, the - * entire data texture array is sent to the GPU. Marking specific - * layers will only transmit subsets of all mipmaps associated with a - * specific depth in the array which is often much more performant. - * - * @param {number} layerIndex - The layer index that should be updated. - */ - addLayerUpdate( layerIndex ) { - - this.layerUpdates.add( layerIndex ); - - } - - /** - * Resets the layer updates registry. - */ - clearLayerUpdates() { - - this.layerUpdates.clear(); - - } - -} - -function WebGLMorphtargets( gl, capabilities, textures ) { - - const morphTextures = new WeakMap(); - const morph = new Vector4(); - - function update( object, geometry, program ) { - - const objectInfluences = object.morphTargetInfluences; - - // the following encodes morph targets into an array of data textures. Each layer represents a single morph target. - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - let entry = morphTextures.get( geometry ); - - if ( entry === undefined || entry.count !== morphTargetsCount ) { - - if ( entry !== undefined ) entry.texture.dispose(); - - const hasMorphPosition = geometry.morphAttributes.position !== undefined; - const hasMorphNormals = geometry.morphAttributes.normal !== undefined; - const hasMorphColors = geometry.morphAttributes.color !== undefined; - - const morphTargets = geometry.morphAttributes.position || []; - const morphNormals = geometry.morphAttributes.normal || []; - const morphColors = geometry.morphAttributes.color || []; - - let vertexDataCount = 0; - - if ( hasMorphPosition === true ) vertexDataCount = 1; - if ( hasMorphNormals === true ) vertexDataCount = 2; - if ( hasMorphColors === true ) vertexDataCount = 3; - - let width = geometry.attributes.position.count * vertexDataCount; - let height = 1; - - if ( width > capabilities.maxTextureSize ) { - - height = Math.ceil( width / capabilities.maxTextureSize ); - width = capabilities.maxTextureSize; - - } - - const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); - - const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); - texture.type = FloatType; - texture.needsUpdate = true; - - // fill buffer - - const vertexDataStride = vertexDataCount * 4; - - for ( let i = 0; i < morphTargetsCount; i ++ ) { - - const morphTarget = morphTargets[ i ]; - const morphNormal = morphNormals[ i ]; - const morphColor = morphColors[ i ]; - - const offset = width * height * 4 * i; - - for ( let j = 0; j < morphTarget.count; j ++ ) { - - const stride = j * vertexDataStride; - - if ( hasMorphPosition === true ) { - - morph.fromBufferAttribute( morphTarget, j ); - - buffer[ offset + stride + 0 ] = morph.x; - buffer[ offset + stride + 1 ] = morph.y; - buffer[ offset + stride + 2 ] = morph.z; - buffer[ offset + stride + 3 ] = 0; - - } - - if ( hasMorphNormals === true ) { - - morph.fromBufferAttribute( morphNormal, j ); - - buffer[ offset + stride + 4 ] = morph.x; - buffer[ offset + stride + 5 ] = morph.y; - buffer[ offset + stride + 6 ] = morph.z; - buffer[ offset + stride + 7 ] = 0; - - } - - if ( hasMorphColors === true ) { - - morph.fromBufferAttribute( morphColor, j ); - - buffer[ offset + stride + 8 ] = morph.x; - buffer[ offset + stride + 9 ] = morph.y; - buffer[ offset + stride + 10 ] = morph.z; - buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; - - } - - } - - } - - entry = { - count: morphTargetsCount, - texture: texture, - size: new Vector2( width, height ) - }; - - morphTextures.set( geometry, entry ); - - function disposeTexture() { - - texture.dispose(); - - morphTextures.delete( geometry ); - - geometry.removeEventListener( 'dispose', disposeTexture ); - - } - - geometry.addEventListener( 'dispose', disposeTexture ); - - } - - // - if ( object.isInstancedMesh === true && object.morphTexture !== null ) { - - program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures ); - - } else { - - let morphInfluencesSum = 0; - - for ( let i = 0; i < objectInfluences.length; i ++ ) { - - morphInfluencesSum += objectInfluences[ i ]; - - } - - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); - - } - - program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); - program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); - - } - - return { - - update: update - - }; - -} - -function WebGLObjects( gl, geometries, attributes, bindingStates, info ) { - - let updateMap = new WeakMap(); - - function update( object ) { - - const frame = info.render.frame; - - const geometry = object.geometry; - const buffergeometry = geometries.get( object, geometry ); - - // Update once per frame - - if ( updateMap.get( buffergeometry ) !== frame ) { - - geometries.update( buffergeometry ); - - updateMap.set( buffergeometry, frame ); - - } - - if ( object.isInstancedMesh ) { - - if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { - - object.addEventListener( 'dispose', onInstancedMeshDispose ); - - } - - if ( updateMap.get( object ) !== frame ) { - - attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); - - if ( object.instanceColor !== null ) { - - attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); - - } - - updateMap.set( object, frame ); - - } - - } - - if ( object.isSkinnedMesh ) { - - const skeleton = object.skeleton; - - if ( updateMap.get( skeleton ) !== frame ) { - - skeleton.update(); - - updateMap.set( skeleton, frame ); - - } - - } - - return buffergeometry; - - } - - function dispose() { - - updateMap = new WeakMap(); - - } - - function onInstancedMeshDispose( event ) { - - const instancedMesh = event.target; - - instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); - - bindingStates.releaseStatesOfObject( instancedMesh ); - - attributes.remove( instancedMesh.instanceMatrix ); - - if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); - - } - - return { - - update: update, - dispose: dispose - - }; - -} - -/** - * This class works just like {@link ShaderMaterial}, except that definitions - * of built-in uniforms and attributes are not automatically prepended to the - * GLSL shader code. - * - * `RawShaderMaterial` can only be used with {@link WebGLRenderer}. - * - * @augments ShaderMaterial - */ -class RawShaderMaterial extends ShaderMaterial { - - /** - * Constructs a new raw shader material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super( parameters ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isRawShaderMaterial = true; - - this.type = 'RawShaderMaterial'; - - } - -} - -const toneMappingMap = { - [ LinearToneMapping ]: 'LINEAR_TONE_MAPPING', - [ ReinhardToneMapping ]: 'REINHARD_TONE_MAPPING', - [ CineonToneMapping ]: 'CINEON_TONE_MAPPING', - [ ACESFilmicToneMapping ]: 'ACES_FILMIC_TONE_MAPPING', - [ AgXToneMapping ]: 'AGX_TONE_MAPPING', - [ NeutralToneMapping ]: 'NEUTRAL_TONE_MAPPING', - [ CustomToneMapping ]: 'CUSTOM_TONE_MAPPING' -}; - -function WebGLOutput( type, width, height, antialias, depth, stencil ) { - - // render target for the scene pass (potentially multisampled) - const targetScene = new WebGLRenderTarget( width, height, { - type: type, - depthBuffer: depth, - stencilBuffer: stencil, - samples: antialias ? 4 : 0, - storeMultisampledDepthBuffer: false, - storeMultisampledStencilBuffer: false, - resolveDepthBuffer: false, - resolveStencilBuffer: false - } ); - - // single-sampled ping-pong buffers for post-processing effects, allocated on demand - let targetA = null; - let targetB = null; - - // create fullscreen triangle geometry - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) ); - geometry.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); - - // create output material with tone mapping support - const material = new RawShaderMaterial( { - uniforms: { - tDiffuse: { value: null } - }, - vertexShader: /* glsl */` - precision highp float; - - uniform mat4 modelViewMatrix; - uniform mat4 projectionMatrix; - - attribute vec3 position; - attribute vec2 uv; - - varying vec2 vUv; - - void main() { - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - }`, - fragmentShader: /* glsl */` - precision highp float; - - uniform sampler2D tDiffuse; - - varying vec2 vUv; - - #include - #include - - void main() { - gl_FragColor = texture2D( tDiffuse, vUv ); - - #ifdef LINEAR_TONE_MAPPING - gl_FragColor.rgb = LinearToneMapping( gl_FragColor.rgb ); - #elif defined( REINHARD_TONE_MAPPING ) - gl_FragColor.rgb = ReinhardToneMapping( gl_FragColor.rgb ); - #elif defined( CINEON_TONE_MAPPING ) - gl_FragColor.rgb = CineonToneMapping( gl_FragColor.rgb ); - #elif defined( ACES_FILMIC_TONE_MAPPING ) - gl_FragColor.rgb = ACESFilmicToneMapping( gl_FragColor.rgb ); - #elif defined( AGX_TONE_MAPPING ) - gl_FragColor.rgb = AgXToneMapping( gl_FragColor.rgb ); - #elif defined( NEUTRAL_TONE_MAPPING ) - gl_FragColor.rgb = NeutralToneMapping( gl_FragColor.rgb ); - #elif defined( CUSTOM_TONE_MAPPING ) - gl_FragColor.rgb = CustomToneMapping( gl_FragColor.rgb ); - #endif - - #ifdef SRGB_TRANSFER - gl_FragColor = sRGBTransferOETF( gl_FragColor ); - #endif - }`, - depthTest: false, - depthWrite: false - } ); - - const mesh = new Mesh( geometry, material ); - const camera = new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); - - let _outputColorSpace = null; - let _outputToneMapping = null; - let _isCompositing = false; - let _savedToneMapping; - let _savedRenderTarget = null; - let _effects = []; - let _hasRenderPass = false; - - this.setSize = function ( width, height ) { - - targetScene.setSize( width, height ); - - if ( targetA !== null ) targetA.setSize( width, height ); - if ( targetB !== null ) targetB.setSize( width, height ); - - for ( let i = 0; i < _effects.length; i ++ ) { - - const effect = _effects[ i ]; - if ( effect.setSize ) effect.setSize( width, height ); - - } - - }; - - this.setEffects = function ( effects ) { - - _effects = effects; - _hasRenderPass = _effects.length > 0 && _effects[ 0 ].isRenderPass === true; - - const width = targetScene.width; - const height = targetScene.height; - - if ( _effects.length > 0 && targetA === null ) { - - targetA = new WebGLRenderTarget( width, height, { type: HalfFloatType, depthBuffer: false, stencilBuffer: false } ); - targetB = new WebGLRenderTarget( width, height, { type: HalfFloatType, depthBuffer: false, stencilBuffer: false } ); - - } - - for ( let i = 0; i < _effects.length; i ++ ) { - - const effect = _effects[ i ]; - if ( effect.setSize ) effect.setSize( width, height ); - - } - - }; - - this.begin = function ( renderer, renderTarget ) { - - // Don't begin during compositing phase (post-processing effects call render()) - if ( _isCompositing ) return false; - - if ( renderer.toneMapping === NoToneMapping && _effects.length === 0 ) return false; - - _savedRenderTarget = renderTarget; - - // resize internal buffers to match render target (e.g. XR resolution) - if ( renderTarget !== null ) { - - const width = renderTarget.width; - const height = renderTarget.height; - - if ( targetScene.width !== width || targetScene.height !== height ) { - - this.setSize( width, height ); - - } - - } - - // if the first effect is a RenderPass, it renders the scene itself (see end()) - if ( _hasRenderPass === false ) { - - renderer.setRenderTarget( targetScene ); - - } - - // disable tone mapping during render - it will be applied in end() - _savedToneMapping = renderer.toneMapping; - renderer.toneMapping = NoToneMapping; - - return true; - - }; - - this.hasRenderPass = function () { - - return _hasRenderPass; - - }; - - this.end = function ( renderer, deltaTime ) { - - // restore tone mapping - renderer.toneMapping = _savedToneMapping; - - _isCompositing = true; - - // run post-processing effects - the scene lives in the multisampled targetScene, - // effects ping-pong between the single-sampled targetA and targetB - let readBuffer = targetScene; - let writeBuffer = targetA; - - for ( let i = 0; i < _effects.length; i ++ ) { - - const effect = _effects[ i ]; - - if ( effect.enabled === false ) continue; - - effect.render( renderer, writeBuffer, readBuffer, deltaTime ); - - if ( effect.needsSwap !== false ) { - - // keep targetScene out of the rotation so only the scene pass is multisampled - readBuffer = writeBuffer; - writeBuffer = ( writeBuffer === targetA ) ? targetB : targetA; - - } - - } - - // update output material defines if settings changed - if ( _outputColorSpace !== renderer.outputColorSpace || _outputToneMapping !== renderer.toneMapping ) { - - _outputColorSpace = renderer.outputColorSpace; - _outputToneMapping = renderer.toneMapping; - - material.defines = {}; - - if ( ColorManagement.getTransfer( _outputColorSpace ) === SRGBTransfer ) material.defines.SRGB_TRANSFER = ''; - - const toneMapping = toneMappingMap[ _outputToneMapping ]; - if ( toneMapping ) material.defines[ toneMapping ] = ''; - - material.needsUpdate = true; - - } - - // final output to canvas (or XR render target) - material.uniforms.tDiffuse.value = readBuffer.texture; - renderer.setRenderTarget( _savedRenderTarget ); - renderer.render( mesh, camera ); - - _savedRenderTarget = null; - _isCompositing = false; - - }; - - this.isCompositing = function () { - - return _isCompositing; - - }; - - this.dispose = function () { - - targetScene.dispose(); - - if ( targetA !== null ) targetA.dispose(); - if ( targetB !== null ) targetB.dispose(); - - geometry.dispose(); - material.dispose(); - - }; - -} - -/** - * Creates a three-dimensional texture from raw data, with parameters to - * divide it into width, height, and depth. - * - * @augments Texture - */ -class Data3DTexture extends Texture { - - /** - * Constructs a new data array texture. - * - * @param {?TypedArray} [data=null] - The buffer data. - * @param {number} [width=1] - The width of the texture. - * @param {number} [height=1] - The height of the texture. - * @param {number} [depth=1] - The depth of the texture. - */ - constructor( data = null, width = 1, height = 1, depth = 1 ) { - - // We're going to add .setXXX() methods for setting properties later. - // Users can still set in Data3DTexture directly. - // - // const texture = new THREE.Data3DTexture( data, width, height, depth ); - // texture.anisotropy = 16; - // - // See #14839 - - super( null ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isData3DTexture = true; - - /** - * The image definition of a data texture. - * - * @type {{data:TypedArray,width:number,height:number,depth:number}} - */ - this.image = { data, width, height, depth }; - - /** - * How the texture is sampled when a texel covers more than one pixel. - * - * Overwritten and set to `NearestFilter` by default. - * - * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} - * @default NearestFilter - */ - this.magFilter = NearestFilter; - - /** - * How the texture is sampled when a texel covers less than one pixel. - * - * Overwritten and set to `NearestFilter` by default. - * - * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} - * @default NearestFilter - */ - this.minFilter = NearestFilter; - - /** - * This defines how the texture is wrapped in the depth and corresponds to - * *W* in UVW mapping. - * - * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} - * @default ClampToEdgeWrapping - */ - this.wrapR = ClampToEdgeWrapping; - - /** - * Whether to generate mipmaps (if possible) for a texture. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.generateMipmaps = false; - - /** - * If set to `true`, the texture is flipped along the vertical axis when - * uploaded to the GPU. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.flipY = false; - - /** - * Specifies the alignment requirements for the start of each pixel row in memory. - * - * Overwritten and set to `1` by default. - * - * @type {boolean} - * @default 1 - */ - this.unpackAlignment = 1; - - } - - /** - * Copies the values of the given texture to this instance. - * - * @param {Data3DTexture} source - The texture to copy. - * @return {Data3DTexture} A reference to this instance. - */ - copy( source ) { - - super.copy( source ); - - this.wrapR = source.wrapR; - - return this; - - } - -} - -/** - * This class can be used to automatically save the depth information of a - * rendering into a texture. - * - * @augments Texture - */ -class DepthTexture extends Texture { - - /** - * Constructs a new depth texture. - * - * @param {number} width - The width of the texture. - * @param {number} height - The height of the texture. - * @param {number} [type=UnsignedIntType] - The texture type. - * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. - * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. - * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. - * @param {number} [magFilter=LinearFilter] - The mag filter value. - * @param {number} [minFilter=LinearFilter] - The min filter value. - * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. - * @param {number} [format=DepthFormat] - The texture format. - * @param {number} [depth=1] - The depth of the texture. - */ - constructor( width, height, type = UnsignedIntType, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat, depth = 1 ) { - - if ( format !== DepthFormat && format !== DepthStencilFormat ) { - - throw new Error( 'THREE.DepthTexture: format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); - - } - - const image = { width: width, height: height, depth: depth }; - - super( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isDepthTexture = true; - - /** - * If set to `true`, the texture is flipped along the vertical axis when - * uploaded to the GPU. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.flipY = false; - - /** - * Whether to generate mipmaps (if possible) for a texture. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.generateMipmaps = false; - - /** - * Code corresponding to the depth compare function. - * - * @type {?(NeverCompare|LessCompare|EqualCompare|LessEqualCompare|GreaterCompare|NotEqualCompare|GreaterEqualCompare|AlwaysCompare)} - * @default null - */ - this.compareFunction = null; - - } - - - copy( source ) { - - super.copy( source ); - - this.source = new TextureSource( Object.assign( {}, source.image ) ); // see #30540 - this.compareFunction = source.compareFunction; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.compareFunction = this.compareFunction; - - return data; - - } - -} - -/** - * Uniforms of a program. - * Those form a tree structure with a special top-level container for the root, - * which you get by calling 'new WebGLUniforms( gl, program )'. - * - * - * Properties of inner nodes including the top-level container: - * - * .seq - array of nested uniforms - * .map - nested uniforms by name - * - * - * Methods of all nodes except the top-level container: - * - * .setValue( gl, value, [textures] ) - * - * uploads a uniform value(s) - * the 'textures' parameter is needed for sampler uniforms - * - * - * Static methods of the top-level container (textures factorizations): - * - * .upload( gl, seq, values, textures ) - * - * sets uniforms in 'seq' to 'values[id].value' - * - * .seqWithValue( seq, values ) : filteredSeq - * - * filters 'seq' entries with corresponding entry in values - * - * - * Methods of the top-level container (textures factorizations): - * - * .setValue( gl, name, value, textures ) - * - * sets uniform with name 'name' to 'value' - * - * .setOptional( gl, obj, prop ) - * - * like .set for an optional property of the object - * - */ - - -const emptyTexture = /*@__PURE__*/ new Texture(); - -const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 ); - -const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); -const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); -const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); - -// --- Utilities --- - -// Array Caches (provide typed arrays for temporary by size) - -const arrayCacheF32 = []; -const arrayCacheI32 = []; - -// Float32Array caches used for uploading Matrix uniforms - -const mat4array = new Float32Array( 16 ); -const mat3array = new Float32Array( 9 ); -const mat2array = new Float32Array( 4 ); - -// Flattening for arrays of vectors and matrices - -function flatten( array, nBlocks, blockSize ) { - - const firstElem = array[ 0 ]; - - if ( firstElem <= 0 || firstElem > 0 ) return array; - // unoptimized: ! isNaN( firstElem ) - // see http://jacksondunstan.com/articles/983 - - const n = nBlocks * blockSize; - let r = arrayCacheF32[ n ]; - - if ( r === undefined ) { - - r = new Float32Array( n ); - arrayCacheF32[ n ] = r; - - } - - if ( nBlocks !== 0 ) { - - firstElem.toArray( r, 0 ); - - for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { - - offset += blockSize; - array[ i ].toArray( r, offset ); - - } - - } - - return r; - -} - -function arraysEqual( a, b ) { - - if ( a.length !== b.length ) return false; - - for ( let i = 0, l = a.length; i < l; i ++ ) { - - if ( a[ i ] !== b[ i ] ) return false; - - } - - return true; - -} - -function copyArray( a, b ) { - - for ( let i = 0, l = b.length; i < l; i ++ ) { - - a[ i ] = b[ i ]; - - } - -} - -// Texture unit allocation - -function allocTexUnits( textures, n ) { - - let r = arrayCacheI32[ n ]; - - if ( r === undefined ) { - - r = new Int32Array( n ); - arrayCacheI32[ n ] = r; - - } - - for ( let i = 0; i !== n; ++ i ) { - - r[ i ] = textures.allocateTextureUnit(); - - } - - return r; - -} - -// --- Setters --- - -// Note: Defining these methods externally, because they come in a bunch -// and this way their names minify. - -// Single scalar - -function setValueV1f( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1f( this.addr, v ); - - cache[ 0 ] = v; - -} - -// Single float vector (from flat array or THREE.VectorN) - -function setValueV2f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2f( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2fv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV3f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3f( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else if ( v.r !== undefined ) { - - if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { - - gl.uniform3f( this.addr, v.r, v.g, v.b ); - - cache[ 0 ] = v.r; - cache[ 1 ] = v.g; - cache[ 2 ] = v.b; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3fv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV4f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4fv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -// Single matrix (from flat array or THREE.MatrixN) - -function setValueM2( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix2fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat2array.set( elements ); - - gl.uniformMatrix2fv( this.addr, false, mat2array ); - - copyArray( cache, elements ); - - } - -} - -function setValueM3( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix3fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat3array.set( elements ); - - gl.uniformMatrix3fv( this.addr, false, mat3array ); - - copyArray( cache, elements ); - - } - -} - -function setValueM4( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix4fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat4array.set( elements ); - - gl.uniformMatrix4fv( this.addr, false, mat4array ); - - copyArray( cache, elements ); - - } - -} - -// Single integer / boolean - -function setValueV1i( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1i( this.addr, v ); - - cache[ 0 ] = v; - -} - -// Single integer / boolean vector (from flat array or THREE.VectorN) - -function setValueV2i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2i( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2iv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV3i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3i( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3iv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV4i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4iv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -// Single unsigned integer - -function setValueV1ui( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1ui( this.addr, v ); - - cache[ 0 ] = v; - -} - -// Single unsigned integer vector (from flat array or THREE.VectorN) - -function setValueV2ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2ui( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2uiv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV3ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3ui( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3uiv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV4ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4uiv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - - -// Single texture (2D / Cube) - -function setValueT1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - let emptyTexture2D; - - if ( this.type === gl.SAMPLER_2D_SHADOW ) { - - emptyShadowTexture.compareFunction = textures.isReversedDepthBuffer() ? GreaterEqualCompare : LessEqualCompare; - emptyTexture2D = emptyShadowTexture; - - } else { - - emptyTexture2D = emptyTexture; - - } - - textures.setTexture2D( v || emptyTexture2D, unit ); - -} - -function setValueT3D1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture3D( v || empty3dTexture, unit ); - -} - -function setValueT6( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTextureCube( v || emptyCubeTexture, unit ); - -} - -function setValueT2DArray1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture2DArray( v || emptyArrayTexture, unit ); - -} - -// Helper to pick the right setter for the singular case - -function getSingularSetter( type ) { - - switch ( type ) { - - case 0x1406: return setValueV1f; // FLOAT - case 0x8b50: return setValueV2f; // _VEC2 - case 0x8b51: return setValueV3f; // _VEC3 - case 0x8b52: return setValueV4f; // _VEC4 - - case 0x8b5a: return setValueM2; // _MAT2 - case 0x8b5b: return setValueM3; // _MAT3 - case 0x8b5c: return setValueM4; // _MAT4 - - case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 - - case 0x1405: return setValueV1ui; // UINT - case 0x8dc6: return setValueV2ui; // _VEC2 - case 0x8dc7: return setValueV3ui; // _VEC3 - case 0x8dc8: return setValueV4ui; // _VEC4 - - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1; - - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3D1; - - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6; - - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArray1; - - } - -} - - -// Array of scalars - -function setValueV1fArray( gl, v ) { - - gl.uniform1fv( this.addr, v ); - -} - -// Array of vectors (from flat array or array of THREE.VectorN) - -function setValueV2fArray( gl, v ) { - - const data = flatten( v, this.size, 2 ); - - gl.uniform2fv( this.addr, data ); - -} - -function setValueV3fArray( gl, v ) { - - const data = flatten( v, this.size, 3 ); - - gl.uniform3fv( this.addr, data ); - -} - -function setValueV4fArray( gl, v ) { - - const data = flatten( v, this.size, 4 ); - - gl.uniform4fv( this.addr, data ); - -} - -// Array of matrices (from flat array or array of THREE.MatrixN) - -function setValueM2Array( gl, v ) { - - const data = flatten( v, this.size, 4 ); - - gl.uniformMatrix2fv( this.addr, false, data ); - -} - -function setValueM3Array( gl, v ) { - - const data = flatten( v, this.size, 9 ); - - gl.uniformMatrix3fv( this.addr, false, data ); - -} - -function setValueM4Array( gl, v ) { - - const data = flatten( v, this.size, 16 ); - - gl.uniformMatrix4fv( this.addr, false, data ); - -} - -// Array of integer / boolean - -function setValueV1iArray( gl, v ) { - - gl.uniform1iv( this.addr, v ); - -} - -// Array of integer / boolean vectors (from flat array) - -function setValueV2iArray( gl, v ) { - - gl.uniform2iv( this.addr, v ); - -} - -function setValueV3iArray( gl, v ) { - - gl.uniform3iv( this.addr, v ); - -} - -function setValueV4iArray( gl, v ) { - - gl.uniform4iv( this.addr, v ); - -} - -// Array of unsigned integer - -function setValueV1uiArray( gl, v ) { - - gl.uniform1uiv( this.addr, v ); - -} - -// Array of unsigned integer vectors (from flat array) - -function setValueV2uiArray( gl, v ) { - - gl.uniform2uiv( this.addr, v ); - -} - -function setValueV3uiArray( gl, v ) { - - gl.uniform3uiv( this.addr, v ); - -} - -function setValueV4uiArray( gl, v ) { - - gl.uniform4uiv( this.addr, v ); - -} - - -// Array of textures (2D / 3D / Cube / 2DArray) - -function setValueT1Array( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - let emptyTexture2D; - - if ( this.type === gl.SAMPLER_2D_SHADOW ) { - - emptyTexture2D = emptyShadowTexture; - - } else { - - emptyTexture2D = emptyTexture; - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture2D( v[ i ] || emptyTexture2D, units[ i ] ); - - } - -} - -function setValueT3DArray( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); - - } - -} - -function setValueT6Array( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); - - } - -} - -function setValueT2DArrayArray( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); - - } - -} - - -// Helper to pick the right setter for a pure (bottom-level) array - -function getPureArraySetter( type ) { - - switch ( type ) { - - case 0x1406: return setValueV1fArray; // FLOAT - case 0x8b50: return setValueV2fArray; // _VEC2 - case 0x8b51: return setValueV3fArray; // _VEC3 - case 0x8b52: return setValueV4fArray; // _VEC4 - - case 0x8b5a: return setValueM2Array; // _MAT2 - case 0x8b5b: return setValueM3Array; // _MAT3 - case 0x8b5c: return setValueM4Array; // _MAT4 - - case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 - - case 0x1405: return setValueV1uiArray; // UINT - case 0x8dc6: return setValueV2uiArray; // _VEC2 - case 0x8dc7: return setValueV3uiArray; // _VEC3 - case 0x8dc8: return setValueV4uiArray; // _VEC4 - - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1Array; - - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3DArray; - - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6Array; - - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArrayArray; - - } - -} - -// --- Uniform Classes --- - -class SingleUniform { - - constructor( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.cache = []; - this.type = activeInfo.type; - this.setValue = getSingularSetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - - } - -} - -class PureArrayUniform { - - constructor( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.cache = []; - this.type = activeInfo.type; - this.size = activeInfo.size; - this.setValue = getPureArraySetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - - } - -} - -class StructuredUniform { - - constructor( id ) { - - this.id = id; - - this.seq = []; - this.map = {}; - - } - - setValue( gl, value, textures ) { - - const seq = this.seq; - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ]; - u.setValue( gl, value[ u.id ], textures ); - - } - - } - -} - -// --- Top-level --- - -// Parser - builds up the property tree from the path strings - -const RePathPart = /(\w+)(\])?(\[|\.)?/g; - -// extracts -// - the identifier (member name or array index) -// - followed by an optional right bracket (found when array index) -// - followed by an optional left bracket or dot (type of subscript) -// -// Note: These portions can be read in a non-overlapping fashion and -// allow straightforward parsing of the hierarchy that WebGL encodes -// in the uniform names. - -function addUniform( container, uniformObject ) { - - container.seq.push( uniformObject ); - container.map[ uniformObject.id ] = uniformObject; - -} - -function parseUniform( activeInfo, addr, container ) { - - const path = activeInfo.name, - pathLength = path.length; - - // reset RegExp object, because of the early exit of a previous run - RePathPart.lastIndex = 0; - - while ( true ) { - - const match = RePathPart.exec( path ), - matchEnd = RePathPart.lastIndex; - - let id = match[ 1 ]; - const idIsIndex = match[ 2 ] === ']', - subscript = match[ 3 ]; - - if ( idIsIndex ) id = id | 0; // convert to integer - - if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { - - // bare name or "pure" bottom-level array "[0]" suffix - - addUniform( container, subscript === undefined ? - new SingleUniform( id, activeInfo, addr ) : - new PureArrayUniform( id, activeInfo, addr ) ); - - break; - - } else { - - // step into inner node / create it in case it doesn't exist - - const map = container.map; - let next = map[ id ]; - - if ( next === undefined ) { - - next = new StructuredUniform( id ); - addUniform( container, next ); - - } - - container = next; - - } - - } - -} - -// Root Container - -class WebGLUniforms { - - constructor( gl, program ) { - - this.seq = []; - this.map = {}; - - const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - - for ( let i = 0; i < n; ++ i ) { - - const info = gl.getActiveUniform( program, i ), - addr = gl.getUniformLocation( program, info.name ); - - parseUniform( info, addr, this ); - - } - - // Sort uniforms to prioritize shadow samplers first (for optimal texture unit allocation) - - const shadowSamplers = []; - const otherUniforms = []; - - for ( const u of this.seq ) { - - if ( u.type === gl.SAMPLER_2D_SHADOW || u.type === gl.SAMPLER_CUBE_SHADOW || u.type === gl.SAMPLER_2D_ARRAY_SHADOW ) { - - shadowSamplers.push( u ); - - } else { - - otherUniforms.push( u ); - - } - - } - - if ( shadowSamplers.length > 0 ) { - - this.seq = shadowSamplers.concat( otherUniforms ); - - } - - } - - setValue( gl, name, value, textures ) { - - const u = this.map[ name ]; - - if ( u !== undefined ) u.setValue( gl, value, textures ); - - } - - setOptional( gl, object, name ) { - - const v = object[ name ]; - - if ( v !== undefined ) this.setValue( gl, name, v ); - - } - - static upload( gl, seq, values, textures ) { - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ], - v = values[ u.id ]; - - if ( v.needsUpdate !== false ) { - - // note: always updating when .needsUpdate is undefined - u.setValue( gl, v.value, textures ); - - } - - } - - } - - static seqWithValue( seq, values ) { - - const r = []; - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ]; - if ( u.id in values ) r.push( u ); - - } - - return r; - - } - -} - -function WebGLShader( gl, type, string ) { - - const shader = gl.createShader( type ); - - gl.shaderSource( shader, string ); - gl.compileShader( shader ); - - return shader; - -} - -// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/ -const COMPLETION_STATUS_KHR = 0x91B1; - -let programIdCount = 0; - -function handleSource( string, errorLine ) { - - const lines = string.split( '\n' ); - const lines2 = []; - - const from = Math.max( errorLine - 6, 0 ); - const to = Math.min( errorLine + 6, lines.length ); - - for ( let i = from; i < to; i ++ ) { - - const line = i + 1; - lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); - - } - - return lines2.join( '\n' ); - -} - -const _m0 = /*@__PURE__*/ new Matrix3(); - -function getEncodingComponents( colorSpace ) { - - ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace ); - - const encodingMatrix = `mat3( ${ _m0.elements.map( ( v ) => v.toFixed( 4 ) ) } )`; - - switch ( ColorManagement.getTransfer( colorSpace ) ) { - - case LinearTransfer: - return [ encodingMatrix, 'LinearTransferOETF' ]; - - case SRGBTransfer: - return [ encodingMatrix, 'sRGBTransferOETF' ]; - - default: - warn( 'WebGLProgram: Unsupported color space: ', colorSpace ); - return [ encodingMatrix, 'LinearTransferOETF' ]; - - } - -} - -function getShaderErrors( gl, shader, type ) { - - const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); - - const shaderInfoLog = gl.getShaderInfoLog( shader ) || ''; - const errors = shaderInfoLog.trim(); - - if ( status && errors === '' ) return ''; - - const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); - if ( errorMatches ) { - - // --enable-privileged-webgl-extension - // log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - - const errorLine = parseInt( errorMatches[ 1 ] ); - return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); - - } else { - - return errors; - - } - -} - -function getTexelEncodingFunction( functionName, colorSpace ) { - - const components = getEncodingComponents( colorSpace ); - - return [ - - `vec4 ${functionName}( vec4 value ) {`, - - ` return ${components[ 1 ]}( vec4( value.rgb * ${components[ 0 ]}, value.a ) );`, - - '}', - - ].join( '\n' ); - -} - -const toneMappingFunctions = { - [ LinearToneMapping ]: 'Linear', - [ ReinhardToneMapping ]: 'Reinhard', - [ CineonToneMapping ]: 'Cineon', - [ ACESFilmicToneMapping ]: 'ACESFilmic', - [ AgXToneMapping ]: 'AgX', - [ NeutralToneMapping ]: 'Neutral', - [ CustomToneMapping ]: 'Custom' -}; - -function getToneMappingFunction( functionName, toneMapping ) { - - const toneMappingName = toneMappingFunctions[ toneMapping ]; - - if ( toneMappingName === undefined ) { - - warn( 'WebGLProgram: Unsupported toneMapping:', toneMapping ); - return 'vec3 ' + functionName + '( vec3 color ) { return LinearToneMapping( color ); }'; - - } - - return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; - -} - -const _v0$1 = /*@__PURE__*/ new Vector3(); - -function getLuminanceFunction() { - - ColorManagement.getLuminanceCoefficients( _v0$1 ); - - const r = _v0$1.x.toFixed( 4 ); - const g = _v0$1.y.toFixed( 4 ); - const b = _v0$1.z.toFixed( 4 ); - - return [ - - 'float luminance( const in vec3 rgb ) {', - - ` const vec3 weights = vec3( ${ r }, ${ g }, ${ b } );`, - - ' return dot( weights, rgb );', - - '}' - - ].join( '\n' ); - -} - -function generateVertexExtensions( parameters ) { - - const chunks = [ - parameters.extensionClipCullDistance ? '#extension GL_ANGLE_clip_cull_distance : require' : '', - parameters.extensionMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '', - ]; - - return chunks.filter( filterEmptyLine ).join( '\n' ); - -} - -function generateDefines( defines ) { - - const chunks = []; - - for ( const name in defines ) { - - const value = defines[ name ]; - - if ( value === false ) continue; - - chunks.push( '#define ' + name + ' ' + value ); - - } - - return chunks.join( '\n' ); - -} - -function fetchAttributeLocations( gl, program ) { - - const attributes = {}; - - const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); - - for ( let i = 0; i < n; i ++ ) { - - const info = gl.getActiveAttrib( program, i ); - const name = info.name; - - let locationSize = 1; - if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; - if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; - if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; - - // log( 'WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); - - attributes[ name ] = { - type: info.type, - location: gl.getAttribLocation( program, name ), - locationSize: locationSize - }; - - } - - return attributes; - -} - -function filterEmptyLine( string ) { - - return string !== ''; - -} - -function replaceLightNums( string, parameters ) { - - const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; - - return string - .replace( /NUM_SUN_LIGHTS/g, parameters.numSunLights ) - .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) - .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) - .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) - .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) - .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) - .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) - .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) - .replace( /NUM_SUN_LIGHT_SHADOWS/g, parameters.numSunLightShadows ) - .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) - .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) - .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) - .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); - -} - -function replaceClippingPlaneNums( string, parameters ) { - - return string - .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) - .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); - -} - -// Resolve Includes - -const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; - -function resolveIncludes( string ) { - - return string.replace( includePattern, includeReplacer ); - -} - -const shaderChunkMap = new Map(); - -function includeReplacer( match, include ) { - - let string = ShaderChunk[ include ]; - - if ( string === undefined ) { - - const newInclude = shaderChunkMap.get( include ); - - if ( newInclude !== undefined ) { - - string = ShaderChunk[ newInclude ]; - warn( 'WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.', include, newInclude ); - - } else { - - throw new Error( 'THREE.WebGLProgram: Can not resolve #include <' + include + '>' ); - - } - - } - - return resolveIncludes( string ); - -} - -// Unroll Loops - -const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; - -function unrollLoops( string ) { - - return string.replace( unrollLoopPattern, loopReplacer ); - -} - -function loopReplacer( match, start, end, snippet ) { - - let string = ''; - - for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { - - string += snippet - .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) - .replace( /UNROLLED_LOOP_INDEX/g, i ); - - } - - return string; - -} - -// - -function generatePrecision( parameters ) { - - let precisionstring = `precision ${parameters.precision} float; - precision ${parameters.precision} int; - precision ${parameters.precision} sampler2D; - precision ${parameters.precision} samplerCube; - precision ${parameters.precision} sampler3D; - precision ${parameters.precision} sampler2DArray; - precision ${parameters.precision} sampler2DShadow; - precision ${parameters.precision} samplerCubeShadow; - precision ${parameters.precision} sampler2DArrayShadow; - precision ${parameters.precision} isampler2D; - precision ${parameters.precision} isampler3D; - precision ${parameters.precision} isamplerCube; - precision ${parameters.precision} isampler2DArray; - precision ${parameters.precision} usampler2D; - precision ${parameters.precision} usampler3D; - precision ${parameters.precision} usamplerCube; - precision ${parameters.precision} usampler2DArray; - `; - - if ( parameters.precision === 'highp' ) { - - precisionstring += '\n#define HIGH_PRECISION'; - - } else if ( parameters.precision === 'mediump' ) { - - precisionstring += '\n#define MEDIUM_PRECISION'; - - } else if ( parameters.precision === 'lowp' ) { - - precisionstring += '\n#define LOW_PRECISION'; - - } - - return precisionstring; - -} - -const shadowMapTypeDefines = { - [ PCFShadowMap ]: 'SHADOWMAP_TYPE_PCF', - [ VSMShadowMap ]: 'SHADOWMAP_TYPE_VSM' -}; - -function generateShadowMapTypeDefine( parameters ) { - - return shadowMapTypeDefines[ parameters.shadowMapType ] || 'SHADOWMAP_TYPE_BASIC'; - -} - -const envMapTypeDefines = { - [ CubeReflectionMapping ]: 'ENVMAP_TYPE_CUBE', - [ CubeRefractionMapping ]: 'ENVMAP_TYPE_CUBE', - [ CubeUVReflectionMapping ]: 'ENVMAP_TYPE_CUBE_UV' -}; - -function generateEnvMapTypeDefine( parameters ) { - - if ( parameters.envMap === false ) return 'ENVMAP_TYPE_CUBE'; - - return envMapTypeDefines[ parameters.envMapMode ] || 'ENVMAP_TYPE_CUBE'; - -} - -const envMapModeDefines = { - [ CubeRefractionMapping ]: 'ENVMAP_MODE_REFRACTION' -}; - -function generateEnvMapModeDefine( parameters ) { - - if ( parameters.envMap === false ) return 'ENVMAP_MODE_REFLECTION'; - - return envMapModeDefines[ parameters.envMapMode ] || 'ENVMAP_MODE_REFLECTION'; - -} - -const envMapBlendingDefines = { - [ MultiplyOperation ]: 'ENVMAP_BLENDING_MULTIPLY', - [ MixOperation ]: 'ENVMAP_BLENDING_MIX', - [ AddOperation ]: 'ENVMAP_BLENDING_ADD' -}; - -function generateEnvMapBlendingDefine( parameters ) { - - if ( parameters.envMap === false ) return 'ENVMAP_BLENDING_NONE'; - - return envMapBlendingDefines[ parameters.combine ] || 'ENVMAP_BLENDING_NONE'; - -} - -function generateCubeUVSize( parameters ) { - - const imageHeight = parameters.envMapCubeUVHeight; - - if ( imageHeight === null ) return null; - - const maxMip = Math.log2( imageHeight ) - 2; - - const texelHeight = 1.0 / imageHeight; - - const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); - - return { texelWidth, texelHeight, maxMip }; - -} - -function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { - - // TODO Send this event to Three.js DevTools - // log( 'WebGLProgram', cacheKey ); - - const gl = renderer.getContext(); - - const defines = parameters.defines; - - let vertexShader = parameters.vertexShader; - let fragmentShader = parameters.fragmentShader; - - const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); - const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); - const envMapModeDefine = generateEnvMapModeDefine( parameters ); - const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); - const envMapCubeUVSize = generateCubeUVSize( parameters ); - - const customVertexExtensions = generateVertexExtensions( parameters ); - - const customDefines = generateDefines( defines ); - - const program = gl.createProgram(); - - let prefixVertex, prefixFragment; - let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; - - if ( parameters.isRawShaderMaterial ) { - - prefixVertex = [ - - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixVertex.length > 0 ) { - - prefixVertex += '\n'; - - } - - prefixFragment = [ - - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixFragment.length > 0 ) { - - prefixFragment += '\n'; - - } - - } else { - - prefixVertex = [ - - generatePrecision( parameters ), - - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines, - - parameters.extensionClipCullDistance ? '#define USE_CLIP_DISTANCE' : '', - parameters.batching ? '#define USE_BATCHING' : '', - parameters.batchingColor ? '#define USE_BATCHING_COLOR' : '', - parameters.instancing ? '#define USE_INSTANCING' : '', - parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', - parameters.instancingMorph ? '#define USE_INSTANCING_MORPH' : '', - - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - - parameters.anisotropy ? '#define USE_ANISOTROPY' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.alphaHash ? '#define USE_ALPHAHASH' : '', - - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - - // - - parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', - parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', - parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', - parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', - parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', - parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', - parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', - parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', - - parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', - parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', - - parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', - - parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', - parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', - parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', - - parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', - parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', - - parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', - parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', - - parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', - parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', - parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', - - parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', - parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', - - // - - parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', - parameters.vertexNormals ? '#define HAS_NORMAL' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', - - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.skinning ? '#define USE_SKINNING' : '', - - parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', - parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', - ( parameters.morphColors ) ? '#define USE_MORPHCOLORS' : '', - ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', - ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - - parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGARITHMIC_DEPTH_BUFFER' : '', - parameters.reversedDepthBuffer ? '#define USE_REVERSED_DEPTH_BUFFER' : '', - - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', - - '#ifdef USE_INSTANCING', - - ' attribute mat4 instanceMatrix;', - - '#endif', - - '#ifdef USE_INSTANCING_COLOR', - - ' attribute vec3 instanceColor;', - - '#endif', - - '#ifdef USE_INSTANCING_MORPH', - - ' uniform sampler2D morphTexture;', - - '#endif', - - 'attribute vec3 position;', - 'attribute vec3 normal;', - 'attribute vec2 uv;', - - '#ifdef USE_UV1', - - ' attribute vec2 uv1;', - - '#endif', - - '#ifdef USE_UV2', - - ' attribute vec2 uv2;', - - '#endif', - - '#ifdef USE_UV3', - - ' attribute vec2 uv3;', - - '#endif', - - '#ifdef USE_TANGENT', - - ' attribute vec4 tangent;', - - '#endif', - - '#if defined( USE_COLOR_ALPHA )', - - ' attribute vec4 color;', - - '#elif defined( USE_COLOR )', - - ' attribute vec3 color;', - - '#endif', - - '#ifdef USE_SKINNING', - - ' attribute vec4 skinIndex;', - ' attribute vec4 skinWeight;', - - '#endif', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - prefixFragment = [ - - generatePrecision( parameters ), - - '#define SHADER_TYPE ' + parameters.shaderType, - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines, - - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - - parameters.alphaToCoverage ? '#define ALPHA_TO_COVERAGE' : '', - parameters.map ? '#define USE_MAP' : '', - parameters.matcap ? '#define USE_MATCAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapTypeDefine : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.envMap ? '#define ' + envMapBlendingDefine : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', - envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.packedNormalMap ? '#define USE_PACKED_NORMALMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - - parameters.anisotropy ? '#define USE_ANISOTROPY' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - - parameters.clearcoat ? '#define USE_CLEARCOAT' : '', - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - - parameters.dispersion ? '#define USE_DISPERSION' : '', - parameters.retroreflection ? '#define USE_RETROREFLECTION' : '', - - parameters.iridescence ? '#define USE_IRIDESCENCE' : '', - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.alphaTest ? '#define USE_ALPHATEST' : '', - parameters.alphaHash ? '#define USE_ALPHAHASH' : '', - - parameters.sheen ? '#define USE_SHEEN' : '', - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - - parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', - parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', - parameters.vertexAlphas || parameters.batchingColor ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', - - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - - parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', - - parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - - parameters.numLightProbeGrids > 0 ? '#define USE_LIGHT_PROBES_GRID' : '', - - parameters.decodeVideoTexture ? '#define DECODE_VIDEO_TEXTURE' : '', - parameters.decodeVideoTextureEmissive ? '#define DECODE_VIDEO_TEXTURE_EMISSIVE' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGARITHMIC_DEPTH_BUFFER' : '', - parameters.reversedDepthBuffer ? '#define USE_REVERSED_DEPTH_BUFFER' : '', - - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', - - ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', - ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below - ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', - - parameters.dithering ? '#define DITHERING' : '', - parameters.opaque ? '#define OPAQUE' : '', - - ShaderChunk[ 'colorspace_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below - getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), - getLuminanceFunction(), - - parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - } - - vertexShader = resolveIncludes( vertexShader ); - vertexShader = replaceLightNums( vertexShader, parameters ); - vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); - - fragmentShader = resolveIncludes( fragmentShader ); - fragmentShader = replaceLightNums( fragmentShader, parameters ); - fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); - - vertexShader = unrollLoops( vertexShader ); - fragmentShader = unrollLoops( fragmentShader ); - - if ( parameters.isRawShaderMaterial !== true ) { - - // GLSL 3.0 conversion for built-in materials and ShaderMaterial - - versionString = '#version 300 es\n'; - - prefixVertex = [ - customVertexExtensions, - '#define attribute in', - '#define varying out', - '#define texture2D texture' - ].join( '\n' ) + '\n' + prefixVertex; - - prefixFragment = [ - '#define varying in', - ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', - ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', - '#define gl_FragDepthEXT gl_FragDepth', - '#define texture2D texture', - '#define textureCube texture', - '#define texture2DProj textureProj', - '#define texture2DLodEXT textureLod', - '#define texture2DProjLodEXT textureProjLod', - '#define textureCubeLodEXT textureLod', - '#define texture2DGradEXT textureGrad', - '#define texture2DProjGradEXT textureProjGrad', - '#define textureCubeGradEXT textureGrad' - ].join( '\n' ) + '\n' + prefixFragment; - - } - - const vertexGlsl = versionString + prefixVertex + vertexShader; - const fragmentGlsl = versionString + prefixFragment + fragmentShader; - - // log( '*VERTEX*', vertexGlsl ); - // log( '*FRAGMENT*', fragmentGlsl ); - - const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); - const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - - gl.attachShader( program, glVertexShader ); - gl.attachShader( program, glFragmentShader ); - - // Force a particular attribute to index 0. - - if ( parameters.index0AttributeName !== undefined ) { - - gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); - - } else if ( parameters.hasPositionAttribute === true ) { - - // below avoids the "Attribute 0 is disabled" performance penalty - - gl.bindAttribLocation( program, 0, 'position' ); - - } - - gl.linkProgram( program ); - - function onFirstUse( self ) { - - // check for link errors - if ( renderer.debug.checkShaderErrors ) { - - const programInfoLog = gl.getProgramInfoLog( program ) || ''; - const vertexShaderInfoLog = gl.getShaderInfoLog( glVertexShader ) || ''; - const fragmentShaderInfoLog = gl.getShaderInfoLog( glFragmentShader ) || ''; - - const programLog = programInfoLog.trim(); - const vertexLog = vertexShaderInfoLog.trim(); - const fragmentLog = fragmentShaderInfoLog.trim(); - - let runnable = true; - let haveDiagnostics = true; - - if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - - runnable = false; - - if ( typeof renderer.debug.onShaderError === 'function' ) { - - renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); - - } else { - - // default error reporting - - const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); - const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); - - error( - 'WebGLProgram: Shader Error ' + gl.getError() + ' - ' + - 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + - 'Material Name: ' + self.name + '\n' + - 'Material Type: ' + self.type + '\n\n' + - 'Program Info Log: ' + programLog + '\n' + - vertexErrors + '\n' + - fragmentErrors - ); - - } - - } else if ( programLog !== '' ) { - - warn( 'WebGLProgram: Program Info Log:', programLog ); - - } else if ( vertexLog === '' || fragmentLog === '' ) { - - haveDiagnostics = false; - - } - - if ( haveDiagnostics ) { - - self.diagnostics = { - - runnable: runnable, - - programLog: programLog, - - vertexShader: { - - log: vertexLog, - prefix: prefixVertex - - }, - - fragmentShader: { - - log: fragmentLog, - prefix: prefixFragment - - } - - }; - - } - - } - - // Clean up - - // Crashes in iOS9 and iOS10. #18402 - // gl.detachShader( program, glVertexShader ); - // gl.detachShader( program, glFragmentShader ); - - gl.deleteShader( glVertexShader ); - gl.deleteShader( glFragmentShader ); - - cachedUniforms = new WebGLUniforms( gl, program ); - cachedAttributes = fetchAttributeLocations( gl, program ); - - } - - // set up caching for uniform locations - - let cachedUniforms; - - this.getUniforms = function () { - - if ( cachedUniforms === undefined ) { - - // Populates cachedUniforms and cachedAttributes - onFirstUse( this ); - - } - - return cachedUniforms; - - }; - - // set up caching for attribute locations - - let cachedAttributes; - - this.getAttributes = function () { - - if ( cachedAttributes === undefined ) { - - // Populates cachedAttributes and cachedUniforms - onFirstUse( this ); - - } - - return cachedAttributes; - - }; - - // indicate when the program is ready to be used. if the KHR_parallel_shader_compile extension isn't supported, - // flag the program as ready immediately. It may cause a stall when it's first used. - - let programReady = ( parameters.rendererExtensionParallelShaderCompile === false ); - - this.isReady = function () { - - if ( programReady === false ) { - - programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR ); - - } - - return programReady; - - }; - - // free resource - - this.destroy = function () { - - bindingStates.releaseStatesOfProgram( this ); - - gl.deleteProgram( program ); - this.program = undefined; - - }; - - // - - this.type = parameters.shaderType; - this.name = parameters.shaderName; - this.id = programIdCount ++; - this.cacheKey = cacheKey; - this.usedTimes = 1; - this.program = program; - this.vertexShader = glVertexShader; - this.fragmentShader = glFragmentShader; - - return this; - -} - -let _id = 0; - -class WebGLShaderCache { - - constructor() { - - this.shaderCache = new Map(); - this.materialCache = new Map(); - - } - - update( material, vertexShaderStage, fragmentShaderStage ) { - - const materialShaders = this._getShaderCacheForMaterial( material ); - - if ( materialShaders.has( vertexShaderStage ) === false ) { - - materialShaders.add( vertexShaderStage ); - vertexShaderStage.usedTimes ++; - - } - - if ( materialShaders.has( fragmentShaderStage ) === false ) { - - materialShaders.add( fragmentShaderStage ); - fragmentShaderStage.usedTimes ++; - - } - - return this; - - } - - remove( material ) { - - const materialShaders = this.materialCache.get( material ); - - for ( const shaderStage of materialShaders ) { - - shaderStage.usedTimes --; - - if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); - - } - - this.materialCache.delete( material ); - - return this; - - } - - getVertexShaderStage( material ) { - - return this._getShaderStage( material.vertexShader ); - - } - - getFragmentShaderStage( material ) { - - return this._getShaderStage( material.fragmentShader ); - - } - - dispose() { - - this.shaderCache.clear(); - this.materialCache.clear(); - - } - - _getShaderCacheForMaterial( material ) { - - const cache = this.materialCache; - let set = cache.get( material ); - - if ( set === undefined ) { - - set = new Set(); - cache.set( material, set ); - - } - - return set; - - } - - _getShaderStage( code ) { - - const cache = this.shaderCache; - let stage = cache.get( code ); - - if ( stage === undefined ) { - - stage = new WebGLShaderStage( code ); - cache.set( code, stage ); - - } - - return stage; - - } - -} - -class WebGLShaderStage { - - constructor( code ) { - - this.id = _id ++; - - this.code = code; - this.usedTimes = 0; - - } - -} - -function isPackedRGFormat( format ) { - - return format === RGFormat || format === RG11_EAC_Format || format === RED_GREEN_RGTC2_Format; - -} - -function WebGLPrograms( renderer, environments, extensions, capabilities, bindingStates, clipping ) { - - const _programLayers = new Layers(); - const _customShaders = new WebGLShaderCache(); - const _activeChannels = new Set(); - const programs = []; - const programsMap = new Map(); - - const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; - - let precision = capabilities.precision; - - const shaderIDs = { - MeshDepthMaterial: 'depth', - MeshDistanceMaterial: 'distance', - MeshNormalMaterial: 'normal', - MeshBasicMaterial: 'basic', - MeshLambertMaterial: 'lambert', - MeshPhongMaterial: 'phong', - MeshToonMaterial: 'toon', - MeshStandardMaterial: 'physical', - MeshPhysicalMaterial: 'physical', - MeshMatcapMaterial: 'matcap', - LineBasicMaterial: 'basic', - LineDashedMaterial: 'dashed', - PointsMaterial: 'points', - ShadowMaterial: 'shadow', - SpriteMaterial: 'sprite' - }; - - function getChannel( value ) { - - _activeChannels.add( value ); - - if ( value === 0 ) return 'uv'; - - return `uv${ value }`; - - } - - function getParameters( material, lights, shadows, scene, object, lightProbeGrids ) { - - const fog = scene.fog; - const geometry = object.geometry; - const environment = ( material.isMeshStandardMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial ) ? scene.environment : null; - - const usePMREM = material.isMeshStandardMaterial || ( material.isMeshLambertMaterial && ! material.envMap ) || ( material.isMeshPhongMaterial && ! material.envMap ); - const envMap = environments.get( material.envMap || environment, usePMREM ); - const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; - - const shaderID = shaderIDs[ material.type ]; - - // heuristics to create shader parameters according to lights in the scene - // (not to blow over maxLights budget) - - if ( material.precision !== null ) { - - precision = capabilities.getMaxPrecision( material.precision ); - - if ( precision !== material.precision ) { - - warn( 'WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); - - } - - } - - // - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - let morphTextureStride = 0; - - if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; - if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; - if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; - - // - - let vertexShader, fragmentShader; - let customVertexShaderID, customFragmentShaderID; - - if ( shaderID ) { - - const shader = ShaderLib[ shaderID ]; - - vertexShader = shader.vertexShader; - fragmentShader = shader.fragmentShader; - - } else { - - vertexShader = material.vertexShader; - fragmentShader = material.fragmentShader; - - const vertexShaderStage = _customShaders.getVertexShaderStage( material ); - const fragmentShaderStage = _customShaders.getFragmentShaderStage( material ); - - _customShaders.update( material, vertexShaderStage, fragmentShaderStage ); - - customVertexShaderID = vertexShaderStage.id; - customFragmentShaderID = fragmentShaderStage.id; - - } - - const currentRenderTarget = renderer.getRenderTarget(); - const reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); - - const IS_INSTANCEDMESH = object.isInstancedMesh === true; - const IS_BATCHEDMESH = object.isBatchedMesh === true; - - const HAS_MAP = !! material.map; - const HAS_MATCAP = !! material.matcap; - const HAS_ENVMAP = !! envMap; - const HAS_AOMAP = !! material.aoMap; - const HAS_LIGHTMAP = !! material.lightMap; - const HAS_BUMPMAP = !! material.bumpMap && material.wireframe === false; - const HAS_NORMALMAP = !! material.normalMap; - const HAS_DISPLACEMENTMAP = !! material.displacementMap; - const HAS_EMISSIVEMAP = !! material.emissiveMap; - - const HAS_METALNESSMAP = !! material.metalnessMap; - const HAS_ROUGHNESSMAP = !! material.roughnessMap; - - const HAS_ANISOTROPY = material.anisotropy > 0; - const HAS_CLEARCOAT = material.clearcoat > 0; - const HAS_DISPERSION = material.dispersion > 0; - const HAS_RETROREFLECTION = material.retroreflectivity > 0; - const HAS_IRIDESCENCE = material.iridescence > 0; - const HAS_SHEEN = material.sheen > 0; - const HAS_TRANSMISSION = material.transmission > 0; - - const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; - - const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; - const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; - const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; - - const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; - const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; - - const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; - const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; - - const HAS_SPECULARMAP = !! material.specularMap; - const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; - const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; - - const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; - const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; - - const HAS_GRADIENTMAP = !! material.gradientMap; - - const HAS_ALPHAMAP = !! material.alphaMap; - - const HAS_ALPHATEST = material.alphaTest > 0; - - const HAS_ALPHAHASH = !! material.alphaHash; - - const HAS_EXTENSIONS = !! material.extensions; - - let toneMapping = NoToneMapping; - - if ( material.toneMapped ) { - - if ( currentRenderTarget === null || currentRenderTarget.isXRRenderTarget === true ) { - - toneMapping = renderer.toneMapping; - - } - - } - - const parameters = { - - shaderID: shaderID, - shaderType: material.type, - shaderName: material.name, - - vertexShader: vertexShader, - fragmentShader: fragmentShader, - defines: material.defines, - - customVertexShaderID: customVertexShaderID, - customFragmentShaderID: customFragmentShaderID, - - isRawShaderMaterial: material.isRawShaderMaterial === true, - glslVersion: material.glslVersion, - - precision: precision, - - batching: IS_BATCHEDMESH, - batchingColor: IS_BATCHEDMESH && object._colorsTexture !== null, - instancing: IS_INSTANCEDMESH, - instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, - instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null, - - outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : ColorManagement.workingColorSpace ), - alphaToCoverage: !! material.alphaToCoverage, - - map: HAS_MAP, - matcap: HAS_MATCAP, - envMap: HAS_ENVMAP, - envMapMode: HAS_ENVMAP && envMap.mapping, - envMapCubeUVHeight: envMapCubeUVHeight, - aoMap: HAS_AOMAP, - lightMap: HAS_LIGHTMAP, - bumpMap: HAS_BUMPMAP, - normalMap: HAS_NORMALMAP, - displacementMap: HAS_DISPLACEMENTMAP, - emissiveMap: HAS_EMISSIVEMAP, - - normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, - normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, - packedNormalMap: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap && isPackedRGFormat( material.normalMap.format ), - - metalnessMap: HAS_METALNESSMAP, - roughnessMap: HAS_ROUGHNESSMAP, - - anisotropy: HAS_ANISOTROPY, - anisotropyMap: HAS_ANISOTROPYMAP, - - clearcoat: HAS_CLEARCOAT, - clearcoatMap: HAS_CLEARCOATMAP, - clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, - clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, - - dispersion: HAS_DISPERSION, - retroreflection: HAS_RETROREFLECTION, - - iridescence: HAS_IRIDESCENCE, - iridescenceMap: HAS_IRIDESCENCEMAP, - iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, - - sheen: HAS_SHEEN, - sheenColorMap: HAS_SHEEN_COLORMAP, - sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, - - specularMap: HAS_SPECULARMAP, - specularColorMap: HAS_SPECULAR_COLORMAP, - specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, - - transmission: HAS_TRANSMISSION, - transmissionMap: HAS_TRANSMISSIONMAP, - thicknessMap: HAS_THICKNESSMAP, - - gradientMap: HAS_GRADIENTMAP, - - opaque: material.transparent === false && material.blending === NormalBlending && material.alphaToCoverage === false, - - alphaMap: HAS_ALPHAMAP, - alphaTest: HAS_ALPHATEST, - alphaHash: HAS_ALPHAHASH, - - combine: material.combine, - - // - - mapUv: HAS_MAP && getChannel( material.map.channel ), - aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), - lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), - bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), - normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), - displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), - emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), - - metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), - roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), - - anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), - - clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), - clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), - clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), - - iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), - iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), - - sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), - sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), - - specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), - specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), - specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), - - transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), - thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), - - alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), - - // - - vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), - vertexNormals: !! geometry.attributes.normal, - vertexColors: material.vertexColors, - vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, - - pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), - - fog: !! fog, - useFog: material.fog === true, - fogExp2: ( !! fog && fog.isFogExp2 ), - - flatShading: material.wireframe === false && ( - material.flatShading === true || - ( geometry.attributes.normal === undefined && HAS_NORMALMAP === false && - ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial || material.isMeshPhysicalMaterial ) - ) - ), - - sizeAttenuation: material.sizeAttenuation === true, - logarithmicDepthBuffer: logarithmicDepthBuffer, - reversedDepthBuffer: reversedDepthBuffer, - - skinning: object.isSkinnedMesh === true, - - hasPositionAttribute: geometry.attributes.position !== undefined, - - morphTargets: geometry.morphAttributes.position !== undefined, - morphNormals: geometry.morphAttributes.normal !== undefined, - morphColors: geometry.morphAttributes.color !== undefined, - morphTargetsCount: morphTargetsCount, - morphTextureStride: morphTextureStride, - - numSunLights: lights.sun.length, - numDirLights: lights.directional.length, - numPointLights: lights.point.length, - numSpotLights: lights.spot.length, - numSpotLightMaps: lights.spotLightMap.length, - numRectAreaLights: lights.rectArea.length, - numHemiLights: lights.hemi.length, - - numSunLightShadows: lights.sunShadowMap.length, - numDirLightShadows: lights.directionalShadowMap.length, - numPointLightShadows: lights.pointShadowMap.length, - numSpotLightShadows: lights.spotShadowMap.length, - numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, - - numLightProbes: lights.numLightProbes, - - numLightProbeGrids: lightProbeGrids.length, - - numClippingPlanes: clipping.numPlanes, - numClipIntersection: clipping.numIntersection, - - dithering: material.dithering, - - shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, - shadowMapType: renderer.shadowMap.type, - - toneMapping: toneMapping, - - decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), - decodeVideoTextureEmissive: HAS_EMISSIVEMAP && ( material.emissiveMap.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.emissiveMap.colorSpace ) === SRGBTransfer ), - - premultipliedAlpha: material.premultipliedAlpha, - - doubleSided: material.side === DoubleSide, - flipSided: material.side === BackSide, - - useDepthPacking: material.depthPacking >= 0, - depthPacking: material.depthPacking || 0, - - index0AttributeName: material.index0AttributeName, - - extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance === true && extensions.has( 'WEBGL_clip_cull_distance' ), - extensionMultiDraw: ( HAS_EXTENSIONS && material.extensions.multiDraw === true || IS_BATCHEDMESH ) && extensions.has( 'WEBGL_multi_draw' ), - - rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ), - - customProgramCacheKey: material.customProgramCacheKey() - - }; - - // the usage of getChannel() determines the active texture channels for this shader - - parameters.vertexUv1s = _activeChannels.has( 1 ); - parameters.vertexUv2s = _activeChannels.has( 2 ); - parameters.vertexUv3s = _activeChannels.has( 3 ); - - _activeChannels.clear(); - - return parameters; - - } - - function getProgramCacheKey( parameters ) { - - const array = []; - - if ( parameters.shaderID ) { - - array.push( parameters.shaderID ); - - } else { - - array.push( parameters.customVertexShaderID ); - array.push( parameters.customFragmentShaderID ); - - } - - if ( parameters.defines !== undefined ) { - - for ( const name in parameters.defines ) { - - array.push( name ); - array.push( parameters.defines[ name ] ); - - } - - } - - if ( parameters.isRawShaderMaterial === false ) { - - getProgramCacheKeyParameters( array, parameters ); - getProgramCacheKeyBooleans( array, parameters ); - array.push( renderer.outputColorSpace ); - - } - - array.push( parameters.customProgramCacheKey ); - - return array.join(); - - } - - function getProgramCacheKeyParameters( array, parameters ) { - - array.push( parameters.precision ); - array.push( parameters.outputColorSpace ); - array.push( parameters.envMapMode ); - array.push( parameters.envMapCubeUVHeight ); - array.push( parameters.mapUv ); - array.push( parameters.alphaMapUv ); - array.push( parameters.lightMapUv ); - array.push( parameters.aoMapUv ); - array.push( parameters.bumpMapUv ); - array.push( parameters.normalMapUv ); - array.push( parameters.displacementMapUv ); - array.push( parameters.emissiveMapUv ); - array.push( parameters.metalnessMapUv ); - array.push( parameters.roughnessMapUv ); - array.push( parameters.anisotropyMapUv ); - array.push( parameters.clearcoatMapUv ); - array.push( parameters.clearcoatNormalMapUv ); - array.push( parameters.clearcoatRoughnessMapUv ); - array.push( parameters.iridescenceMapUv ); - array.push( parameters.iridescenceThicknessMapUv ); - array.push( parameters.sheenColorMapUv ); - array.push( parameters.sheenRoughnessMapUv ); - array.push( parameters.specularMapUv ); - array.push( parameters.specularColorMapUv ); - array.push( parameters.specularIntensityMapUv ); - array.push( parameters.transmissionMapUv ); - array.push( parameters.thicknessMapUv ); - array.push( parameters.combine ); - array.push( parameters.fogExp2 ); - array.push( parameters.sizeAttenuation ); - array.push( parameters.morphTargetsCount ); - array.push( parameters.morphAttributeCount ); - array.push( parameters.numSunLights ); - array.push( parameters.numDirLights ); - array.push( parameters.numPointLights ); - array.push( parameters.numSpotLights ); - array.push( parameters.numSpotLightMaps ); - array.push( parameters.numHemiLights ); - array.push( parameters.numRectAreaLights ); - array.push( parameters.numSunLightShadows ); - array.push( parameters.numDirLightShadows ); - array.push( parameters.numPointLightShadows ); - array.push( parameters.numSpotLightShadows ); - array.push( parameters.numSpotLightShadowsWithMaps ); - array.push( parameters.numLightProbes ); - array.push( parameters.shadowMapType ); - array.push( parameters.toneMapping ); - array.push( parameters.numClippingPlanes ); - array.push( parameters.numClipIntersection ); - array.push( parameters.depthPacking ); - - } - - function getProgramCacheKeyBooleans( array, parameters ) { - - _programLayers.disableAll(); - - if ( parameters.instancing ) - _programLayers.enable( 0 ); - if ( parameters.instancingColor ) - _programLayers.enable( 1 ); - if ( parameters.instancingMorph ) - _programLayers.enable( 2 ); - if ( parameters.matcap ) - _programLayers.enable( 3 ); - if ( parameters.envMap ) - _programLayers.enable( 4 ); - if ( parameters.normalMapObjectSpace ) - _programLayers.enable( 5 ); - if ( parameters.normalMapTangentSpace ) - _programLayers.enable( 6 ); - if ( parameters.clearcoat ) - _programLayers.enable( 7 ); - if ( parameters.iridescence ) - _programLayers.enable( 8 ); - if ( parameters.alphaTest ) - _programLayers.enable( 9 ); - if ( parameters.vertexColors ) - _programLayers.enable( 10 ); - if ( parameters.vertexAlphas ) - _programLayers.enable( 11 ); - if ( parameters.vertexUv1s ) - _programLayers.enable( 12 ); - if ( parameters.vertexUv2s ) - _programLayers.enable( 13 ); - if ( parameters.vertexUv3s ) - _programLayers.enable( 14 ); - if ( parameters.vertexTangents ) - _programLayers.enable( 15 ); - if ( parameters.anisotropy ) - _programLayers.enable( 16 ); - if ( parameters.alphaHash ) - _programLayers.enable( 17 ); - if ( parameters.batching ) - _programLayers.enable( 18 ); - if ( parameters.dispersion ) - _programLayers.enable( 19 ); - if ( parameters.retroreflection ) - _programLayers.enable( 24 ); - if ( parameters.batchingColor ) - _programLayers.enable( 20 ); - if ( parameters.gradientMap ) - _programLayers.enable( 21 ); - if ( parameters.packedNormalMap ) - _programLayers.enable( 22 ); - if ( parameters.vertexNormals ) - _programLayers.enable( 23 ); - - array.push( _programLayers.mask ); - _programLayers.disableAll(); - - if ( parameters.fog ) - _programLayers.enable( 0 ); - if ( parameters.useFog ) - _programLayers.enable( 1 ); - if ( parameters.flatShading ) - _programLayers.enable( 2 ); - if ( parameters.logarithmicDepthBuffer ) - _programLayers.enable( 3 ); - if ( parameters.reversedDepthBuffer ) - _programLayers.enable( 4 ); - if ( parameters.skinning ) - _programLayers.enable( 5 ); - if ( parameters.morphTargets ) - _programLayers.enable( 6 ); - if ( parameters.morphNormals ) - _programLayers.enable( 7 ); - if ( parameters.morphColors ) - _programLayers.enable( 8 ); - if ( parameters.premultipliedAlpha ) - _programLayers.enable( 9 ); - if ( parameters.shadowMapEnabled ) - _programLayers.enable( 10 ); - if ( parameters.doubleSided ) - _programLayers.enable( 11 ); - if ( parameters.flipSided ) - _programLayers.enable( 12 ); - if ( parameters.useDepthPacking ) - _programLayers.enable( 13 ); - if ( parameters.dithering ) - _programLayers.enable( 14 ); - if ( parameters.transmission ) - _programLayers.enable( 15 ); - if ( parameters.sheen ) - _programLayers.enable( 16 ); - if ( parameters.opaque ) - _programLayers.enable( 17 ); - if ( parameters.pointsUvs ) - _programLayers.enable( 18 ); - if ( parameters.decodeVideoTexture ) - _programLayers.enable( 19 ); - if ( parameters.decodeVideoTextureEmissive ) - _programLayers.enable( 20 ); - if ( parameters.alphaToCoverage ) - _programLayers.enable( 21 ); - if ( parameters.numLightProbeGrids > 0 ) - _programLayers.enable( 22 ); - if ( parameters.hasPositionAttribute ) - _programLayers.enable( 23 ); - - array.push( _programLayers.mask ); - - } - - function getUniforms( material ) { - - const shaderID = shaderIDs[ material.type ]; - let uniforms; - - if ( shaderID ) { - - const shader = ShaderLib[ shaderID ]; - uniforms = UniformsUtils.clone( shader.uniforms ); - - } else { - - uniforms = material.uniforms; - - } - - return uniforms; - - } - - function acquireProgram( parameters, cacheKey ) { - - let program = programsMap.get( cacheKey ); - - if ( program !== undefined ) { - - ++ program.usedTimes; - - } else { - - program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); - programs.push( program ); - - programsMap.set( cacheKey, program ); - - } - - return program; - - } - - function releaseProgram( program ) { - - if ( -- program.usedTimes === 0 ) { - - // Remove from unordered set - const i = programs.indexOf( program ); - programs[ i ] = programs[ programs.length - 1 ]; - programs.pop(); - - // Remove from map - programsMap.delete( program.cacheKey ); - - // Free WebGL resources - program.destroy(); - - } - - } - - function releaseShaderCache( material ) { - - _customShaders.remove( material ); - - } - - function dispose() { - - _customShaders.dispose(); - - } - - return { - getParameters: getParameters, - getProgramCacheKey: getProgramCacheKey, - getUniforms: getUniforms, - acquireProgram: acquireProgram, - releaseProgram: releaseProgram, - releaseShaderCache: releaseShaderCache, - // Exposed for resource monitoring & error feedback via renderer.info: - programs: programs, - dispose: dispose - }; - -} - -function WebGLProperties() { - - let properties = new WeakMap(); - - function has( object ) { - - return properties.has( object ); - - } - - function get( object ) { - - let map = properties.get( object ); - - if ( map === undefined ) { - - map = {}; - properties.set( object, map ); - - } - - return map; - - } - - function remove( object ) { - - properties.delete( object ); - - } - - function update( object, key, value ) { - - properties.get( object )[ key ] = value; - - } - - function dispose() { - - properties = new WeakMap(); - - } - - return { - has: has, - get: get, - remove: remove, - update: update, - dispose: dispose - }; - -} - -function painterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.material.id !== b.material.id ) { - - return a.material.id - b.material.id; - - } else if ( a.materialVariant !== b.materialVariant ) { - - return a.materialVariant - b.materialVariant; - - } else if ( a.z !== b.z ) { - - return a.z - b.z; - - } else { - - return a.id - b.id; - - } - -} - -function reversePainterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.z !== b.z ) { - - return b.z - a.z; - - } else { - - return a.id - b.id; - - } - -} - - -function WebGLRenderList() { - - const renderItems = []; - let renderItemsIndex = 0; - - const opaque = []; - const transmissive = []; - const transparent = []; - - function init() { - - renderItemsIndex = 0; - - opaque.length = 0; - transmissive.length = 0; - transparent.length = 0; - - } - - function materialVariant( object ) { - - let variant = 0; - if ( object.isInstancedMesh ) variant += 2; - if ( object.isSkinnedMesh ) variant += 1; - return variant; - - } - - function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { - - let renderItem = renderItems[ renderItemsIndex ]; - - if ( renderItem === undefined ) { - - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - materialVariant: materialVariant( object ), - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group - }; - - renderItems[ renderItemsIndex ] = renderItem; - - } else { - - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.materialVariant = materialVariant( object ); - renderItem.groupOrder = groupOrder; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; - - } - - renderItemsIndex ++; - - return renderItem; - - } - - function push( object, geometry, material, groupOrder, z, group, camera ) { - - // with a reversed depth buffer the projected z is inverted - - if ( camera.reversedDepth === true ) z = - z; - - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - if ( material.transmission > 0.0 ) { - - transmissive.push( renderItem ); - - } else if ( material.transparent === true ) { - - transparent.push( renderItem ); - - } else { - - opaque.push( renderItem ); - - } - - } - - function unshift( object, geometry, material, groupOrder, z, group ) { - - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - if ( material.transmission > 0.0 ) { - - transmissive.unshift( renderItem ); - - } else if ( material.transparent === true ) { - - transparent.unshift( renderItem ); - - } else { - - opaque.unshift( renderItem ); - - } - - } - - function sort( customOpaqueSort, customTransparentSort ) { - - if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); - if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); - if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); - - } - - function finish() { - - // Clear references from inactive renderItems in the list - - for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { - - const renderItem = renderItems[ i ]; - - if ( renderItem.id === null ) break; - - renderItem.id = null; - renderItem.object = null; - renderItem.geometry = null; - renderItem.material = null; - renderItem.group = null; - - } - - } - - return { - - opaque: opaque, - transmissive: transmissive, - transparent: transparent, - - init: init, - push: push, - unshift: unshift, - finish: finish, - - sort: sort - }; - -} - -function WebGLRenderLists() { - - let lists = new WeakMap(); - - function get( scene, renderCallDepth ) { - - const listArray = lists.get( scene ); - let list; - - if ( listArray === undefined ) { - - list = new WebGLRenderList(); - lists.set( scene, [ list ] ); - - } else { - - if ( renderCallDepth >= listArray.length ) { - - list = new WebGLRenderList(); - listArray.push( list ); - - } else { - - list = listArray[ renderCallDepth ]; - - } - - } - - return list; - - } - - function dispose() { - - lists = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - -} - -function UniformsCache() { - - const lights = {}; - - return { - - get: function ( light ) { - - if ( lights[ light.id ] !== undefined ) { - - return lights[ light.id ]; - - } - - let uniforms; - - switch ( light.type ) { - - case 'SunLight': - case 'DirectionalLight': - uniforms = { - direction: new Vector3(), - color: new Color() - }; - break; - - case 'SpotLight': - uniforms = { - position: new Vector3(), - direction: new Vector3(), - color: new Color(), - distance: 0, - coneCos: 0, - penumbraCos: 0, - decay: 0 - }; - break; - - case 'PointLight': - uniforms = { - position: new Vector3(), - color: new Color(), - distance: 0, - decay: 0 - }; - break; - - case 'HemisphereLight': - uniforms = { - direction: new Vector3(), - skyColor: new Color(), - groundColor: new Color() - }; - break; - - case 'RectAreaLight': - uniforms = { - color: new Color(), - position: new Vector3(), - halfWidth: new Vector3(), - halfHeight: new Vector3() - }; - break; - - } - - lights[ light.id ] = uniforms; - - return uniforms; - - } - - }; - -} - -function ShadowUniformsCache() { - - const lights = {}; - - return { - - get: function ( light ) { - - if ( lights[ light.id ] !== undefined ) { - - return lights[ light.id ]; - - } - - let uniforms; - - switch ( light.type ) { - - case 'SunLight': - case 'DirectionalLight': - uniforms = { - shadowIntensity: 1, - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'SpotLight': - uniforms = { - shadowIntensity: 1, - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'PointLight': - uniforms = { - shadowIntensity: 1, - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2(), - shadowCameraNear: 1, - shadowCameraFar: 1000 - }; - break; - - // TODO (abelnation): set RectAreaLight shadow uniforms - - } - - lights[ light.id ] = uniforms; - - return uniforms; - - } - - }; - -} - - - -let nextVersion = 0; - -function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { - - return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); - -} - -function WebGLLights( extensions ) { - - const cache = new UniformsCache(); - - const shadowCache = ShadowUniformsCache(); - - const state = { - - version: 0, - - hash: { - sunLength: -1, - directionalLength: -1, - pointLength: -1, - spotLength: -1, - rectAreaLength: -1, - hemiLength: -1, - - numSunShadows: -1, - numDirectionalShadows: -1, - numPointShadows: -1, - numSpotShadows: -1, - numSpotMaps: -1, - - numLightProbes: -1 - }, - - ambient: [ 0, 0, 0 ], - probe: [], - sun: [], - sunShadow: [], - sunShadowMap: [], - sunShadowMatrix: [], - sunShadowCascade: [], - directional: [], - directionalShadow: [], - directionalShadowMap: [], - directionalShadowMatrix: [], - spot: [], - spotLightMap: [], - spotShadow: [], - spotShadowMap: [], - spotLightMatrix: [], - rectArea: [], - rectAreaLTC1: null, - rectAreaLTC2: null, - point: [], - pointShadow: [], - pointShadowMap: [], - pointShadowMatrix: [], - hemi: [], - numSpotLightShadowsWithMaps: 0, - numLightProbes: 0 - - }; - - for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); - - const vector3 = new Vector3(); - const matrix4 = new Matrix4(); - const matrix42 = new Matrix4(); - - function setup( lights ) { - - let r = 0, g = 0, b = 0; - - for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); - - let sunLength = 0; - let numSunShadows = 0; - let numSunShadowCascades = 0; - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; - - let numDirectionalShadows = 0; - let numPointShadows = 0; - let numSpotShadows = 0; - let numSpotMaps = 0; - let numSpotShadowsWithMaps = 0; - - let numLightProbes = 0; - - // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] - lights.sort( shadowCastingAndTexturingLightsFirst ); - - for ( let i = 0, l = lights.length; i < l; i ++ ) { - - const light = lights[ i ]; - - const color = light.color; - const intensity = light.intensity; - const distance = light.distance; - - let shadowMap = null; - - if ( light.shadow && light.shadow.map ) { - - if ( light.shadow.map.texture.format === RGFormat ) { - - // VSM uses color texture with blurred mean/std_dev - shadowMap = light.shadow.map.texture; - - } else { - - // Other types use depth texture - shadowMap = light.shadow.map.depthTexture || light.shadow.map.texture; - - } - - } - - if ( light.isAmbientLight ) { - - r += color.r * intensity; - g += color.g * intensity; - b += color.b * intensity; - - } else if ( light.isLightProbe ) { - - for ( let j = 0; j < 9; j ++ ) { - - state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); - - } - - numLightProbes ++; - - } else if ( light.isSunLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - - if ( light.castShadow ) { - - const shadow = light.shadow; - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowIntensity = shadow.intensity; - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize.copy( shadow.mapSize ).multiply( shadow.getFrameExtents() ); - - state.sunShadow[ numSunShadows ] = shadowUniforms; - state.sunShadowMap[ numSunShadows ] = shadowMap; - - const cascadeCount = shadow.getViewportCount(); - - for ( let j = 0; j < cascadeCount; j ++ ) { - - state.sunShadowMatrix[ numSunShadowCascades + j ] = shadow.getMatrix( j ); - state.sunShadowCascade[ numSunShadowCascades + j ] = shadow._cascadeData[ j ]; - - } - - numSunShadowCascades += cascadeCount; - numSunShadows ++; - - } - - state.sun[ sunLength ] = uniforms; - - sunLength ++; - - } else if ( light.isDirectionalLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - - if ( light.castShadow ) { - - const shadow = light.shadow; - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowIntensity = shadow.intensity; - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - - state.directionalShadow[ directionalLength ] = shadowUniforms; - state.directionalShadowMap[ directionalLength ] = shadowMap; - state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; - - numDirectionalShadows ++; - - } - - state.directional[ directionalLength ] = uniforms; - - directionalLength ++; - - } else if ( light.isSpotLight ) { - - const uniforms = cache.get( light ); - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - - uniforms.color.copy( color ).multiplyScalar( intensity ); - uniforms.distance = distance; - - uniforms.coneCos = Math.cos( light.angle ); - uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); - uniforms.decay = light.decay; - - state.spot[ spotLength ] = uniforms; - - const shadow = light.shadow; - - if ( light.map ) { - - state.spotLightMap[ numSpotMaps ] = light.map; - numSpotMaps ++; - - // make sure the lightMatrix is up to date - // TODO : do it if required only - shadow.updateMatrices( light ); - - if ( light.castShadow ) numSpotShadowsWithMaps ++; - - } - - state.spotLightMatrix[ spotLength ] = shadow.matrix; - - if ( light.castShadow ) { - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowIntensity = shadow.intensity; - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - - state.spotShadow[ spotLength ] = shadowUniforms; - state.spotShadowMap[ spotLength ] = shadowMap; - - numSpotShadows ++; - - } - - spotLength ++; - - } else if ( light.isRectAreaLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( color ).multiplyScalar( intensity ); - - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - - state.rectArea[ rectAreaLength ] = uniforms; - - rectAreaLength ++; - - } else if ( light.isPointLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - uniforms.distance = light.distance; - uniforms.decay = light.decay; - - if ( light.castShadow ) { - - const shadow = light.shadow; - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowIntensity = shadow.intensity; - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - shadowUniforms.shadowCameraNear = shadow.camera.near; - shadowUniforms.shadowCameraFar = shadow.camera.far; - - state.pointShadow[ pointLength ] = shadowUniforms; - state.pointShadowMap[ pointLength ] = shadowMap; - state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; - - numPointShadows ++; - - } - - state.point[ pointLength ] = uniforms; - - pointLength ++; - - } else if ( light.isHemisphereLight ) { - - const uniforms = cache.get( light ); - - uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); - uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); - - state.hemi[ hemiLength ] = uniforms; - - hemiLength ++; - - } - - } - - if ( rectAreaLength > 0 ) { - - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - - } else { - - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - - } - - } - - state.ambient[ 0 ] = r; - state.ambient[ 1 ] = g; - state.ambient[ 2 ] = b; - - const hash = state.hash; - - if ( hash.sunLength !== sunLength || - hash.directionalLength !== directionalLength || - hash.pointLength !== pointLength || - hash.spotLength !== spotLength || - hash.rectAreaLength !== rectAreaLength || - hash.hemiLength !== hemiLength || - hash.numSunShadows !== numSunShadows || - hash.numDirectionalShadows !== numDirectionalShadows || - hash.numPointShadows !== numPointShadows || - hash.numSpotShadows !== numSpotShadows || - hash.numSpotMaps !== numSpotMaps || - hash.numLightProbes !== numLightProbes ) { - - state.sun.length = sunLength; - state.directional.length = directionalLength; - state.spot.length = spotLength; - state.rectArea.length = rectAreaLength; - state.point.length = pointLength; - state.hemi.length = hemiLength; - - state.sunShadow.length = numSunShadows; - state.sunShadowMap.length = numSunShadows; - state.sunShadowMatrix.length = numSunShadowCascades; - state.sunShadowCascade.length = numSunShadowCascades; - state.directionalShadow.length = numDirectionalShadows; - state.directionalShadowMap.length = numDirectionalShadows; - state.directionalShadowMatrix.length = numDirectionalShadows; - state.pointShadow.length = numPointShadows; - state.pointShadowMap.length = numPointShadows; - state.pointShadowMatrix.length = numPointShadows; - state.spotShadow.length = numSpotShadows; - state.spotShadowMap.length = numSpotShadows; - state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; - state.spotLightMap.length = numSpotMaps; - state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; - state.numLightProbes = numLightProbes; - - hash.sunLength = sunLength; - hash.directionalLength = directionalLength; - hash.pointLength = pointLength; - hash.spotLength = spotLength; - hash.rectAreaLength = rectAreaLength; - hash.hemiLength = hemiLength; - - hash.numSunShadows = numSunShadows; - hash.numDirectionalShadows = numDirectionalShadows; - hash.numPointShadows = numPointShadows; - hash.numSpotShadows = numSpotShadows; - hash.numSpotMaps = numSpotMaps; - - hash.numLightProbes = numLightProbes; - - state.version = nextVersion ++; - - } - - } - - function setupView( lights, camera ) { - - let sunLength = 0; - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; - - const viewMatrix = camera.matrixWorldInverse; - - for ( let i = 0, l = lights.length; i < l; i ++ ) { - - const light = lights[ i ]; - - if ( light.isSunLight ) { - - const uniforms = state.sun[ sunLength ]; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - uniforms.direction.transformDirection( viewMatrix ); - - sunLength ++; - - } else if ( light.isDirectionalLight ) { - - const uniforms = state.directional[ directionalLength ]; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - directionalLength ++; - - } else if ( light.isSpotLight ) { - - const uniforms = state.spot[ spotLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - spotLength ++; - - } else if ( light.isRectAreaLight ) { - - const uniforms = state.rectArea[ rectAreaLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - // extract local rotation of light to derive width/height half vectors - matrix42.identity(); - matrix4.copy( light.matrixWorld ); - matrix4.premultiply( viewMatrix ); - matrix42.extractRotation( matrix4 ); - - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - - uniforms.halfWidth.applyMatrix4( matrix42 ); - uniforms.halfHeight.applyMatrix4( matrix42 ); - - rectAreaLength ++; - - } else if ( light.isPointLight ) { - - const uniforms = state.point[ pointLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - pointLength ++; - - } else if ( light.isHemisphereLight ) { - - const uniforms = state.hemi[ hemiLength ]; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - uniforms.direction.transformDirection( viewMatrix ); - - hemiLength ++; - - } - - } - - } - - return { - setup: setup, - setupView: setupView, - state: state - }; - -} - -function WebGLRenderState( extensions ) { - - const lights = new WebGLLights( extensions ); - - const lightsArray = []; - const shadowsArray = []; - const lightProbeGridArray = []; - - function init( camera ) { - - state.camera = camera; - - lightsArray.length = 0; - shadowsArray.length = 0; - lightProbeGridArray.length = 0; - - } - - function pushLight( light ) { - - lightsArray.push( light ); - - } - - function pushShadow( shadowLight ) { - - shadowsArray.push( shadowLight ); - - } - - function pushLightProbeGrid( volume ) { - - lightProbeGridArray.push( volume ); - - } - - function setupLights() { - - lights.setup( lightsArray ); - - } - - function setupLightsView( camera ) { - - lights.setupView( lightsArray, camera ); - - } - - const state = { - lightsArray: lightsArray, - shadowsArray: shadowsArray, - lightProbeGridArray: lightProbeGridArray, - - camera: null, - - lights: lights, - - transmissionRenderTarget: {}, - textureUnits: 0 - }; - - return { - init: init, - state: state, - setupLights: setupLights, - setupLightsView: setupLightsView, - - pushLight: pushLight, - pushShadow: pushShadow, - pushLightProbeGrid: pushLightProbeGrid - }; - -} - -function WebGLRenderStates( extensions ) { - - let renderStates = new WeakMap(); - - function get( scene, renderCallDepth = 0 ) { - - const renderStateArray = renderStates.get( scene ); - let renderState; - - if ( renderStateArray === undefined ) { - - renderState = new WebGLRenderState( extensions ); - renderStates.set( scene, [ renderState ] ); - - } else { - - if ( renderCallDepth >= renderStateArray.length ) { - - renderState = new WebGLRenderState( extensions ); - renderStateArray.push( renderState ); - - } else { - - renderState = renderStateArray[ renderCallDepth ]; - - } - - } - - return renderState; - - } - - function dispose() { - - renderStates = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - -} - -/** - * A material for drawing geometry by depth. Depth is based off of the camera - * near and far plane. White is nearest, black is farthest. - * - * @augments Material - * @demo scenes/material-browser.html#MeshDepthMaterial - */ -class MeshDepthMaterial extends Material { - - /** - * Constructs a new mesh depth material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshDepthMaterial = true; - - this.type = 'MeshDepthMaterial'; - - /** - * Type for depth packing. - * - * @type {(BasicDepthPacking|RGBADepthPacking|RGBDepthPacking|RGDepthPacking)} - * @default BasicDepthPacking - */ - this.depthPacking = BasicDepthPacking; - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. - * - * `displacementMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * WebGL and WebGPU ignore this property and always render - * 1 pixel wide lines. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.depthPacking = source.depthPacking; - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - return this; - - } - -} - -/** - * A material used internally for implementing shadow mapping with - * point lights. - * - * Can also be used to customize the shadow casting of an object by assigning - * an instance of `MeshDistanceMaterial` to {@link Object3D#customDistanceMaterial}. - * The following examples demonstrates this approach in order to ensure - * transparent parts of objects do not cast shadows. - * - * @augments Material - */ -class MeshDistanceMaterial extends Material { - - /** - * Constructs a new mesh distance material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshDistanceMaterial = true; - - this.type = 'MeshDistanceMaterial'; - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. - * - * `displacementMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - return this; - - } - -} - -/** - * This class can be used to automatically save the depth information of a - * cube rendering into a cube texture with depth format. Used for PointLight shadows. - * - * @augments DepthTexture - */ -class CubeDepthTexture extends DepthTexture { - - /** - * Constructs a new cube depth texture. - * - * @param {number} size - The size (width and height) of each cube face. - * @param {number} [type=UnsignedIntType] - The texture type. - * @param {number} [mapping=CubeReflectionMapping] - The texture mapping. - * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. - * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. - * @param {number} [magFilter=NearestFilter] - The mag filter value. - * @param {number} [minFilter=NearestFilter] - The min filter value. - * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. - * @param {number} [format=DepthFormat] - The texture format. - */ - constructor( size, type = UnsignedIntType, mapping = CubeReflectionMapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat ) { - - // Create 6 identical image descriptors for the cube faces - const image = { width: size, height: size, depth: 1 }; - const images = [ image, image, image, image, image, image ]; - - // Call DepthTexture constructor with width, height - super( size, size, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ); - - // Replace the single image with the array of 6 images - this.image = images; - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCubeDepthTexture = true; - - /** - * Set to true for cube texture handling in WebGLTextures. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCubeTexture = true; - - } - - /** - * Alias for {@link CubeDepthTexture#image}. - * - * @type {Array} - */ - get images() { - - return this.image; - - } - - set images( value ) { - - this.image = value; - - } - -} - -const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; - -const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ).rg;\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ).r;\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( max( 0.0, squared_mean - mean * mean ) );\n\tgl_FragColor = vec4( mean, std_dev, 0.0, 1.0 );\n}"; - -const _cubeDirections = [ - /*@__PURE__*/ new Vector3( 1, 0, 0 ), /*@__PURE__*/ new Vector3( -1, 0, 0 ), /*@__PURE__*/ new Vector3( 0, 1, 0 ), - /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ), /*@__PURE__*/ new Vector3( 0, 0, -1 ) -]; - -const _cubeUps = [ - /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, 0, 1 ), - /*@__PURE__*/ new Vector3( 0, 0, -1 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ), /*@__PURE__*/ new Vector3( 0, -1, 0 ) -]; - -const _projScreenMatrix$2 = /*@__PURE__*/ new Matrix4(); -const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); -const _lookTarget$1 = /*@__PURE__*/ new Vector3(); - -function WebGLShadowMap( renderer, objects, capabilities ) { - - let _frustum = new Frustum(); - - const _shadowMapSize = new Vector2(), - _viewportSize = new Vector2(), - - _viewport = new Vector4(), - - _depthMaterial = new MeshDepthMaterial(), - _distanceMaterial = new MeshDistanceMaterial(), - - _materialCache = {}, - - _maxTextureSize = capabilities.maxTextureSize; - - const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; - - const shadowMaterialVertical = new ShaderMaterial( { - defines: { - VSM_SAMPLES: 8 - }, - uniforms: { - shadow_pass: { value: null }, - resolution: { value: new Vector2() }, - radius: { value: 4.0 } - }, - - vertexShader: vertex, - fragmentShader: fragment - - } ); - - const shadowMaterialHorizontal = shadowMaterialVertical.clone(); - shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; - - const fullScreenTri = new BufferGeometry(); - fullScreenTri.setAttribute( - 'position', - new BufferAttribute( - new Float32Array( [ -1, -1, 0.5, 3, -1, 0.5, -1, 3, 0.5 ] ), - 3 - ) - ); - - const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); - - const scope = this; - - this.enabled = false; - - this.autoUpdate = true; - this.needsUpdate = false; - - this.type = PCFShadowMap; - let _previousType = this.type; - - this.render = function ( lights, scene, camera ) { - - if ( scope.enabled === false ) return; - if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - - if ( lights.length === 0 ) return; - - if ( this.type === PCFSoftShadowMap ) { - - warn( 'WebGLShadowMap: PCFSoftShadowMap has been removed. Using PCFShadowMap instead.' ); - this.type = PCFShadowMap; - - } - - const currentRenderTarget = renderer.getRenderTarget(); - const activeCubeFace = renderer.getActiveCubeFace(); - const activeMipmapLevel = renderer.getActiveMipmapLevel(); - - const _state = renderer.state; - - // Set GL state for depth map. - _state.setBlending( NoBlending ); - - if ( _state.buffers.depth.getReversed() === true ) { - - _state.buffers.color.setClear( 0, 0, 0, 0 ); - - } else { - - _state.buffers.color.setClear( 1, 1, 1, 1 ); - - } - - _state.buffers.depth.setTest( true ); - _state.setScissorTest( false ); - - // check for shadow map type changes - - const typeChanged = _previousType !== this.type; - - // When shadow map type changes, materials need recompilation because sampler types change - // (sampler2DShadow for PCF vs sampler2D for Basic) - if ( typeChanged ) { - - scene.traverse( function ( object ) { - - if ( object.material ) { - - if ( Array.isArray( object.material ) ) { - - object.material.forEach( mat => mat.needsUpdate = true ); - - } else { - - object.material.needsUpdate = true; - - } - - } - - } ); - - } - - // render depth map - - for ( let i = 0, il = lights.length; i < il; i ++ ) { - - const light = lights[ i ]; - const shadow = light.shadow; - - if ( shadow === undefined ) { - - warn( 'WebGLShadowMap:', light, 'has no shadow.' ); - continue; - - } - - if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; - - _shadowMapSize.copy( shadow.mapSize ); - - const shadowFrameExtents = shadow.getFrameExtents(); - - _shadowMapSize.multiply( shadowFrameExtents ); - - _viewportSize.copy( shadow.mapSize ); - - if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { - - if ( _shadowMapSize.x > _maxTextureSize ) { - - _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); - _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; - shadow.mapSize.x = _viewportSize.x; - - } - - if ( _shadowMapSize.y > _maxTextureSize ) { - - _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); - _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; - shadow.mapSize.y = _viewportSize.y; - - } - - } - - const reversedDepthBuffer = renderer.state.buffers.depth.getReversed(); - shadow.camera._reversedDepth = reversedDepthBuffer; - - if ( shadow.map === null || typeChanged === true ) { - - if ( shadow.map !== null ) { - - if ( shadow.map.depthTexture !== null ) { - - shadow.map.depthTexture.dispose(); - shadow.map.depthTexture = null; - - } - - shadow.map.dispose(); - - } - - if ( this.type === VSMShadowMap ) { - - if ( light.isPointLight ) { - - warn( 'WebGLShadowMap: VSM shadow maps are not supported for PointLights. Use PCF or BasicShadowMap instead.' ); - continue; - - } - - shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, { - format: RGFormat, - type: HalfFloatType, - minFilter: LinearFilter, - magFilter: LinearFilter, - generateMipmaps: false - } ); - shadow.map.texture.name = light.name + '.shadowMap'; - - // Native depth texture for VSM - depth is captured here, then blurred into the color texture - shadow.map.depthTexture = new DepthTexture( _shadowMapSize.x, _shadowMapSize.y, FloatType ); - shadow.map.depthTexture.name = light.name + '.shadowMapDepth'; - shadow.map.depthTexture.format = DepthFormat; - shadow.map.depthTexture.compareFunction = null; // For regular sampling (not shadow comparison) - shadow.map.depthTexture.minFilter = NearestFilter; - shadow.map.depthTexture.magFilter = NearestFilter; - - } else { - - if ( light.isPointLight ) { - - shadow.map = new WebGLCubeRenderTarget( _shadowMapSize.x ); - shadow.map.depthTexture = new CubeDepthTexture( _shadowMapSize.x, UnsignedIntType ); - - } else { - - shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); - shadow.map.depthTexture = new DepthTexture( _shadowMapSize.x, _shadowMapSize.y, UnsignedIntType ); - - } - - shadow.map.depthTexture.name = light.name + '.shadowMap'; - shadow.map.depthTexture.format = DepthFormat; - - if ( this.type === PCFShadowMap ) { - - shadow.map.depthTexture.compareFunction = reversedDepthBuffer ? GreaterEqualCompare : LessEqualCompare; - shadow.map.depthTexture.minFilter = LinearFilter; - shadow.map.depthTexture.magFilter = LinearFilter; - - } else { - - shadow.map.depthTexture.compareFunction = null; - shadow.map.depthTexture.minFilter = NearestFilter; - shadow.map.depthTexture.magFilter = NearestFilter; - - } - - } - - shadow.camera.updateProjectionMatrix(); - - } - - if ( shadow.map.isWebGLCubeRenderTarget !== true && ( shadow.map.width !== _shadowMapSize.x || shadow.map.height !== _shadowMapSize.y ) ) { - - shadow.map.setSize( _shadowMapSize.x, _shadowMapSize.y ); - - } - - // For cube render targets (PointLights), render all 6 faces. Sun lights - // render one atlas viewport per cascade. - const faceCount = shadow.map.isWebGLCubeRenderTarget ? 6 : shadow.getViewportCount(); - - if ( light.isPointLight !== true ) shadow.updateMatrices( light, camera ); - - for ( let face = 0; face < faceCount; face ++ ) { - - const shadowCamera = shadow.getCamera( face ); - - if ( light.isPointLight ) { - - const camera = shadow.camera; - const shadowMatrix = shadow.matrix; - - const far = light.distance || camera.far; - - if ( far !== camera.far ) { - - camera.far = far; - camera.updateProjectionMatrix(); - - } - - _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); - camera.position.copy( _lightPositionWorld$1 ); - - _lookTarget$1.copy( camera.position ); - _lookTarget$1.add( _cubeDirections[ face ] ); - camera.up.copy( _cubeUps[ face ] ); - camera.lookAt( _lookTarget$1 ); - camera.updateMatrixWorld(); - - shadowMatrix.makeTranslation( - _lightPositionWorld$1.x, - _lightPositionWorld$1.y, - _lightPositionWorld$1.z ); - - _projScreenMatrix$2.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - shadow._frustum.setFromProjectionMatrix( _projScreenMatrix$2, camera.coordinateSystem, camera.reversedDepth ); - - } - - // For cube render targets, render to each face separately - if ( shadow.map.isWebGLCubeRenderTarget ) { - - renderer.setRenderTarget( shadow.map, face ); - renderer.clear(); - - } else { - - // For 2D render targets, use viewports - if ( face === 0 ) { - - renderer.setRenderTarget( shadow.map ); - renderer.clear(); - - } - - const viewport = shadow.getViewport( face ); - - _viewport.set( - _viewportSize.x * viewport.x, - _viewportSize.y * viewport.y, - _viewportSize.x * viewport.z, - _viewportSize.y * viewport.w - ); - - _state.viewport( _viewport ); - - } - - _frustum = shadow.getFrustum( face ); - - renderObject( scene, camera, shadowCamera, light, this.type ); - - } - - // do blur pass for VSM - - if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { - - VSMPass( shadow, camera ); - - } - - shadow.needsUpdate = false; - - } - - _previousType = this.type; - - scope.needsUpdate = false; - - renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); - - }; - - function VSMPass( shadow, camera ) { - - const geometry = objects.update( fullScreenMesh ); - - if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { - - shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; - shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; - - shadowMaterialVertical.needsUpdate = true; - shadowMaterialHorizontal.needsUpdate = true; - - } - - if ( shadow.mapPass === null ) { - - shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, { - format: RGFormat, - type: HalfFloatType - } ); - - } else if ( shadow.mapPass.width !== shadow.map.width || shadow.mapPass.height !== shadow.map.height ) { - - shadow.mapPass.setSize( shadow.map.width, shadow.map.height ); - - } - - // vertical pass - read from native depth texture - - shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.depthTexture; - shadowMaterialVertical.uniforms.resolution.value.set( shadow.map.width, shadow.map.height ); - shadowMaterialVertical.uniforms.radius.value = shadow.radius; - renderer.setRenderTarget( shadow.mapPass ); - renderer.clear(); - renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); - - // horizontal pass - - shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; - shadowMaterialHorizontal.uniforms.resolution.value.set( shadow.map.width, shadow.map.height ); - shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; - renderer.setRenderTarget( shadow.map ); - renderer.clear(); - renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); - - } - - function getDepthMaterial( object, material, light, type ) { - - let result = null; - - const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; - - if ( customMaterial !== undefined ) { - - result = customMaterial; - - } else { - - result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; - - if ( ( renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || - ( material.displacementMap && material.displacementScale !== 0 ) || - ( material.alphaMap && material.alphaTest > 0 ) || - ( material.map && material.alphaTest > 0 ) || - ( material.alphaToCoverage === true ) ) { - - // in this case we need a unique material instance reflecting the - // appropriate state - - const keyA = result.uuid, keyB = material.uuid; - - let materialsForVariant = _materialCache[ keyA ]; - - if ( materialsForVariant === undefined ) { - - materialsForVariant = {}; - _materialCache[ keyA ] = materialsForVariant; - - } - - let cachedMaterial = materialsForVariant[ keyB ]; - - if ( cachedMaterial === undefined ) { - - cachedMaterial = result.clone(); - materialsForVariant[ keyB ] = cachedMaterial; - material.addEventListener( 'dispose', onMaterialDispose ); - - } - - result = cachedMaterial; - - } - - } - - result.visible = material.visible; - result.wireframe = material.wireframe; - - if ( type === VSMShadowMap ) { - - result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; - - } else { - - result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; - - } - - result.alphaMap = material.alphaMap; - result.alphaTest = ( material.alphaToCoverage === true ) ? 0.5 : material.alphaTest; // approximate alphaToCoverage by using a fixed alphaTest value - result.map = material.map; - - result.clipShadows = material.clipShadows; - result.clippingPlanes = material.clippingPlanes; - result.clipIntersection = material.clipIntersection; - - result.displacementMap = material.displacementMap; - result.displacementScale = material.displacementScale; - result.displacementBias = material.displacementBias; - - result.wireframeLinewidth = material.wireframeLinewidth; - result.linewidth = material.linewidth; - - if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { - - const materialProperties = renderer.properties.get( result ); - materialProperties.light = light; - - } - - return result; - - } - - function renderObject( object, camera, shadowCamera, light, type ) { - - if ( object.visible === false ) return; - - const visible = object.layers.test( camera.layers ); - - if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { - - if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || object.intersectsFrustum( _frustum ) ) ) { - - object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - - const geometry = objects.update( object ); - const material = object.material; - - if ( Array.isArray( material ) ) { - - const groups = geometry.groups; - - for ( let k = 0, kl = groups.length; k < kl; k ++ ) { - - const group = groups[ k ]; - const groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); - - object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - - renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - - object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - - } - - } - - } else if ( material.visible ) { - - const depthMaterial = getDepthMaterial( object, material, light, type ); - - object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - - renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - - object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - renderObject( children[ i ], camera, shadowCamera, light, type ); - - } - - } - - function onMaterialDispose( event ) { - - const material = event.target; - - material.removeEventListener( 'dispose', onMaterialDispose ); - - // make sure to remove the unique distance/depth materials used for shadow map rendering - - for ( const id in _materialCache ) { - - const cache = _materialCache[ id ]; - - const uuid = event.target.uuid; - - if ( uuid in cache ) { - - const shadowMaterial = cache[ uuid ]; - shadowMaterial.dispose(); - delete cache[ uuid ]; - - } - - } - - } - -} - -function WebGLState( gl, extensions ) { - - function ColorBuffer() { - - let locked = false; - - const color = new Vector4(); - let currentColorMask = null; - const currentColorClear = new Vector4( 0, 0, 0, 0 ); - - return { - - setMask: function ( colorMask ) { - - if ( currentColorMask !== colorMask && ! locked ) { - - gl.colorMask( colorMask, colorMask, colorMask, colorMask ); - currentColorMask = colorMask; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( r, g, b, a, premultipliedAlpha ) { - - if ( premultipliedAlpha === true ) { - - r *= a; g *= a; b *= a; - - } - - color.set( r, g, b, a ); - - if ( currentColorClear.equals( color ) === false ) { - - gl.clearColor( r, g, b, a ); - currentColorClear.copy( color ); - - } - - }, - - reset: function () { - - locked = false; - - currentColorMask = null; - currentColorClear.set( -1, 0, 0, 0 ); // set to invalid state - - } - - }; - - } - - function DepthBuffer() { - - let locked = false; - - let currentReversed = false; - let currentDepthMask = null; - let currentDepthFunc = null; - let currentDepthClear = null; - - return { - - setReversed: function ( reversed ) { - - if ( currentReversed !== reversed ) { - - const ext = extensions.get( 'EXT_clip_control' ); - - if ( reversed ) { - - ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.ZERO_TO_ONE_EXT ); - - } else { - - ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.NEGATIVE_ONE_TO_ONE_EXT ); - - } - - currentReversed = reversed; - - const oldDepth = currentDepthClear; - currentDepthClear = null; - this.setClear( oldDepth ); - - } - - }, - - getReversed: function () { - - return currentReversed; - - }, - - setTest: function ( depthTest ) { - - if ( depthTest ) { - - enable( gl.DEPTH_TEST ); - - } else { - - disable( gl.DEPTH_TEST ); - - } - - }, - - setMask: function ( depthMask ) { - - if ( currentDepthMask !== depthMask && ! locked ) { - - gl.depthMask( depthMask ); - currentDepthMask = depthMask; - - } - - }, - - setFunc: function ( depthFunc ) { - - if ( currentReversed ) depthFunc = ReversedDepthFuncs[ depthFunc ]; - - if ( currentDepthFunc !== depthFunc ) { - - switch ( depthFunc ) { - - case NeverDepth: - - gl.depthFunc( gl.NEVER ); - break; - - case AlwaysDepth: - - gl.depthFunc( gl.ALWAYS ); - break; - - case LessDepth: - - gl.depthFunc( gl.LESS ); - break; - - case LessEqualDepth: - - gl.depthFunc( gl.LEQUAL ); - break; - - case EqualDepth: - - gl.depthFunc( gl.EQUAL ); - break; - - case GreaterEqualDepth: - - gl.depthFunc( gl.GEQUAL ); - break; - - case GreaterDepth: - - gl.depthFunc( gl.GREATER ); - break; - - case NotEqualDepth: - - gl.depthFunc( gl.NOTEQUAL ); - break; - - default: - - gl.depthFunc( gl.LEQUAL ); - - } - - currentDepthFunc = depthFunc; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( depth ) { - - if ( currentDepthClear !== depth ) { - - currentDepthClear = depth; - - if ( currentReversed ) { - - depth = 1 - depth; - - } - - gl.clearDepth( depth ); - - } - - }, - - reset: function () { - - locked = false; - - currentDepthMask = null; - currentDepthFunc = null; - currentDepthClear = null; - currentReversed = false; - - } - - }; - - } - - function StencilBuffer() { - - let locked = false; - - let currentStencilMask = null; - let currentStencilFunc = null; - let currentStencilRef = null; - let currentStencilFuncMask = null; - let currentStencilFail = null; - let currentStencilZFail = null; - let currentStencilZPass = null; - let currentStencilClear = null; - - return { - - setTest: function ( stencilTest ) { - - if ( ! locked ) { - - if ( stencilTest ) { - - enable( gl.STENCIL_TEST ); - - } else { - - disable( gl.STENCIL_TEST ); - - } - - } - - }, - - setMask: function ( stencilMask ) { - - if ( currentStencilMask !== stencilMask && ! locked ) { - - gl.stencilMask( stencilMask ); - currentStencilMask = stencilMask; - - } - - }, - - setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - - if ( currentStencilFunc !== stencilFunc || - currentStencilRef !== stencilRef || - currentStencilFuncMask !== stencilMask ) { - - gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); - - currentStencilFunc = stencilFunc; - currentStencilRef = stencilRef; - currentStencilFuncMask = stencilMask; - - } - - }, - - setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - - if ( currentStencilFail !== stencilFail || - currentStencilZFail !== stencilZFail || - currentStencilZPass !== stencilZPass ) { - - gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); - - currentStencilFail = stencilFail; - currentStencilZFail = stencilZFail; - currentStencilZPass = stencilZPass; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( stencil ) { - - if ( currentStencilClear !== stencil ) { - - gl.clearStencil( stencil ); - currentStencilClear = stencil; - - } - - }, - - reset: function () { - - locked = false; - - currentStencilMask = null; - currentStencilFunc = null; - currentStencilRef = null; - currentStencilFuncMask = null; - currentStencilFail = null; - currentStencilZFail = null; - currentStencilZPass = null; - currentStencilClear = null; - - } - - }; - - } - - // - - const colorBuffer = new ColorBuffer(); - const depthBuffer = new DepthBuffer(); - const stencilBuffer = new StencilBuffer(); - - const uboBindings = new WeakMap(); - const uboProgramMap = new WeakMap(); - - let enabledCapabilities = {}; - let parameters = {}; - - let currentBoundFramebuffers = {}; - let currentDrawbuffers = new WeakMap(); - let defaultDrawbuffers = []; - - let currentProgram = null; - - let currentBlendingEnabled = false; - let currentBlending = null; - let currentBlendEquation = null; - let currentBlendSrc = null; - let currentBlendDst = null; - let currentBlendEquationAlpha = null; - let currentBlendSrcAlpha = null; - let currentBlendDstAlpha = null; - let currentBlendColor = new Color( 0, 0, 0 ); - let currentBlendAlpha = 0; - let currentPremultipledAlpha = false; - - let currentFlipSided = null; - let currentCullFace = null; - - let currentLineWidth = null; - - let currentPolygonOffsetFactor = null; - let currentPolygonOffsetUnits = null; - - const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); - - let lineWidthAvailable = false; - let version = 0; - const glVersion = gl.getParameter( gl.VERSION ); - - if ( glVersion.indexOf( 'WebGL' ) !== -1 ) { - - version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 1.0 ); - - } else if ( glVersion.indexOf( 'OpenGL ES' ) !== -1 ) { - - version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 2.0 ); - - } - - let currentTextureSlot = null; - let currentBoundTextures = {}; - - const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); - const viewportParam = gl.getParameter( gl.VIEWPORT ); - - const currentScissor = new Vector4().fromArray( scissorParam ); - const currentViewport = new Vector4().fromArray( viewportParam ); - - function createTexture( type, target, count, dimensions ) { - - const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. - const texture = gl.createTexture(); - - gl.bindTexture( type, texture ); - gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - - for ( let i = 0; i < count; i ++ ) { - - if ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) { - - gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - - } else { - - gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - - } - - } - - return texture; - - } - - const emptyTextures = {}; - emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); - emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); - emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); - emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); - - // init - - colorBuffer.setClear( 0, 0, 0, 1 ); - depthBuffer.setClear( 1 ); - stencilBuffer.setClear( 0 ); - - enable( gl.DEPTH_TEST ); - depthBuffer.setFunc( LessEqualDepth ); - - setFlipSided( false ); - setCullFace( CullFaceBack ); - enable( gl.CULL_FACE ); - - setBlending( NoBlending ); - - // - - function enable( id ) { - - if ( enabledCapabilities[ id ] !== true ) { - - gl.enable( id ); - enabledCapabilities[ id ] = true; - - } - - } - - function disable( id ) { - - if ( enabledCapabilities[ id ] !== false ) { - - gl.disable( id ); - enabledCapabilities[ id ] = false; - - } - - } - - function bindFramebuffer( target, framebuffer ) { - - if ( currentBoundFramebuffers[ target ] !== framebuffer ) { - - gl.bindFramebuffer( target, framebuffer ); - - currentBoundFramebuffers[ target ] = framebuffer; - - // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER - - if ( target === gl.DRAW_FRAMEBUFFER ) { - - currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; - - } - - if ( target === gl.FRAMEBUFFER ) { - - currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; - - } - - return true; - - } - - return false; - - } - - function drawBuffers( renderTarget, framebuffer ) { - - let drawBuffers = defaultDrawbuffers; - - let needsUpdate = false; - - if ( renderTarget ) { - - drawBuffers = currentDrawbuffers.get( framebuffer ); - - if ( drawBuffers === undefined ) { - - drawBuffers = []; - currentDrawbuffers.set( framebuffer, drawBuffers ); - - } - - const textures = renderTarget.textures; - - if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; - - } - - drawBuffers.length = textures.length; - - needsUpdate = true; - - } - - } else { - - if ( drawBuffers[ 0 ] !== gl.BACK ) { - - drawBuffers[ 0 ] = gl.BACK; - - needsUpdate = true; - - } - - } - - if ( needsUpdate ) { - - gl.drawBuffers( drawBuffers ); - - } - - } - - function useProgram( program ) { - - if ( currentProgram !== program ) { - - gl.useProgram( program ); - - currentProgram = program; - - return true; - - } - - return false; - - } - - const equationToGL = { - [ AddEquation ]: gl.FUNC_ADD, - [ SubtractEquation ]: gl.FUNC_SUBTRACT, - [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT - }; - - equationToGL[ MinEquation ] = gl.MIN; - equationToGL[ MaxEquation ] = gl.MAX; - - const factorToGL = { - [ ZeroFactor ]: gl.ZERO, - [ OneFactor ]: gl.ONE, - [ SrcColorFactor ]: gl.SRC_COLOR, - [ SrcAlphaFactor ]: gl.SRC_ALPHA, - [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, - [ DstColorFactor ]: gl.DST_COLOR, - [ DstAlphaFactor ]: gl.DST_ALPHA, - [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, - [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, - [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, - [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA, - [ ConstantColorFactor ]: gl.CONSTANT_COLOR, - [ OneMinusConstantColorFactor ]: gl.ONE_MINUS_CONSTANT_COLOR, - [ ConstantAlphaFactor ]: gl.CONSTANT_ALPHA, - [ OneMinusConstantAlphaFactor ]: gl.ONE_MINUS_CONSTANT_ALPHA - }; - - function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) { - - if ( blending === NoBlending ) { - - if ( currentBlendingEnabled === true ) { - - disable( gl.BLEND ); - currentBlendingEnabled = false; - - } - - return; - - } - - if ( currentBlendingEnabled === false ) { - - enable( gl.BLEND ); - currentBlendingEnabled = true; - - } - - if ( blending !== CustomBlending ) { - - if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { - - if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { - - gl.blendEquation( gl.FUNC_ADD ); - - currentBlendEquation = AddEquation; - currentBlendEquationAlpha = AddEquation; - - } - - if ( premultipliedAlpha ) { - - switch ( blending ) { - - case NormalBlending: - gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; - - case AdditiveBlending: - gl.blendFunc( gl.ONE, gl.ONE ); - break; - - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; - - case MultiplyBlending: - gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE ); - break; - - default: - error( 'WebGLState: Invalid blending: ', blending ); - break; - - } - - } else { - - switch ( blending ) { - - case NormalBlending: - gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; - - case AdditiveBlending: - gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE ); - break; - - case SubtractiveBlending: - error( 'WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' ); - break; - - case MultiplyBlending: - error( 'WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' ); - break; - - default: - error( 'WebGLState: Invalid blending: ', blending ); - break; - - } - - } - - currentBlendSrc = null; - currentBlendDst = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - currentBlendColor.set( 0, 0, 0 ); - currentBlendAlpha = 0; - - currentBlending = blending; - currentPremultipledAlpha = premultipliedAlpha; - - } - - return; - - } - - // custom blending - - blendEquationAlpha = blendEquationAlpha || blendEquation; - blendSrcAlpha = blendSrcAlpha || blendSrc; - blendDstAlpha = blendDstAlpha || blendDst; - - if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - - gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); - - currentBlendEquation = blendEquation; - currentBlendEquationAlpha = blendEquationAlpha; - - } - - if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - - gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); - - currentBlendSrc = blendSrc; - currentBlendDst = blendDst; - currentBlendSrcAlpha = blendSrcAlpha; - currentBlendDstAlpha = blendDstAlpha; - - } - - if ( blendColor.equals( currentBlendColor ) === false || blendAlpha !== currentBlendAlpha ) { - - gl.blendColor( blendColor.r, blendColor.g, blendColor.b, blendAlpha ); - - currentBlendColor.copy( blendColor ); - currentBlendAlpha = blendAlpha; - - } - - currentBlending = blending; - currentPremultipledAlpha = false; - - } - - function setMaterial( material, frontFaceCW ) { - - material.side === DoubleSide - ? disable( gl.CULL_FACE ) - : enable( gl.CULL_FACE ); - - let flipSided = ( material.side === BackSide ); - if ( frontFaceCW ) flipSided = ! flipSided; - - setFlipSided( flipSided ); - - ( material.blending === NormalBlending && material.transparent === false ) - ? setBlending( NoBlending ) - : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha ); - - depthBuffer.setFunc( material.depthFunc ); - depthBuffer.setTest( material.depthTest ); - depthBuffer.setMask( material.depthWrite ); - colorBuffer.setMask( material.colorWrite ); - - const stencilWrite = material.stencilWrite; - stencilBuffer.setTest( stencilWrite ); - if ( stencilWrite ) { - - stencilBuffer.setMask( material.stencilWriteMask ); - stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); - stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); - - } - - setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - - material.alphaToCoverage === true - ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) - : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - - } - - // - - function setFlipSided( flipSided ) { - - if ( currentFlipSided !== flipSided ) { - - if ( flipSided ) { - - gl.frontFace( gl.CW ); - - } else { - - gl.frontFace( gl.CCW ); - - } - - currentFlipSided = flipSided; - - } - - } - - function setCullFace( cullFace ) { - - if ( cullFace !== CullFaceNone ) { - - enable( gl.CULL_FACE ); - - if ( cullFace !== currentCullFace ) { - - if ( cullFace === CullFaceBack ) { - - gl.cullFace( gl.BACK ); - - } else if ( cullFace === CullFaceFront ) { - - gl.cullFace( gl.FRONT ); - - } else { - - gl.cullFace( gl.FRONT_AND_BACK ); - - } - - } - - } else { - - disable( gl.CULL_FACE ); - - } - - currentCullFace = cullFace; - - } - - function setLineWidth( width ) { - - if ( width !== currentLineWidth ) { - - if ( lineWidthAvailable ) gl.lineWidth( width ); - - currentLineWidth = width; - - } - - } - - function setPolygonOffset( polygonOffset, factor, units ) { - - if ( polygonOffset ) { - - enable( gl.POLYGON_OFFSET_FILL ); - - if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { - - currentPolygonOffsetFactor = factor; - currentPolygonOffsetUnits = units; - - if ( depthBuffer.getReversed() ) { - - factor = - factor; - - } - - gl.polygonOffset( factor, units ); - - } - - } else { - - disable( gl.POLYGON_OFFSET_FILL ); - - } - - } - - function setScissorTest( scissorTest ) { - - if ( scissorTest ) { - - enable( gl.SCISSOR_TEST ); - - } else { - - disable( gl.SCISSOR_TEST ); - - } - - } - - // texture - - function activeTexture( webglSlot ) { - - if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - - if ( currentTextureSlot !== webglSlot ) { - - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; - - } - - } - - function bindTexture( webglType, webglTexture, webglSlot ) { - - if ( webglSlot === undefined ) { - - if ( currentTextureSlot === null ) { - - webglSlot = gl.TEXTURE0 + maxTextures - 1; - - } else { - - webglSlot = currentTextureSlot; - - } - - } - - let boundTexture = currentBoundTextures[ webglSlot ]; - - if ( boundTexture === undefined ) { - - boundTexture = { type: undefined, texture: undefined }; - currentBoundTextures[ webglSlot ] = boundTexture; - - } - - if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - - if ( currentTextureSlot !== webglSlot ) { - - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; - - } - - gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); - - boundTexture.type = webglType; - boundTexture.texture = webglTexture; - - } - - } - - function unbindTexture() { - - const boundTexture = currentBoundTextures[ currentTextureSlot ]; - - if ( boundTexture !== undefined && boundTexture.type !== undefined ) { - - gl.bindTexture( boundTexture.type, null ); - - boundTexture.type = undefined; - boundTexture.texture = undefined; - - } - - } - - function compressedTexImage2D() { - - try { - - gl.compressedTexImage2D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function compressedTexImage3D() { - - try { - - gl.compressedTexImage3D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function texSubImage2D() { - - try { - - gl.texSubImage2D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function texSubImage3D() { - - try { - - gl.texSubImage3D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function compressedTexSubImage2D() { - - try { - - gl.compressedTexSubImage2D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function compressedTexSubImage3D() { - - try { - - gl.compressedTexSubImage3D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function texStorage2D() { - - try { - - gl.texStorage2D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function texStorage3D() { - - try { - - gl.texStorage3D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function texImage2D() { - - try { - - gl.texImage2D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function texImage3D() { - - try { - - gl.texImage3D( ...arguments ); - - } catch ( e ) { - - error( 'WebGLState:', e ); - - } - - } - - function getParameter( name ) { - - if ( parameters[ name ] !== undefined ) { - - return parameters[ name ]; - - } else { - - return gl.getParameter( name ); - - } - - } - - function pixelStorei( name, value ) { - - if ( parameters[ name ] !== value ) { - - gl.pixelStorei( name, value ); - parameters[ name ] = value; - - } - - } - - // - - function scissor( scissor ) { - - if ( currentScissor.equals( scissor ) === false ) { - - gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); - currentScissor.copy( scissor ); - - } - - } - - function viewport( viewport ) { - - if ( currentViewport.equals( viewport ) === false ) { - - gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); - currentViewport.copy( viewport ); - - } - - } - - function updateUBOMapping( uniformsGroup, program ) { - - let mapping = uboProgramMap.get( program ); - - if ( mapping === undefined ) { - - mapping = new WeakMap(); - - uboProgramMap.set( program, mapping ); - - } - - let blockIndex = mapping.get( uniformsGroup ); - - if ( blockIndex === undefined ) { - - blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); - - mapping.set( uniformsGroup, blockIndex ); - - } - - } - - function uniformBlockBinding( uniformsGroup, program ) { - - const mapping = uboProgramMap.get( program ); - const blockIndex = mapping.get( uniformsGroup ); - - if ( uboBindings.get( program ) !== blockIndex ) { - - // bind shader specific block index to global block point - gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); - - uboBindings.set( program, blockIndex ); - - } - - } - - // - - function reset() { - - // reset state - - gl.disable( gl.BLEND ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.POLYGON_OFFSET_FILL ); - gl.disable( gl.SCISSOR_TEST ); - gl.disable( gl.STENCIL_TEST ); - gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.ONE, gl.ZERO ); - gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); - gl.blendColor( 0, 0, 0, 0 ); - - gl.colorMask( true, true, true, true ); - gl.clearColor( 0, 0, 0, 0 ); - - gl.depthMask( true ); - gl.depthFunc( gl.LESS ); - - depthBuffer.setReversed( false ); - - gl.clearDepth( 1 ); - - gl.stencilMask( 0xffffffff ); - gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); - gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); - gl.clearStencil( 0 ); - - gl.cullFace( gl.BACK ); - gl.frontFace( gl.CCW ); - - gl.polygonOffset( 0, 0 ); - - gl.activeTexture( gl.TEXTURE0 ); - - gl.bindFramebuffer( gl.FRAMEBUFFER, null ); - gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); - gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - - gl.useProgram( null ); - - gl.lineWidth( 1 ); - - gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); - gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); - - gl.pixelStorei( gl.PACK_ALIGNMENT, 4 ); - gl.pixelStorei( gl.UNPACK_ALIGNMENT, 4 ); - gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false ); - gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); - gl.pixelStorei( gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.BROWSER_DEFAULT_WEBGL ); - gl.pixelStorei( gl.PACK_ROW_LENGTH, 0 ); - gl.pixelStorei( gl.PACK_SKIP_PIXELS, 0 ); - gl.pixelStorei( gl.PACK_SKIP_ROWS, 0 ); - gl.pixelStorei( gl.UNPACK_ROW_LENGTH, 0 ); - gl.pixelStorei( gl.UNPACK_IMAGE_HEIGHT, 0 ); - gl.pixelStorei( gl.UNPACK_SKIP_PIXELS, 0 ); - gl.pixelStorei( gl.UNPACK_SKIP_ROWS, 0 ); - gl.pixelStorei( gl.UNPACK_SKIP_IMAGES, 0 ); - - // reset internals - - enabledCapabilities = {}; - parameters = {}; - - currentTextureSlot = null; - currentBoundTextures = {}; - - currentBoundFramebuffers = {}; - currentDrawbuffers = new WeakMap(); - defaultDrawbuffers = []; - - currentProgram = null; - - currentBlendingEnabled = false; - currentBlending = null; - currentBlendEquation = null; - currentBlendSrc = null; - currentBlendDst = null; - currentBlendEquationAlpha = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - currentBlendColor = new Color( 0, 0, 0 ); - currentBlendAlpha = 0; - currentPremultipledAlpha = false; - - currentFlipSided = null; - currentCullFace = null; - - currentLineWidth = null; - - currentPolygonOffsetFactor = null; - currentPolygonOffsetUnits = null; - - currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); - currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); - - colorBuffer.reset(); - depthBuffer.reset(); - stencilBuffer.reset(); - - } - - return { - - buffers: { - color: colorBuffer, - depth: depthBuffer, - stencil: stencilBuffer - }, - - enable: enable, - disable: disable, - - bindFramebuffer: bindFramebuffer, - drawBuffers: drawBuffers, - - useProgram: useProgram, - - setBlending: setBlending, - setMaterial: setMaterial, - - setFlipSided: setFlipSided, - setCullFace: setCullFace, - - setLineWidth: setLineWidth, - setPolygonOffset: setPolygonOffset, - - setScissorTest: setScissorTest, - - activeTexture: activeTexture, - bindTexture: bindTexture, - unbindTexture: unbindTexture, - compressedTexImage2D: compressedTexImage2D, - compressedTexImage3D: compressedTexImage3D, - texImage2D: texImage2D, - texImage3D: texImage3D, - pixelStorei: pixelStorei, - getParameter: getParameter, - - updateUBOMapping: updateUBOMapping, - uniformBlockBinding: uniformBlockBinding, - - texStorage2D: texStorage2D, - texStorage3D: texStorage3D, - texSubImage2D: texSubImage2D, - texSubImage3D: texSubImage3D, - compressedTexSubImage2D: compressedTexSubImage2D, - compressedTexSubImage3D: compressedTexSubImage3D, - - scissor: scissor, - viewport: viewport, - - reset: reset - - }; - -} - -/** - * Determines how many bytes must be used to represent the texture. - * - * @param {number} width - The width of the texture. - * @param {number} height - The height of the texture. - * @param {number} format - The texture's format. - * @param {number} type - The texture's type. - * @return {number} The byte length. - */ -function getByteLength( width, height, format, type ) { - - const typeByteLength = getTextureTypeByteLength( type ); - - switch ( format ) { - - // https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml - case AlphaFormat: - return width * height; - case RedFormat: - return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; - case RedIntegerFormat: - return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; - case RGFormat: - return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; - case RGIntegerFormat: - return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; - case RGBFormat: - return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength; - case RGBAFormat: - return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; - case RGBAIntegerFormat: - return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; - - // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/ - case RGB_S3TC_DXT1_Format: - case RGBA_S3TC_DXT1_Format: - return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; - case RGBA_S3TC_DXT3_Format: - case RGBA_S3TC_DXT5_Format: - return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; - - // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/ - case RGB_PVRTC_2BPPV1_Format: - case RGBA_PVRTC_2BPPV1_Format: - return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4; - case RGB_PVRTC_4BPPV1_Format: - case RGBA_PVRTC_4BPPV1_Format: - return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2; - - // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/ - case RGB_ETC1_Format: - case RGB_ETC2_Format: - case R11_EAC_Format: - case SIGNED_R11_EAC_Format: - return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; - case RGBA_ETC2_EAC_Format: - case RG11_EAC_Format: - case SIGNED_RG11_EAC_Format: - return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; - - // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/ - case RGBA_ASTC_4x4_Format: - return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; - case RGBA_ASTC_5x4_Format: - return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16; - case RGBA_ASTC_5x5_Format: - return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16; - case RGBA_ASTC_6x5_Format: - return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16; - case RGBA_ASTC_6x6_Format: - return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16; - case RGBA_ASTC_8x5_Format: - return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16; - case RGBA_ASTC_8x6_Format: - return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16; - case RGBA_ASTC_8x8_Format: - return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16; - case RGBA_ASTC_10x5_Format: - return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16; - case RGBA_ASTC_10x6_Format: - return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16; - case RGBA_ASTC_10x8_Format: - return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16; - case RGBA_ASTC_10x10_Format: - return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16; - case RGBA_ASTC_12x10_Format: - return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16; - case RGBA_ASTC_12x12_Format: - return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16; - - // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/ - case RGBA_BPTC_Format: - case RGB_BPTC_SIGNED_Format: - case RGB_BPTC_UNSIGNED_Format: - return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; - - // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/ - case RED_RGTC1_Format: - case SIGNED_RED_RGTC1_Format: - return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8; - case RED_GREEN_RGTC2_Format: - case SIGNED_RED_GREEN_RGTC2_Format: - return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; - - } - - throw new Error( - `Unable to determine texture byte length for ${format} format.`, - ); - -} - -function getTextureTypeByteLength( type ) { - - switch ( type ) { - - case UnsignedByteType: - case ByteType: - return { byteLength: 1, components: 1 }; - case UnsignedShortType: - case ShortType: - case HalfFloatType: - return { byteLength: 2, components: 1 }; - case UnsignedShort4444Type: - case UnsignedShort5551Type: - return { byteLength: 2, components: 4 }; - case UnsignedIntType: - case IntType: - case FloatType: - return { byteLength: 4, components: 1 }; - case UnsignedInt5999Type: - case UnsignedInt101111Type: - return { byteLength: 4, components: 3 }; - - } - - throw new Error( `THREE.TextureUtils: Unknown texture type ${type}.` ); - -} - -function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { - - const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; - const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); - - const _imageDimensions = new Vector2(); - const _videoTextures = new WeakMap(); - const _htmlTextures = new Set(); - let _canvas; - - const _sources = new WeakMap(); // maps WebglTexture objects to instances of TextureSource - - // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, - // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! - // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). - - let useOffscreenCanvas = false; - - try { - - useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' - && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; - - - } catch ( err ) { - - // Ignore any errors - - } - - function createCanvas( width, height ) { - - // Use OffscreenCanvas when available. Specially needed in web workers - - return useOffscreenCanvas ? - new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); - - } - - function resizeImage( image, needsNewCanvas, maxSize ) { - - let scale = 1; - - const dimensions = getDimensions( image ); - - // handle case if texture exceeds max size - - if ( dimensions.width > maxSize || dimensions.height > maxSize ) { - - scale = maxSize / Math.max( dimensions.width, dimensions.height ); - - } - - // only perform resize if necessary - - if ( scale < 1 ) { - - // only perform resize for certain image types - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || - ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) ) { - - const width = Math.floor( scale * dimensions.width ); - const height = Math.floor( scale * dimensions.height ); - - if ( _canvas === undefined ) _canvas = createCanvas( width, height ); - - // cube textures can't reuse the same canvas - - const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; - - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, width, height ); - - warn( 'WebGLRenderer: Texture has been resized from (' + dimensions.width + 'x' + dimensions.height + ') to (' + width + 'x' + height + ').' ); - - return canvas; - - } else { - - if ( 'data' in image ) { - - warn( 'WebGLRenderer: Image in DataTexture is too big (' + dimensions.width + 'x' + dimensions.height + ').' ); - - } - - return image; - - } - - } - - return image; - - } - - function textureNeedsGenerateMipmaps( texture ) { - - return texture.generateMipmaps; - - } - - function generateMipmap( target ) { - - _gl.generateMipmap( target ); - - } - - function getTargetType( texture ) { - - if ( texture.isWebGLCubeRenderTarget ) return _gl.TEXTURE_CUBE_MAP; - if ( texture.isWebGL3DRenderTarget ) return _gl.TEXTURE_3D; - if ( texture.isWebGLArrayRenderTarget || texture.isCompressedArrayTexture ) return _gl.TEXTURE_2D_ARRAY; - return _gl.TEXTURE_2D; - - } - - function getInternalFormat( internalFormatName, glFormat, glType, normalized, colorSpace, forceLinearTransfer = false ) { - - if ( internalFormatName !== null ) { - - if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; - - warn( 'WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); - - } - - let ext_texture_norm16; - - if ( normalized ) { - - ext_texture_norm16 = extensions.get( 'EXT_texture_norm16' ); - - if ( ! ext_texture_norm16 ) { - - warn( 'WebGLRenderer: Unable to use normalized textures without EXT_texture_norm16 extension' ); - - } - - } - - let internalFormat = glFormat; - - if ( glFormat === _gl.RED ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; - if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_EXT; - if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.R16_SNORM_EXT; - - } - - if ( glFormat === _gl.RED_INTEGER ) { - - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8UI; - if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.R16UI; - if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.R32UI; - if ( glType === _gl.BYTE ) internalFormat = _gl.R8I; - if ( glType === _gl.SHORT ) internalFormat = _gl.R16I; - if ( glType === _gl.INT ) internalFormat = _gl.R32I; - - } - - if ( glFormat === _gl.RG ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; - if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_EXT; - if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RG16_SNORM_EXT; - - } - - if ( glFormat === _gl.RG_INTEGER ) { - - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8UI; - if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RG16UI; - if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RG32UI; - if ( glType === _gl.BYTE ) internalFormat = _gl.RG8I; - if ( glType === _gl.SHORT ) internalFormat = _gl.RG16I; - if ( glType === _gl.INT ) internalFormat = _gl.RG32I; - - } - - if ( glFormat === _gl.RGB_INTEGER ) { - - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGB8UI; - if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGB16UI; - if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGB32UI; - if ( glType === _gl.BYTE ) internalFormat = _gl.RGB8I; - if ( glType === _gl.SHORT ) internalFormat = _gl.RGB16I; - if ( glType === _gl.INT ) internalFormat = _gl.RGB32I; - - } - - if ( glFormat === _gl.RGBA_INTEGER ) { - - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGBA8UI; - if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGBA16UI; - if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGBA32UI; - if ( glType === _gl.BYTE ) internalFormat = _gl.RGBA8I; - if ( glType === _gl.SHORT ) internalFormat = _gl.RGBA16I; - if ( glType === _gl.INT ) internalFormat = _gl.RGBA32I; - - } - - if ( glFormat === _gl.RGB ) { - - if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_EXT; - if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGB16_SNORM_EXT; - if ( glType === _gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = _gl.RGB9_E5; - if ( glType === _gl.UNSIGNED_INT_10F_11F_11F_REV ) internalFormat = _gl.R11F_G11F_B10F; - - } - - if ( glFormat === _gl.RGBA ) { - - const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); - - if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; - if ( glType === _gl.UNSIGNED_SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_EXT; - if ( glType === _gl.SHORT && ext_texture_norm16 ) internalFormat = ext_texture_norm16.RGBA16_SNORM_EXT; - if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; - if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; - - } - - if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || - internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || - internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { - - extensions.get( 'EXT_color_buffer_float' ); - - } - - return internalFormat; - - } - - function getInternalDepthFormat( useStencil, depthType ) { - - let glInternalFormat; - if ( useStencil ) { - - if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - - glInternalFormat = _gl.DEPTH24_STENCIL8; - - } else if ( depthType === FloatType ) { - - glInternalFormat = _gl.DEPTH32F_STENCIL8; - - } else if ( depthType === UnsignedShortType ) { - - glInternalFormat = _gl.DEPTH24_STENCIL8; - warn( 'DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.' ); - - } - - } else { - - if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } else if ( depthType === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( depthType === UnsignedShortType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT16; - - } - - } - - return glInternalFormat; - - } - - function getMipLevels( texture, image ) { - - if ( textureNeedsGenerateMipmaps( texture ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { - - return Math.log2( Math.max( image.width, image.height ) ) + 1; - - } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { - - // user-defined mipmaps - - return texture.mipmaps.length; - - } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { - - return image.mipmaps.length; - - } else { - - // texture without mipmaps (only base level) - - return 1; - - } - - } - - // - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - deallocateTexture( texture ); - - if ( texture.isVideoTexture ) { - - _videoTextures.delete( texture ); - - } - - if ( texture.isHTMLTexture ) { - - _htmlTextures.delete( texture ); - - } - - } - - function onRenderTargetDispose( event ) { - - const renderTarget = event.target; - - renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - - deallocateRenderTarget( renderTarget ); - - } - - // - - function deallocateTexture( texture ) { - - const textureProperties = properties.get( texture ); - - if ( textureProperties.__webglInit === undefined ) return; - - // check if it's necessary to remove the WebGLTexture object - - const source = texture.source; - const webglTextures = _sources.get( source ); - - if ( webglTextures ) { - - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - webglTexture.usedTimes --; - - // the WebGLTexture object is not used anymore, remove it - - if ( webglTexture.usedTimes === 0 ) { - - deleteTexture( texture ); - - } - - // remove the weak map entry if no WebGLTexture uses the source anymore - - if ( Object.keys( webglTextures ).length === 0 ) { - - _sources.delete( source ); - - } - - } - - properties.remove( texture ); - - } - - function deleteTexture( texture ) { - - const textureProperties = properties.get( texture ); - _gl.deleteTexture( textureProperties.__webglTexture ); - - const source = texture.source; - const webglTextures = _sources.get( source ); - delete webglTextures[ textureProperties.__cacheKey ]; - - info.memory.textures --; - - } - - function deallocateRenderTarget( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - - if ( renderTarget.depthTexture ) { - - renderTarget.depthTexture.dispose(); - - properties.remove( renderTarget.depthTexture ); - - } - - if ( renderTarget.isWebGLCubeRenderTarget ) { - - for ( let i = 0; i < 6; i ++ ) { - - if ( Array.isArray( renderTargetProperties.__webglFramebuffer[ i ] ) ) { - - for ( let level = 0; level < renderTargetProperties.__webglFramebuffer[ i ].length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ][ level ] ); - - } else { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); - - } - - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); - - } - - } else { - - if ( Array.isArray( renderTargetProperties.__webglFramebuffer ) ) { - - for ( let level = 0; level < renderTargetProperties.__webglFramebuffer.length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ level ] ); - - } else { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - - } - - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); - if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); - - if ( renderTargetProperties.__webglColorRenderbuffer ) { - - for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { - - if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - } - - if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); - - } - - const textures = renderTarget.textures; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( textures[ i ] ); - - if ( attachmentProperties.__webglTexture ) { - - _gl.deleteTexture( attachmentProperties.__webglTexture ); - - info.memory.textures --; - - } - - properties.remove( textures[ i ] ); - - } - - properties.remove( renderTarget ); - - } - - // - - let textureUnits = 0; - - function resetTextureUnits() { - - textureUnits = 0; - - } - - function getTextureUnits() { - - return textureUnits; - - } - - function setTextureUnits( value ) { - - textureUnits = value; - - } - - function allocateTextureUnit() { - - const textureUnit = textureUnits; - - if ( textureUnit >= capabilities.maxTextures ) { - - warn( 'WebGLTextures: Trying to use ' + ( textureUnit + 1 ) + ' texture units while this GPU supports only ' + capabilities.maxTextures ); - - } - - textureUnits += 1; - - return textureUnit; - - } - - function getTextureCacheKey( texture ) { - - const array = []; - - array.push( texture.wrapS ); - array.push( texture.wrapT ); - array.push( texture.wrapR || 0 ); - array.push( texture.magFilter ); - array.push( texture.minFilter ); - array.push( texture.anisotropy ); - array.push( texture.internalFormat ); - array.push( texture.format ); - array.push( texture.type ); - array.push( texture.generateMipmaps ); - array.push( texture.premultiplyAlpha ); - array.push( texture.flipY ); - array.push( texture.unpackAlignment ); - array.push( texture.colorSpace ); - - return array.join(); - - } - - // - - function setTexture2D( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.isVideoTexture ) updateVideoTexture( texture ); - - if ( texture.isRenderTargetTexture === false && texture.isExternalTexture !== true && texture.version > 0 && textureProperties.__version !== texture.version ) { - - const image = texture.image; - - if ( image === null ) { - - warn( 'WebGLRenderer: Texture marked for update but no image data found.' ); - - } else if ( image.complete === false ) { - - warn( 'WebGLRenderer: Texture marked for update but image is incomplete' ); - - } else { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - } else if ( texture.isExternalTexture ) { - - textureProperties.__webglTexture = texture.sourceTexture ? texture.sourceTexture : null; - - } - - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTexture2DArray( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadTexture( textureProperties, texture, slot ); - return; - - } else if ( texture.isExternalTexture ) { - - textureProperties.__webglTexture = texture.sourceTexture ? texture.sourceTexture : null; - - } - - state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTexture3D( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTextureCube( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.isCubeDepthTexture !== true && texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadCubeTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - const wrappingToGL = { - [ RepeatWrapping ]: _gl.REPEAT, - [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, - [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT - }; - - const filterToGL = { - [ NearestFilter ]: _gl.NEAREST, - [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, - [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, - - [ LinearFilter ]: _gl.LINEAR, - [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, - [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR - }; - - const compareToGL = { - [ NeverCompare ]: _gl.NEVER, - [ AlwaysCompare ]: _gl.ALWAYS, - [ LessCompare ]: _gl.LESS, - [ LessEqualCompare ]: _gl.LEQUAL, - [ EqualCompare ]: _gl.EQUAL, - [ GreaterEqualCompare ]: _gl.GEQUAL, - [ GreaterCompare ]: _gl.GREATER, - [ NotEqualCompare ]: _gl.NOTEQUAL - }; - - function setTextureParameters( textureType, texture ) { - - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false && - ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || - texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter ) ) { - - warn( 'WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device.' ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); - - if ( texture.compareFunction ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); - - } - - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - - if ( texture.magFilter === NearestFilter ) return; - if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension - - if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); - properties.get( texture ).__currentAnisotropy = texture.anisotropy; - - } - - } - - } - - function initTexture( textureProperties, texture ) { - - let forceUpload = false; - - if ( textureProperties.__webglInit === undefined ) { - - textureProperties.__webglInit = true; - - texture.addEventListener( 'dispose', onTextureDispose ); - - } - - // create TextureSource <-> WebGLTextures mapping if necessary - - const source = texture.source; - let webglTextures = _sources.get( source ); - - if ( webglTextures === undefined ) { - - webglTextures = {}; - _sources.set( source, webglTextures ); - - } - - // check if there is already a WebGLTexture object for the given texture parameters - - const textureCacheKey = getTextureCacheKey( texture ); - - if ( textureCacheKey !== textureProperties.__cacheKey ) { - - // if not, create a new instance of WebGLTexture - - if ( webglTextures[ textureCacheKey ] === undefined ) { - - // create new entry - - webglTextures[ textureCacheKey ] = { - texture: _gl.createTexture(), - usedTimes: 0 - }; - - info.memory.textures ++; - - // when a new instance of WebGLTexture was created, a texture upload is required - // even if the image contents are identical - - forceUpload = true; - - } - - webglTextures[ textureCacheKey ].usedTimes ++; - - // every time the texture cache key changes, it's necessary to check if an instance of - // WebGLTexture can be deleted in order to avoid a memory leak. - - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - - if ( webglTexture !== undefined ) { - - webglTextures[ textureProperties.__cacheKey ].usedTimes --; - - if ( webglTexture.usedTimes === 0 ) { - - deleteTexture( texture ); - - } - - } - - // store references to cache key and WebGLTexture object - - textureProperties.__cacheKey = textureCacheKey; - textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; - - } - - return forceUpload; - - } - - function getRow( index, rowLength, componentStride ) { - - return Math.floor( Math.floor( index / componentStride ) / rowLength ); - - } - - function updateTexture( texture, image, glFormat, glType ) { - - const componentStride = 4; // only RGBA supported - - const updateRanges = texture.updateRanges; - - if ( updateRanges.length === 0 ) { - - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); - - } else { - - // Before applying update ranges, we merge any adjacent / overlapping - // ranges to reduce load on `gl.texSubImage2D`. Empirically, this has led - // to performance improvements for applications which make heavy use of - // update ranges. Likely due to GPU command overhead. - // - // Note that to reduce garbage collection between frames, we merge the - // update ranges in-place. This is safe because this method will clear the - // update ranges once updated. - - updateRanges.sort( ( a, b ) => a.start - b.start ); - - // To merge the update ranges in-place, we work from left to right in the - // existing updateRanges array, merging ranges. This may result in a final - // array which is smaller than the original. This index tracks the last - // index representing a merged range, any data after this index can be - // trimmed once the merge algorithm is completed. - let mergeIndex = 0; - - for ( let i = 1; i < updateRanges.length; i ++ ) { - - const previousRange = updateRanges[ mergeIndex ]; - const range = updateRanges[ i ]; - - // Only merge if in the same row and overlapping/adjacent - const previousEnd = previousRange.start + previousRange.count; - const currentRow = getRow( range.start, image.width, componentStride ); - const previousRow = getRow( previousRange.start, image.width, componentStride ); - - // We add one here to merge adjacent ranges. This is safe because ranges - // operate over positive integers. - if ( - range.start <= previousEnd + 1 && - currentRow === previousRow && - getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill - ) { - - previousRange.count = Math.max( - previousRange.count, - range.start + range.count - previousRange.start - ); - - } else { - - ++ mergeIndex; - updateRanges[ mergeIndex ] = range; - - } - - - } - - // Trim the array to only contain the merged ranges. - updateRanges.length = mergeIndex + 1; - - const currentUnpackRowLen = state.getParameter( _gl.UNPACK_ROW_LENGTH ); - const currentUnpackSkipPixels = state.getParameter( _gl.UNPACK_SKIP_PIXELS ); - const currentUnpackSkipRows = state.getParameter( _gl.UNPACK_SKIP_ROWS ); - - state.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - - for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { - - const range = updateRanges[ i ]; - - const pixelStart = Math.floor( range.start / componentStride ); - const pixelCount = Math.ceil( range.count / componentStride ); - - const x = pixelStart % image.width; - const y = Math.floor( pixelStart / image.width ); - - // Assumes update ranges refer to contiguous memory - const width = pixelCount; - const height = 1; - - state.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x ); - state.pixelStorei( _gl.UNPACK_SKIP_ROWS, y ); - - state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data ); - - } - - texture.clearUpdateRanges(); - - state.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); - state.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); - state.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); - - } - - } - - function uploadTexture( textureProperties, texture, slot ) { - - let textureType = _gl.TEXTURE_2D; - - if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; - if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; - - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; - - state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - const sourceProperties = properties.get( source ); - - if ( source.version !== sourceProperties.__version || forceUpload === true ) { - - state.activeTexture( _gl.TEXTURE0 + slot ); - - const isImageBitmap = ( typeof ImageBitmap !== 'undefined' && texture.image instanceof ImageBitmap ); - - if ( isImageBitmap === false ) { - - const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); - const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); - const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; - - state.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - state.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - state.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - - } - - state.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - - let image = resizeImage( texture.image, false, capabilities.maxTextureSize ); - image = verifyColorSpace( texture, image ); - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - - const glType = utils.convert( texture.type ); - let glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace, texture.isVideoTexture ); - - setTextureParameters( textureType, texture ); - - let mipmap; - const mipmaps = texture.mipmaps; - - const useTexStorage = ( texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - const dataReady = source.dataReady; - const levels = getMipLevels( texture, image ); - - if ( texture.isDepthTexture ) { - - glInternalFormat = getInternalDepthFormat( texture.format === DepthStencilFormat, texture.type ); - - // - - if ( allocateMemory ) { - - if ( useTexStorage ) { - - state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); - - } - - } - - } else if ( texture.isDataTexture ) { - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - texture.generateMipmaps = false; - - } else { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } - - if ( dataReady ) { - - updateTexture( texture, image, glFormat, glType ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); - - } - - } - - } else if ( texture.isCompressedTexture ) { - - if ( texture.isCompressedArrayTexture ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - if ( dataReady ) { - - if ( texture.layerUpdates.size > 0 ) { - - const layerByteLength = getByteLength( mipmap.width, mipmap.height, texture.format, texture.type ); - - for ( const layerIndex of texture.layerUpdates ) { - - const layerData = mipmap.data.subarray( - layerIndex * layerByteLength / mipmap.data.BYTES_PER_ELEMENT, - ( layerIndex + 1 ) * layerByteLength / mipmap.data.BYTES_PER_ELEMENT - ); - state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, layerIndex, mipmap.width, mipmap.height, 1, glFormat, layerData ); - - } - - } else { - - state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data ); - - } - - } - - } else { - - state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); - - } - - } else { - - warn( 'WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - - } - - } else { - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); - - } - - } else { - - state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - if ( texture.layerUpdates.size > 0 ) texture.clearLayerUpdates(); - - } else { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - - } - - } else { - - state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } - - } else { - - warn( 'WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - - } - - } else { - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } - - } else if ( texture.isDataArrayTexture ) { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); - - } - - if ( dataReady ) { - - if ( texture.layerUpdates.size > 0 ) { - - const layerByteLength = getByteLength( image.width, image.height, texture.format, texture.type ); - - for ( const layerIndex of texture.layerUpdates ) { - - const layerData = image.data.subarray( - layerIndex * layerByteLength / image.data.BYTES_PER_ELEMENT, - ( layerIndex + 1 ) * layerByteLength / image.data.BYTES_PER_ELEMENT - ); - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, layerIndex, image.width, image.height, 1, glFormat, glType, layerData ); - - } - - texture.clearLayerUpdates(); - - } else { - - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - - } - - } - - } else { - - state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - - } - - } else if ( texture.isData3DTexture ) { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); - - } - - if ( dataReady ) { - - state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - - } - - } else { - - state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - - } - - } else if ( texture.isFramebufferTexture ) { - - if ( allocateMemory ) { - - if ( useTexStorage ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } else { - - let width = image.width, height = image.height; - - for ( let i = 0; i < levels; i ++ ) { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); - - width >>= 1; - height >>= 1; - - } - - } - - } - - } else if ( texture.isHTMLTexture ) { - - if ( 'texElementImage2D' in _gl ) { - - const canvas = _gl.canvas; - - // Ensure the canvas supports HTML-in-Canvas and the element is a child. - if ( ! canvas.hasAttribute( 'layoutsubtree' ) ) { - - canvas.setAttribute( 'layoutsubtree', 'true' ); - - } - - if ( image.parentNode !== canvas ) { - - canvas.appendChild( image ); - - // Register and set up a shared paint callback for all HTMLTextures. - _htmlTextures.add( texture ); - - canvas.onpaint = ( event ) => { - - const changed = event.changedElements; - - for ( const t of _htmlTextures ) { - - if ( changed.includes( t.image ) ) { - - t.needsUpdate = true; - - } - - } - - }; - - canvas.requestPaint(); - return; - - } - - if ( _gl.texElementImage2D.length === 3 ) { - - // Chrome 150+ - - _gl.texElementImage2D( _gl.TEXTURE_2D, _gl.RGBA8, image ); - - } else { - - // Chrome 138 - 149 - - const level = 0; - const internalFormat = _gl.RGBA; - const srcFormat = _gl.RGBA; - const srcType = _gl.UNSIGNED_BYTE; - - _gl.texElementImage2D( _gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, image ); - - } - - _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.LINEAR ); - _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); - _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); - - } - - } else { - - // regular Texture (image, video, canvas) - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 ) { - - if ( useTexStorage && allocateMemory ) { - - const dimensions = getDimensions( mipmaps[ 0 ] ); - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); - - } - - } - - texture.generateMipmaps = false; - - } else { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - const dimensions = getDimensions( image ); - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - - } - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); - - } - - } - - } - - if ( textureNeedsGenerateMipmaps( texture ) ) { - - generateMipmap( textureType ); - - } - - sourceProperties.__version = source.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } - - textureProperties.__version = texture.version; - - } - - function uploadCubeTexture( textureProperties, texture, slot ) { - - if ( texture.image.length !== 6 ) return; - - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - const sourceProperties = properties.get( source ); - - if ( source.version !== sourceProperties.__version || forceUpload === true ) { - - state.activeTexture( _gl.TEXTURE0 + slot ); - - const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); - const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); - const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; - - state.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - state.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - state.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - state.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - - const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); - const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); - - const cubeImage = []; - - for ( let i = 0; i < 6; i ++ ) { - - if ( ! isCompressed && ! isDataTexture ) { - - cubeImage[ i ] = resizeImage( texture.image[ i ], true, capabilities.maxCubemapSize ); - - } else { - - cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - - } - - cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); - - } - - const image = cubeImage[ 0 ], - glFormat = utils.convert( texture.format, texture.colorSpace ), - glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace ); - - const useTexStorage = ( texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - const dataReady = source.dataReady; - let levels = getMipLevels( texture, image ); - - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - - let mipmaps; - - if ( isCompressed ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); - - } - - for ( let i = 0; i < 6; i ++ ) { - - mipmaps = cubeImage[ i ].mipmaps; - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - - } - - } else { - - state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } - - } else { - - warn( 'WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); - - } - - } else { - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } - - } else { - - mipmaps = texture.mipmaps; - - if ( useTexStorage && allocateMemory ) { - - // TODO: Uniformly handle mipmap definitions - // Normal textures and compressed cube textures define base level + mips with their mipmap array - // Uncompressed cube textures use their mipmap array only for mips (no base level) - - if ( mipmaps.length > 0 ) levels ++; - - const dimensions = getDimensions( cubeImage[ 0 ] ); - - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, dimensions.width, dimensions.height ); - - } - - for ( let i = 0; i < 6; i ++ ) { - - if ( isDataTexture ) { - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - - } - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - const mipmapImage = mipmap.image[ i ].image; - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); - - } - - } - - } else { - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); - - } - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - - if ( useTexStorage ) { - - if ( dataReady ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); - - } - - } - - } - - } - - } - - if ( textureNeedsGenerateMipmaps( texture ) ) { - - // We assume images for cube map have the same size. - generateMipmap( _gl.TEXTURE_CUBE_MAP ); - - } - - sourceProperties.__version = source.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } - - textureProperties.__version = texture.version; - - } - - // Render targets - - // Setup storage for target texture and bind it to correct framebuffer - function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget, level ) { - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace ); - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); - - textureProperties.__renderTarget = renderTarget; - - if ( ! renderTargetProperties.__hasExternalTextures ) { - - const width = Math.max( 1, renderTarget.width >> level ); - const height = Math.max( 1, renderTarget.height >> level ); - - if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { - - state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null ); - - } else { - - state.texImage2D( textureTarget, level, glInternalFormat, width, height, 0, glFormat, glType, null ); - - } - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); - - } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, level ); - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - // Setup storage for internal depth/stencil buffers and bind to correct framebuffer - function setupRenderBufferStorage( renderbuffer, renderTarget, useMultisample ) { - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - - if ( renderTarget.depthBuffer ) { - - // retrieve the depth attachment types - const depthTexture = renderTarget.depthTexture; - const depthType = depthTexture && depthTexture.isDepthTexture ? depthTexture.type : null; - const glInternalFormat = getInternalDepthFormat( renderTarget.stencilBuffer, depthType ); - const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - - // set up the attachment - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, getRenderTargetSamples( renderTarget ), glInternalFormat, renderTarget.width, renderTarget.height ); - - } else if ( useMultisample ) { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, getRenderTargetSamples( renderTarget ), glInternalFormat, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - - } else { - - const textures = renderTarget.textures; - - for ( let i = 0; i < textures.length; i ++ ) { - - const texture = textures[ i ]; - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, getRenderTargetSamples( renderTarget ), glInternalFormat, renderTarget.width, renderTarget.height ); - - } else if ( useMultisample ) { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, getRenderTargetSamples( renderTarget ), glInternalFormat, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - } - - } - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - - } - - // Setup resources for a Depth Texture for a FBO (needs an extension) - function setupDepthTexture( framebuffer, renderTarget, cubeFace ) { - - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { - - throw new Error( 'THREE.WebGLTextures: renderTarget.depthTexture must be an instance of THREE.DepthTexture.' ); - - } - - const textureProperties = properties.get( renderTarget.depthTexture ); - textureProperties.__renderTarget = renderTarget; - - // upload an empty depth texture with framebuffer size - if ( ! textureProperties.__webglTexture || - renderTarget.depthTexture.image.width !== renderTarget.width || - renderTarget.depthTexture.image.height !== renderTarget.height ) { - - renderTarget.depthTexture.image.width = renderTarget.width; - renderTarget.depthTexture.image.height = renderTarget.height; - renderTarget.depthTexture.needsUpdate = true; - - } - - if ( isCube ) { - - // For cube depth textures, initialize and bind without uploading image data - if ( textureProperties.__webglInit === undefined ) { - - textureProperties.__webglInit = true; - renderTarget.depthTexture.addEventListener( 'dispose', onTextureDispose ); - - } - - // Only create and allocate storage once - if ( textureProperties.__webglTexture === undefined ) { - - textureProperties.__webglTexture = _gl.createTexture(); - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.depthTexture ); - - // Allocate storage for all 6 faces with correct depth texture format - const glFormat = utils.convert( renderTarget.depthTexture.format ); - const glType = utils.convert( renderTarget.depthTexture.type ); - - // Use proper internal format for depth textures - let glInternalFormat; - if ( renderTarget.depthTexture.format === DepthFormat ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - - glInternalFormat = _gl.DEPTH24_STENCIL8; - - } - - for ( let i = 0; i < 6; i ++ ) { - - _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); - - } - - } - - } else { - - setTexture2D( renderTarget.depthTexture, 0 ); - - } - - const webglDepthTexture = textureProperties.__webglTexture; - const samples = getRenderTargetSamples( renderTarget ); - - const glTextureType = isCube ? _gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace : _gl.TEXTURE_2D; - const glAttachmentType = renderTarget.depthTexture.format === DepthStencilFormat ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - - if ( renderTarget.depthTexture.format === DepthFormat ) { - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, glAttachmentType, glTextureType, webglDepthTexture, 0, samples ); - - } else { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, glAttachmentType, glTextureType, webglDepthTexture, 0 ); - - } - - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, glAttachmentType, glTextureType, webglDepthTexture, 0, samples ); - - } else { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, glAttachmentType, glTextureType, webglDepthTexture, 0 ); - - } - - } else { - - throw new Error( 'THREE.WebGLTextures: Unknown depthTexture format.' ); - - } - - } - - // Setup GL resources for a non-texture depth buffer - function setupDepthRenderbuffer( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - - // if the bound depth texture has changed - if ( renderTargetProperties.__boundDepthTexture !== renderTarget.depthTexture ) { - - // fire the dispose event to get rid of stored state associated with the previously bound depth buffer - const depthTexture = renderTarget.depthTexture; - if ( renderTargetProperties.__depthDisposeCallback ) { - - renderTargetProperties.__depthDisposeCallback(); - - } - - // set up dispose listeners to track when the currently attached buffer is implicitly unbound - if ( depthTexture ) { - - const disposeEvent = () => { - - delete renderTargetProperties.__boundDepthTexture; - delete renderTargetProperties.__depthDisposeCallback; - depthTexture.removeEventListener( 'dispose', disposeEvent ); - - }; - - depthTexture.addEventListener( 'dispose', disposeEvent ); - renderTargetProperties.__depthDisposeCallback = disposeEvent; - - } - - renderTargetProperties.__boundDepthTexture = depthTexture; - - } - - if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { - - if ( isCube ) { - - // For cube render targets with depth texture, setup each face - for ( let i = 0; i < 6; i ++ ) { - - setupDepthTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, i ); - - } - - } else { - - const mipmaps = renderTarget.texture.mipmaps; - - if ( mipmaps && mipmaps.length > 0 ) { - - setupDepthTexture( renderTargetProperties.__webglFramebuffer[ 0 ], renderTarget, 0 ); - - } else { - - setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget, 0 ); - - } - - } - - } else { - - if ( isCube ) { - - renderTargetProperties.__webglDepthbuffer = []; - - for ( let i = 0; i < 6; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - - if ( renderTargetProperties.__webglDepthbuffer[ i ] === undefined ) { - - renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); - - } else { - - // attach buffer if it's been created already - const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderbuffer = renderTargetProperties.__webglDepthbuffer[ i ]; - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - - } - - } - - } else { - - const mipmaps = renderTarget.texture.mipmaps; - - if ( mipmaps && mipmaps.length > 0 ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - - } else { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - - } - - if ( renderTargetProperties.__webglDepthbuffer === undefined ) { - - renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); - - } else { - - // attach buffer if it's been created already - const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderbuffer = renderTargetProperties.__webglDepthbuffer; - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - - } - - } - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - // rebind framebuffer with external textures - function rebindTextures( renderTarget, colorTexture, depthTexture ) { - - const renderTargetProperties = properties.get( renderTarget ); - - if ( colorTexture !== undefined ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, 0 ); - - } - - if ( depthTexture !== undefined ) { - - setupDepthRenderbuffer( renderTarget ); - - } - - } - - // Set up GL resources for the render target - function setupRenderTarget( renderTarget ) { - - const texture = renderTarget.texture; - - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); - - renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - - const textures = renderTarget.textures; - - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - const isMultipleRenderTargets = ( textures.length > 1 ); - - if ( ! isMultipleRenderTargets ) { - - if ( textureProperties.__webglTexture === undefined ) { - - textureProperties.__webglTexture = _gl.createTexture(); - - } - - textureProperties.__version = texture.version; - info.memory.textures ++; - - } - - // Setup framebuffer - - if ( isCube ) { - - renderTargetProperties.__webglFramebuffer = []; - - for ( let i = 0; i < 6; i ++ ) { - - if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - - renderTargetProperties.__webglFramebuffer[ i ] = []; - - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - - renderTargetProperties.__webglFramebuffer[ i ][ level ] = _gl.createFramebuffer(); - - } - - } else { - - renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); - - } - - } - - } else { - - if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - - renderTargetProperties.__webglFramebuffer = []; - - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - - renderTargetProperties.__webglFramebuffer[ level ] = _gl.createFramebuffer(); - - } - - } else { - - renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - - } - - if ( isMultipleRenderTargets ) { - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( textures[ i ] ); - - if ( attachmentProperties.__webglTexture === undefined ) { - - attachmentProperties.__webglTexture = _gl.createTexture(); - - info.memory.textures ++; - - } - - } - - } - - if ( ( renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - - renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); - renderTargetProperties.__webglColorRenderbuffer = []; - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - - for ( let i = 0; i < textures.length; i ++ ) { - - const texture = textures[ i ]; - renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.normalized, texture.colorSpace, renderTarget.isXRRenderTarget === true ); - const samples = getRenderTargetSamples( renderTarget ); - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - - if ( renderTarget.depthBuffer ) { - - renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - } - - // Setup color buffer - - if ( isCube ) { - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - - for ( let i = 0; i < 6; i ++ ) { - - if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ][ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level ); - - } - - } else { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 ); - - } - - } - - if ( textureNeedsGenerateMipmaps( texture ) ) { - - generateMipmap( _gl.TEXTURE_CUBE_MAP ); - - } - - state.unbindTexture(); - - } else if ( isMultipleRenderTargets ) { - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachment = textures[ i ]; - const attachmentProperties = properties.get( attachment ); - - let glTextureType = _gl.TEXTURE_2D; - - if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - - glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - - } - - state.bindTexture( glTextureType, attachmentProperties.__webglTexture ); - setTextureParameters( glTextureType, attachment ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, glTextureType, 0 ); - - if ( textureNeedsGenerateMipmaps( attachment ) ) { - - generateMipmap( glTextureType ); - - } - - } - - state.unbindTexture(); - - } else { - - let glTextureType = _gl.TEXTURE_2D; - - if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - - glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - - } - - state.bindTexture( glTextureType, textureProperties.__webglTexture ); - setTextureParameters( glTextureType, texture ); - - if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - - for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, level ); - - } - - } else { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, 0 ); - - } - - if ( textureNeedsGenerateMipmaps( texture ) ) { - - generateMipmap( glTextureType ); - - } - - state.unbindTexture(); - - } - - // Setup depth and stencil buffers - - if ( renderTarget.depthBuffer ) { - - setupDepthRenderbuffer( renderTarget ); - - } - - } - - function updateRenderTargetMipmap( renderTarget ) { - - const textures = renderTarget.textures; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const texture = textures[ i ]; - - if ( textureNeedsGenerateMipmaps( texture ) ) { - - const targetType = getTargetType( renderTarget ); - const webglTexture = properties.get( texture ).__webglTexture; - - state.bindTexture( targetType, webglTexture ); - generateMipmap( targetType ); - state.unbindTexture(); - - } - - } - - } - - const invalidationArrayRead = []; - const invalidationArrayDraw = []; - - function updateMultisampleRenderTarget( renderTarget ) { - - if ( renderTarget.samples > 0 ) { - - if ( useMultisampledRTT( renderTarget ) === false ) { - - const textures = renderTarget.textures; - const width = renderTarget.width; - const height = renderTarget.height; - let mask = _gl.COLOR_BUFFER_BIT; - const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderTargetProperties = properties.get( renderTarget ); - const isMultipleRenderTargets = ( textures.length > 1 ); - - // If MRT we need to remove FBO attachments - if ( isMultipleRenderTargets ) { - - for ( let i = 0; i < textures.length; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); - - } - - } - - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - - const mipmaps = renderTarget.texture.mipmaps; - - if ( mipmaps && mipmaps.length > 0 ) { - - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - - } else { - - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - - } - - for ( let i = 0; i < textures.length; i ++ ) { - - if ( renderTarget.resolveDepthBuffer ) { - - if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - - // resolving stencil is slow with a D3D backend. disable it for all transmission render targets (see #27799) - - if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; - - } - - if ( isMultipleRenderTargets ) { - - _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); - - } - - _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); - - if ( supportsInvalidateFramebuffer === true ) { - - invalidationArrayRead.length = 0; - invalidationArrayDraw.length = 0; - - invalidationArrayRead.push( _gl.COLOR_ATTACHMENT0 + i ); - - if ( renderTarget.depthBuffer && renderTarget.storeMultisampledDepthBuffer === false ) { - - invalidationArrayRead.push( depthStyle ); - invalidationArrayDraw.push( depthStyle ); - - _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, invalidationArrayDraw ); - - } - - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArrayRead ); - - } - - } - - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - - // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments - if ( isMultipleRenderTargets ) { - - for ( let i = 0; i < textures.length; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); - - } - - } - - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - - } else { - - if ( renderTarget.depthBuffer && renderTarget.storeMultisampledDepthBuffer === false && supportsInvalidateFramebuffer ) { - - const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - - _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); - - } - - } - - } - - } - - function getRenderTargetSamples( renderTarget ) { - - return Math.min( capabilities.maxSamples, renderTarget.samples ); - - } - - function useMultisampledRTT( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - - return renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; - - } - - function updateVideoTexture( texture ) { - - const frame = info.render.frame; - - // Check the last frame we updated the VideoTexture - - if ( _videoTextures.get( texture ) !== frame ) { - - _videoTextures.set( texture, frame ); - texture.update(); - - } - - } - - function verifyColorSpace( texture, image ) { - - const colorSpace = texture.colorSpace; - const format = texture.format; - const type = texture.type; - - if ( texture.isCompressedTexture === true || texture.isVideoTexture === true ) return image; - - if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { - - // sRGB - - if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { - - // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format - - if ( format !== RGBAFormat || type !== UnsignedByteType ) { - - warn( 'WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); - - } - - } else { - - error( 'WebGLTextures: Unsupported texture color space:', colorSpace ); - - } - - } - - return image; - - } - - function getDimensions( image ) { - - if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) { - - // if intrinsic data are not available, fallback to width/height - - _imageDimensions.width = image.naturalWidth || image.width; - _imageDimensions.height = image.naturalHeight || image.height; - - } else if ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) { - - _imageDimensions.width = image.displayWidth; - _imageDimensions.height = image.displayHeight; - - } else { - - _imageDimensions.width = image.width; - _imageDimensions.height = image.height; - - } - - return _imageDimensions; - - } - - // - - this.allocateTextureUnit = allocateTextureUnit; - this.resetTextureUnits = resetTextureUnits; - this.getTextureUnits = getTextureUnits; - this.setTextureUnits = setTextureUnits; - - this.setTexture2D = setTexture2D; - this.setTexture2DArray = setTexture2DArray; - this.setTexture3D = setTexture3D; - this.setTextureCube = setTextureCube; - this.rebindTextures = rebindTextures; - this.setupRenderTarget = setupRenderTarget; - this.updateRenderTargetMipmap = updateRenderTargetMipmap; - this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; - this.setupDepthRenderbuffer = setupDepthRenderbuffer; - this.setupFrameBufferTexture = setupFrameBufferTexture; - this.useMultisampledRTT = useMultisampledRTT; - - this.isReversedDepthBuffer = function () { - - return state.buffers.depth.getReversed(); - - }; - -} - -function WebGLUtils( gl, extensions ) { - - function convert( p, colorSpace = NoColorSpace ) { - - let extension; - - const transfer = ColorManagement.getTransfer( colorSpace ); - - if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; - if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; - if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; - if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV; - if ( p === UnsignedInt101111Type ) return gl.UNSIGNED_INT_10F_11F_11F_REV; - - if ( p === ByteType ) return gl.BYTE; - if ( p === ShortType ) return gl.SHORT; - if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; - if ( p === IntType ) return gl.INT; - if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; - if ( p === FloatType ) return gl.FLOAT; - if ( p === HalfFloatType ) return gl.HALF_FLOAT; - - if ( p === AlphaFormat ) return gl.ALPHA; - if ( p === RGBFormat ) return gl.RGB; - if ( p === RGBAFormat ) return gl.RGBA; - if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; - if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; - - // WebGL2 formats. - - if ( p === RedFormat ) return gl.RED; - if ( p === RedIntegerFormat ) return gl.RED_INTEGER; - if ( p === RGFormat ) return gl.RG; - if ( p === RGIntegerFormat ) return gl.RG_INTEGER; - if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; - - // S3TC - - if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { - - if ( transfer === SRGBTransfer ) { - - extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); - - if ( extension !== null ) { - - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; - - } else { - - return null; - - } - - } else { - - extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - - if ( extension !== null ) { - - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - - } else { - - return null; - - } - - } - - } - - // PVRTC - - if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - - if ( extension !== null ) { - - if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; - if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; - - } else { - - return null; - - } - - } - - // ETC - - if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format || p === R11_EAC_Format || p === SIGNED_R11_EAC_Format || p === RG11_EAC_Format || p === SIGNED_RG11_EAC_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_etc' ); - - if ( extension !== null ) { - - if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; - if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; - if ( p === R11_EAC_Format ) return extension.COMPRESSED_R11_EAC; - if ( p === SIGNED_R11_EAC_Format ) return extension.COMPRESSED_SIGNED_R11_EAC; - if ( p === RG11_EAC_Format ) return extension.COMPRESSED_RG11_EAC; - if ( p === SIGNED_RG11_EAC_Format ) return extension.COMPRESSED_SIGNED_RG11_EAC; - - } else { - - return null; - - } - - } - - // ASTC - - if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || - p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || - p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || - p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || - p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_astc' ); - - if ( extension !== null ) { - - if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; - if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; - if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; - if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; - if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; - if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; - if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; - if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; - if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; - if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; - if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; - if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; - if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; - if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; - - } else { - - return null; - - } - - } - - // BPTC - - if ( p === RGBA_BPTC_Format || p === RGB_BPTC_SIGNED_Format || p === RGB_BPTC_UNSIGNED_Format ) { - - extension = extensions.get( 'EXT_texture_compression_bptc' ); - - if ( extension !== null ) { - - if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; - if ( p === RGB_BPTC_SIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT; - if ( p === RGB_BPTC_UNSIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT; - - } else { - - return null; - - } - - } - - // RGTC - - if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { - - extension = extensions.get( 'EXT_texture_compression_rgtc' ); - - if ( extension !== null ) { - - if ( p === RED_RGTC1_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; - if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; - if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; - if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; - - } else { - - return null; - - } - - } - - // - - if ( p === UnsignedInt248Type ) return gl.UNSIGNED_INT_24_8; - - // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) - - return ( gl[ p ] !== undefined ) ? gl[ p ] : null; - - } - - return { convert: convert }; - -} - -/** - * This type of camera can be used in order to efficiently render a scene with a - * predefined set of cameras. This is an important performance aspect for - * rendering VR scenes. - * - * An instance of `ArrayCamera` always has an array of sub cameras. It's mandatory - * to define for each sub camera the `viewport` property which determines the - * part of the viewport that is rendered with this camera. - * - * @augments PerspectiveCamera - */ -class ArrayCamera extends PerspectiveCamera { - - /** - * Constructs a new array camera. - * - * @param {Array} [array=[]] - An array of perspective sub cameras. - */ - constructor( array = [] ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isArrayCamera = true; - - /** - * Whether this camera is used with multiview rendering or not. - * - * @type {boolean} - * @readonly - * @default false - */ - this.isMultiViewCamera = false; - - /** - * An array of perspective sub cameras. - * - * @type {Array} - */ - this.cameras = array; - - } - -} - -/** - * This is almost identical to an {@link Object3D}. Its purpose is to - * make working with groups of objects syntactically clearer. - * - * ```js - * // Create a group and add the two cubes. - * // These cubes can now be rotated / scaled etc as a group. - * const group = new THREE.Group(); - * - * group.add( meshA ); - * group.add( meshB ); - * - * scene.add( group ); - * ``` - * - * @augments Object3D - */ -let Group$1 = class Group extends Object3D { - - constructor() { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isGroup = true; - - this.type = 'Group'; - - } - -}; - -const _moveEvent = { type: 'move' }; - -/** - * Class for representing a XR controller with its - * different coordinate systems. - * - * @private - */ -class WebXRController { - - /** - * Constructs a new XR controller. - */ - constructor() { - - /** - * A group representing the target ray space - * of the XR controller. - * - * @private - * @type {?Group} - * @default null - */ - this._targetRay = null; - - /** - * A group representing the grip space - * of the XR controller. - * - * @private - * @type {?Group} - * @default null - */ - this._grip = null; - - /** - * A group representing the hand space - * of the XR controller. - * - * @private - * @type {?Group} - * @default null - */ - this._hand = null; - - } - - /** - * Returns a group representing the hand space of the XR controller. - * - * @return {Group} A group representing the hand space of the XR controller. - */ - getHandSpace() { - - if ( this._hand === null ) { - - this._hand = new Group$1(); - this._hand.matrixAutoUpdate = false; - this._hand.visible = false; - - this._hand.joints = {}; - this._hand.inputState = { pinching: false }; - - } - - return this._hand; - - } - - /** - * Returns a group representing the target ray space of the XR controller. - * - * @return {Group} A group representing the target ray space of the XR controller. - */ - getTargetRaySpace() { - - if ( this._targetRay === null ) { - - this._targetRay = new Group$1(); - this._targetRay.matrixAutoUpdate = false; - this._targetRay.visible = false; - this._targetRay.hasLinearVelocity = false; - this._targetRay.linearVelocity = new Vector3(); - this._targetRay.hasAngularVelocity = false; - this._targetRay.angularVelocity = new Vector3(); - - } - - return this._targetRay; - - } - - /** - * Returns a group representing the grip space of the XR controller. - * - * @return {Group} A group representing the grip space of the XR controller. - */ - getGripSpace() { - - if ( this._grip === null ) { - - this._grip = new Group$1(); - this._grip.matrixAutoUpdate = false; - this._grip.visible = false; - this._grip.hasLinearVelocity = false; - this._grip.linearVelocity = new Vector3(); - this._grip.hasAngularVelocity = false; - this._grip.angularVelocity = new Vector3(); - this._grip.eventsEnabled = false; - - } - - return this._grip; - - } - - /** - * Dispatches the given event to the groups representing - * the different coordinate spaces of the XR controller. - * - * @param {Object} event - The event to dispatch. - * @return {WebXRController} A reference to this instance. - */ - dispatchEvent( event ) { - - if ( this._targetRay !== null ) { - - this._targetRay.dispatchEvent( event ); - - } - - if ( this._grip !== null ) { - - this._grip.dispatchEvent( event ); - - } - - if ( this._hand !== null ) { - - this._hand.dispatchEvent( event ); - - } - - return this; - - } - - /** - * Connects the controller with the given XR input source. - * - * @param {XRInputSource} inputSource - The input source. - * @return {WebXRController} A reference to this instance. - */ - connect( inputSource ) { - - if ( inputSource && inputSource.hand ) { - - const hand = this._hand; - - if ( hand ) { - - for ( const inputjoint of inputSource.hand.values() ) { - - // Initialize hand with joints when connected - this._getHandJoint( hand, inputjoint ); - - } - - } - - } - - this.dispatchEvent( { type: 'connected', data: inputSource } ); - - return this; - - } - - /** - * Disconnects the controller from the given XR input source. - * - * @param {XRInputSource} inputSource - The input source. - * @return {WebXRController} A reference to this instance. - */ - disconnect( inputSource ) { - - this.dispatchEvent( { type: 'disconnected', data: inputSource } ); - - if ( this._targetRay !== null ) { - - this._targetRay.visible = false; - - } - - if ( this._grip !== null ) { - - this._grip.visible = false; - - } - - if ( this._hand !== null ) { - - this._hand.visible = false; - - } - - return this; - - } - - /** - * Updates the controller with the given input source, XR frame and reference space. - * This updates the transformations of the groups that represent the different - * coordinate systems of the controller. - * - * @param {XRInputSource} inputSource - The input source. - * @param {XRFrame} frame - The XR frame. - * @param {XRReferenceSpace} referenceSpace - The reference space. - * @return {WebXRController} A reference to this instance. - */ - update( inputSource, frame, referenceSpace ) { - - let inputPose = null; - let gripPose = null; - let handPose = null; - - const targetRay = this._targetRay; - const grip = this._grip; - const hand = this._hand; - - if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { - - if ( hand && inputSource.hand ) { - - handPose = true; - - for ( const inputjoint of inputSource.hand.values() ) { - - // Update the joints groups with the XRJoint poses - const jointPose = frame.getJointPose( inputjoint, referenceSpace ); - - // The transform of this joint will be updated with the joint pose on each frame - const joint = this._getHandJoint( hand, inputjoint ); - - if ( jointPose !== null ) { - - joint.matrix.fromArray( jointPose.transform.matrix ); - joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); - joint.matrixWorldNeedsUpdate = true; - joint.jointRadius = jointPose.radius; - - } - - joint.visible = jointPose !== null; - - } - - // Custom events - - // Check pinchz - const indexTip = hand.joints[ 'index-finger-tip' ]; - const thumbTip = hand.joints[ 'thumb-tip' ]; - const distance = indexTip.position.distanceTo( thumbTip.position ); - - const distanceToPinch = 0.02; - const threshold = 0.005; - - if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { - - hand.inputState.pinching = false; - this.dispatchEvent( { - type: 'pinchend', - handedness: inputSource.handedness, - target: this - } ); - - } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { - - hand.inputState.pinching = true; - this.dispatchEvent( { - type: 'pinchstart', - handedness: inputSource.handedness, - target: this - } ); - - } - - } else { - - if ( grip !== null && inputSource.gripSpace ) { - - gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); - - if ( gripPose !== null ) { - - grip.matrix.fromArray( gripPose.transform.matrix ); - grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); - grip.matrixWorldNeedsUpdate = true; - - if ( gripPose.linearVelocity ) { - - grip.hasLinearVelocity = true; - grip.linearVelocity.copy( gripPose.linearVelocity ); - - } else { - - grip.hasLinearVelocity = false; - - } - - if ( gripPose.angularVelocity ) { - - grip.hasAngularVelocity = true; - grip.angularVelocity.copy( gripPose.angularVelocity ); - - } else { - - grip.hasAngularVelocity = false; - - } - - // grip update event if enabled - if ( grip.eventsEnabled ) { - - grip.dispatchEvent( { - type: 'gripUpdated', - data: inputSource, - target: this - } ); - - } - - } - - } - - } - - if ( targetRay !== null ) { - - inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); - - // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it - if ( inputPose === null && gripPose !== null ) { - - inputPose = gripPose; - - } - - if ( inputPose !== null ) { - - targetRay.matrix.fromArray( inputPose.transform.matrix ); - targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); - targetRay.matrixWorldNeedsUpdate = true; - - if ( inputPose.linearVelocity ) { - - targetRay.hasLinearVelocity = true; - targetRay.linearVelocity.copy( inputPose.linearVelocity ); - - } else { - - targetRay.hasLinearVelocity = false; - - } - - if ( inputPose.angularVelocity ) { - - targetRay.hasAngularVelocity = true; - targetRay.angularVelocity.copy( inputPose.angularVelocity ); - - } else { - - targetRay.hasAngularVelocity = false; - - } - - this.dispatchEvent( _moveEvent ); - - } - - } - - - } - - if ( targetRay !== null ) { - - targetRay.visible = ( inputPose !== null ); - - } - - if ( grip !== null ) { - - grip.visible = ( gripPose !== null ); - - } - - if ( hand !== null ) { - - hand.visible = ( handPose !== null ); - - } - - return this; - - } - - /** - * Returns a group representing the hand joint for the given input joint. - * - * @private - * @param {Group} hand - The group representing the hand space. - * @param {XRJointSpace} inputjoint - The hand joint data. - * @return {Group} A group representing the hand joint for the given input joint. - */ - _getHandJoint( hand, inputjoint ) { - - if ( hand.joints[ inputjoint.jointName ] === undefined ) { - - const joint = new Group$1(); - joint.matrixAutoUpdate = false; - joint.visible = false; - hand.joints[ inputjoint.jointName ] = joint; - - hand.add( joint ); - - } - - return hand.joints[ inputjoint.jointName ]; - - } - -} - -/** - * Represents a texture created externally with the same renderer context. - * - * This may be a texture from a protected media stream, device camera feed, - * or other data feeds like a depth sensor. - * - * @augments Texture - */ -class ExternalTexture extends Texture { - - /** - * Creates a new raw texture. - * - * @param {?(WebGLTexture|GPUTexture)} [sourceTexture=null] - The external texture. - */ - constructor( sourceTexture = null ) { - - super(); - - /** - * The external source texture. - * - * @type {?(WebGLTexture|GPUTexture)} - * @default null - */ - this.sourceTexture = sourceTexture; - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isExternalTexture = true; - - } - - copy( source ) { - - super.copy( source ); - - this.sourceTexture = source.sourceTexture; - - return this; - - } - -} - -const _occlusion_vertex = ` -void main() { - - gl_Position = vec4( position, 1.0 ); - -}`; - -const _occlusion_fragment = ` -uniform sampler2DArray depthColor; -uniform float depthWidth; -uniform float depthHeight; - -void main() { - - vec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight ); - - if ( coord.x >= 1.0 ) { - - gl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; - - } else { - - gl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; - - } - -}`; - -/** - * A XR module that manages the access to the Depth Sensing API. - */ -class WebXRDepthSensing { - - /** - * Constructs a new depth sensing module. - */ - constructor() { - - /** - * An opaque texture representing the depth of the user's environment. - * - * @type {?ExternalTexture} - */ - this.texture = null; - - /** - * A plane mesh for visualizing the depth texture. - * - * @type {?Mesh} - */ - this.mesh = null; - - /** - * The depth near value. - * - * @type {number} - */ - this.depthNear = 0; - - /** - * The depth near far. - * - * @type {number} - */ - this.depthFar = 0; - - } - - /** - * Inits the depth sensing module - * - * @param {XRWebGLDepthInformation} depthData - The XR depth data. - * @param {XRRenderState} renderState - The XR render state. - */ - init( depthData, renderState ) { - - if ( this.texture === null ) { - - const texture = new ExternalTexture( depthData.texture ); - - if ( ( depthData.depthNear !== renderState.depthNear ) || ( depthData.depthFar !== renderState.depthFar ) ) { - - this.depthNear = depthData.depthNear; - this.depthFar = depthData.depthFar; - - } - - this.texture = texture; - - } - - } - - /** - * Returns a plane mesh that visualizes the depth texture. - * - * @param {ArrayCamera} cameraXR - The XR camera. - * @return {?Mesh} The plane mesh. - */ - getMesh( cameraXR ) { - - if ( this.texture !== null ) { - - if ( this.mesh === null ) { - - const viewport = cameraXR.cameras[ 0 ].viewport; - const material = new ShaderMaterial( { - vertexShader: _occlusion_vertex, - fragmentShader: _occlusion_fragment, - uniforms: { - depthColor: { value: this.texture }, - depthWidth: { value: viewport.z }, - depthHeight: { value: viewport.w } - } - } ); - - this.mesh = new Mesh( new PlaneGeometry( 20, 20 ), material ); - - } - - } - - return this.mesh; - - } - - /** - * Resets the module - */ - reset() { - - this.texture = null; - this.mesh = null; - - } - - /** - * Returns a texture representing the depth of the user's environment. - * - * @return {?ExternalTexture} The depth texture. - */ - getDepthTexture() { - - return this.texture; - - } - -} - -/** - * This class represents an abstraction of the WebXR Device API and is - * internally used by {@link WebGLRenderer}. `WebXRManager` also provides a public - * interface that allows users to enable/disable XR and perform XR related - * tasks like for instance retrieving controllers. - * - * @augments EventDispatcher - * @hideconstructor - */ -class WebXRManager extends EventDispatcher { - - /** - * Constructs a new WebGL renderer. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGL2RenderingContext} gl - The rendering context. - */ - constructor( renderer, gl ) { - - super(); - - const scope = this; - - let session = null; - - let framebufferScaleFactor = 1.0; - - let referenceSpace = null; - let referenceSpaceType = 'local-floor'; - // Set default foveation to maximum. - let foveation = 1.0; - let customReferenceSpace = null; - - let pose = null; - let glBinding = null; - let glProjLayer = null; - let glBaseLayer = null; - let xrFrame = null; - - const supportsGlBinding = typeof XRWebGLBinding !== 'undefined'; - - const depthSensing = new WebXRDepthSensing(); - const cameraAccessTextures = {}; - const attributes = gl.getContextAttributes(); - - let initialRenderTarget = null; - let newRenderTarget = null; - - const controllers = []; - const controllerInputSources = []; - - const currentSize = new Vector2(); - let currentPixelRatio = null; - let currentCameraSettings = null; - - // - - const cameraL = new PerspectiveCamera(); - cameraL.viewport = new Vector4(); - - const cameraR = new PerspectiveCamera(); - cameraR.viewport = new Vector4(); - - const cameras = [ cameraL, cameraR ]; - - const cameraXR = new ArrayCamera(); - - let _currentDepthNear = null; - let _currentDepthFar = null; - - // - - /** - * Whether the manager's XR camera should be automatically updated or not. - * - * @type {boolean} - * @default true - */ - this.cameraAutoUpdate = true; - - /** - * This flag notifies the renderer to be ready for XR rendering. Set it to `true` - * if you are going to use XR in your app. - * - * @type {boolean} - * @default false - */ - this.enabled = false; - - /** - * Whether XR presentation is active or not. - * - * @type {boolean} - * @readonly - * @default false - */ - this.isPresenting = false; - - /** - * Returns a group representing the `target ray` space of the XR controller. - * Use this space for visualizing 3D objects that support the user in pointing - * tasks like UI interaction. - * - * @param {number} index - The index of the controller. - * @return {Group} A group representing the `target ray` space. - */ - this.getController = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getTargetRaySpace(); - - }; - - /** - * Returns a group representing the `grip` space of the XR controller. - * Use this space for visualizing 3D objects that support the user in pointing - * tasks like UI interaction. - * - * Note: If you want to show something in the user's hand AND offer a - * pointing ray at the same time, you'll want to attached the handheld object - * to the group returned by `getControllerGrip()` and the ray to the - * group returned by `getController()`. The idea is to have two - * different groups in two different coordinate spaces for the same WebXR - * controller. - * - * @param {number} index - The index of the controller. - * @return {Group} A group representing the `grip` space. - */ - this.getControllerGrip = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getGripSpace(); - - }; - - /** - * Returns a group representing the `hand` space of the XR controller. - * Use this space for visualizing 3D objects that support the user in pointing - * tasks like UI interaction. - * - * @param {number} index - The index of the controller. - * @return {Group} A group representing the `hand` space. - */ - this.getHand = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getHandSpace(); - - }; - - // - - function onSessionEvent( event ) { - - const controllerIndex = controllerInputSources.indexOf( event.inputSource ); - - if ( controllerIndex === -1 ) { - - return; - - } - - const controller = controllers[ controllerIndex ]; - - if ( controller !== undefined ) { - - controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); - controller.dispatchEvent( { type: event.type, data: event.inputSource } ); - - } - - } - - function onSessionEnd() { - - session.removeEventListener( 'select', onSessionEvent ); - session.removeEventListener( 'selectstart', onSessionEvent ); - session.removeEventListener( 'selectend', onSessionEvent ); - session.removeEventListener( 'squeeze', onSessionEvent ); - session.removeEventListener( 'squeezestart', onSessionEvent ); - session.removeEventListener( 'squeezeend', onSessionEvent ); - session.removeEventListener( 'end', onSessionEnd ); - session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); - - for ( let i = 0; i < controllers.length; i ++ ) { - - const inputSource = controllerInputSources[ i ]; - - if ( inputSource === null ) continue; - - controllerInputSources[ i ] = null; - - controllers[ i ].disconnect( inputSource ); - - } - - _currentDepthNear = null; - _currentDepthFar = null; - - depthSensing.reset(); - for ( const key in cameraAccessTextures ) { - - delete cameraAccessTextures[ key ]; - - } - - // restore framebuffer/rendering state - - renderer.setRenderTarget( initialRenderTarget ); - - glBaseLayer = null; - glProjLayer = null; - glBinding = null; - session = null; - newRenderTarget = null; - - // - - animation.stop(); - - scope.isPresenting = false; - - renderer.setPixelRatio( currentPixelRatio ); - renderer.setSize( currentSize.width, currentSize.height, false ); - - if ( currentCameraSettings !== null ) { - - const camera = currentCameraSettings.camera; - - camera.fov = currentCameraSettings.fov; - camera.zoom = currentCameraSettings.zoom; - camera.updateProjectionMatrix(); - - currentCameraSettings = null; - - } - - scope.dispatchEvent( { type: 'sessionend' } ); - - } - - /** - * Sets the framebuffer scale factor. - * - * This method can not be used during a XR session. - * - * @param {number} value - The framebuffer scale factor. - */ - this.setFramebufferScaleFactor = function ( value ) { - - framebufferScaleFactor = value; - - if ( scope.isPresenting === true ) { - - warn( 'WebXRManager: Cannot change framebuffer scale while presenting.' ); - - } - - }; - - /** - * Sets the reference space type. Can be used to configure a spatial relationship with the user's physical - * environment. Depending on how the user moves in 3D space, setting an appropriate reference space can - * improve tracking. Default is `local-floor`. Valid values can be found here - * https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace#reference_space_types. - * - * This method can not be used during a XR session. - * - * @param {string} value - The reference space type. - */ - this.setReferenceSpaceType = function ( value ) { - - referenceSpaceType = value; - - if ( scope.isPresenting === true ) { - - warn( 'WebXRManager: Cannot change reference space type while presenting.' ); - - } - - }; - - /** - * Returns the XR reference space. - * - * @return {XRReferenceSpace} The XR reference space. - */ - this.getReferenceSpace = function () { - - return customReferenceSpace || referenceSpace; - - }; - - /** - * Sets a custom XR reference space. - * - * @param {XRReferenceSpace} space - The XR reference space. - */ - this.setReferenceSpace = function ( space ) { - - customReferenceSpace = space; - - }; - - /** - * Returns the current base layer. - * - * This is an `XRProjectionLayer` when the targeted XR device supports the - * WebXR Layers API, or an `XRWebGLLayer` otherwise. - * - * @return {?(XRWebGLLayer|XRProjectionLayer)} The XR base layer. - */ - this.getBaseLayer = function () { - - return glProjLayer !== null ? glProjLayer : glBaseLayer; - - }; - - /** - * Returns the current XR binding. - * - * Creates a new binding if needed and the browser is - * capable of doing so. - * - * @return {?XRWebGLBinding} The XR binding. Returns `null` if one cannot be created. - */ - this.getBinding = function () { - - if ( glBinding === null && supportsGlBinding ) { - - glBinding = new XRWebGLBinding( session, gl ); - - } - - return glBinding; - - }; - - /** - * Returns the current XR frame. - * - * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session. - */ - this.getFrame = function () { - - return xrFrame; - - }; - - /** - * Returns the current XR session. - * - * @return {?XRSession} The XR session. Returns `null` when used outside a XR session. - */ - this.getSession = function () { - - return session; - - }; - - /** - * After a XR session has been requested usually with one of the `*Button` modules, it - * is injected into the renderer with this method. This method triggers the start of - * the actual XR rendering. - * - * @async - * @param {XRSession} value - The XR session to set. - * @return {Promise} A Promise that resolves when the session has been set. - */ - this.setSession = async function ( value ) { - - session = value; - - if ( session !== null ) { - - initialRenderTarget = renderer.getRenderTarget(); - - session.addEventListener( 'select', onSessionEvent ); - session.addEventListener( 'selectstart', onSessionEvent ); - session.addEventListener( 'selectend', onSessionEvent ); - session.addEventListener( 'squeeze', onSessionEvent ); - session.addEventListener( 'squeezestart', onSessionEvent ); - session.addEventListener( 'squeezeend', onSessionEvent ); - session.addEventListener( 'end', onSessionEnd ); - session.addEventListener( 'inputsourceschange', onInputSourcesChange ); - - if ( attributes.xrCompatible !== true ) { - - await gl.makeXRCompatible(); - - } - - currentPixelRatio = renderer.getPixelRatio(); - renderer.getSize( currentSize ); - - - // Check that the browser implements the necessary APIs to use an - // XRProjectionLayer rather than an XRWebGLLayer - const supportsLayers = supportsGlBinding && 'createProjectionLayer' in XRWebGLBinding.prototype; - - if ( ! supportsLayers ) { - - const layerInit = { - antialias: attributes.antialias, - alpha: true, - depth: attributes.depth, - stencil: attributes.stencil, - framebufferScaleFactor: framebufferScaleFactor - }; - - glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); - - session.updateRenderState( { baseLayer: glBaseLayer } ); - - renderer.setPixelRatio( 1 ); - renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false ); - - newRenderTarget = new WebGLRenderTarget( - glBaseLayer.framebufferWidth, - glBaseLayer.framebufferHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - colorSpace: renderer.outputColorSpace, - stencilBuffer: attributes.stencil, - resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), - resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ), - storeMultisampledDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), - storeMultisampledStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ) - - } - ); - - } else { - - let depthFormat = null; - let depthType = null; - let glDepthFormat = null; - - if ( attributes.depth ) { - - glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; - depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; - depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; - - } - - const projectionlayerInit = { - colorFormat: gl.RGBA8, - depthFormat: glDepthFormat, - scaleFactor: framebufferScaleFactor - }; - - glBinding = this.getBinding(); - - glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - - session.updateRenderState( { layers: [ glProjLayer ] } ); - - renderer.setPixelRatio( 1 ); - renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false ); - - newRenderTarget = new WebGLRenderTarget( - glProjLayer.textureWidth, - glProjLayer.textureHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), - stencilBuffer: attributes.stencil, - colorSpace: renderer.outputColorSpace, - samples: attributes.antialias ? 4 : 0, - resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), - resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ), - storeMultisampledDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), - storeMultisampledStencilBuffer: ( glProjLayer.ignoreDepthValues === false ) - } ); - - } - - newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 - - this.setFoveation( foveation ); - - customReferenceSpace = null; - referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); - - animation.setContext( session ); - animation.start(); - - scope.isPresenting = true; - - scope.dispatchEvent( { type: 'sessionstart' } ); - - } - - }; - - /** - * Returns the environment blend mode from the current XR session. - * - * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session. - */ - this.getEnvironmentBlendMode = function () { - - if ( session !== null ) { - - return session.environmentBlendMode; - - } - - }; - - /** - * Returns the current depth texture computed via depth sensing. - * - * See {@link WebXRDepthSensing#getDepthTexture}. - * - * @return {?Texture} The depth texture. - */ - this.getDepthTexture = function () { - - return depthSensing.getDepthTexture(); - - }; - - function onInputSourcesChange( event ) { - - // Notify disconnected - - for ( let i = 0; i < event.removed.length; i ++ ) { - - const inputSource = event.removed[ i ]; - const index = controllerInputSources.indexOf( inputSource ); - - if ( index >= 0 ) { - - controllerInputSources[ index ] = null; - controllers[ index ].disconnect( inputSource ); - - } - - } - - // Notify connected - - for ( let i = 0; i < event.added.length; i ++ ) { - - const inputSource = event.added[ i ]; - - let controllerIndex = controllerInputSources.indexOf( inputSource ); - - if ( controllerIndex === -1 ) { - - // Assign input source a controller that currently has no input source - - for ( let i = 0; i < controllers.length; i ++ ) { - - if ( i >= controllerInputSources.length ) { - - controllerInputSources.push( inputSource ); - controllerIndex = i; - break; - - } else if ( controllerInputSources[ i ] === null ) { - - controllerInputSources[ i ] = inputSource; - controllerIndex = i; - break; - - } - - } - - // If all controllers do currently receive input we ignore new ones - - if ( controllerIndex === -1 ) break; - - } - - const controller = controllers[ controllerIndex ]; - - if ( controller ) { - - controller.connect( inputSource ); - - } - - } - - } - - // - - const cameraLPos = new Vector3(); - const cameraRPos = new Vector3(); - - /** - * Assumes 2 cameras that are parallel and share an X-axis, and that - * the cameras' projection and world matrices have already been set. - * And that near and far planes are identical for both cameras. - * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 - * - * @param {ArrayCamera} camera - The camera to update. - * @param {PerspectiveCamera} cameraL - The left camera. - * @param {PerspectiveCamera} cameraR - The right camera. - */ - function setProjectionFromUnion( camera, cameraL, cameraR ) { - - cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); - cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); - - const ipd = cameraLPos.distanceTo( cameraRPos ); - - const projL = cameraL.projectionMatrix.elements; - const projR = cameraR.projectionMatrix.elements; - - // VR systems will have identical far and near planes, and - // most likely identical top and bottom frustum extents. - // Use the left camera for these values. - const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); - const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); - const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; - const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; - - const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; - const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; - const left = near * leftFov; - const right = near * rightFov; - - // Calculate the new camera's position offset from the - // left camera. xOffset should be roughly half `ipd`. - const zOffset = ipd / ( - leftFov + rightFov ); - const xOffset = zOffset * - leftFov; - - // TODO: Better way to apply this offset? - cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); - camera.translateX( xOffset ); - camera.translateZ( zOffset ); - camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - - // Check if the projection uses an infinite far plane. - if ( projL[ 10 ] === -1 ) { - - // Use the projection matrix from the left eye. - // The camera offset is sufficient to include the view volumes - // of both eyes (assuming symmetric projections). - camera.projectionMatrix.copy( cameraL.projectionMatrix ); - camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse ); - - } else { - - // Find the union of the frustum values of the cameras and scale - // the values so that the near plane's position does not change in world space, - // although must now be relative to the new union camera. - const near2 = near + zOffset; - const far2 = far + zOffset; - const left2 = left - xOffset; - const right2 = right + ( ipd - xOffset ); - const top2 = topFov * far / far2 * near2; - const bottom2 = bottomFov * far / far2 * near2; - - camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - - } - - } - - function updateCamera( camera, parent ) { - - if ( parent === null ) { - - camera.matrixWorld.copy( camera.matrix ); - - } else { - - camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); - - } - - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - - } - - /** - * Updates the state of the XR camera. Use this method on app level if you - * set `cameraAutoUpdate` to `false`. The method requires the non-XR - * camera of the scene as a parameter. The passed in camera's transformation - * is automatically adjusted to the position of the XR camera when calling - * this method. - * - * @param {Camera} camera - The camera. - */ - this.updateCamera = function ( camera ) { - - if ( session === null ) return; - - let depthNear = camera.near; - let depthFar = camera.far; - - if ( depthSensing.texture !== null ) { - - if ( depthSensing.depthNear > 0 ) depthNear = depthSensing.depthNear; - if ( depthSensing.depthFar > 0 ) depthFar = depthSensing.depthFar; - - } - - cameraXR.near = cameraR.near = cameraL.near = depthNear; - cameraXR.far = cameraR.far = cameraL.far = depthFar; - - if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { - - // Note that the new renderState won't apply until the next frame. See #18320 - - session.updateRenderState( { - depthNear: cameraXR.near, - depthFar: cameraXR.far - } ); - - _currentDepthNear = cameraXR.near; - _currentDepthFar = cameraXR.far; - - } - - // inherit camera layers and enable eye layers (1 = left, 2 = right) - cameraXR.layers.mask = camera.layers.mask | 0b110; - cameraL.layers.mask = cameraXR.layers.mask & -5; - cameraR.layers.mask = cameraXR.layers.mask & -3; - - const parent = camera.parent; - const cameras = cameraXR.cameras; - - updateCamera( cameraXR, parent ); - - for ( let i = 0; i < cameras.length; i ++ ) { - - updateCamera( cameras[ i ], parent ); - - } - - // update projection matrix for proper view frustum culling - - if ( cameras.length === 2 ) { - - setProjectionFromUnion( cameraXR, cameraL, cameraR ); - - } else { - - // assume single camera setup (AR) - - cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); - - } - - // update user camera and its children - - if ( currentCameraSettings === null && camera.isPerspectiveCamera ) { - - currentCameraSettings = { camera: camera, fov: camera.fov, zoom: camera.zoom }; - - } - - updateUserCamera( camera, cameraXR, parent ); - - }; - - function updateUserCamera( camera, cameraXR, parent ) { - - if ( parent === null ) { - - camera.matrix.copy( cameraXR.matrixWorld ); - - } else { - - camera.matrix.copy( parent.matrixWorld ); - camera.matrix.invert(); - camera.matrix.multiply( cameraXR.matrixWorld ); - - } - - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.updateMatrixWorld( true ); - - camera.projectionMatrix.copy( cameraXR.projectionMatrix ); - camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); - - if ( camera.isPerspectiveCamera ) { - - camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); - camera.zoom = 1; - - } - - } - - /** - * Returns an instance of {@link ArrayCamera} which represents the XR camera - * of the active XR session. For each view it holds a separate camera object. - * - * The camera's `fov` is currently not used and does not reflect the fov of - * the XR camera. If you need the fov on app level, you have to compute in - * manually from the XR camera's projection matrices. - * - * @return {ArrayCamera} The XR camera. - */ - this.getCamera = function () { - - return cameraXR; - - }; - - /** - * Returns the amount of foveation used by the XR compositor for the projection layer. - * - * @return {number|undefined} The amount of foveation. - */ - this.getFoveation = function () { - - if ( glProjLayer === null && glBaseLayer === null ) { - - return undefined; - - } - - return foveation; - - }; - - /** - * Sets the foveation value. - * - * @param {number} value - A number in the range `[0,1]` where `0` means no foveation (full resolution) - * and `1` means maximum foveation (the edges render at lower resolution). - */ - this.setFoveation = function ( value ) { - - // 0 = no foveation = full resolution - // 1 = maximum foveation = the edges render at lower resolution - - foveation = value; - - if ( glProjLayer !== null ) { - - glProjLayer.fixedFoveation = value; - - } - - if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { - - glBaseLayer.fixedFoveation = value; - - } - - }; - - /** - * Returns `true` if depth sensing is supported. - * - * @return {boolean} Whether depth sensing is supported or not. - */ - this.hasDepthSensing = function () { - - return depthSensing.texture !== null; - - }; - - /** - * Returns the depth sensing mesh. - * - * See {@link WebXRDepthSensing#getMesh}. - * - * @return {Mesh} The depth sensing mesh. - */ - this.getDepthSensingMesh = function () { - - return depthSensing.getMesh( cameraXR ); - - }; - - /** - * Retrieves an opaque texture from the view-aligned {@link XRCamera}. - * Only available during the current animation loop. - * - * @param {XRCamera} xrCamera - The camera to query. - * @return {?Texture} An opaque texture representing the current raw camera frame. - */ - this.getCameraTexture = function ( xrCamera ) { - - return cameraAccessTextures[ xrCamera ]; - - }; - - // Animation Loop - - let onAnimationFrameCallback = null; - - function onAnimationFrame( time, frame ) { - - pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); - xrFrame = frame; - - if ( pose !== null ) { - - const views = pose.views; - - if ( glBaseLayer !== null ) { - - renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); - renderer.setRenderTarget( newRenderTarget ); - - } - - let cameraXRNeedsUpdate = false; - - // check if it's necessary to rebuild cameraXR's camera list - - if ( views.length !== cameraXR.cameras.length ) { - - cameraXR.cameras.length = 0; - cameraXRNeedsUpdate = true; - - } - - for ( let i = 0; i < views.length; i ++ ) { - - const view = views[ i ]; - - let viewport = null; - - if ( glBaseLayer !== null ) { - - viewport = glBaseLayer.getViewport( view ); - - } else { - - const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); - viewport = glSubImage.viewport; - - // For side-by-side projection, we only produce a single texture for both eyes. - if ( i === 0 ) { - - renderer.setRenderTargetTextures( - newRenderTarget, - glSubImage.colorTexture, - glSubImage.depthStencilTexture ); - - renderer.setRenderTarget( newRenderTarget ); - - } - - } - - let camera = cameras[ i ]; - - if ( camera === undefined ) { - - camera = new PerspectiveCamera(); - camera.layers.enable( i ); - camera.viewport = new Vector4(); - cameras[ i ] = camera; - - } - - camera.matrix.fromArray( view.transform.matrix ); - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.projectionMatrix.fromArray( view.projectionMatrix ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); - - if ( i === 0 ) { - - cameraXR.matrix.copy( camera.matrix ); - cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); - - } - - if ( cameraXRNeedsUpdate === true ) { - - cameraXR.cameras.push( camera ); - - } - - } - - // - - const enabledFeatures = session.enabledFeatures; - const gpuDepthSensingEnabled = enabledFeatures && - enabledFeatures.includes( 'depth-sensing' ) && - session.depthUsage == 'gpu-optimized'; - - if ( gpuDepthSensingEnabled && supportsGlBinding ) { - - glBinding = scope.getBinding(); - - const depthData = glBinding.getDepthInformation( views[ 0 ] ); - - if ( depthData && depthData.isValid && depthData.texture ) { - - depthSensing.init( depthData, session.renderState ); - - } - - } - - const cameraAccessEnabled = enabledFeatures && - enabledFeatures.includes( 'camera-access' ); - - if ( cameraAccessEnabled && supportsGlBinding ) { - - renderer.state.unbindTexture(); - - glBinding = scope.getBinding(); - - for ( let i = 0; i < views.length; i ++ ) { - - const camera = views[ i ].camera; - - if ( camera ) { - - let cameraTex = cameraAccessTextures[ camera ]; - - if ( ! cameraTex ) { - - cameraTex = new ExternalTexture(); - cameraAccessTextures[ camera ] = cameraTex; - - } - - const glTexture = glBinding.getCameraImage( camera ); - cameraTex.sourceTexture = glTexture; - - } - - } - - } - - } - - // - - for ( let i = 0; i < controllers.length; i ++ ) { - - const inputSource = controllerInputSources[ i ]; - const controller = controllers[ i ]; - - if ( inputSource !== null && controller !== undefined ) { - - controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); - - } - - } - - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - - if ( frame.detectedPlanes ) { - - scope.dispatchEvent( { type: 'planesdetected', data: frame } ); - - } - - xrFrame = null; - - } - - const animation = new WebGLAnimation(); - - animation.setAnimationLoop( onAnimationFrame ); - - this.setAnimationLoop = function ( callback ) { - - onAnimationFrameCallback = callback; - - }; - - this.dispose = function () {}; - - } - -} - -const _m1 = /*@__PURE__*/ new Matrix4(); -const _m = /*@__PURE__*/ new Matrix3(); - -_m.set( -1, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ); - -function WebGLMaterials( renderer, properties ) { - - function refreshTransformUniform( map, uniform ) { - - if ( map.matrixAutoUpdate === true ) { - - map.updateMatrix(); - - } - - uniform.value.copy( map.matrix ); - - } - - function refreshFogUniforms( uniforms, fog ) { - - fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); - - if ( fog.isFog ) { - - uniforms.fogNear.value = fog.near; - uniforms.fogFar.value = fog.far; - - } else if ( fog.isFogExp2 ) { - - uniforms.fogDensity.value = fog.density; - - } - - } - - function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { - - if ( material.isNodeMaterial ) { - - material.uniformsNeedUpdate = false; - - } else if ( material.isMeshBasicMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshLambertMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - if ( material.envMap ) { - - uniforms.envMapIntensity.value = material.envMapIntensity; - - } - - } else if ( material.isMeshToonMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsToon( uniforms, material ); - - } else if ( material.isMeshPhongMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsPhong( uniforms, material ); - - if ( material.envMap ) { - - uniforms.envMapIntensity.value = material.envMapIntensity; - - } - - } else if ( material.isMeshStandardMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsStandard( uniforms, material ); - - if ( material.isMeshPhysicalMaterial ) { - - refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); - - } - - } else if ( material.isMeshMatcapMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsMatcap( uniforms, material ); - - } else if ( material.isMeshDepthMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshDistanceMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsDistance( uniforms, material ); - - } else if ( material.isMeshNormalMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isLineBasicMaterial ) { - - refreshUniformsLine( uniforms, material ); - - if ( material.isLineDashedMaterial ) { - - refreshUniformsDash( uniforms, material ); - - } - - } else if ( material.isPointsMaterial ) { - - refreshUniformsPoints( uniforms, material, pixelRatio, height ); - - } else if ( material.isSpriteMaterial ) { - - refreshUniformsSprites( uniforms, material ); - - } else if ( material.isShadowMaterial ) { - - uniforms.color.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - - } else if ( material.isShaderMaterial ) { - - material.uniformsNeedUpdate = false; // #15581 - - } - - } - - function refreshUniformsCommon( uniforms, material ) { - - uniforms.opacity.value = material.opacity; - - if ( material.color ) { - - uniforms.diffuse.value.copy( material.color ); - - } - - if ( material.emissive ) { - - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); - - } - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - - } - - if ( material.bumpMap ) { - - uniforms.bumpMap.value = material.bumpMap; - - refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); - - uniforms.bumpScale.value = material.bumpScale; - - if ( material.side === BackSide ) { - - uniforms.bumpScale.value *= -1; - - } - - } - - if ( material.normalMap ) { - - uniforms.normalMap.value = material.normalMap; - - refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); - - uniforms.normalScale.value.copy( material.normalScale ); - - if ( material.side === BackSide ) { - - uniforms.normalScale.value.negate(); - - } - - } - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - - refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); - - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - if ( material.emissiveMap ) { - - uniforms.emissiveMap.value = material.emissiveMap; - - refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); - - } - - if ( material.specularMap ) { - - uniforms.specularMap.value = material.specularMap; - - refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - const materialProperties = properties.get( material ); - - const envMap = materialProperties.envMap; - const envMapRotation = materialProperties.envMapRotation; - - if ( envMap ) { - - uniforms.envMap.value = envMap; - - // note: since the matrix is orthonormal, we can use the more-efficient transpose() in lieu of invert() - uniforms.envMapRotation.value.setFromMatrix4( _m1.makeRotationFromEuler( envMapRotation ) ).transpose(); - - - if ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) { - - uniforms.envMapRotation.value.premultiply( _m ); - - } - - uniforms.reflectivity.value = material.reflectivity; - uniforms.ior.value = material.ior; - uniforms.refractionRatio.value = material.refractionRatio; - - } - - if ( material.lightMap ) { - - uniforms.lightMap.value = material.lightMap; - uniforms.lightMapIntensity.value = material.lightMapIntensity; - - refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); - - } - - if ( material.aoMap ) { - - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; - - refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); - - } - - } - - function refreshUniformsLine( uniforms, material ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - } - - function refreshUniformsDash( uniforms, material ) { - - uniforms.dashSize.value = material.dashSize; - uniforms.totalSize.value = material.dashSize + material.gapSize; - uniforms.scale.value = material.scale; - - } - - function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.size.value = material.size * pixelRatio; - uniforms.scale.value = height * 0.5; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.uvTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - } - - function refreshUniformsSprites( uniforms, material ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.rotation.value = material.rotation; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - } - - function refreshUniformsPhong( uniforms, material ) { - - uniforms.specular.value.copy( material.specular ); - uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) - - } - - function refreshUniformsToon( uniforms, material ) { - - if ( material.gradientMap ) { - - uniforms.gradientMap.value = material.gradientMap; - - } - - } - - function refreshUniformsStandard( uniforms, material ) { - - uniforms.metalness.value = material.metalness; - - if ( material.metalnessMap ) { - - uniforms.metalnessMap.value = material.metalnessMap; - - refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); - - } - - uniforms.roughness.value = material.roughness; - - if ( material.roughnessMap ) { - - uniforms.roughnessMap.value = material.roughnessMap; - - refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); - - } - - if ( material.envMap ) { - - //uniforms.envMap.value = material.envMap; // part of uniforms common - - uniforms.envMapIntensity.value = material.envMapIntensity; - - } - - } - - function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { - - uniforms.ior.value = material.ior; // also part of uniforms common - - if ( material.sheen > 0 ) { - - uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); - - uniforms.sheenRoughness.value = material.sheenRoughness; - - if ( material.sheenColorMap ) { - - uniforms.sheenColorMap.value = material.sheenColorMap; - - refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); - - } - - if ( material.sheenRoughnessMap ) { - - uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; - - refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); - - } - - } - - if ( material.clearcoat > 0 ) { - - uniforms.clearcoat.value = material.clearcoat; - uniforms.clearcoatRoughness.value = material.clearcoatRoughness; - - if ( material.clearcoatMap ) { - - uniforms.clearcoatMap.value = material.clearcoatMap; - - refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); - - } - - if ( material.clearcoatRoughnessMap ) { - - uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; - - refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); - - } - - if ( material.clearcoatNormalMap ) { - - uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; - - refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); - - uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); - - if ( material.side === BackSide ) { - - uniforms.clearcoatNormalScale.value.negate(); - - } - - } - - } - - if ( material.dispersion > 0 ) { - - uniforms.dispersion.value = material.dispersion; - - } - - if ( material.retroreflectivity > 0 ) { - - uniforms.retroreflectivity.value = material.retroreflectivity; - - } - - if ( material.iridescence > 0 ) { - - uniforms.iridescence.value = material.iridescence; - uniforms.iridescenceIOR.value = material.iridescenceIOR; - uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; - uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; - - if ( material.iridescenceMap ) { - - uniforms.iridescenceMap.value = material.iridescenceMap; - - refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); - - } - - if ( material.iridescenceThicknessMap ) { - - uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; - - refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); - - } - - } - - if ( material.transmission > 0 ) { - - uniforms.transmission.value = material.transmission; - uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; - uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); - - if ( material.transmissionMap ) { - - uniforms.transmissionMap.value = material.transmissionMap; - - refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); - - } - - uniforms.thickness.value = material.thickness; - - if ( material.thicknessMap ) { - - uniforms.thicknessMap.value = material.thicknessMap; - - refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); - - } - - uniforms.attenuationDistance.value = material.attenuationDistance; - uniforms.attenuationColor.value.copy( material.attenuationColor ); - - } - - if ( material.anisotropy > 0 ) { - - uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); - - if ( material.anisotropyMap ) { - - uniforms.anisotropyMap.value = material.anisotropyMap; - - refreshTransformUniform( material.anisotropyMap, uniforms.anisotropyMapTransform ); - - } - - } - - uniforms.specularIntensity.value = material.specularIntensity; - uniforms.specularColor.value.copy( material.specularColor ); - - if ( material.specularColorMap ) { - - uniforms.specularColorMap.value = material.specularColorMap; - - refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); - - } - - if ( material.specularIntensityMap ) { - - uniforms.specularIntensityMap.value = material.specularIntensityMap; - - refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); - - } - - } - - function refreshUniformsMatcap( uniforms, material ) { - - if ( material.matcap ) { - - uniforms.matcap.value = material.matcap; - - } - - } - - function refreshUniformsDistance( uniforms, material ) { - - const light = properties.get( material ).light; - - uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); - uniforms.nearDistance.value = light.shadow.camera.near; - uniforms.farDistance.value = light.shadow.camera.far; - - } - - return { - refreshFogUniforms: refreshFogUniforms, - refreshMaterialUniforms: refreshMaterialUniforms - }; - -} - -function WebGLUniformsGroups( gl, info, capabilities, state ) { - - let buffers = {}; - let updateList = {}; - let allocatedBindingPoints = []; - - const maxBindingPoints = gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ); // binding points are global whereas block indices are per shader program - - function bind( uniformsGroup, program ) { - - const webglProgram = program.program; - state.uniformBlockBinding( uniformsGroup, webglProgram ); - - } - - function update( uniformsGroup, program ) { - - let buffer = buffers[ uniformsGroup.id ]; - - if ( buffer === undefined ) { - - prepareUniformsGroup( uniformsGroup ); - - buffer = createBuffer( uniformsGroup ); - buffers[ uniformsGroup.id ] = buffer; - - uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); - - } - - // ensure to update the binding points/block indices mapping for this program - - const webglProgram = program.program; - state.updateUBOMapping( uniformsGroup, webglProgram ); - - // update UBO once per frame - - const frame = info.render.frame; - - if ( updateList[ uniformsGroup.id ] !== frame ) { - - updateBufferData( uniformsGroup ); - - updateList[ uniformsGroup.id ] = frame; - - } - - } - - function createBuffer( uniformsGroup ) { - - // the setup of an UBO is independent of a particular shader program but global - - const bindingPointIndex = allocateBindingPointIndex(); - uniformsGroup.__bindingPointIndex = bindingPointIndex; - - const buffer = gl.createBuffer(); - const size = uniformsGroup.__size; - const usage = uniformsGroup.usage; - - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); - - return buffer; - - } - - function allocateBindingPointIndex() { - - for ( let i = 0; i < maxBindingPoints; i ++ ) { - - if ( allocatedBindingPoints.indexOf( i ) === -1 ) { - - allocatedBindingPoints.push( i ); - return i; - - } - - } - - error( 'WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); - - return 0; - - } - - function updateBufferData( uniformsGroup ) { - - const buffer = buffers[ uniformsGroup.id ]; - const uniforms = uniformsGroup.uniforms; - const cache = uniformsGroup.__cache; - - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - - for ( let i = 0, il = uniforms.length; i < il; i ++ ) { - - const uniformItem = uniforms[ i ]; - - if ( Array.isArray( uniformItem ) ) { - - for ( let j = 0, jl = uniformItem.length; j < jl; j ++ ) { - - updateUniform( uniformItem[ j ], i, j, cache ); - - } - - } else { - - updateUniform( uniformItem, i, 0, cache ); - - } - - } - - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - - } - - function updateUniform( uniform, index, indexArray, cache ) { - - if ( hasUniformChanged( uniform, index, indexArray, cache ) === true ) { - - const offset = uniform.__offset; - const value = uniform.value; - - if ( Array.isArray( value ) ) { - - let arrayOffset = 0; - - for ( let k = 0; k < value.length; k ++ ) { - - const val = value[ k ]; - const info = getUniformSize( val ); - - writeUniformValue( val, uniform.__data, arrayOffset ); - - // only toArray() values advance arrayOffset - if ( typeof val !== 'number' && typeof val !== 'boolean' && ! val.isMatrix3 && ! ArrayBuffer.isView( val ) ) { - - arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; - - } - - } - - } else { - - writeUniformValue( value, uniform.__data, 0 ); - - } - - gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); - - } - - } - - function writeUniformValue( value, data, offset ) { - - // TODO add integer and struct support - if ( typeof value === 'number' || typeof value === 'boolean' ) { - - data[ 0 ] = value; - - } else if ( value.isMatrix3 ) { - - // manually converting 3x3 to 3x4 - - data[ 0 ] = value.elements[ 0 ]; - data[ 1 ] = value.elements[ 1 ]; - data[ 2 ] = value.elements[ 2 ]; - data[ 3 ] = 0; - data[ 4 ] = value.elements[ 3 ]; - data[ 5 ] = value.elements[ 4 ]; - data[ 6 ] = value.elements[ 5 ]; - data[ 7 ] = 0; - data[ 8 ] = value.elements[ 6 ]; - data[ 9 ] = value.elements[ 7 ]; - data[ 10 ] = value.elements[ 8 ]; - data[ 11 ] = 0; - - } else if ( ArrayBuffer.isView( value ) ) { - - // copy the buffer data using "set" - data.set( new value.constructor( value.buffer, value.byteOffset, data.length ) ); - - } else { - - value.toArray( data, offset ); - - } - - } - - function hasUniformChanged( uniform, index, indexArray, cache ) { - - const value = uniform.value; - const indexString = index + '_' + indexArray; - - if ( cache[ indexString ] === undefined ) { - - // cache entry does not exist so far - - if ( typeof value === 'number' || typeof value === 'boolean' ) { - - cache[ indexString ] = value; - - } else if ( ArrayBuffer.isView( value ) ) { - - cache[ indexString ] = value.slice(); - - } else { - - cache[ indexString ] = value.clone(); - - } - - return true; - - } else { - - const cachedObject = cache[ indexString ]; - - // compare current value with cached entry - - if ( typeof value === 'number' || typeof value === 'boolean' ) { - - if ( cachedObject !== value ) { - - cache[ indexString ] = value; - return true; - - } - - } else if ( ArrayBuffer.isView( value ) ) { - - // always update the array buffers - return true; - - } else { - - if ( cachedObject.equals( value ) === false ) { - - cachedObject.copy( value ); - return true; - - } - - } - - } - - return false; - - } - - function prepareUniformsGroup( uniformsGroup ) { - - // determine total buffer size according to the STD140 layout - // Hint: STD140 is the only supported layout in WebGL 2 - - const uniforms = uniformsGroup.uniforms; - - let offset = 0; // global buffer offset in bytes - const chunkSize = 16; // size of a chunk in bytes - - for ( let i = 0, l = uniforms.length; i < l; i ++ ) { - - const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; - - for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { - - const uniform = uniformArray[ j ]; - - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - - for ( let k = 0, kl = values.length; k < kl; k ++ ) { - - const value = values[ k ]; - - const info = getUniformSize( value ); - - const chunkOffset = offset % chunkSize; // offset in the current chunk - const chunkPadding = chunkOffset % info.boundary; // required padding to match boundary - const chunkStart = chunkOffset + chunkPadding; // the start position in the current chunk for the data - - offset += chunkPadding; - - // Check for chunk overflow - if ( chunkStart !== 0 && ( chunkSize - chunkStart ) < info.storage ) { - - // Add padding and adjust offset - offset += ( chunkSize - chunkStart ); - - } - - // the following two properties will be used for partial buffer updates - uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); - uniform.__offset = offset; - - // Update the global offset - offset += info.storage; - - } - - } - - } - - // ensure correct final padding - - const chunkOffset = offset % chunkSize; - - if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); - - // - - uniformsGroup.__size = offset; - uniformsGroup.__cache = {}; - - return this; - - } - - function getUniformSize( value ) { - - const info = { - boundary: 0, // bytes - storage: 0 // bytes - }; - - // determine sizes according to STD140 - - if ( typeof value === 'number' || typeof value === 'boolean' ) { - - // float/int/bool - - info.boundary = 4; - info.storage = 4; - - } else if ( value.isVector2 ) { - - // vec2 - - info.boundary = 8; - info.storage = 8; - - } else if ( value.isVector3 || value.isColor ) { - - // vec3 - - info.boundary = 16; - info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes - - } else if ( value.isVector4 ) { - - // vec4 - - info.boundary = 16; - info.storage = 16; - - } else if ( value.isMatrix3 ) { - - // mat3 (in STD140 a 3x3 matrix is represented as 3x4) - - info.boundary = 48; - info.storage = 48; - - } else if ( value.isMatrix4 ) { - - // mat4 - - info.boundary = 64; - info.storage = 64; - - } else if ( value.isTexture ) { - - warn( 'WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); - - } else if ( ArrayBuffer.isView( value ) ) { - - info.boundary = 16; - info.storage = value.byteLength; - - } else { - - warn( 'WebGLRenderer: Unsupported uniform value type.', value ); - - } - - return info; - - } - - function onUniformsGroupsDispose( event ) { - - const uniformsGroup = event.target; - - uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); - - const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); - allocatedBindingPoints.splice( index, 1 ); - - gl.deleteBuffer( buffers[ uniformsGroup.id ] ); - - delete buffers[ uniformsGroup.id ]; - delete updateList[ uniformsGroup.id ]; - - } - - function dispose() { - - for ( const id in buffers ) { - - gl.deleteBuffer( buffers[ id ] ); - - } - - allocatedBindingPoints = []; - buffers = {}; - updateList = {}; - - } - - return { - - bind: bind, - update: update, - - dispose: dispose - - }; - -} - -/** - * Creates a texture directly from raw buffer data. - * - * The interpretation of the data depends on type and format: If the type is - * `UnsignedByteType`, a `Uint8Array` will be useful for addressing the - * texel data. If the format is `RGBAFormat`, data needs four values for - * one texel; Red, Green, Blue and Alpha (typically the opacity). - * - * @augments Texture - */ -class DataTexture extends Texture { - - /** - * Constructs a new data texture. - * - * @param {?TypedArray} [data=null] - The buffer data. - * @param {number} [width=1] - The width of the texture. - * @param {number} [height=1] - The height of the texture. - * @param {number} [format=RGBAFormat] - The texture format. - * @param {number} [type=UnsignedByteType] - The texture type. - * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. - * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. - * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. - * @param {number} [magFilter=NearestFilter] - The mag filter value. - * @param {number} [minFilter=NearestFilter] - The min filter value. - * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. - * @param {string} [colorSpace=NoColorSpace] - The color space. - */ - constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { - - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isDataTexture = true; - - /** - * The image definition of a data texture. - * - * @type {{data:TypedArray,width:number,height:number}} - */ - this.image = { data: data, width: width, height: height }; - - /** - * Whether to generate mipmaps (if possible) for a texture. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.generateMipmaps = false; - - /** - * If set to `true`, the texture is flipped along the vertical axis when - * uploaded to the GPU. - * - * Overwritten and set to `false` by default. - * - * @type {boolean} - * @default false - */ - this.flipY = false; - - /** - * Specifies the alignment requirements for the start of each pixel row in memory. - * - * Overwritten and set to `1` by default. - * - * @type {boolean} - * @default 1 - */ - this.unpackAlignment = 1; - - } - -} - -/** - * Precomputed DFG LUT for physically based specular lighting, used by both - * image-based lighting and direct-light multi-scattering energy compensation - * Resolution: 16x16 - * Samples: 4096 per texel - * Format: RG16F (2 half floats per texel: scale, bias) - */ - - -const DATA = new Uint16Array( [ - 0x30b5, 0x3ad1, 0x314c, 0x3a4d, 0x33d2, 0x391c, 0x35ef, 0x3828, 0x37f3, 0x36a6, 0x38d1, 0x3539, 0x3979, 0x3410, 0x39f8, 0x3252, 0x3a53, 0x30f0, 0x3a94, 0x2fc9, 0x3abf, 0x2e35, 0x3ada, 0x2d05, 0x3ae8, 0x2c1f, 0x3aed, 0x2ae0, 0x3aea, 0x29d1, 0x3ae1, 0x28ff, - 0x3638, 0x38e4, 0x364a, 0x38ce, 0x3699, 0x385e, 0x374e, 0x372c, 0x3839, 0x35a4, 0x38dc, 0x3462, 0x396e, 0x32c4, 0x39de, 0x3134, 0x3a2b, 0x3003, 0x3a59, 0x2e3a, 0x3a6d, 0x2ce1, 0x3a6e, 0x2bba, 0x3a5f, 0x2a33, 0x3a49, 0x290a, 0x3a2d, 0x2826, 0x3a0a, 0x26e8, - 0x3894, 0x36d7, 0x3897, 0x36c9, 0x38a3, 0x3675, 0x38bc, 0x35ac, 0x38ee, 0x349c, 0x393e, 0x3332, 0x3997, 0x3186, 0x39e2, 0x3038, 0x3a13, 0x2e75, 0x3a29, 0x2cf5, 0x3a2d, 0x2bac, 0x3a21, 0x29ff, 0x3a04, 0x28bc, 0x39dc, 0x2790, 0x39ad, 0x261a, 0x3978, 0x24fa, - 0x39ac, 0x34a8, 0x39ac, 0x34a3, 0x39ae, 0x3480, 0x39ae, 0x3423, 0x39b1, 0x330e, 0x39c2, 0x31a9, 0x39e0, 0x3063, 0x39fc, 0x2eb5, 0x3a0c, 0x2d1d, 0x3a14, 0x2bcf, 0x3a07, 0x29ff, 0x39e9, 0x28a3, 0x39be, 0x273c, 0x3989, 0x25b3, 0x394a, 0x2488, 0x3907, 0x2345, - 0x3a77, 0x3223, 0x3a76, 0x321f, 0x3a73, 0x3204, 0x3a6a, 0x31b3, 0x3a58, 0x3114, 0x3a45, 0x303b, 0x3a34, 0x2eb6, 0x3a26, 0x2d31, 0x3a1e, 0x2bef, 0x3a0b, 0x2a0d, 0x39ec, 0x28a1, 0x39c0, 0x271b, 0x3987, 0x2580, 0x3944, 0x2449, 0x38fa, 0x22bd, 0x38ac, 0x2155, - 0x3b07, 0x2fca, 0x3b06, 0x2fca, 0x3b00, 0x2fb8, 0x3af4, 0x2f7c, 0x3adb, 0x2eea, 0x3ab4, 0x2e00, 0x3a85, 0x2cec, 0x3a5e, 0x2bc5, 0x3a36, 0x2a00, 0x3a0d, 0x2899, 0x39dc, 0x2707, 0x39a0, 0x2562, 0x395a, 0x2424, 0x390b, 0x2268, 0x38b7, 0x20fd, 0x385f, 0x1fd1, - 0x3b69, 0x2cb9, 0x3b68, 0x2cbb, 0x3b62, 0x2cbb, 0x3b56, 0x2cae, 0x3b3b, 0x2c78, 0x3b0d, 0x2c0a, 0x3acf, 0x2ae3, 0x3a92, 0x2998, 0x3a54, 0x2867, 0x3a17, 0x26d0, 0x39d3, 0x253c, 0x3989, 0x2402, 0x3935, 0x2226, 0x38dc, 0x20bd, 0x387d, 0x1f54, 0x381d, 0x1db3, - 0x3ba9, 0x296b, 0x3ba8, 0x296f, 0x3ba3, 0x297b, 0x3b98, 0x2987, 0x3b7f, 0x2976, 0x3b4e, 0x2927, 0x3b0e, 0x2895, 0x3ac2, 0x27b7, 0x3a73, 0x263b, 0x3a23, 0x24e7, 0x39d0, 0x239b, 0x3976, 0x21d9, 0x3917, 0x207e, 0x38b2, 0x1ee7, 0x384b, 0x1d53, 0x37c7, 0x1c1e, - 0x3bd2, 0x25cb, 0x3bd1, 0x25d3, 0x3bcd, 0x25f0, 0x3bc2, 0x261f, 0x3bad, 0x2645, 0x3b7d, 0x262d, 0x3b3e, 0x25c4, 0x3aec, 0x250f, 0x3a93, 0x243a, 0x3a32, 0x22ce, 0x39d0, 0x215b, 0x3969, 0x202a, 0x38fe, 0x1e6e, 0x388f, 0x1cf1, 0x381f, 0x1b9b, 0x3762, 0x19dd, - 0x3be9, 0x21ab, 0x3be9, 0x21b7, 0x3be5, 0x21e5, 0x3bdd, 0x2241, 0x3bc9, 0x22a7, 0x3ba0, 0x22ec, 0x3b62, 0x22cd, 0x3b0f, 0x2247, 0x3aae, 0x2175, 0x3a44, 0x2088, 0x39d4, 0x1f49, 0x3960, 0x1dbe, 0x38e9, 0x1c77, 0x3870, 0x1ae8, 0x37f1, 0x1953, 0x3708, 0x181b, - 0x3bf6, 0x1cea, 0x3bf6, 0x1cfb, 0x3bf3, 0x1d38, 0x3bec, 0x1dbd, 0x3bda, 0x1e7c, 0x3bb7, 0x1f25, 0x3b7d, 0x1f79, 0x3b2c, 0x1f4c, 0x3ac6, 0x1ea6, 0x3a55, 0x1dbb, 0x39da, 0x1cbd, 0x395a, 0x1b9d, 0x38d8, 0x1a00, 0x3855, 0x18ac, 0x37ab, 0x173c, 0x36b7, 0x1598, - 0x3bfc, 0x1736, 0x3bfc, 0x1759, 0x3bf9, 0x17e7, 0x3bf4, 0x1896, 0x3be4, 0x1997, 0x3bc6, 0x1aa8, 0x3b91, 0x1b84, 0x3b43, 0x1bd2, 0x3ade, 0x1b8a, 0x3a65, 0x1acd, 0x39e2, 0x19d3, 0x3957, 0x18cd, 0x38ca, 0x17b3, 0x383e, 0x1613, 0x376d, 0x14bf, 0x366f, 0x135e, - 0x3bff, 0x101b, 0x3bff, 0x1039, 0x3bfc, 0x10c8, 0x3bf9, 0x1226, 0x3bea, 0x1428, 0x3bcf, 0x1584, 0x3b9f, 0x16c5, 0x3b54, 0x179a, 0x3af0, 0x17ce, 0x3a76, 0x1771, 0x39ea, 0x16a4, 0x3956, 0x15a7, 0x38bf, 0x14a7, 0x3829, 0x1379, 0x3735, 0x11ea, 0x362d, 0x10a1, - 0x3c00, 0x061b, 0x3c00, 0x066a, 0x3bfe, 0x081c, 0x3bfa, 0x0a4c, 0x3bed, 0x0d16, 0x3bd5, 0x0fb3, 0x3ba9, 0x114d, 0x3b63, 0x127c, 0x3b01, 0x132f, 0x3a85, 0x1344, 0x39f4, 0x12d2, 0x3957, 0x120d, 0x38b5, 0x1122, 0x3817, 0x103c, 0x3703, 0x0ed3, 0x35f0, 0x0d6d, - 0x3c00, 0x007a, 0x3c00, 0x0089, 0x3bfe, 0x011d, 0x3bfb, 0x027c, 0x3bf0, 0x04fa, 0x3bda, 0x0881, 0x3bb1, 0x0acd, 0x3b6f, 0x0c97, 0x3b10, 0x0d7b, 0x3a93, 0x0df1, 0x39fe, 0x0def, 0x3959, 0x0d8a, 0x38af, 0x0ce9, 0x3808, 0x0c31, 0x36d5, 0x0af0, 0x35b9, 0x09a3, - 0x3c00, 0x0000, 0x3c00, 0x0001, 0x3bff, 0x0015, 0x3bfb, 0x0059, 0x3bf2, 0x00fd, 0x3bdd, 0x01df, 0x3bb7, 0x031c, 0x3b79, 0x047c, 0x3b1d, 0x05d4, 0x3aa0, 0x06d5, 0x3a08, 0x075a, 0x395d, 0x075e, 0x38aa, 0x06f7, 0x37f4, 0x0648, 0x36ac, 0x0576, 0x3586, 0x049f -] ); - -let lut = null; - -function getDFGLUT() { - - if ( lut === null ) { - - lut = new DataTexture( DATA, 16, 16, RGFormat, HalfFloatType ); - lut.name = 'DFG_LUT'; - lut.minFilter = LinearFilter; - lut.magFilter = LinearFilter; - lut.wrapS = ClampToEdgeWrapping; - lut.wrapT = ClampToEdgeWrapping; - lut.generateMipmaps = false; - lut.needsUpdate = true; - - } - - return lut; - -} - -/** - * This renderer uses WebGL 2 to display scenes. - * - * WebGL 1 is not supported since `r163`. - */ -class WebGLRenderer { - - /** - * Constructs a new WebGL renderer. - * - * @param {WebGLRenderer~Options} [parameters] - The configuration parameter. - */ - constructor( parameters = {} ) { - - const { - canvas = createCanvasElement(), - context = null, - depth = true, - stencil = false, - alpha = false, - antialias = false, - premultipliedAlpha = true, - preserveDrawingBuffer = false, - powerPreference = 'default', - failIfMajorPerformanceCaveat = false, - reversedDepthBuffer = false, - outputBufferType = UnsignedByteType, - } = parameters; - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isWebGLRenderer = true; - - let _alpha; - - if ( context !== null ) { - - if ( typeof WebGLRenderingContext !== 'undefined' && context instanceof WebGLRenderingContext ) { - - throw new Error( 'THREE.WebGLRenderer: WebGL 1 is not supported since r163.' ); - - } - - _alpha = context.getContextAttributes().alpha; - - } else { - - _alpha = alpha; - - } - - const _outputBufferType = outputBufferType; - - const INTEGER_FORMATS = new Set( [ - RGBAIntegerFormat, - RGIntegerFormat, - RedIntegerFormat - ] ); - - const UNSIGNED_TYPES = new Set( [ - UnsignedByteType, - UnsignedIntType, - UnsignedShortType, - UnsignedInt248Type, - UnsignedShort4444Type, - UnsignedShort5551Type - ] ); - - const uintClearColor = new Uint32Array( 4 ); - const intClearColor = new Int32Array( 4 ); - const objectPosition = new Vector3(); - - let currentRenderList = null; - let currentRenderState = null; - - // render() can be called from within a callback triggered by another render. - // We track this so that the nested render call gets its list and state isolated from the parent render call. - - const renderListStack = []; - const renderStateStack = []; - - // internal render target for non-UnsignedByteType color buffer - - let output = null; - - // public properties - - /** - * A canvas where the renderer draws its output. This is automatically created by the renderer - * in the constructor (if not provided already); you just need to add it to your page like so: - * ```js - * document.body.appendChild( renderer.domElement ); - * ``` - * - * @type {HTMLCanvasElement|OffscreenCanvas} - */ - this.domElement = canvas; - - /** - * A object with debug configuration settings. - * - * - `checkShaderErrors`: If it is `true`, defines whether material shader programs are - * checked for errors during compilation and linkage process. It may be useful to disable - * this check in production for performance gain. It is strongly recommended to keep these - * checks enabled during development. If the shader does not compile and link, it will not - * work and associated material will not render. - * - `onShaderError(gl, program, glVertexShader,glFragmentShader)`: A callback function that - * can be used for custom error reporting. The callback receives the WebGL context, an instance - * of WebGLProgram as well two instances of WebGLShader representing the vertex and fragment shader. - * Assigning a custom function disables the default error reporting. - * - * @type {Object} - */ - this.debug = { - - /** - * Enables error checking and reporting when shader programs are being compiled. - * @type {boolean} - */ - checkShaderErrors: true, - /** - * Diagnostics configuration for the shader generation. Only relevant for TSL. - * @type {Object} - * @property {boolean} keywords - Whether declaration names that collide with reserved keywords of the shading language should be renamed or not. - */ - diagnostics: { - keywords: false - }, - /** - * Callback for custom error reporting. - * @type {?Function} - */ - onShaderError: null - }; - - // clearing - - /** - * Whether the renderer should automatically clear its output before rendering a frame or not. - * - * @type {boolean} - * @default true - */ - this.autoClear = true; - - /** - * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear - * the color buffer or not. - * - * @type {boolean} - * @default true - */ - this.autoClearColor = true; - - /** - * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear - * the depth buffer or not. - * - * @type {boolean} - * @default true - */ - this.autoClearDepth = true; - - /** - * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear - * the stencil buffer or not. - * - * @type {boolean} - * @default true - */ - this.autoClearStencil = true; - - // scene graph - - /** - * Whether the renderer should sort objects or not. - * - * Note: Sorting is used to attempt to properly render objects that have some - * degree of transparency. By definition, sorting objects may not work in all - * cases. Depending on the needs of application, it may be necessary to turn - * off sorting and use other methods to deal with transparency rendering e.g. - * manually determining each object's rendering order. - * - * @type {boolean} - * @default true - */ - this.sortObjects = true; - - // user-defined clipping - - /** - * User-defined clipping planes specified in world space. These planes apply globally. - * Points in space whose dot product with the plane is negative are cut away. - * - * @type {Array} - */ - this.clippingPlanes = []; - - /** - * Whether the renderer respects object-level clipping planes or not. - * - * @type {boolean} - * @default false - */ - this.localClippingEnabled = false; - - // tone mapping - - /** - * The tone mapping technique of the renderer. - * - * @type {(NoToneMapping|LinearToneMapping|ReinhardToneMapping|CineonToneMapping|ACESFilmicToneMapping|CustomToneMapping|AgXToneMapping|NeutralToneMapping)} - * @default NoToneMapping - */ - this.toneMapping = NoToneMapping; - - /** - * Exposure level of tone mapping. - * - * @type {number} - * @default 1 - */ - this.toneMappingExposure = 1.0; - - // transmission - - /** - * The normalized resolution scale for the transmission render target, measured in percentage - * of viewport dimensions. Lowering this value can result in significant performance improvements - * when using {@link MeshPhysicalMaterial#transmission}. - * - * @type {number} - * @default 1 - */ - this.transmissionResolutionScale = 1.0; - - // internal properties - - const _this = this; - - let _isContextLost = false; - let _nodesHandler = null; - - let _scratchFramebuffer = null; - let _srcFramebuffer = null; - let _dstFramebuffer = null; - - // internal state cache - - this._outputColorSpace = SRGBColorSpace; - - let _currentActiveCubeFace = 0; - let _currentActiveMipmapLevel = 0; - let _currentRenderTarget = null; - let _currentMaterialId = -1; - - let _currentCamera = null; - - const _currentViewport = new Vector4(); - const _currentScissor = new Vector4(); - let _currentScissorTest = null; - - const _currentClearColor = new Color( 0x000000 ); - let _currentClearAlpha = 0; - - // - - let _width = canvas.width; - let _height = canvas.height; - - let _pixelRatio = 1; - let _opaqueSort = null; - let _transparentSort = null; - - const _viewport = new Vector4( 0, 0, _width, _height ); - const _scissor = new Vector4( 0, 0, _width, _height ); - let _scissorTest = false; - - // frustum - - const _frustum = new Frustum(); - - // clipping - - let _clippingEnabled = false; - let _localClippingEnabled = false; - - // camera matrices cache - - const _projScreenMatrix = new Matrix4(); - - const _vector3 = new Vector3(); - - const _vector4 = new Vector4(); - - const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; - - let _renderBackground = false; - - function getTargetPixelRatio() { - - return _currentRenderTarget === null ? _pixelRatio : 1; - - } - - // initialize - - let _gl = context; - - function getContext( contextName, contextAttributes ) { - - return canvas.getContext( contextName, contextAttributes ); - - } - - let extensions, capabilities, state, info; - let properties, textures, environments, attributes, geometries, objects; - let programCache, materials, renderLists, renderStates, clipping, shadowMap; - - let background, morphtargets, bufferRenderer, indexedBufferRenderer; - - let utils, bindingStates, uniformsGroups; - - try { - - const contextAttributes = { - alpha: true, - depth, - stencil, - antialias, - premultipliedAlpha, - preserveDrawingBuffer, - powerPreference, - failIfMajorPerformanceCaveat, - }; - - // OffscreenCanvas does not have setAttribute, see #22811 - if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); - - // event listeners must be registered before WebGL context is created, see #12753 - canvas.addEventListener( 'webglcontextlost', onContextLost, false ); - canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - - if ( _gl === null ) { - - const contextName = 'webgl2'; - - _gl = getContext( contextName, contextAttributes ); - - if ( _gl === null ) { - - if ( getContext( contextName ) ) { - - throw new Error( 'THREE.WebGLRenderer: Error creating WebGL context with your selected attributes.' ); - - } else { - - throw new Error( 'THREE.WebGLRenderer: Error creating WebGL context.' ); - - } - - } - - } - - initGLContext(); - - } catch ( e ) { - - canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - - error( 'WebGLRenderer: ' + e.message ); - throw e; - - } - - function initGLContext() { - - extensions = new WebGLExtensions( _gl ); - extensions.init(); - - utils = new WebGLUtils( _gl, extensions ); - - capabilities = new WebGLCapabilities( _gl, extensions, parameters, utils ); - - state = new WebGLState( _gl, extensions ); - - if ( capabilities.reversedDepthBuffer && reversedDepthBuffer ) { - - state.buffers.depth.setReversed( true ); - - } - - _scratchFramebuffer = _gl.createFramebuffer(); - _srcFramebuffer = _gl.createFramebuffer(); - _dstFramebuffer = _gl.createFramebuffer(); - - info = new WebGLInfo( _gl ); - properties = new WebGLProperties(); - textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); - environments = new WebGLEnvironments( _this ); - attributes = new WebGLAttributes( _gl ); - bindingStates = new WebGLBindingStates( _gl, attributes ); - geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); - objects = new WebGLObjects( _gl, geometries, attributes, bindingStates, info ); - morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); - clipping = new WebGLClipping( properties ); - programCache = new WebGLPrograms( _this, environments, extensions, capabilities, bindingStates, clipping ); - materials = new WebGLMaterials( _this, properties ); - renderLists = new WebGLRenderLists(); - renderStates = new WebGLRenderStates( extensions ); - background = new WebGLBackground( _this, environments, state, objects, _alpha, premultipliedAlpha ); - shadowMap = new WebGLShadowMap( _this, objects, capabilities ); - uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); - - bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info ); - indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info ); - - info.programs = programCache.programs; - - /** - * Holds details about the capabilities of the current rendering context. - * - * @name WebGLRenderer#capabilities - * @type {WebGLRenderer~Capabilities} - */ - _this.capabilities = capabilities; - - /** - * Provides methods for retrieving and testing WebGL extensions. - * - * - `get(extensionName:string)`: Used to check whether a WebGL extension is supported - * and return the extension object if available. - * - `has(extensionName:string)`: returns `true` if the extension is supported. - * - * @name WebGLRenderer#extensions - * @type {Object} - */ - _this.extensions = extensions; - - /** - * Used to track properties of other objects like native WebGL objects. - * - * @name WebGLRenderer#properties - * @type {Object} - */ - _this.properties = properties; - - /** - * Manages the render lists of the renderer. - * - * @name WebGLRenderer#renderLists - * @type {Object} - */ - _this.renderLists = renderLists; - - - - /** - * Interface for managing shadows. - * - * @name WebGLRenderer#shadowMap - * @type {WebGLRenderer~ShadowMap} - */ - _this.shadowMap = shadowMap; - - /** - * Interface for managing the WebGL state. - * - * @name WebGLRenderer#state - * @type {Object} - */ - _this.state = state; - - /** - * Holds a series of statistical information about the GPU memory - * and the rendering process. Useful for debugging and monitoring. - * - * By default these data are reset at each render call but when having - * multiple render passes per frame (e.g. when using post processing) it can - * be preferred to reset with a custom pattern. First, set `autoReset` to - * `false`. - * ```js - * renderer.info.autoReset = false; - * ``` - * Call `reset()` whenever you have finished to render a single frame. - * ```js - * renderer.info.reset(); - * ``` - * - * @name WebGLRenderer#info - * @type {WebGLRenderer~Info} - */ - _this.info = info; - - } - - // initialize internal render target for non-UnsignedByteType color buffer - - if ( _outputBufferType !== UnsignedByteType ) { - - output = new WebGLOutput( _outputBufferType, canvas.width, canvas.height, antialias, depth, stencil ); - - } - - // xr - - const xr = new WebXRManager( _this, _gl ); - - /** - * A reference to the XR manager. - * - * @type {WebXRManager} - */ - this.xr = xr; - - /** - * Returns the rendering context. - * - * @return {WebGL2RenderingContext} The rendering context. - */ - this.getContext = function () { - - return _gl; - - }; - - /** - * Returns the rendering context attributes. - * - * @return {WebGLContextAttributes} The rendering context attributes. - */ - this.getContextAttributes = function () { - - return _gl.getContextAttributes(); - - }; - - /** - * Simulates a loss of the WebGL context. This requires support for the `WEBGL_lose_context` extension. - */ - this.forceContextLoss = function () { - - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.loseContext(); - - }; - - /** - * Simulates a restore of the WebGL context. This requires support for the `WEBGL_lose_context` extension. - */ - this.forceContextRestore = function () { - - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.restoreContext(); - - }; - - /** - * Returns the pixel ratio. - * - * @return {number} The pixel ratio. - */ - this.getPixelRatio = function () { - - return _pixelRatio; - - }; - - /** - * Sets the given pixel ratio and resizes the canvas if necessary. - * - * @param {number} value - The pixel ratio. - */ - this.setPixelRatio = function ( value ) { - - if ( value === undefined ) return; - - _pixelRatio = value; - - this.setSize( _width, _height, false ); - - }; - - /** - * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio. - * - * @param {Vector2} target - The method writes the result in this target object. - * @return {Vector2} The renderer's size in logical pixels. - */ - this.getSize = function ( target ) { - - return target.set( _width, _height ); - - }; - - /** - * Resizes the output canvas to (width, height) with device pixel ratio taken - * into account, and also sets the viewport to fit that size, starting in (0, - * 0). Setting `updateStyle` to false prevents any style changes to the output canvas. - * - * @param {number} width - The width in logical pixels. - * @param {number} height - The height in logical pixels. - * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not. - */ - this.setSize = function ( width, height, updateStyle = true ) { - - if ( xr.isPresenting ) { - - warn( 'WebGLRenderer: Can\'t change size while VR device is presenting.' ); - return; - - } - - _width = width; - _height = height; - - canvas.width = Math.floor( width * _pixelRatio ); - canvas.height = Math.floor( height * _pixelRatio ); - - if ( updateStyle === true ) { - - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; - - } - - if ( output !== null ) { - - output.setSize( canvas.width, canvas.height ); - - } - - this.setViewport( 0, 0, width, height ); - - }; - - /** - * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio. - * - * @param {Vector2} target - The method writes the result in this target object. - * @return {Vector2} The drawing buffer size. - */ - this.getDrawingBufferSize = function ( target ) { - - return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); - - }; - - /** - * This method allows to define the drawing buffer size by specifying - * width, height and pixel ratio all at once. The size of the drawing - * buffer is computed with this formula: - * ```js - * size.x = width * pixelRatio; - * size.y = height * pixelRatio; - * ``` - * - * @param {number} width - The width in logical pixels. - * @param {number} height - The height in logical pixels. - * @param {number} pixelRatio - The pixel ratio. - */ - this.setDrawingBufferSize = function ( width, height, pixelRatio ) { - - _width = width; - _height = height; - - _pixelRatio = pixelRatio; - - canvas.width = Math.floor( width * pixelRatio ); - canvas.height = Math.floor( height * pixelRatio ); - - this.setViewport( 0, 0, width, height ); - - }; - - /** - * Sets the post-processing effects to be applied after rendering. - * - * @param {Array} effects - An array of post-processing effects. - */ - this.setEffects = function ( effects ) { - - if ( _outputBufferType === UnsignedByteType ) { - - error( 'WebGLRenderer: setEffects() requires outputBufferType set to HalfFloatType or FloatType.' ); - return; - - } - - if ( effects ) { - - for ( let i = 0; i < effects.length; i ++ ) { - - if ( effects[ i ].isOutputPass === true ) { - - warn( 'WebGLRenderer: OutputPass is not needed in setEffects(). Tone mapping and color space conversion are applied automatically.' ); - break; - - } - - } - - } - - output.setEffects( effects || [] ); - - }; - - /** - * Returns the current viewport definition. - * - * @param {Vector2} target - The method writes the result in this target object. - * @return {Vector2} The current viewport definition. - */ - this.getCurrentViewport = function ( target ) { - - return target.copy( _currentViewport ); - - }; - - /** - * Returns the viewport definition. - * - * @param {Vector4} target - The method writes the result in this target object. - * @return {Vector4} The viewport definition. - */ - this.getViewport = function ( target ) { - - return target.copy( _viewport ); - - }; - - /** - * Sets the viewport to render from `(x, y)` to `(x + width, y + height)`. - * - * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit. - * Or alternatively a four-component vector specifying all the parameters of the viewport. - * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit. - * @param {number} width - The width of the viewport in logical pixel unit. - * @param {number} height - The height of the viewport in logical pixel unit. - */ - this.setViewport = function ( x, y, width, height ) { - - if ( x.isVector4 ) { - - _viewport.set( x.x, x.y, x.z, x.w ); - - } else { - - _viewport.set( x, y, width, height ); - - } - - state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).round() ); - - }; - - /** - * Returns the scissor region. - * - * @param {Vector4} target - The method writes the result in this target object. - * @return {Vector4} The scissor region. - */ - this.getScissor = function ( target ) { - - return target.copy( _scissor ); - - }; - - /** - * Sets the scissor region to render from `(x, y)` to `(x + width, y + height)`. - * - * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the scissor region origin in logical pixel unit. - * Or alternatively a four-component vector specifying all the parameters of the scissor region. - * @param {number} y - The vertical coordinate for the lower left corner of the scissor region origin in logical pixel unit. - * @param {number} width - The width of the scissor region in logical pixel unit. - * @param {number} height - The height of the scissor region in logical pixel unit. - */ - this.setScissor = function ( x, y, width, height ) { - - if ( x.isVector4 ) { - - _scissor.set( x.x, x.y, x.z, x.w ); - - } else { - - _scissor.set( x, y, width, height ); - - } - - state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).round() ); - - }; - - /** - * Returns `true` if the scissor test is enabled. - * - * @return {boolean} Whether the scissor test is enabled or not. - */ - this.getScissorTest = function () { - - return _scissorTest; - - }; - - /** - * Enable or disable the scissor test. When this is enabled, only the pixels - * within the defined scissor area will be affected by further renderer - * actions. - * - * @param {boolean} boolean - Whether the scissor test is enabled or not. - */ - this.setScissorTest = function ( boolean ) { - - state.setScissorTest( _scissorTest = boolean ); - - }; - - /** - * Sets a custom opaque sort function for the render lists. Pass `null` - * to use the default `painterSortStable` function. - * - * @param {?Function} method - The opaque sort function. - */ - this.setOpaqueSort = function ( method ) { - - _opaqueSort = method; - - }; - - /** - * Sets a custom transparent sort function for the render lists. Pass `null` - * to use the default `reversePainterSortStable` function. - * - * @param {?Function} method - The opaque sort function. - */ - this.setTransparentSort = function ( method ) { - - _transparentSort = method; - - }; - - // Clearing - - /** - * Returns the clear color. - * - * @param {Color} target - The method writes the result in this target object. - * @return {Color} The clear color. - */ - this.getClearColor = function ( target ) { - - return target.copy( background.getClearColor() ); - - }; - - /** - * Sets the clear color and alpha. - * - * @param {Color} color - The clear color. - * @param {number} [alpha=1] - The clear alpha. - */ - this.setClearColor = function () { - - background.setClearColor( ...arguments ); - - }; - - /** - * Returns the clear alpha. Ranges within `[0,1]`. - * - * @return {number} The clear alpha. - */ - this.getClearAlpha = function () { - - return background.getClearAlpha(); - - }; - - /** - * Sets the clear alpha. - * - * @param {number} alpha - The clear alpha. - */ - this.setClearAlpha = function () { - - background.setClearAlpha( ...arguments ); - - }; - - /** - * Tells the renderer to clear its color, depth or stencil drawing buffer(s). - * This method initializes the buffers to the current clear color values. - * - * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. - * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. - * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. - */ - this.clear = function ( color = true, depth = true, stencil = true ) { - - let bits = 0; - - if ( color ) { - - // check if we're trying to clear an integer target - let isIntegerFormat = false; - if ( _currentRenderTarget !== null ) { - - const targetFormat = _currentRenderTarget.texture.format; - isIntegerFormat = INTEGER_FORMATS.has( targetFormat ); - - } - - // use the appropriate clear functions to clear the target if it's a signed - // or unsigned integer target - if ( isIntegerFormat ) { - - const targetType = _currentRenderTarget.texture.type; - const isUnsignedType = UNSIGNED_TYPES.has( targetType ); - - const clearColor = background.getClearColor(); - const a = background.getClearAlpha(); - const r = clearColor.r; - const g = clearColor.g; - const b = clearColor.b; - - if ( isUnsignedType ) { - - uintClearColor[ 0 ] = r; - uintClearColor[ 1 ] = g; - uintClearColor[ 2 ] = b; - uintClearColor[ 3 ] = a; - _gl.clearBufferuiv( _gl.COLOR, 0, uintClearColor ); - - } else { - - intClearColor[ 0 ] = r; - intClearColor[ 1 ] = g; - intClearColor[ 2 ] = b; - intClearColor[ 3 ] = a; - _gl.clearBufferiv( _gl.COLOR, 0, intClearColor ); - - } - - } else { - - bits |= _gl.COLOR_BUFFER_BIT; - - } - - } - - if ( depth ) { - - bits |= _gl.DEPTH_BUFFER_BIT; - this.state.buffers.depth.setMask( true ); - - } - - if ( stencil ) { - - bits |= _gl.STENCIL_BUFFER_BIT; - this.state.buffers.stencil.setMask( 0xffffffff ); - - } - - if ( bits !== 0 ) { - - _gl.clear( bits ); - - } - - }; - - /** - * Clears the color buffer. Equivalent to calling `renderer.clear( true, false, false )`. - */ - this.clearColor = function () { - - this.clear( true, false, false ); - - }; - - /** - * Clears the depth buffer. Equivalent to calling `renderer.clear( false, true, false )`. - */ - this.clearDepth = function () { - - this.clear( false, true, false ); - - }; - - /** - * Clears the stencil buffer. Equivalent to calling `renderer.clear( false, false, true )`. - */ - this.clearStencil = function () { - - this.clear( false, false, true ); - - }; - - /** - * Sets a compatibility node builder for rendering node materials with WebGLRenderer. - * This enables using TSL (Three.js Shading Language) node materials to prepare - * for migration to WebGPURenderer. - * - * @param {WebGLNodesHandler} nodesHandler - The node builder instance. - */ - this.setNodesHandler = function ( nodesHandler ) { - - nodesHandler.setRenderer( this ); - _nodesHandler = nodesHandler; - - }; - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - */ - this.dispose = function () { - - canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - - background.dispose(); - renderLists.dispose(); - renderStates.dispose(); - properties.dispose(); - environments.dispose(); - objects.dispose(); - bindingStates.dispose(); - uniformsGroups.dispose(); - programCache.dispose(); - - xr.dispose(); - - xr.removeEventListener( 'sessionstart', onXRSessionStart ); - xr.removeEventListener( 'sessionend', onXRSessionEnd ); - - animation.stop(); - - }; - - // Events - - function onContextLost( event ) { - - event.preventDefault(); - - log( 'WebGLRenderer: Context Lost.' ); - - _isContextLost = true; - - } - - function onContextRestore( /* event */ ) { - - log( 'WebGLRenderer: Context Restored.' ); - - _isContextLost = false; - - const infoAutoReset = info.autoReset; - const shadowMapEnabled = shadowMap.enabled; - const shadowMapAutoUpdate = shadowMap.autoUpdate; - const shadowMapNeedsUpdate = shadowMap.needsUpdate; - const shadowMapType = shadowMap.type; - - initGLContext(); - - info.autoReset = infoAutoReset; - shadowMap.enabled = shadowMapEnabled; - shadowMap.autoUpdate = shadowMapAutoUpdate; - shadowMap.needsUpdate = shadowMapNeedsUpdate; - shadowMap.type = shadowMapType; - - } - - function onContextCreationError( event ) { - - error( 'WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); - - } - - function onMaterialDispose( event ) { - - const material = event.target; - - material.removeEventListener( 'dispose', onMaterialDispose ); - - deallocateMaterial( material ); - - } - - // Buffer deallocation - - function deallocateMaterial( material ) { - - releaseMaterialProgramReferences( material ); - - properties.remove( material ); - - } - - - function releaseMaterialProgramReferences( material ) { - - const programs = properties.get( material ).programs; - - if ( programs !== undefined ) { - - programs.forEach( function ( program ) { - - programCache.releaseProgram( program ); - - } ); - - if ( material.isShaderMaterial ) { - - programCache.releaseShaderCache( material ); - - } - - } - - } - - // Buffer rendering - - this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { - - if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) - - const frontFaceCW = ( object.isMesh && object.matrixWorld.determinantAffine() < 0 ); - - const program = setProgram( camera, scene, geometry, material, object ); - - state.setMaterial( material, frontFaceCW ); - - // - - let index = geometry.index; - let rangeFactor = 1; - - if ( material.wireframe === true ) { - - index = geometries.getWireframeAttribute( geometry ); - - if ( index === undefined ) return; - - rangeFactor = 2; - - } - - // - - const drawRange = geometry.drawRange; - const position = geometry.attributes.position; - - let drawStart = drawRange.start * rangeFactor; - let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; - - if ( group !== null ) { - - drawStart = Math.max( drawStart, group.start * rangeFactor ); - drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); - - } - - if ( index !== null ) { - - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, index.count ); - - } else if ( position !== undefined && position !== null ) { - - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, position.count ); - - } - - const drawCount = drawEnd - drawStart; - - if ( drawCount < 0 || drawCount === Infinity ) return; - - // - - bindingStates.setup( object, material, program, geometry, index ); - - let attribute; - let renderer = bufferRenderer; - - if ( index !== null ) { - - attribute = attributes.get( index ); - - renderer = indexedBufferRenderer; - renderer.setIndex( attribute ); - - } - - // - - if ( object.isMesh ) { - - if ( material.wireframe === true ) { - - state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); - renderer.setMode( _gl.LINES ); - - } else { - - renderer.setMode( _gl.TRIANGLES ); - - } - - } else if ( object.isLine ) { - - let lineWidth = material.linewidth; - - if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - - state.setLineWidth( lineWidth * getTargetPixelRatio() ); - - if ( object.isLineSegments ) { - - renderer.setMode( _gl.LINES ); - - } else if ( object.isLineLoop ) { - - renderer.setMode( _gl.LINE_LOOP ); - - } else { - - renderer.setMode( _gl.LINE_STRIP ); - - } - - } else if ( object.isPoints ) { - - renderer.setMode( _gl.POINTS ); - - } else if ( object.isSprite ) { - - renderer.setMode( _gl.TRIANGLES ); - - } - - if ( object.isBatchedMesh ) { - - if ( ! extensions.get( 'WEBGL_multi_draw' ) ) { - - const starts = object._multiDrawStarts; - const counts = object._multiDrawCounts; - const drawCount = object._multiDrawCount; - const bytesPerElement = index ? attributes.get( index ).bytesPerElement : 1; - const uniforms = properties.get( material ).currentProgram.getUniforms(); - for ( let i = 0; i < drawCount; i ++ ) { - - uniforms.setValue( _gl, '_gl_DrawID', i ); - renderer.render( starts[ i ] / bytesPerElement, counts[ i ] ); - - } - - } else { - - renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); - - } - - } else if ( object.isInstancedMesh ) { - - renderer.renderInstances( drawStart, drawCount, object.count ); - - } else if ( geometry.isInstancedBufferGeometry ) { - - const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; - const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); - - renderer.renderInstances( drawStart, drawCount, instanceCount ); - - } else { - - renderer.render( drawStart, drawCount ); - - } - - }; - - // Compile - - function prepareMaterial( material, scene, camera, object ) { - - if ( _nodesHandler !== null && material.isNodeMaterial ) _nodesHandler.setObject( object, material ); - - if ( _clippingEnabled === true ) clipping.setState( material, camera, false ); - - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - - material.side = BackSide; - material.needsUpdate = true; - getProgram( material, scene, object ); - - material.side = FrontSide; - material.needsUpdate = true; - getProgram( material, scene, object ); - - material.side = DoubleSide; - - } else { - - getProgram( material, scene, object ); - - } - - } - - /** - * Compiles all materials in the scene with the camera. This is useful to precompile shaders - * before the first rendering. If you want to add a 3D object to an existing scene, use the third - * optional parameter for applying the target scene. - * - * Note that the (target) scene's lighting and environment must be configured before calling this method. - * - * @param {Object3D} scene - The scene or another type of 3D object to precompile. - * @param {Camera} camera - The camera. - * @param {?Scene} [targetScene=null] - The target scene. - * @return {Set} The precompiled materials. - */ - this.compile = function ( scene, camera, targetScene = null ) { - - if ( targetScene === null ) targetScene = scene; - if ( _nodesHandler !== null ) _nodesHandler.renderStart( scene, camera, targetScene ); - - currentRenderState = renderStates.get( targetScene ); - currentRenderState.init( camera ); - - renderStateStack.push( currentRenderState ); - - // gather lights from both the target scene and the new object that will be added to the scene. - - targetScene.traverseVisible( function ( object ) { - - if ( object.isLight && object.layers.test( camera.layers ) ) { - - currentRenderState.pushLight( object ); - - if ( object.castShadow ) { - - currentRenderState.pushShadow( object ); - - } - - } - - } ); - - if ( scene !== targetScene ) { - - scene.traverseVisible( function ( object ) { - - if ( object.isLight && object.layers.test( camera.layers ) ) { - - currentRenderState.pushLight( object ); - - if ( object.castShadow ) { - - currentRenderState.pushShadow( object ); - - } - - } - - } ); - - } - - currentRenderState.setupLights(); - if ( _nodesHandler !== null ) _nodesHandler.updateLights( currentRenderState.state.lightsArray ); - - _localClippingEnabled = this.localClippingEnabled; - _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); - - if ( _clippingEnabled === true ) clipping.setGlobalState( this.clippingPlanes, camera ); - - // node materials reference the shadow map when they are built, so it must exist by now - - if ( _nodesHandler !== null ) shadowMap.render( currentRenderState.state.shadowsArray, targetScene, camera ); - - // Only initialize materials in the new scene, not the targetScene. - - const materials = new Set(); - - scene.traverse( function ( object ) { - - if ( ! ( object.isMesh || object.isPoints || object.isLine || object.isSprite ) ) { - - return; - - } - - const material = object.material; - - if ( material ) { - - if ( Array.isArray( material ) ) { - - for ( let i = 0; i < material.length; i ++ ) { - - const material2 = material[ i ]; - - prepareMaterial( material2, targetScene, camera, object ); - materials.add( material2 ); - - } - - } else { - - prepareMaterial( material, targetScene, camera, object ); - materials.add( material ); - - } - - } - - } ); - - currentRenderState = renderStateStack.pop(); - if ( _nodesHandler !== null ) _nodesHandler.renderEnd(); - - return materials; - - }; - - // compileAsync - - /** - * Asynchronous version of {@link WebGLRenderer#compile}. - * - * This method makes use of the `KHR_parallel_shader_compile` WebGL extension. Hence, - * it is recommended to use this version of `compile()` whenever possible. - * - * @async - * @param {Object3D} scene - The scene or another type of 3D object to precompile. - * @param {Camera} camera - The camera. - * @param {?Scene} [targetScene=null] - The target scene. - * @return {Promise} A Promise that resolves when the given scene can be rendered without unnecessary stalling due to shader compilation. - */ - this.compileAsync = function ( scene, camera, targetScene = null ) { - - const materials = this.compile( scene, camera, targetScene ); - - // Wait for all the materials in the new object to indicate that they're - // ready to be used before resolving the promise. - - return new Promise( ( resolve ) => { - - function checkMaterialsReady() { - - materials.forEach( function ( material ) { - - const materialProperties = properties.get( material ); - const program = materialProperties.currentProgram; - - if ( program === undefined || program.isReady() ) { - - // stop waiting for materials that are ready to use or have been disposed - materials.delete( material ); - - } - - } ); - - // once the list of compiling materials is empty, call the callback - - if ( materials.size === 0 ) { - - resolve( scene ); - return; - - } - - // if some materials are still not ready, wait a bit and check again - - setTimeout( checkMaterialsReady, 10 ); - - } - - if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) { - - // If we can check the compilation status of the materials without - // blocking then do so right away. - - checkMaterialsReady(); - - } else { - - // Otherwise start by waiting a bit to give the materials we just - // initialized a chance to finish. - - setTimeout( checkMaterialsReady, 10 ); - - } - - } ); - - }; - - // Animation Loop - - let onAnimationFrameCallback = null; - - function onAnimationFrame( time ) { - - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); - - } - - function onXRSessionStart() { - - animation.stop(); - - } - - function onXRSessionEnd() { - - animation.start(); - - } - - const animation = new WebGLAnimation(); - animation.setAnimationLoop( onAnimationFrame ); - - if ( typeof self !== 'undefined' ) animation.setContext( self ); - - /** - * Applications are advised to always define the animation loop - * with this method and not manually with `requestAnimationFrame()` - * for best compatibility. - * - * @param {?onAnimationCallback} callback - The application's animation loop. - */ - this.setAnimationLoop = function ( callback ) { - - onAnimationFrameCallback = callback; - xr.setAnimationLoop( callback ); - - ( callback === null ) ? animation.stop() : animation.start(); - - }; - - xr.addEventListener( 'sessionstart', onXRSessionStart ); - xr.addEventListener( 'sessionend', onXRSessionEnd ); - - // Rendering - - /** - * Renders the given scene (or other type of 3D object) using the given camera. - * - * The render is done to a previously specified render target set by calling {@link WebGLRenderer#setRenderTarget} - * or to the canvas as usual. - * - * By default render buffers are cleared before rendering but you can prevent - * this by setting the property `autoClear` to `false`. If you want to prevent - * only certain buffers being cleared you can `autoClearColor`, `autoClearDepth` - * or `autoClearStencil` to `false`. To force a clear, use {@link WebGLRenderer#clear}. - * - * @param {Object3D} scene - The scene to render. - * @param {Camera} camera - The camera. - */ - this.render = function ( scene, camera ) { - - if ( camera !== undefined && camera.isCamera !== true ) { - - error( 'WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); - return; - - } - - if ( _isContextLost === true ) return; - - // update node builder if available - if ( _nodesHandler !== null ) { - - _nodesHandler.renderStart( scene, camera ); - - } - - // use internal render target for HalfFloatType color buffer (only when tone mapping is enabled) - - const isXRPresenting = xr.enabled === true && xr.isPresenting === true; - - const useOutput = output !== null && ( _currentRenderTarget === null || isXRPresenting ) && output.begin( _this, _currentRenderTarget ); - - // update scene graph - - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - - // update camera matrices and frustum - - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - - if ( xr.enabled === true && xr.isPresenting === true && ( output === null || output.isCompositing() === false ) ) { - - if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); - - camera = xr.getCamera(); // use XR camera for rendering - - } - - // - if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); - - currentRenderState = renderStates.get( scene, renderStateStack.length ); - currentRenderState.init( camera ); - - currentRenderState.state.textureUnits = textures.getTextureUnits(); - renderStateStack.push( currentRenderState ); - - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromProjectionMatrix( _projScreenMatrix, WebGLCoordinateSystem, camera.reversedDepth ); - - _localClippingEnabled = this.localClippingEnabled; - _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); - - currentRenderList = renderLists.get( scene, renderListStack.length ); - currentRenderList.init(); - - renderListStack.push( currentRenderList ); - - if ( xr.enabled === true && xr.isPresenting === true ) { - - const depthSensingMesh = _this.xr.getDepthSensingMesh(); - - if ( depthSensingMesh !== null ) { - - projectObject( depthSensingMesh, camera, - Infinity, _this.sortObjects ); - - } - - } - - projectObject( scene, camera, 0, _this.sortObjects ); - - currentRenderList.finish(); - if ( _nodesHandler !== null ) _nodesHandler.updateLights( currentRenderState.state.lightsArray ); - - if ( _this.sortObjects === true ) { - - currentRenderList.sort( _opaqueSort, _transparentSort ); - - } - - _renderBackground = xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false; - if ( _renderBackground ) { - - background.addToRenderList( currentRenderList, scene ); - - } - - // - - this.info.render.frame ++; - - if ( this.info.autoReset === true ) this.info.reset(); - - if ( _clippingEnabled === true ) clipping.beginShadows(); - - const shadowsArray = currentRenderState.state.shadowsArray; - - shadowMap.render( shadowsArray, scene, camera ); - - if ( _clippingEnabled === true ) clipping.endShadows(); - - // - - // render scene (skip if first effect is a render pass - it will render the scene itself) - - const skipSceneRender = useOutput && output.hasRenderPass(); - - if ( skipSceneRender === false ) { - - const opaqueObjects = currentRenderList.opaque; - const transmissiveObjects = currentRenderList.transmissive; - - currentRenderState.setupLights(); - - if ( camera.isArrayCamera ) { - - const cameras = camera.cameras; - - if ( transmissiveObjects.length > 0 ) { - - for ( let i = 0, l = cameras.length; i < l; i ++ ) { - - const camera2 = cameras[ i ]; - - renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 ); - - } - - } - - if ( _renderBackground ) background.render( scene ); - - for ( let i = 0, l = cameras.length; i < l; i ++ ) { - - const camera2 = cameras[ i ]; - - renderScene( currentRenderList, scene, camera2, camera2.viewport ); - - } - - } else { - - if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); - - if ( _renderBackground ) background.render( scene ); - - renderScene( currentRenderList, scene, camera ); - - } - - } - - // - - if ( _currentRenderTarget !== null && _currentActiveMipmapLevel === 0 ) { - - // resolve multisample renderbuffers to a single-sample texture if necessary - - textures.updateMultisampleRenderTarget( _currentRenderTarget ); - - // Generate mipmap if we're using any kind of mipmap filtering - - textures.updateRenderTargetMipmap( _currentRenderTarget ); - - } - - // copy from internal render target to canvas using fullscreen quad - - if ( useOutput ) { - - output.end( _this ); - - } - - // - - if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); - - // _gl.finish(); - - bindingStates.resetDefaultState(); - _currentMaterialId = -1; - _currentCamera = null; - - renderStateStack.pop(); - - if ( renderStateStack.length > 0 ) { - - currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; - - textures.setTextureUnits( currentRenderState.state.textureUnits ); - - if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, currentRenderState.state.camera ); - - } else { - - currentRenderState = null; - - } - - renderListStack.pop(); - - if ( renderListStack.length > 0 ) { - - currentRenderList = renderListStack[ renderListStack.length - 1 ]; - - } else { - - currentRenderList = null; - - } - - if ( _nodesHandler !== null ) { - - _nodesHandler.renderEnd(); - - } - - }; - - function projectObject( object, camera, groupOrder, sortObjects ) { - - if ( object.visible === false ) return; - - const visible = object.layers.test( camera.layers ); - - if ( visible ) { - - if ( object.isGroup ) { - - groupOrder = object.renderOrder; - - } else if ( object.isLOD ) { - - if ( object.autoUpdate === true ) object.update( camera ); - - } else if ( object.isLightProbeGrid ) { - - currentRenderState.pushLightProbeGrid( object ); - - } else if ( object.isLight ) { - - currentRenderState.pushLight( object ); - - if ( object.castShadow ) { - - currentRenderState.pushShadow( object ); - - } - - } else if ( object.isSprite ) { - - if ( ! object.frustumCulled || object.intersectsFrustum( _frustum ) ) { - - if ( sortObjects ) { - - _vector4.setFromMatrixPosition( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - const geometry = objects.update( object ); - const material = object.material; - - if ( material.visible ) { - - currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null, camera ); - - } - - } - - } else if ( object.isMesh || object.isLine || object.isPoints ) { - - if ( ! object.frustumCulled || object.intersectsFrustum( _frustum ) ) { - - const geometry = objects.update( object ); - const material = object.material; - - if ( sortObjects ) { - - if ( object.boundingSphere !== undefined ) { - - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - _vector4.copy( object.boundingSphere.center ); - - } else { - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - _vector4.copy( geometry.boundingSphere.center ); - - } - - _vector4 - .applyMatrix4( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - if ( Array.isArray( material ) ) { - - const groups = geometry.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group, camera ); - - } - - } - - } else if ( material.visible ) { - - currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null, camera ); - - } - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - projectObject( children[ i ], camera, groupOrder, sortObjects ); - - } - - } - - function renderScene( currentRenderList, scene, camera, viewport ) { - - const { opaque: opaqueObjects, transmissive: transmissiveObjects, transparent: transparentObjects } = currentRenderList; - - currentRenderState.setupLightsView( camera ); - - if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - - if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); - - if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); - if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); - if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); - - // Ensure depth buffer writing is enabled so it can be cleared on next render - - state.buffers.depth.setTest( true ); - state.buffers.depth.setMask( true ); - state.buffers.color.setMask( true ); - - state.setPolygonOffset( false ); - - } - - function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { - - const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - - if ( overrideMaterial !== null ) { - - return; - - } - - if ( currentRenderState.state.transmissionRenderTarget[ camera.id ] === undefined ) { - - const hasHalfFloatSupport = extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ); - - currentRenderState.state.transmissionRenderTarget[ camera.id ] = new WebGLRenderTarget( 1, 1, { - generateMipmaps: true, - type: hasHalfFloatSupport ? HalfFloatType : UnsignedByteType, - minFilter: LinearMipmapLinearFilter, - samples: Math.max( 4, capabilities.samples ), // to avoid feedback loops, the transmission render target requires a resolve, see #26177 - stencilBuffer: stencil, - resolveDepthBuffer: false, - resolveStencilBuffer: false, - storeMultisampledDepthBuffer: false, - storeMultisampledStencilBuffer: false, - colorSpace: ColorManagement.workingColorSpace, - } ); - - // debug - - /* - const geometry = new PlaneGeometry(); - const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); - - const mesh = new Mesh( geometry, material ); - scene.add( mesh ); - */ - - } - - const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget[ camera.id ]; - - const activeViewport = camera.viewport || _currentViewport; - transmissionRenderTarget.setSize( activeViewport.z * _this.transmissionResolutionScale, activeViewport.w * _this.transmissionResolutionScale ); - - // - - const currentRenderTarget = _this.getRenderTarget(); - const currentActiveCubeFace = _this.getActiveCubeFace(); - const currentActiveMipmapLevel = _this.getActiveMipmapLevel(); - - _this.setRenderTarget( transmissionRenderTarget ); - - _this.getClearColor( _currentClearColor ); - _currentClearAlpha = _this.getClearAlpha(); - if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); - - _this.clear(); - - if ( _renderBackground ) background.render( scene ); - - // Turn off the features which can affect the frag color for opaque objects pass. - // Otherwise they are applied twice in opaque objects pass and transmission objects pass. - const currentToneMapping = _this.toneMapping; - _this.toneMapping = NoToneMapping; - - // Remove viewport from camera to avoid nested render calls resetting viewport to it (e.g Reflector). - // Transmission render pass requires viewport to match the transmissionRenderTarget. - const currentCameraViewport = camera.viewport; - if ( camera.viewport !== undefined ) camera.viewport = undefined; - - currentRenderState.setupLightsView( camera ); - - if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - - renderObjects( opaqueObjects, scene, camera ); - - textures.updateMultisampleRenderTarget( transmissionRenderTarget ); - textures.updateRenderTargetMipmap( transmissionRenderTarget ); - - if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === false ) { // see #28131 - - let renderTargetNeedsUpdate = false; - - for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { - - const renderItem = transmissiveObjects[ i ]; - - const { object, geometry, material, group } = renderItem; - - if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { - - const currentSide = material.side; - - material.side = BackSide; - material.needsUpdate = true; - - renderObject( object, scene, camera, geometry, material, group ); - - material.side = currentSide; - material.needsUpdate = true; - - renderTargetNeedsUpdate = true; - - } - - } - - if ( renderTargetNeedsUpdate === true ) { - - textures.updateMultisampleRenderTarget( transmissionRenderTarget ); - textures.updateRenderTargetMipmap( transmissionRenderTarget ); - - } - - } - - _this.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); - - _this.setClearColor( _currentClearColor, _currentClearAlpha ); - - if ( currentCameraViewport !== undefined ) camera.viewport = currentCameraViewport; - - _this.toneMapping = currentToneMapping; - - } - - function renderObjects( renderList, scene, camera ) { - - const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - - for ( let i = 0, l = renderList.length; i < l; i ++ ) { - - const renderItem = renderList[ i ]; - - const { object, geometry, group } = renderItem; - let material = renderItem.material; - - if ( material.allowOverride === true && overrideMaterial !== null ) { - - material = overrideMaterial; - - } - - if ( object.layers.test( camera.layers ) ) { - - renderObject( object, scene, camera, geometry, material, group ); - - } - - } - - } - - function renderObject( object, scene, camera, geometry, material, group ) { - - if ( _nodesHandler !== null && material.isNodeMaterial ) _nodesHandler.setObject( object, material ); - object.onBeforeRender( _this, scene, camera, geometry, material, group ); - - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - - material.onBeforeRender( _this, scene, camera, geometry, object, group ); - - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - - material.side = BackSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - material.side = FrontSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - material.side = DoubleSide; - - } else { - - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - } - - object.onAfterRender( _this, scene, camera, geometry, material, group ); - - } - - function getProgram( material, scene, object ) { - - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - - const materialProperties = properties.get( material ); - - const lights = currentRenderState.state.lights; - const shadowsArray = currentRenderState.state.shadowsArray; - - const lightsStateVersion = lights.state.version; - - const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object, currentRenderState.state.lightProbeGridArray ); - const programCacheKey = programCache.getProgramCacheKey( parameters ); - - let programs = materialProperties.programs; - - // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change - - materialProperties.environment = ( material.isMeshStandardMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial ) ? scene.environment : null; - materialProperties.fog = scene.fog; - - const usePMREM = material.isMeshStandardMaterial || ( material.isMeshLambertMaterial && ! material.envMap ) || ( material.isMeshPhongMaterial && ! material.envMap ); - materialProperties.envMap = environments.get( material.envMap || materialProperties.environment, usePMREM ); - materialProperties.envMapRotation = ( materialProperties.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; - - if ( programs === undefined ) { - - // new material - - material.addEventListener( 'dispose', onMaterialDispose ); - - programs = new Map(); - materialProperties.programs = programs; - - } - - let program = programs.get( programCacheKey ); - - if ( program !== undefined ) { - - // early out if program and light state is identical - - if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { - - updateCommonMaterialProperties( material, parameters ); - - return program; - - } - - } else { - - parameters.uniforms = programCache.getUniforms( material ); - - // Use node builder for node materials if available - if ( _nodesHandler !== null && material.isNodeMaterial ) { - - _nodesHandler.build( material, object, parameters ); - - } - - material.onBeforeCompile( parameters, _this ); - - program = programCache.acquireProgram( parameters, programCacheKey ); - programs.set( programCacheKey, program ); - - materialProperties.uniforms = parameters.uniforms; - - } - - const uniforms = materialProperties.uniforms; - - if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { - - uniforms.clippingPlanes = clipping.uniform; - - } - - updateCommonMaterialProperties( material, parameters ); - - // store the light setup it was created for - - materialProperties.needsLights = materialNeedsLights( material ); - materialProperties.lightsStateVersion = lightsStateVersion; - - if ( materialProperties.needsLights ) { - - // wire up the material to this renderer's lighting state - - uniforms.ambientLightColor.value = lights.state.ambient; - uniforms.lightProbe.value = lights.state.probe; - uniforms.sunLights.value = lights.state.sun; - uniforms.sunLightShadows.value = lights.state.sunShadow; - uniforms.directionalLights.value = lights.state.directional; - uniforms.directionalLightShadows.value = lights.state.directionalShadow; - uniforms.spotLights.value = lights.state.spot; - uniforms.spotLightShadows.value = lights.state.spotShadow; - uniforms.rectAreaLights.value = lights.state.rectArea; - uniforms.ltc_1.value = lights.state.rectAreaLTC1; - uniforms.ltc_2.value = lights.state.rectAreaLTC2; - uniforms.pointLights.value = lights.state.point; - uniforms.pointLightShadows.value = lights.state.pointShadow; - uniforms.hemisphereLights.value = lights.state.hemi; - - uniforms.sunShadowMatrix.value = lights.state.sunShadowMatrix; - uniforms.sunShadowCascade.value = lights.state.sunShadowCascade; - uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; - uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; - uniforms.spotLightMap.value = lights.state.spotLightMap; - uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; - // TODO (abelnation): add area lights shadow info to uniforms - - } - - materialProperties.lightProbeGrid = currentRenderState.state.lightProbeGridArray.length > 0; - - materialProperties.currentProgram = program; - materialProperties.uniformsList = null; - - return program; - - } - - function getUniformList( materialProperties ) { - - if ( materialProperties.uniformsList === null ) { - - const progUniforms = materialProperties.currentProgram.getUniforms(); - materialProperties.uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, materialProperties.uniforms ); - - } - - return materialProperties.uniformsList; - - } - - function updateCommonMaterialProperties( material, parameters ) { - - const materialProperties = properties.get( material ); - - materialProperties.outputColorSpace = parameters.outputColorSpace; - materialProperties.batching = parameters.batching; - materialProperties.batchingColor = parameters.batchingColor; - materialProperties.instancing = parameters.instancing; - materialProperties.instancingColor = parameters.instancingColor; - materialProperties.instancingMorph = parameters.instancingMorph; - materialProperties.skinning = parameters.skinning; - materialProperties.morphTargets = parameters.morphTargets; - materialProperties.morphNormals = parameters.morphNormals; - materialProperties.morphColors = parameters.morphColors; - materialProperties.morphTargetsCount = parameters.morphTargetsCount; - materialProperties.numClippingPlanes = parameters.numClippingPlanes; - materialProperties.numIntersection = parameters.numClipIntersection; - materialProperties.vertexAlphas = parameters.vertexAlphas; - materialProperties.vertexTangents = parameters.vertexTangents; - materialProperties.toneMapping = parameters.toneMapping; - - } - - function findLightProbeGrid( volumes, object ) { - - if ( volumes.length === 0 ) return null; - - if ( volumes.length === 1 ) { - - return volumes[ 0 ].texture !== null ? volumes[ 0 ] : null; - - } - - objectPosition.setFromMatrixPosition( object.matrixWorld ); - - for ( let i = 0, l = volumes.length; i < l; i ++ ) { - - const v = volumes[ i ]; - - if ( v.texture !== null && v.boundingBox.containsPoint( objectPosition ) ) return v; - - } - - return null; - - } - - function setProgram( camera, scene, geometry, material, object ) { - - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - - textures.resetTextureUnits(); - - const fog = scene.fog; - const environment = ( material.isMeshStandardMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial ) ? scene.environment : null; - const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : ColorManagement.workingColorSpace ); - const usePMREM = material.isMeshStandardMaterial || ( material.isMeshLambertMaterial && ! material.envMap ) || ( material.isMeshPhongMaterial && ! material.envMap ); - const envMap = environments.get( material.envMap || environment, usePMREM ); - const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; - const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); - const morphTargets = !! geometry.morphAttributes.position; - const morphNormals = !! geometry.morphAttributes.normal; - const morphColors = !! geometry.morphAttributes.color; - - let toneMapping = NoToneMapping; - - if ( material.toneMapped ) { - - if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) { - - toneMapping = _this.toneMapping; - - } - - } - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - const materialProperties = properties.get( material ); - const lights = currentRenderState.state.lights; - - if ( _clippingEnabled === true ) { - - if ( _localClippingEnabled === true || camera !== _currentCamera ) { - - const useCache = - camera === _currentCamera && - material.id === _currentMaterialId; - - // we might want to call this function with some ClippingGroup - // object instead of the material, once it becomes feasible - // (#8465, #8379) - clipping.setState( material, camera, useCache ); - - } - - } - - // - - let needsProgramChange = false; - - if ( material.version === materialProperties.__version ) { - - if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { - - needsProgramChange = true; - - } else if ( materialProperties.outputColorSpace !== colorSpace ) { - - needsProgramChange = true; - - } else if ( object.isBatchedMesh && materialProperties.batching === false ) { - - needsProgramChange = true; - - } else if ( ! object.isBatchedMesh && materialProperties.batching === true ) { - - needsProgramChange = true; - - } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object._colorsTexture === null ) { - - needsProgramChange = true; - - } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object._colorsTexture !== null ) { - - needsProgramChange = true; - - } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { - - needsProgramChange = true; - - } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { - - needsProgramChange = true; - - } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { - - needsProgramChange = true; - - } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { - - needsProgramChange = true; - - } else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) { - - needsProgramChange = true; - - } else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) { - - needsProgramChange = true; - - } else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) { - - needsProgramChange = true; - - } else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) { - - needsProgramChange = true; - - } else if ( materialProperties.envMap !== envMap ) { - - needsProgramChange = true; - - } else if ( material.fog === true && materialProperties.fog !== fog ) { - - needsProgramChange = true; - - } else if ( materialProperties.numClippingPlanes !== undefined && - ( materialProperties.numClippingPlanes !== clipping.numPlanes || - materialProperties.numIntersection !== clipping.numIntersection ) ) { - - needsProgramChange = true; - - } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { - - needsProgramChange = true; - - } else if ( materialProperties.vertexTangents !== vertexTangents ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphTargets !== morphTargets ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphNormals !== morphNormals ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphColors !== morphColors ) { - - needsProgramChange = true; - - } else if ( materialProperties.toneMapping !== toneMapping ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphTargetsCount !== morphTargetsCount ) { - - needsProgramChange = true; - - } else if ( !! materialProperties.lightProbeGrid !== ( currentRenderState.state.lightProbeGridArray.length > 0 ) ) { - - needsProgramChange = true; - - } - - } else { - - needsProgramChange = true; - materialProperties.__version = material.version; - - } - - // - - let program = materialProperties.currentProgram; - - if ( needsProgramChange === true ) { - - program = getProgram( material, scene, object ); - - // notify the node builder that the program has changed so uniforms and update nodes can - // be cached and triggered. - if ( _nodesHandler && material.isNodeMaterial ) { - - _nodesHandler.onUpdateProgram( material, program, materialProperties ); - - } - - } - - let refreshProgram = false; - let refreshMaterial = false; - let refreshLights = false; - - const p_uniforms = program.getUniforms(), - m_uniforms = materialProperties.uniforms; - - if ( state.useProgram( program.program ) ) { - - refreshProgram = true; - refreshMaterial = true; - refreshLights = true; - - } - - if ( material.id !== _currentMaterialId ) { - - _currentMaterialId = material.id; - - refreshMaterial = true; - - } - - if ( materialProperties.needsLights ) { - - const objectVolume = findLightProbeGrid( currentRenderState.state.lightProbeGridArray, object ); - - if ( materialProperties.lightProbeGrid !== objectVolume ) { - - materialProperties.lightProbeGrid = objectVolume; - refreshMaterial = true; - - } - - } - - if ( refreshProgram || _currentCamera !== camera ) { - - // common camera uniforms - - const reversedDepthBuffer = state.buffers.depth.getReversed(); - - if ( reversedDepthBuffer && camera.reversedDepth !== true ) { - - camera._reversedDepth = true; - camera.updateProjectionMatrix(); - - } - - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); - - const uCamPos = p_uniforms.map.cameraPosition; - - if ( uCamPos !== undefined ) { - - uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) ); - - } - - if ( capabilities.logarithmicDepthBuffer ) { - - p_uniforms.setValue( _gl, 'logDepthBufFC', - 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - - } - - // consider moving isOrthographic to UniformLib and WebGLMaterials, see https://github.com/mrdoob/three.js/pull/26467#issuecomment-1645185067 - - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial ) { - - p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); - - } - - if ( _currentCamera !== camera ) { - - _currentCamera = camera; - - // lighting uniforms depend on the camera so enforce an update - // now, in case this material supports lights - or later, when - // the next material that does gets activated: - - refreshMaterial = true; // set to true on material change - refreshLights = true; // remains set until update done - - } - - } - - // Pre-allocate texture units for shadow samplers before setting data textures - if ( materialProperties.needsLights ) { - - // Set shadow map uniforms first to ensure they get the first texture units - if ( lights.state.sunShadowMap.length > 0 ) { - - p_uniforms.setValue( _gl, 'sunShadowMap', lights.state.sunShadowMap, textures ); - - } - - if ( lights.state.directionalShadowMap.length > 0 ) { - - p_uniforms.setValue( _gl, 'directionalShadowMap', lights.state.directionalShadowMap, textures ); - - } - - if ( lights.state.spotShadowMap.length > 0 ) { - - p_uniforms.setValue( _gl, 'spotShadowMap', lights.state.spotShadowMap, textures ); - - } - - if ( lights.state.pointShadowMap.length > 0 ) { - - p_uniforms.setValue( _gl, 'pointShadowMap', lights.state.pointShadowMap, textures ); - - } - - } - - // skinning and morph target uniforms must be set even if material didn't change - // auto-setting of texture unit for bone and morph texture must go before other textures - // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures - - if ( object.isSkinnedMesh ) { - - p_uniforms.setOptional( _gl, object, 'bindMatrix' ); - p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); - - const skeleton = object.skeleton; - - if ( skeleton ) { - - if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); - - p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - - } - - } - - if ( object.isBatchedMesh ) { - - p_uniforms.setOptional( _gl, object, 'batchingTexture' ); - p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); - - p_uniforms.setOptional( _gl, object, 'batchingIdTexture' ); - p_uniforms.setValue( _gl, 'batchingIdTexture', object._indirectTexture, textures ); - - p_uniforms.setOptional( _gl, object, 'batchingColorTexture' ); - if ( object._colorsTexture !== null ) { - - p_uniforms.setValue( _gl, 'batchingColorTexture', object._colorsTexture, textures ); - - } - - } - - const morphAttributes = geometry.morphAttributes; - - if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined ) ) { - - morphtargets.update( object, geometry, program ); - - } - - if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { - - materialProperties.receiveShadow = object.receiveShadow; - p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); - - } - - if ( ( material.isMeshStandardMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial ) && material.envMap === null && scene.environment !== null ) { - - m_uniforms.envMapIntensity.value = scene.environmentIntensity; - - } - - // Set DFG LUT for physically-based materials - if ( m_uniforms.dfgLUT !== undefined ) { - - m_uniforms.dfgLUT.value = getDFGLUT(); - - } - - if ( refreshMaterial ) { - - p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); - - if ( materialProperties.needsLights ) { - - // the current material requires lighting info - - // note: all lighting uniforms are always set correctly - // they simply reference the renderer's state for their - // values - // - // use the current material's .needsUpdate flags to set - // the GL state when required - - markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); - - } - - // refresh uniforms common to several materials - - if ( fog && material.fog === true ) { - - materials.refreshFogUniforms( m_uniforms, fog ); - - } - - materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget[ camera.id ] ); - - // light probe volume - - if ( materialProperties.needsLights && materialProperties.lightProbeGrid ) { - - const volume = materialProperties.lightProbeGrid; - - m_uniforms.probesSH.value = volume.texture; - m_uniforms.probesMin.value.copy( volume.boundingBox.min ); - m_uniforms.probesMax.value.copy( volume.boundingBox.max ); - m_uniforms.probesResolution.value.copy( volume.resolution ); - - } - - WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); - - } - - if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { - - WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); - material.uniformsNeedUpdate = false; - - } - - if ( material.isSpriteMaterial ) { - - p_uniforms.setValue( _gl, 'center', object.center ); - - } - - // common matrices - - p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); - p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); - p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); - - // UBOs - - if ( material.uniformsGroups !== undefined ) { - - const groups = material.uniformsGroups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - - uniformsGroups.update( group, program ); - uniformsGroups.bind( group, program ); - - } - - } - - return program; - - } - - // If uniforms are marked as clean, they don't need to be loaded to the GPU. - - function markUniformsLightsNeedsUpdate( uniforms, value ) { - - uniforms.ambientLightColor.needsUpdate = value; - uniforms.lightProbe.needsUpdate = value; - - uniforms.sunLights.needsUpdate = value; - uniforms.sunLightShadows.needsUpdate = value; - uniforms.directionalLights.needsUpdate = value; - uniforms.directionalLightShadows.needsUpdate = value; - uniforms.pointLights.needsUpdate = value; - uniforms.pointLightShadows.needsUpdate = value; - uniforms.spotLights.needsUpdate = value; - uniforms.spotLightShadows.needsUpdate = value; - uniforms.rectAreaLights.needsUpdate = value; - uniforms.hemisphereLights.needsUpdate = value; - - } - - function materialNeedsLights( material ) { - - return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || - material.isMeshStandardMaterial || material.isShadowMaterial || - ( material.isShaderMaterial && material.lights === true ); - - } - - /** - * Returns the active cube face. - * - * @return {number} The active cube face. - */ - this.getActiveCubeFace = function () { - - return _currentActiveCubeFace; - - }; - - /** - * Returns the active mipmap level. - * - * @return {number} The active mipmap level. - */ - this.getActiveMipmapLevel = function () { - - return _currentActiveMipmapLevel; - - }; - - /** - * Returns the active render target. - * - * @return {?WebGLRenderTarget} The active render target. Returns `null` if no render target - * is currently set. - */ - this.getRenderTarget = function () { - - return _currentRenderTarget; - - }; - - this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - - const renderTargetProperties = properties.get( renderTarget ); - - renderTargetProperties.__autoAllocateDepthBuffer = renderTarget.resolveDepthBuffer === false; - if ( renderTargetProperties.__autoAllocateDepthBuffer === false ) { - - // The multisample_render_to_texture extension doesn't work properly if there - // are midframe flushes and an external depth buffer. Disable use of the extension. - renderTargetProperties.__useRenderToTexture = false; - - } - - properties.get( renderTarget.texture ).__webglTexture = colorTexture; - properties.get( renderTarget.depthTexture ).__webglTexture = renderTargetProperties.__autoAllocateDepthBuffer ? undefined : depthTexture; - - renderTargetProperties.__hasExternalTextures = true; - - }; - - this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { - - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__webglFramebuffer = defaultFramebuffer; - renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; - - }; - - /** - * Sets the active rendertarget. - * - * @param {?WebGLRenderTarget} renderTarget - The render target to set. When `null` is given, - * the canvas is set as the active render target instead. - * @param {number} [activeCubeFace=0] - The active cube face when using a cube render target. - * Indicates the z layer to render in to when using 3D or array render targets. - * @param {number} [activeMipmapLevel=0] - The active mipmap level. - */ - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { - - _currentRenderTarget = renderTarget; - _currentActiveCubeFace = activeCubeFace; - _currentActiveMipmapLevel = activeMipmapLevel; - - let framebuffer = null; - let isCube = false; - let isRenderTarget3D = false; - - if ( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - - if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { - - // Externally-managed framebuffer (e.g. XR) - // Bind to the stored framebuffer (may be null for default, or a WebGLFramebuffer) - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - - _currentViewport.copy( renderTarget.viewport ); - _currentScissor.copy( renderTarget.scissor ); - _currentScissorTest = renderTarget.scissorTest; - - state.viewport( _currentViewport ); - state.scissor( _currentScissor ); - state.setScissorTest( _currentScissorTest ); - - _currentMaterialId = -1; - - return; - - } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { - - textures.setupRenderTarget( renderTarget ); - - } else if ( renderTargetProperties.__hasExternalTextures ) { - - // Color and depth texture must be rebound in order for the swapchain to update. - textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); - - } else if ( renderTarget.depthBuffer ) { - - // check if the depth texture is already bound to the frame buffer and that it's been initialized - const depthTexture = renderTarget.depthTexture; - if ( renderTargetProperties.__boundDepthTexture !== depthTexture ) { - - // check if the depth texture is compatible - if ( - depthTexture !== null && - properties.has( depthTexture ) && - ( renderTarget.width !== depthTexture.image.width || renderTarget.height !== depthTexture.image.height ) - ) { - - throw new Error( 'THREE.WebGLRenderer: Attached DepthTexture is initialized to the incorrect size.' ); - - } - - // Swap the depth buffer to the currently attached one - textures.setupDepthRenderbuffer( renderTarget ); - - } - - } - - const texture = renderTarget.texture; - - if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - - isRenderTarget3D = true; - - } - - const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( renderTarget.isWebGLCubeRenderTarget ) { - - if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) { - - framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ]; - - } else { - - framebuffer = __webglFramebuffer[ activeCubeFace ]; - - } - - isCube = true; - - } else if ( ( renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { - - framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; - - } else { - - if ( Array.isArray( __webglFramebuffer ) ) { - - framebuffer = __webglFramebuffer[ activeMipmapLevel ]; - - } else { - - framebuffer = __webglFramebuffer; - - } - - } - - _currentViewport.copy( renderTarget.viewport ); - _currentScissor.copy( renderTarget.scissor ); - _currentScissorTest = renderTarget.scissorTest; - - } else { - - _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); - _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); - _currentScissorTest = _scissorTest; - - } - - // Use a scratch frame buffer if rendering to a mip level to avoid depth buffers - // being bound that are different sizes. - if ( activeMipmapLevel !== 0 ) { - - framebuffer = _scratchFramebuffer; - - } - - const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( framebufferBound ) { - - state.drawBuffers( renderTarget, framebuffer ); - - } - - state.viewport( _currentViewport ); - state.scissor( _currentScissor ); - state.setScissorTest( _currentScissorTest ); - - if ( isCube ) { - - const textureProperties = properties.get( renderTarget.texture ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); - - } else if ( isRenderTarget3D ) { - - const layer = activeCubeFace; - - for ( let i = 0; i < renderTarget.textures.length; i ++ ) { - - const textureProperties = properties.get( renderTarget.textures[ i ] ); - - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, textureProperties.__webglTexture, activeMipmapLevel, layer ); - - } - - } else if ( renderTarget !== null && activeMipmapLevel !== 0 ) { - - // Only bind the frame buffer if we are using a scratch frame buffer to render to a mipmap. - // If we rebind the texture when using a multi sample buffer then an error about inconsistent samples will be thrown. - const textureProperties = properties.get( renderTarget.texture ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, textureProperties.__webglTexture, activeMipmapLevel ); - - } - - _currentMaterialId = -1; // reset current material to ensure correct uniform bindings - - }; - - function getReadableState( texture ) { - - const textureProperties = properties.get( texture ); - - if ( textureProperties.__readFormat !== texture.format || textureProperties.__readType !== texture.type ) { - - textureProperties.__readFormat = texture.format; - textureProperties.__readType = texture.type; - textureProperties.__formatReadable = capabilities.textureFormatReadable( texture.format ); - textureProperties.__typeReadable = capabilities.textureTypeReadable( texture.type ); - - } - - return textureProperties; - - } - - /** - * Reads the pixel data from the given render target into the given buffer. - * - * @param {WebGLRenderTarget} renderTarget - The render target to read from. - * @param {number} x - The `x` coordinate of the copy region's origin. - * @param {number} y - The `y` coordinate of the copy region's origin. - * @param {number} width - The width of the copy region. - * @param {number} height - The height of the copy region. - * @param {TypedArray} buffer - The result buffer. - * @param {number} [activeCubeFaceIndex] - The active cube face index. - * @param {number} [textureIndex=0] - The texture index of an MRT render target. - */ - this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { - - if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - - error( 'WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - return; - - } - - let framebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - - framebuffer = framebuffer[ activeCubeFaceIndex ]; - - } - - if ( framebuffer ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - try { - - const texture = renderTarget.textures[ textureIndex ]; - const textureFormat = texture.format; - const textureType = texture.type; - - // when using MRT, select the correct color buffer for the subsequent read command - - if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); - - const readableState = getReadableState( texture ); - - if ( readableState.__formatReadable === false ) { - - error( 'WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); - return; - - } - - if ( readableState.__typeReadable === false ) { - - error( 'WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); - return; - - } - - // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - - if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - - _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); - - } - - } finally { - - // restore framebuffer of current render target if necessary - - const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - } - - } - - }; - - /** - * Asynchronous, non-blocking version of {@link WebGLRenderer#readRenderTargetPixels}. - * - * It is recommended to use this version of `readRenderTargetPixels()` whenever possible. - * - * @async - * @param {WebGLRenderTarget} renderTarget - The render target to read from. - * @param {number} x - The `x` coordinate of the copy region's origin. - * @param {number} y - The `y` coordinate of the copy region's origin. - * @param {number} width - The width of the copy region. - * @param {number} height - The height of the copy region. - * @param {TypedArray} buffer - The result buffer. - * @param {number} [activeCubeFaceIndex] - The active cube face index. - * @param {number} [textureIndex=0] - The texture index of an MRT render target. - * @return {Promise} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array. - */ - this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { - - if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - - throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - - } - - let framebuffer = properties.get( renderTarget ).__webglFramebuffer; - if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - - framebuffer = framebuffer[ activeCubeFaceIndex ]; - - } - - if ( framebuffer ) { - - // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - - // set the active frame buffer to the one we want to read - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - const texture = renderTarget.textures[ textureIndex ]; - const textureFormat = texture.format; - const textureType = texture.type; - - // when using MRT, select the correct color buffer for the subsequent read command - - if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); - - const readableState = getReadableState( texture ); - - if ( readableState.__formatReadable === false ) { - - throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.' ); - - } - - if ( readableState.__typeReadable === false ) { - - throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.' ); - - } - - const glBuffer = _gl.createBuffer(); - _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); - _gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ ); - - _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 ); - _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, null ); - - // reset the frame buffer to the currently set buffer before waiting - const currFramebuffer = _currentRenderTarget !== null ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; - state.bindFramebuffer( _gl.FRAMEBUFFER, currFramebuffer ); - - // check if the commands have finished every 8 ms - const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); - - _gl.flush(); - - await probeAsync( _gl, sync, 4 ); - - // read the data and delete the buffer - _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); - _gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer ); - _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, null ); - _gl.deleteBuffer( glBuffer ); - _gl.deleteSync( sync ); - - return buffer; - - } else { - - throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.' ); - - } - - } - - }; - - /** - * Copies pixels from the current bound framebuffer into the given texture. - * - * @param {FramebufferTexture} texture - The texture. - * @param {?Vector2} [position=null] - The start position of the copy operation. - * @param {number} [level=0] - The mip level. The default represents the base mip. - */ - this.copyFramebufferToTexture = function ( texture, position = null, level = 0 ) { - - const levelScale = Math.pow( 2, - level ); - const width = Math.floor( texture.image.width * levelScale ); - const height = Math.floor( texture.image.height * levelScale ); - - const x = position !== null ? position.x : 0; - const y = position !== null ? position.y : 0; - - textures.setTexture2D( texture, 0 ); - - _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, x, y, width, height ); - - state.unbindTexture(); - - }; - - - /** - * Copies data of the given source texture into a destination texture. - * - * When using render target textures as `srcTexture` and `dstTexture`, you must make sure both render targets are initialized - * {@link WebGLRenderer#initRenderTarget}. - * - * @param {Texture} srcTexture - The source texture. - * @param {Texture} dstTexture - The destination texture. - * @param {?(Box2|Box3)} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional. - * @param {?(Vector2|Vector3)} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional. - * @param {number} [srcLevel=0] - The source mipmap level to copy. - * @param {?number} [dstLevel=0] - The destination mipmap level. - */ - this.copyTextureToTexture = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { - - // gather the necessary dimensions to copy - let width, height, depth, minX, minY, minZ; - let dstX, dstY, dstZ; - const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image; - if ( srcRegion !== null ) { - - width = srcRegion.max.x - srcRegion.min.x; - height = srcRegion.max.y - srcRegion.min.y; - depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; - minX = srcRegion.min.x; - minY = srcRegion.min.y; - minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; - - } else { - - const levelScale = Math.pow( 2, - srcLevel ); - width = Math.floor( image.width * levelScale ); - height = Math.floor( image.height * levelScale ); - if ( srcTexture.isDataArrayTexture ) { - - depth = image.depth; - - } else if ( srcTexture.isData3DTexture ) { - - depth = Math.floor( image.depth * levelScale ); - - } else { - - depth = 1; - - } - - minX = 0; - minY = 0; - minZ = 0; - - } - - if ( dstPosition !== null ) { - - dstX = dstPosition.x; - dstY = dstPosition.y; - dstZ = dstPosition.z; - - } else { - - dstX = 0; - dstY = 0; - dstZ = 0; - - } - - // Set up the destination target - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); - let glTarget; - - if ( dstTexture.isData3DTexture ) { - - textures.setTexture3D( dstTexture, 0 ); - glTarget = _gl.TEXTURE_3D; - - } else if ( dstTexture.isDataArrayTexture || dstTexture.isCompressedArrayTexture ) { - - textures.setTexture2DArray( dstTexture, 0 ); - glTarget = _gl.TEXTURE_2D_ARRAY; - - } else { - - textures.setTexture2D( dstTexture, 0 ); - glTarget = _gl.TEXTURE_2D; - - } - - state.activeTexture( _gl.TEXTURE0 ); // see #33153 - - state.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - state.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - state.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - - // used for copying data from cpu - const currentUnpackRowLen = state.getParameter( _gl.UNPACK_ROW_LENGTH ); - const currentUnpackImageHeight = state.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); - const currentUnpackSkipPixels = state.getParameter( _gl.UNPACK_SKIP_PIXELS ); - const currentUnpackSkipRows = state.getParameter( _gl.UNPACK_SKIP_ROWS ); - const currentUnpackSkipImages = state.getParameter( _gl.UNPACK_SKIP_IMAGES ); - - state.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - state.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); - state.pixelStorei( _gl.UNPACK_SKIP_PIXELS, minX ); - state.pixelStorei( _gl.UNPACK_SKIP_ROWS, minY ); - state.pixelStorei( _gl.UNPACK_SKIP_IMAGES, minZ ); - - // set up the src texture - const isSrc3D = srcTexture.isDataArrayTexture || srcTexture.isData3DTexture; - const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture; - if ( srcTexture.isDepthTexture ) { - - const srcTextureProperties = properties.get( srcTexture ); - const dstTextureProperties = properties.get( dstTexture ); - const srcRenderTargetProperties = properties.get( srcTextureProperties.__renderTarget ); - const dstRenderTargetProperties = properties.get( dstTextureProperties.__renderTarget ); - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, srcRenderTargetProperties.__webglFramebuffer ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, dstRenderTargetProperties.__webglFramebuffer ); - - for ( let i = 0; i < depth; i ++ ) { - - // if the source or destination are a 3d target then a layer needs to be bound - if ( isSrc3D ) { - - _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( srcTexture ).__webglTexture, srcLevel, minZ + i ); - _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( dstTexture ).__webglTexture, dstLevel, dstZ + i ); - - } - - _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.DEPTH_BUFFER_BIT, _gl.NEAREST ); - - } - - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - - } else if ( srcLevel !== 0 || srcTexture.isRenderTargetTexture || properties.has( srcTexture ) ) { - - // get the appropriate frame buffers - const srcTextureProperties = properties.get( srcTexture ); - const dstTextureProperties = properties.get( dstTexture ); - - // bind the frame buffer targets - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, _srcFramebuffer ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, _dstFramebuffer ); - - for ( let i = 0; i < depth; i ++ ) { - - // assign the correct layers and mip maps to the frame buffers - if ( isSrc3D ) { - - _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, srcTextureProperties.__webglTexture, srcLevel, minZ + i ); - - } else { - - _gl.framebufferTexture2D( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, srcTextureProperties.__webglTexture, srcLevel ); - - } - - if ( isDst3D ) { - - _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, dstTextureProperties.__webglTexture, dstLevel, dstZ + i ); - - } else { - - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, dstTextureProperties.__webglTexture, dstLevel ); - - } - - // copy the data using the fastest function that can achieve the copy - if ( srcLevel !== 0 ) { - - _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.COLOR_BUFFER_BIT, _gl.NEAREST ); - - } else if ( isDst3D ) { - - _gl.copyTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ + i, minX, minY, width, height ); - - } else { - - _gl.copyTexSubImage2D( glTarget, dstLevel, dstX, dstY, minX, minY, width, height ); - - } - - } - - // unbind read, draw buffers - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - - } else { - - if ( isDst3D ) { - - // copy data into the 3d texture - if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { - - _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); - - } else if ( dstTexture.isCompressedArrayTexture ) { - - _gl.compressedTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); - - } else { - - _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); - - } - - } else { - - // copy data into the 2d texture - if ( srcTexture.isDataTexture ) { - - _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data ); - - } else if ( srcTexture.isCompressedTexture ) { - - _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data ); - - } else { - - _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image ); - - } - - } - - } - - // reset values - state.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); - state.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); - state.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); - state.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); - state.pixelStorei( _gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); - - // Generate mipmaps only when copying level 0 - if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - - _gl.generateMipmap( glTarget ); - - } - - state.unbindTexture(); - - }; - - /** - * Initializes the given WebGLRenderTarget memory. Useful for initializing a render target so data - * can be copied into it using {@link WebGLRenderer#copyTextureToTexture} before it has been - * rendered to. - * - * @param {WebGLRenderTarget} target - The render target. - */ - this.initRenderTarget = function ( target ) { - - if ( properties.get( target ).__webglFramebuffer === undefined ) { - - textures.setupRenderTarget( target ); - - } - - }; - - /** - * Initializes the given texture. Useful for preloading a texture rather than waiting until first - * render (which can cause noticeable lags due to decode and GPU upload overhead). - * - * @param {Texture} texture - The texture. - */ - this.initTexture = function ( texture ) { - - if ( texture.isCubeTexture ) { - - textures.setTextureCube( texture, 0 ); - - } else if ( texture.isData3DTexture ) { - - textures.setTexture3D( texture, 0 ); - - } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - - textures.setTexture2DArray( texture, 0 ); - - } else { - - textures.setTexture2D( texture, 0 ); - - } - - state.unbindTexture(); - - }; - - /** - * Can be used to reset the internal WebGL state. This method is mostly - * relevant for applications which share a single WebGL context across - * multiple WebGL libraries. - */ - this.resetState = function () { - - _currentActiveCubeFace = 0; - _currentActiveMipmapLevel = 0; - _currentRenderTarget = null; - - state.reset(); - bindingStates.reset(); - - }; - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - - } - - } - - /** - * Defines the coordinate system of the renderer. - * - * In `WebGLRenderer`, the value is always `WebGLCoordinateSystem`. - * - * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} - * @default WebGLCoordinateSystem - * @readonly - */ - get coordinateSystem() { - - return WebGLCoordinateSystem; - - } - - /** - * Defines the output color space of the renderer. - * - * @type {SRGBColorSpace|LinearSRGBColorSpace} - * @default SRGBColorSpace - */ - get outputColorSpace() { - - return this._outputColorSpace; - - } - - set outputColorSpace( colorSpace ) { - - this._outputColorSpace = colorSpace; - - const gl = this.getContext(); - gl.drawingBufferColorSpace = ColorManagement._getDrawingBufferColorSpace( colorSpace ); - gl.unpackColorSpace = ColorManagement._getUnpackColorSpace(); - - } - -} - -/** - * This class can be used to define a linear fog that grows linearly denser - * with the distance. - * - * ```js - * const scene = new THREE.Scene(); - * scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); - * ``` - */ -class Fog { - - /** - * Constructs a new fog. - * - * @param {number|Color} color - The fog's color. - * @param {number} [near=1] - The minimum distance to start applying fog. - * @param {number} [far=1000] - The maximum distance at which fog stops being calculated and applied. - */ - constructor( color, near = 1, far = 1000 ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isFog = true; - - /** - * The name of the fog. - * - * @type {string} - */ - this.name = ''; - - /** - * The fog's color. - * - * @type {Color} - */ - this.color = new Color( color ); - - /** - * The minimum distance to start applying fog. Objects that are less than - * `near` units from the active camera won't be affected by fog. - * - * @type {number} - * @default 1 - */ - this.near = near; - - /** - * The maximum distance at which fog stops being calculated and applied. - * Objects that are more than `far` units away from the active camera won't - * be affected by fog. - * - * @type {number} - * @default 1000 - */ - this.far = far; - - } - - /** - * Returns a new fog with copied values from this instance. - * - * @return {Fog} A clone of this instance. - */ - clone() { - - return new Fog( this.color, this.near, this.far ); - - } - - /** - * Serializes the fog into JSON. - * - * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. - * @return {Object} A JSON object representing the serialized fog - */ - toJSON( /* meta */ ) { - - return { - type: 'Fog', - name: this.name, - color: this.color.getHex(), - near: this.near, - far: this.far - }; - - } - -} - -/** - * Scenes allow you to set up what is to be rendered and where by three.js. - * This is where you place 3D objects like meshes, lines or lights. - * - * @augments Object3D - */ -class Scene extends Object3D { - - /** - * Constructs a new scene. - */ - constructor() { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isScene = true; - - this.type = 'Scene'; - - /** - * Defines the background of the scene. Valid inputs are: - * - * - A color for defining a uniform colored background. - * - A texture for defining a (flat) textured background. - * - Cube textures or equirectangular textures for defining a skybox. - * - * @type {?(Color|Texture)} - * @default null - */ - this.background = null; - - /** - * Sets the environment map for all physical materials in the scene. However, - * it's not possible to overwrite an existing texture assigned to the `envMap` - * material property. - * - * @type {?Texture} - * @default null - */ - this.environment = null; - - /** - * A fog instance defining the type of fog that affects everything - * rendered in the scene. - * - * @type {?(Fog|FogExp2)} - * @default null - */ - this.fog = null; - - /** - * Sets the blurriness of the background. Only influences environment maps - * assigned to {@link Scene#background}. Valid input is a float between `0` - * and `1`. - * - * @type {number} - * @default 0 - */ - this.backgroundBlurriness = 0; - - /** - * Attenuates the color of the background. Only applies to background textures. - * - * @type {number} - * @default 1 - */ - this.backgroundIntensity = 1; - - /** - * The rotation of the background in radians. Only influences environment maps - * assigned to {@link Scene#background}. - * - * @type {Euler} - * @default (0,0,0) - */ - this.backgroundRotation = new Euler(); - - /** - * Attenuates the color of the environment. Only influences environment maps - * assigned to {@link Scene#environment}. - * - * @type {number} - * @default 1 - */ - this.environmentIntensity = 1; - - /** - * The rotation of the environment map in radians. Only influences physical materials - * in the scene when {@link Scene#environment} is used. - * - * @type {Euler} - * @default (0,0,0) - */ - this.environmentRotation = new Euler(); - - /** - * Forces everything in the scene to be rendered with the defined material. It is possible - * to exclude materials from override by setting {@link Material#allowOverride} to `false`. - * - * @type {?Material} - * @default null - */ - this.overrideMaterial = null; - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.background !== null ) this.background = source.background.clone(); - if ( source.environment !== null ) this.environment = source.environment.clone(); - if ( source.fog !== null ) this.fog = source.fog.clone(); - - this.backgroundBlurriness = source.backgroundBlurriness; - this.backgroundIntensity = source.backgroundIntensity; - this.backgroundRotation.copy( source.backgroundRotation ); - - this.environmentIntensity = source.environmentIntensity; - this.environmentRotation.copy( source.environmentRotation ); - - if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); - - this.matrixAutoUpdate = source.matrixAutoUpdate; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); - - data.object.backgroundBlurriness = this.backgroundBlurriness; - data.object.backgroundIntensity = this.backgroundIntensity; - data.object.backgroundRotation = this.backgroundRotation.toArray(); - - data.object.environmentIntensity = this.environmentIntensity; - data.object.environmentRotation = this.environmentRotation.toArray(); - - return data; - - } - -} - -/** - * An instanced version of a buffer attribute. - * - * @augments BufferAttribute - */ -class InstancedBufferAttribute extends BufferAttribute { - - /** - * Constructs a new instanced buffer attribute. - * - * @param {TypedArray} array - The array holding the attribute data. - * @param {number} itemSize - The item size. - * @param {boolean} [normalized=false] - Whether the data are normalized or not. - * @param {number} [meshPerAttribute=1] - How often a value of this buffer attribute should be repeated. - */ - constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { - - super( array, itemSize, normalized ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isInstancedBufferAttribute = true; - - /** - * Defines how often a value of this buffer attribute should be repeated. A - * value of one means that each value of the instanced attribute is used for - * a single instance. A value of two means that each value is used for two - * consecutive instances (and so on). - * - * @type {number} - * @default 1 - */ - this.meshPerAttribute = meshPerAttribute; - - } - - copy( source ) { - - super.copy( source ); - - this.meshPerAttribute = source.meshPerAttribute; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.meshPerAttribute = this.meshPerAttribute; - - data.isInstancedBufferAttribute = true; - - return data; - - } - -} - -const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); -const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); - -const _instanceIntersects = []; - -const _box3 = /*@__PURE__*/ new Box3(); -const _identity = /*@__PURE__*/ new Matrix4(); -const _mesh$1 = /*@__PURE__*/ new Mesh(); -const _sphere$4 = /*@__PURE__*/ new Sphere(); - -/** - * A special version of a mesh with instanced rendering support. Use - * this class if you have to render a large number of objects with the same - * geometry and material(s) but with different world transformations. The usage - * of 'InstancedMesh' will help you to reduce the number of draw calls and thus - * improve the overall rendering performance in your application. - * - * @augments Mesh - */ -class InstancedMesh extends Mesh { - - /** - * Constructs a new instanced mesh. - * - * @param {BufferGeometry} [geometry] - The mesh geometry. - * @param {Material|Array} [material] - The mesh material. - * @param {number} count - The number of instances. - */ - constructor( geometry, material, count ) { - - super( geometry, material ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isInstancedMesh = true; - - /** - * Represents the local transformation of all instances. You have to set its - * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data - * via {@link InstancedMesh#setMatrixAt}. - * - * @type {InstancedBufferAttribute} - */ - this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); - - /** - * Represents the color of all instances. You have to set its - * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data - * via {@link InstancedMesh#setColorAt}. - * - * @type {?InstancedBufferAttribute} - * @default null - */ - this.instanceColor = null; - - /** - * Represents the morph target weights of all instances. You have to set its - * {@link Texture#needsUpdate} flag to true if you modify instanced data - * via {@link InstancedMesh#setMorphAt}. - * - * @type {?DataTexture} - * @default null - */ - this.morphTexture = null; - - /** - * The number of instances. - * - * @type {number} - */ - this.count = count; - - /** - * The bounding box of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingBox}. - * - * @type {?Box3} - * @default null - */ - this.boundingBox = null; - - /** - * The bounding sphere of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingSphere}. - * - * @type {?Sphere} - * @default null - */ - this.boundingSphere = null; - - for ( let i = 0; i < count; i ++ ) { - - this.setMatrixAt( i, _identity ); - - } - - } - - /** - * Computes the bounding box of the instanced mesh, and updates {@link InstancedMesh#boundingBox}. - * The bounding box is not automatically computed by the engine; this method must be called by your app. - * You may need to recompute the bounding box if an instance is transformed via {@link InstancedMesh#setMatrixAt}. - */ - computeBoundingBox() { - - const geometry = this.geometry; - const count = this.count; - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - if ( geometry.boundingBox === null ) { - - geometry.computeBoundingBox(); - - } - - this.boundingBox.makeEmpty(); - - for ( let i = 0; i < count; i ++ ) { - - this.getMatrixAt( i, _instanceLocalMatrix ); - - _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); - - this.boundingBox.union( _box3 ); - - } - - } - - /** - * Computes the bounding sphere of the instanced mesh, and updates {@link InstancedMesh#boundingSphere} - * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. - * You may need to recompute the bounding sphere if an instance is transformed via {@link InstancedMesh#setMatrixAt}. - */ - computeBoundingSphere() { - - const geometry = this.geometry; - const count = this.count; - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - if ( geometry.boundingSphere === null ) { - - geometry.computeBoundingSphere(); - - } - - this.boundingSphere.makeEmpty(); - - for ( let i = 0; i < count; i ++ ) { - - this.getMatrixAt( i, _instanceLocalMatrix ); - - _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); - - this.boundingSphere.union( _sphere$4 ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.instanceMatrix.copy( source.instanceMatrix ); - - if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone(); - if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); - - this.count = source.count; - - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); - - return this; - - } - - /** - * Gets the color of the defined instance. - * - * @param {number} index - The instance index. - * @param {Color} color - The target object that is used to store the method's result. - * @return {Color} A reference to the target color. - */ - getColorAt( index, color ) { - - if ( this.instanceColor === null ) { - - return color.setRGB( 1, 1, 1 ); - - } else { - - return color.fromArray( this.instanceColor.array, index * 3 ); - - } - - } - - /** - * Gets the local transformation matrix of the defined instance. - * - * @param {number} index - The instance index. - * @param {Matrix4} matrix - The target object that is used to store the method's result. - * @return {Matrix4} A reference to the target matrix. - */ - getMatrixAt( index, matrix ) { - - return matrix.fromArray( this.instanceMatrix.array, index * 16 ); - - } - - /** - * Gets the morph target weights of the defined instance. - * - * @param {number} index - The instance index. - * @param {Mesh} object - The target object that is used to store the method's result. - */ - getMorphAt( index, object ) { - - const objectInfluences = object.morphTargetInfluences; - - const array = this.morphTexture.source.data.data; - - const len = objectInfluences.length + 1; // All influences + the baseInfluenceSum - - const dataIndex = index * len + 1; // Skip the baseInfluenceSum at the beginning - - for ( let i = 0; i < objectInfluences.length; i ++ ) { - - objectInfluences[ i ] = array[ dataIndex + i ]; - - } - - } - - raycast( raycaster, intersects ) { - - const matrixWorld = this.matrixWorld; - const raycastTimes = this.count; - - _mesh$1.geometry = this.geometry; - _mesh$1.material = this.material; - - if ( _mesh$1.material === undefined ) return; - - // test with bounding sphere first - - if ( this.boundingSphere === null ) this.computeBoundingSphere(); - - _sphere$4.copy( this.boundingSphere ); - _sphere$4.applyMatrix4( matrixWorld ); - - if ( raycaster.ray.intersectsSphere( _sphere$4 ) === false ) return; - - // now test each instance - - for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { - - // calculate the world matrix for each instance - - this.getMatrixAt( instanceId, _instanceLocalMatrix ); - - _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); - - // the mesh represents this single instance - - _mesh$1.matrixWorld = _instanceWorldMatrix; - - _mesh$1.raycast( raycaster, _instanceIntersects ); - - // process the result of raycast - - for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { - - const intersect = _instanceIntersects[ i ]; - intersect.instanceId = instanceId; - intersect.object = this; - intersects.push( intersect ); - - } - - _instanceIntersects.length = 0; - - } - - } - - /** - * Sets the given color to the defined instance. Make sure you set the `needsUpdate` flag of - * {@link InstancedMesh#instanceColor} to `true` after updating all the colors. - * - * @param {number} index - The instance index. - * @param {Color} color - The instance color. - * @return {InstancedMesh} A reference to this instanced mesh. - */ - setColorAt( index, color ) { - - if ( this.instanceColor === null ) { - - this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ).fill( 1 ), 3 ); - - } - - color.toArray( this.instanceColor.array, index * 3 ); - return this; - - } - - /** - * Sets the given local transformation matrix to the defined instance. Make sure you set the `needsUpdate` flag of - * {@link InstancedMesh#instanceMatrix} to `true` after updating all the matrices. - * - * @param {number} index - The instance index. - * @param {Matrix4} matrix - The local transformation. - * @return {InstancedMesh} A reference to this instanced mesh. - */ - setMatrixAt( index, matrix ) { - - matrix.toArray( this.instanceMatrix.array, index * 16 ); - return this; - - } - - /** - * Sets the morph target weights to the defined instance. Make sure you set the `needsUpdate` flag of - * {@link InstancedMesh#morphTexture} to `true` after updating all the influences. - * - * @param {number} index - The instance index. - * @param {Mesh} object - A mesh which `morphTargetInfluences` property containing the morph target weights - * of a single instance. - * @return {InstancedMesh} A reference to this instanced mesh. - */ - setMorphAt( index, object ) { - - const objectInfluences = object.morphTargetInfluences; - - const len = objectInfluences.length + 1; // morphBaseInfluence + all influences - - if ( this.morphTexture === null ) { - - this.morphTexture = new DataTexture( new Float32Array( len * this.count ), len, this.count, RedFormat, FloatType ); - - } - - const array = this.morphTexture.source.data.data; - - let morphInfluencesSum = 0; - - for ( let i = 0; i < objectInfluences.length; i ++ ) { - - morphInfluencesSum += objectInfluences[ i ]; - - } - - const morphBaseInfluence = this.geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - const dataIndex = len * index; - - array[ dataIndex ] = morphBaseInfluence; - - array.set( objectInfluences, dataIndex + 1 ); - return this; - - } - - updateMorphTargets() { - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - */ - dispose() { - - super.dispose(); - - if ( this.morphTexture !== null ) { - - this.morphTexture.dispose(); - this.morphTexture = null; - - } - - } - -} - -/** - * A material for rendering line primitives. - * - * Materials define the appearance of renderable 3D objects. - * - * ```js - * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); - * ``` - * - * @augments Material - */ -class LineBasicMaterial extends Material { - - /** - * Constructs a new line basic material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isLineBasicMaterial = true; - - this.type = 'LineBasicMaterial'; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); - - /** - * Sets the color of the lines using data from a texture. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * Controls line thickness or lines. - * - * Can only be used with {@link SVGRenderer}. WebGL and WebGPU - * ignore this setting and always render line primitives with a - * width of one pixel. - * - * @type {number} - * @default 1 - */ - this.linewidth = 1; - - /** - * Defines appearance of line ends. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('butt'|'round'|'square')} - * @default 'round' - */ - this.linecap = 'round'; - - /** - * Defines appearance of line joints. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.linejoin = 'round'; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.linewidth = source.linewidth; - this.linecap = source.linecap; - this.linejoin = source.linejoin; - - this.fog = source.fog; - - return this; - - } - -} - -const _vStart = /*@__PURE__*/ new Vector3(); -const _vEnd = /*@__PURE__*/ new Vector3(); - -const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); -const _ray$2 = /*@__PURE__*/ new Ray(); -const _sphere$3 = /*@__PURE__*/ new Sphere(); - -const _intersectPointOnRay = /*@__PURE__*/ new Vector3(); -const _intersectPointOnSegment = /*@__PURE__*/ new Vector3(); - -/** - * A continuous line. The line are rendered by connecting consecutive - * vertices with straight lines. - * - * ```js - * const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - * - * const points = []; - * points.push( new THREE.Vector3( - 10, 0, 0 ) ); - * points.push( new THREE.Vector3( 0, 10, 0 ) ); - * points.push( new THREE.Vector3( 10, 0, 0 ) ); - * - * const geometry = new THREE.BufferGeometry().setFromPoints( points ); - * - * const line = new THREE.Line( geometry, material ); - * scene.add( line ); - * ``` - * - * @augments Object3D - */ -let Line$1 = class Line extends Object3D { - - /** - * Constructs a new line. - * - * @param {BufferGeometry} [geometry] - The line geometry. - * @param {Material|Array} [material] - The line material. - */ - constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isLine = true; - - this.type = 'Line'; - - /** - * The line geometry. - * - * @type {BufferGeometry} - */ - this.geometry = geometry; - - /** - * The line material. - * - * @type {Material|Array} - * @default LineBasicMaterial - */ - this.material = material; - - /** - * A dictionary representing the morph targets in the geometry. The key is the - * morph targets name, the value its attribute index. This member is `undefined` - * by default and only set when morph targets are detected in the geometry. - * - * @type {Object|undefined} - * @default undefined - */ - this.morphTargetDictionary = undefined; - - /** - * An array of weights typically in the range `[0,1]` that specify how much of the morph - * is applied. This member is `undefined` by default and only set when morph targets are - * detected in the geometry. - * - * @type {Array|undefined} - * @default undefined - */ - this.morphTargetInfluences = undefined; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; - this.geometry = source.geometry; - - return this; - - } - - /** - * Computes an array of distance values which are necessary for rendering dashed lines. - * For each vertex in the geometry, the method calculates the cumulative length from the - * current point to the very beginning of the line. - * - * @return {Line} A reference to this line. - */ - computeLineDistances() { - - const geometry = this.geometry; - - // we assume non-indexed geometry - - if ( geometry.index === null ) { - - const positionAttribute = geometry.attributes.position; - const lineDistances = [ 0 ]; - - for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { - - _vStart.fromBufferAttribute( positionAttribute, i - 1 ); - _vEnd.fromBufferAttribute( positionAttribute, i ); - - lineDistances[ i ] = lineDistances[ i - 1 ]; - lineDistances[ i ] += _vStart.distanceTo( _vEnd ); - - } - - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - - } else { - - warn( 'Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); - - } - - return this; - - } - - /** - * Returns `true` if this line intersects the given frustum. - * - * @param {Frustum|FrustumArray} frustum - The frustum to test. - * @return {boolean} Whether this line intersects the given frustum or not. - */ - intersectsFrustum( frustum ) { - - return frustum.intersectsObject( this ); - - } - - /** - * Computes intersection points between a casted ray and this line. - * - * @param {Raycaster} raycaster - The raycaster. - * @param {Array} intersects - The target array that holds the intersection points. - */ - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Line.threshold; - const drawRange = geometry.drawRange; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$3.copy( geometry.boundingSphere ); - _sphere$3.applyMatrix4( matrixWorld ); - _sphere$3.radius += threshold; - - if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return; - - // - - _inverseMatrix$2.copy( matrixWorld ).invert(); - _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); - - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; - - const step = this.isLineSegments ? 2 : 1; - - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; - - if ( index !== null ) { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end - 1; i < l; i += step ) { - - const a = index.getX( i ); - const b = index.getX( i + 1 ); - - const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, a, b, i ); - - if ( intersect ) { - - intersects.push( intersect ); - - } - - } - - if ( this.isLineLoop ) { - - const a = index.getX( end - 1 ); - const b = index.getX( start ); - - const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, a, b, end - 1 ); - - if ( intersect ) { - - intersects.push( intersect ); - - } - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end - 1; i < l; i += step ) { - - const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, i, i + 1, i ); - - if ( intersect ) { - - intersects.push( intersect ); - - } - - } - - if ( this.isLineLoop ) { - - const intersect = checkIntersection( this, raycaster, _ray$2, localThresholdSq, end - 1, start, end - 1 ); - - if ( intersect ) { - - intersects.push( intersect ); - - } - - } - - } - - } - - /** - * Sets the values of {@link Line#morphTargetDictionary} and {@link Line#morphTargetInfluences} - * to make sure existing morph targets can influence this 3D object. - */ - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - -}; - -function checkIntersection( object, raycaster, ray, thresholdSq, a, b, i ) { - - const positionAttribute = object.geometry.attributes.position; - - _vStart.fromBufferAttribute( positionAttribute, a ); - _vEnd.fromBufferAttribute( positionAttribute, b ); - - const distSq = ray.distanceSqToSegment( _vStart, _vEnd, _intersectPointOnRay, _intersectPointOnSegment ); - - if ( distSq > thresholdSq ) return; - - _intersectPointOnRay.applyMatrix4( object.matrixWorld ); // Move back to world space for distance calculation - - const distance = raycaster.ray.origin.distanceTo( _intersectPointOnRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - return { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: _intersectPointOnSegment.clone().applyMatrix4( object.matrixWorld ), - index: i, - face: null, - faceIndex: null, - barycoord: null, - object: object - - }; - -} - -const _start = /*@__PURE__*/ new Vector3(); -const _end = /*@__PURE__*/ new Vector3(); - -/** - * A series of lines drawn between pairs of vertices. - * - * @augments Line - */ -class LineSegments extends Line$1 { - - /** - * Constructs a new line segments. - * - * @param {BufferGeometry} [geometry] - The line geometry. - * @param {Material|Array} [material] - The line material. - */ - constructor( geometry, material ) { - - super( geometry, material ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isLineSegments = true; - - this.type = 'LineSegments'; - - } - - computeLineDistances() { - - const geometry = this.geometry; - - // we assume non-indexed geometry - - if ( geometry.index === null ) { - - const positionAttribute = geometry.attributes.position; - const lineDistances = []; - - for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { - - _start.fromBufferAttribute( positionAttribute, i ); - _end.fromBufferAttribute( positionAttribute, i + 1 ); - - lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; - lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); - - } - - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - - } else { - - warn( 'LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); - - } - - return this; - - } - -} - -/** - * A material for rendering point primitives. - * - * Materials define the appearance of renderable 3D objects. - * - * ```js - * const vertices = []; - * - * for ( let i = 0; i < 10000; i ++ ) { - * const x = THREE.MathUtils.randFloatSpread( 2000 ); - * const y = THREE.MathUtils.randFloatSpread( 2000 ); - * const z = THREE.MathUtils.randFloatSpread( 2000 ); - * - * vertices.push( x, y, z ); - * } - * - * const geometry = new THREE.BufferGeometry(); - * geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); - * const material = new THREE.PointsMaterial( { color: 0x888888 } ); - * const points = new THREE.Points( geometry, material ); - * scene.add( points ); - * ``` - * - * @augments Material - */ -class PointsMaterial extends Material { - - /** - * Constructs a new points material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isPointsMaterial = true; - - this.type = 'PointsMaterial'; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * Defines the size of the points in pixels. - * - * Might be capped if the value exceeds hardware dependent parameters like [gl.ALIASED_POINT_SIZE_RANGE](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getParamete). - * - * @type {number} - * @default 1 - */ - this.size = 1; - - /** - * Specifies whether size of individual points is attenuated by the camera depth (perspective camera only). - * - * @type {boolean} - * @default true - */ - this.sizeAttenuation = true; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.size = source.size; - this.sizeAttenuation = source.sizeAttenuation; - - this.fog = source.fog; - - return this; - - } - -} - -const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _ray$1 = /*@__PURE__*/ new Ray(); -const _sphere$2 = /*@__PURE__*/ new Sphere(); -const _position = /*@__PURE__*/ new Vector3(); - -/** - * A class for displaying points or point clouds. - * - * @augments Object3D - */ -class Points extends Object3D { - - /** - * Constructs a new point cloud. - * - * @param {BufferGeometry} [geometry] - The points geometry. - * @param {Material|Array} [material] - The points material. - */ - constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isPoints = true; - - this.type = 'Points'; - - /** - * The points geometry. - * - * @type {BufferGeometry} - */ - this.geometry = geometry; - - /** - * The line material. - * - * @type {Material|Array} - * @default PointsMaterial - */ - this.material = material; - - /** - * A dictionary representing the morph targets in the geometry. The key is the - * morph targets name, the value its attribute index. This member is `undefined` - * by default and only set when morph targets are detected in the geometry. - * - * @type {Object|undefined} - * @default undefined - */ - this.morphTargetDictionary = undefined; - - /** - * An array of weights typically in the range `[0,1]` that specify how much of the morph - * is applied. This member is `undefined` by default and only set when morph targets are - * detected in the geometry. - * - * @type {Array|undefined} - * @default undefined - */ - this.morphTargetInfluences = undefined; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; - this.geometry = source.geometry; - - return this; - - } - - /** - * Returns `true` if this point cloud intersects the given frustum. - * - * @param {Frustum|FrustumArray} frustum - The frustum to test. - * @return {boolean} Whether this point cloud intersects the given frustum or not. - */ - intersectsFrustum( frustum ) { - - return frustum.intersectsObject( this ); - - } - - /** - * Computes intersection points between a casted ray and this point cloud. - * - * @param {Raycaster} raycaster - The raycaster. - * @param {Array} intersects - The target array that holds the intersection points. - */ - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Points.threshold; - const drawRange = geometry.drawRange; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$2.copy( geometry.boundingSphere ); - _sphere$2.applyMatrix4( matrixWorld ); - _sphere$2.radius += threshold; - - if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return; - - // - - _inverseMatrix$1.copy( matrixWorld ).invert(); - _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); - - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; - - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; - - if ( index !== null ) { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i ++ ) { - - const a = index.getX( i ); - - _position.fromBufferAttribute( positionAttribute, a ); - - testPoint( _position, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end; i < l; i ++ ) { - - _position.fromBufferAttribute( positionAttribute, i ); - - testPoint( _position, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); - - } - - } - - } - - /** - * Sets the values of {@link Points#morphTargetDictionary} and {@link Points#morphTargetInfluences} - * to make sure existing morph targets can influence this 3D object. - */ - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - -} - -function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { - - const rayPointDistanceSq = _ray$1.distanceSqToPoint( point ); - - if ( rayPointDistanceSq < localThresholdSq ) { - - const intersectPoint = new Vector3(); - - _ray$1.closestPointToPoint( point, intersectPoint ); - intersectPoint.applyMatrix4( matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( intersectPoint ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - intersects.push( { - - distance: distance, - distanceToRay: Math.sqrt( rayPointDistanceSq ), - point: intersectPoint, - index: index, - face: null, - faceIndex: null, - barycoord: null, - object: object - - } ); - - } - -} - -/** - * Creates a texture from a canvas element. - * - * This is almost the same as the base texture class, except that it sets {@link Texture#needsUpdate} - * to `true` immediately since a canvas can directly be used for rendering. - * - * @augments Texture - */ -class CanvasTexture extends Texture { - - /** - * Constructs a new texture. - * - * @param {HTMLCanvasElement} [canvas] - The HTML canvas element. - * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. - * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. - * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. - * @param {number} [magFilter=LinearFilter] - The mag filter value. - * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. - * @param {number} [format=RGBAFormat] - The texture format. - * @param {number} [type=UnsignedByteType] - The texture type. - * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. - */ - constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - - super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCanvasTexture = true; - - this.needsUpdate = true; - - } - -} - -/** - * A simple shape of Euclidean geometry. It is constructed from a - * number of triangular segments that are oriented around a central point and - * extend as far out as a given radius. It is built counter-clockwise from a - * start angle and a given central angle. It can also be used to create - * regular polygons, where the number of segments determines the number of - * sides. - * - * ```js - * const geometry = new THREE.CircleGeometry( 5, 32 ); - * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); - * const circle = new THREE.Mesh( geometry, material ); - * scene.add( circle ) - * ``` - * - * @augments BufferGeometry - * @demo scenes/geometry-browser.html#CircleGeometry - */ -class CircleGeometry extends BufferGeometry { - - /** - * Constructs a new circle geometry. - * - * @param {number} [radius=1] - Radius of the circle. - * @param {number} [segments=32] - Number of segments (triangles), minimum = `3`. - * @param {number} [thetaStart=0] - Start angle for first segment in radians. - * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, - * of the circular sector in radians. The default value results in a complete circle. - */ - constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { - - super(); - - this.type = 'CircleGeometry'; - - /** - * Holds the constructor parameters that have been - * used to generate the geometry. Any modification - * after instantiation does not change the geometry. - * - * @type {Object} - */ - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - segments = Math.max( 3, segments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - const vertex = new Vector3(); - const uv = new Vector2(); - - // center point - - vertices.push( 0, 0, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( 0.5, 0.5 ); - - for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { - - const segment = thetaStart + s / segments * thetaLength; - - // vertex - - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normals.push( 0, 0, 1 ); - - // uvs - - uv.x = ( vertices[ i ] / radius + 1 ) / 2; - uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; - - uvs.push( uv.x, uv.y ); - - } - - // indices - - for ( let i = 1; i <= segments; i ++ ) { - - indices.push( i, i + 1, 0 ); - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - /** - * Factory method for creating an instance of this class from the given - * JSON object. - * - * @param {Object} data - A JSON object representing the serialized geometry. - * @return {CircleGeometry} A new instance. - */ - static fromJSON( data ) { - - return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); - - } - -} - -/** - * An abstract base class for creating an analytic curve object that contains methods - * for interpolation. - * - * @abstract - */ -class Curve { - - /** - * Constructs a new curve. - */ - constructor() { - - /** - * The type property is used for detecting the object type - * in context of serialization/deserialization. - * - * @type {string} - * @readonly - */ - this.type = 'Curve'; - - /** - * This value determines the amount of divisions when calculating the - * cumulative segment lengths of a curve via {@link Curve#getLengths}. To ensure - * precision when using methods like {@link Curve#getSpacedPoints}, it is - * recommended to increase the value of this property if the curve is very large. - * - * @type {number} - * @default 200 - */ - this.arcLengthDivisions = 200; - - /** - * Must be set to `true` if the curve parameters have changed. - * - * @type {boolean} - * @default false - */ - this.needsUpdate = false; - - /** - * An internal cache that holds precomputed curve length values. - * - * @private - * @type {?Array} - * @default null - */ - this.cacheArcLengths = null; - - } - - /** - * This method returns a vector in 2D or 3D space (depending on the curve definition) - * for the given interpolation factor. - * - * @abstract - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. - * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. - */ - getPoint( /* t, optionalTarget */ ) { - - warn( 'Curve: .getPoint() not implemented.' ); - - } - - /** - * This method returns a vector in 2D or 3D space (depending on the curve definition) - * for the given interpolation factor. Unlike {@link Curve#getPoint}, this method honors the length - * of the curve which equidistant samples. - * - * @param {number} u - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. - * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. - */ - getPointAt( u, optionalTarget ) { - - const t = this.getUtoTmapping( u ); - return this.getPoint( t, optionalTarget ); - - } - - /** - * This method samples the curve via {@link Curve#getPoint} and returns an array of points representing - * the curve shape. - * - * @param {number} [divisions=5] - The number of divisions. - * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. - */ - getPoints( divisions = 5 ) { - - const points = []; - - for ( let d = 0; d <= divisions; d ++ ) { - - points.push( this.getPoint( d / divisions ) ); - - } - - return points; - - } - - // Get sequence of points using getPointAt( u ) - - /** - * This method samples the curve via {@link Curve#getPointAt} and returns an array of points representing - * the curve shape. Unlike {@link Curve#getPoints}, this method returns equi-spaced points across the entire - * curve. - * - * @param {number} [divisions=5] - The number of divisions. - * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. - */ - getSpacedPoints( divisions = 5 ) { - - const points = []; - - for ( let d = 0; d <= divisions; d ++ ) { - - points.push( this.getPointAt( d / divisions ) ); - - } - - return points; - - } - - /** - * Returns the total arc length of the curve. - * - * @return {number} The length of the curve. - */ - getLength() { - - const lengths = this.getLengths(); - return lengths[ lengths.length - 1 ]; - - } - - /** - * Returns an array of cumulative segment lengths of the curve. - * - * @param {number} [divisions=this.arcLengthDivisions] - The number of divisions. - * @return {Array} An array holding the cumulative segment lengths. - */ - getLengths( divisions = this.arcLengthDivisions ) { - - if ( this.cacheArcLengths && - ( this.cacheArcLengths.length === divisions + 1 ) && - ! this.needsUpdate ) { - - return this.cacheArcLengths; - - } - - this.needsUpdate = false; - - const cache = []; - let current, last = this.getPoint( 0 ); - let sum = 0; - - cache.push( 0 ); - - for ( let p = 1; p <= divisions; p ++ ) { - - current = this.getPoint( p / divisions ); - sum += current.distanceTo( last ); - cache.push( sum ); - last = current; - - } - - this.cacheArcLengths = cache; - - return cache; // { sums: cache, sum: sum }; Sum is in the last element. - - } - - /** - * Update the cumulative segment distance cache. The method must be called - * every time curve parameters are changed. If an updated curve is part of a - * composed curve like {@link CurvePath}, this method must be called on the - * composed curve, too. - */ - updateArcLengths() { - - this.needsUpdate = true; - this.getLengths(); - - } - - /** - * Given an interpolation factor in the range `[0,1]`, this method returns an updated - * interpolation factor in the same range that can be ued to sample equidistant points - * from a curve. - * - * @param {number} u - The interpolation factor. - * @param {?number} distance - An optional distance on the curve. - * @return {number} The updated interpolation factor. - */ - getUtoTmapping( u, distance = null ) { - - const arcLengths = this.getLengths(); - - let i = 0; - const il = arcLengths.length; - - let targetArcLength; // The targeted u distance value to get - - if ( distance ) { - - targetArcLength = distance; - - } else { - - targetArcLength = u * arcLengths[ il - 1 ]; - - } - - // binary search for the index with largest value smaller than target u distance - - let low = 0, high = il - 1, comparison; - - while ( low <= high ) { - - i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats - - comparison = arcLengths[ i ] - targetArcLength; - - if ( comparison < 0 ) { - - low = i + 1; - - } else if ( comparison > 0 ) { - - high = i - 1; - - } else { - - high = i; - break; - - // DONE - - } - - } - - i = high; - - if ( arcLengths[ i ] === targetArcLength ) { - - return i / ( il - 1 ); - - } - - // we could get finer grain at lengths, or use simple interpolation between two points - - const lengthBefore = arcLengths[ i ]; - const lengthAfter = arcLengths[ i + 1 ]; - - const segmentLength = lengthAfter - lengthBefore; - - // determine where we are between the 'before' and 'after' points - - const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - - // add that fractional amount to t - - const t = ( i + segmentFraction ) / ( il - 1 ); - - return t; - - } - - /** - * Returns a unit vector tangent for the given interpolation factor. - * If the derived curve does not implement its tangent derivation, - * two points a small delta apart will be used to find its gradient - * which seems to give a reasonable approximation. - * - * @param {number} t - The interpolation factor. - * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. - * @return {(Vector2|Vector3)} The tangent vector. - */ - getTangent( t, optionalTarget ) { - - const delta = 0.0001; - let t1 = t - delta; - let t2 = t + delta; - - // Capping in case of danger - - if ( t1 < 0 ) t1 = 0; - if ( t2 > 1 ) t2 = 1; - - const pt1 = this.getPoint( t1 ); - const pt2 = this.getPoint( t2 ); - - const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); - - tangent.copy( pt2 ).sub( pt1 ).normalize(); - - return tangent; - - } - - /** - * Same as {@link Curve#getTangent} but with equidistant samples. - * - * @param {number} u - The interpolation factor. - * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. - * @return {(Vector2|Vector3)} The tangent vector. - * @see {@link Curve#getPointAt} - */ - getTangentAt( u, optionalTarget ) { - - const t = this.getUtoTmapping( u ); - return this.getTangent( t, optionalTarget ); - - } - - /** - * Generates the Frenet Frames. Requires a curve definition in 3D space. Used - * in geometries like {@link TubeGeometry} or {@link ExtrudeGeometry}. - * - * @param {number} segments - The number of segments. - * @param {boolean} [closed=false] - Whether the curve is closed or not. - * @return {{tangents: Array, normals: Array, binormals: Array}} The Frenet Frames. - */ - computeFrenetFrames( segments, closed = false ) { - - // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf - - const normal = new Vector3(); - - const tangents = []; - const normals = []; - const binormals = []; - - const vec = new Vector3(); - const mat = new Matrix4(); - - // compute the tangent vectors for each segment on the curve - - for ( let i = 0; i <= segments; i ++ ) { - - const u = i / segments; - - tangents[ i ] = this.getTangentAt( u, new Vector3() ); - - } - - // select an initial normal vector perpendicular to the first tangent vector, - // and in the direction of the minimum tangent xyz component - - normals[ 0 ] = new Vector3(); - binormals[ 0 ] = new Vector3(); - let min = Number.MAX_VALUE; - const tx = Math.abs( tangents[ 0 ].x ); - const ty = Math.abs( tangents[ 0 ].y ); - const tz = Math.abs( tangents[ 0 ].z ); - - if ( tx <= min ) { - - min = tx; - normal.set( 1, 0, 0 ); - - } - - if ( ty <= min ) { - - min = ty; - normal.set( 0, 1, 0 ); - - } - - if ( tz <= min ) { - - normal.set( 0, 0, 1 ); - - } - - vec.crossVectors( tangents[ 0 ], normal ).normalize(); - - normals[ 0 ].crossVectors( tangents[ 0 ], vec ); - binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - - - // compute the slowly-varying normal and binormal vectors for each segment on the curve - - for ( let i = 1; i <= segments; i ++ ) { - - normals[ i ] = normals[ i - 1 ].clone(); - - binormals[ i ] = binormals[ i - 1 ].clone(); - - vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); - - if ( vec.length() > Number.EPSILON ) { - - vec.normalize(); - - const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), -1, 1 ) ); // clamp for floating pt errors - - normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - - } - - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same - - if ( closed === true ) { - - let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), -1, 1 ) ); - theta /= segments; - - if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { - - theta = - theta; - - } - - for ( let i = 1; i <= segments; i ++ ) { - - // twist a little... - normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - } - - return { - tangents: tangents, - normals: normals, - binormals: binormals - }; - - } - - /** - * Returns a new curve with copied values from this instance. - * - * @return {Curve} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the values of the given curve to this instance. - * - * @param {Curve} source - The curve to copy. - * @return {Curve} A reference to this curve. - */ - copy( source ) { - - this.arcLengthDivisions = source.arcLengthDivisions; - - return this; - - } - - /** - * Serializes the curve into JSON. - * - * @return {Object} A JSON object representing the serialized curve. - * @see {@link ObjectLoader#parse} - */ - toJSON() { - - const data = { - metadata: { - version: 4.7, - type: 'Curve', - generator: 'Curve.toJSON' - } - }; - - data.arcLengthDivisions = this.arcLengthDivisions; - data.type = this.type; - - return data; - - } - - /** - * Deserializes the curve from the given JSON. - * - * @param {Object} json - The JSON holding the serialized curve. - * @return {Curve} A reference to this curve. - */ - fromJSON( json ) { - - this.arcLengthDivisions = json.arcLengthDivisions; - - return this; - - } - -} - -/** - * A curve representing an ellipse. - * - * ```js - * const curve = new THREE.EllipseCurve( - * 0, 0, - * 10, 10, - * 0, 2 * Math.PI, - * false, - * 0 - * ); - * - * const points = curve.getPoints( 50 ); - * const geometry = new THREE.BufferGeometry().setFromPoints( points ); - * - * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - * - * // Create the final object to add to the scene - * const ellipse = new THREE.Line( geometry, material ); - * ``` - * - * @augments Curve - */ -class EllipseCurve extends Curve { - - /** - * Constructs a new ellipse curve. - * - * @param {number} [aX=0] - The X center of the ellipse. - * @param {number} [aY=0] - The Y center of the ellipse. - * @param {number} [xRadius=1] - The radius of the ellipse in the x direction. - * @param {number} [yRadius=1] - The radius of the ellipse in the y direction. - * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. - * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. - * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. - * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. - */ - constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isEllipseCurve = true; - - this.type = 'EllipseCurve'; - - /** - * The X center of the ellipse. - * - * @type {number} - * @default 0 - */ - this.aX = aX; - - /** - * The Y center of the ellipse. - * - * @type {number} - * @default 0 - */ - this.aY = aY; - - /** - * The radius of the ellipse in the x direction. - * Setting the this value equal to the {@link EllipseCurve#yRadius} will result in a circle. - * - * @type {number} - * @default 1 - */ - this.xRadius = xRadius; - - /** - * The radius of the ellipse in the y direction. - * Setting the this value equal to the {@link EllipseCurve#xRadius} will result in a circle. - * - * @type {number} - * @default 1 - */ - this.yRadius = yRadius; - - /** - * The start angle of the curve in radians starting from the positive X axis. - * - * @type {number} - * @default 0 - */ - this.aStartAngle = aStartAngle; - - /** - * The end angle of the curve in radians starting from the positive X axis. - * - * @type {number} - * @default Math.PI*2 - */ - this.aEndAngle = aEndAngle; - - /** - * Whether the ellipse is drawn clockwise or not. - * - * @type {boolean} - * @default false - */ - this.aClockwise = aClockwise; - - /** - * The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. - * - * @type {number} - * @default 0 - */ - this.aRotation = aRotation; - - } - - /** - * Returns a point on the curve. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector2} The position on the curve. - */ - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const twoPi = Math.PI * 2; - let deltaAngle = this.aEndAngle - this.aStartAngle; - const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; - - // ensures that deltaAngle is 0 .. 2 PI - while ( deltaAngle < 0 ) deltaAngle += twoPi; - while ( deltaAngle > twoPi ) deltaAngle -= twoPi; - - if ( deltaAngle < Number.EPSILON ) { - - if ( samePoints ) { - - deltaAngle = 0; - - } else { - - deltaAngle = twoPi; - - } - - } - - if ( this.aClockwise === true && ! samePoints ) { - - if ( deltaAngle === twoPi ) { - - deltaAngle = - twoPi; - - } else { - - deltaAngle = deltaAngle - twoPi; - - } - - } - - const angle = this.aStartAngle + t * deltaAngle; - let x = this.aX + this.xRadius * Math.cos( angle ); - let y = this.aY + this.yRadius * Math.sin( angle ); - - if ( this.aRotation !== 0 ) { - - const cos = Math.cos( this.aRotation ); - const sin = Math.sin( this.aRotation ); - - const tx = x - this.aX; - const ty = y - this.aY; - - // Rotate the point about the center of the ellipse. - x = tx * cos - ty * sin + this.aX; - y = tx * sin + ty * cos + this.aY; - - } - - return point.set( x, y ); - - } - - copy( source ) { - - super.copy( source ); - - this.aX = source.aX; - this.aY = source.aY; - - this.xRadius = source.xRadius; - this.yRadius = source.yRadius; - - this.aStartAngle = source.aStartAngle; - this.aEndAngle = source.aEndAngle; - - this.aClockwise = source.aClockwise; - - this.aRotation = source.aRotation; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.aX = this.aX; - data.aY = this.aY; - - data.xRadius = this.xRadius; - data.yRadius = this.yRadius; - - data.aStartAngle = this.aStartAngle; - data.aEndAngle = this.aEndAngle; - - data.aClockwise = this.aClockwise; - - data.aRotation = this.aRotation; - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.aX = json.aX; - this.aY = json.aY; - - this.xRadius = json.xRadius; - this.yRadius = json.yRadius; - - this.aStartAngle = json.aStartAngle; - this.aEndAngle = json.aEndAngle; - - this.aClockwise = json.aClockwise; - - this.aRotation = json.aRotation; - - return this; - - } - -} - -/** - * A curve representing an arc. - * - * @augments EllipseCurve - */ -class ArcCurve extends EllipseCurve { - - /** - * Constructs a new arc curve. - * - * @param {number} [aX=0] - The X center of the ellipse. - * @param {number} [aY=0] - The Y center of the ellipse. - * @param {number} [aRadius=1] - The radius of the ellipse in the x direction. - * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. - * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. - * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. - */ - constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isArcCurve = true; - - this.type = 'ArcCurve'; - - } - -} - -function CubicPoly() { - - /** - * Centripetal CatmullRom Curve - which is useful for avoiding - * cusps and self-intersections in non-uniform catmull rom curves. - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf - * - * curve.type accepts centripetal(default), chordal and catmullrom - * curve.tension is used for catmullrom which defaults to 0.5 - */ - - /* - Based on an optimized c++ solution in - - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - - http://ideone.com/NoEbVM - - This CubicPoly class could be used for reusing some variables and calculations, - but for three.js curve use, it could be possible inlined and flatten into a single function call - which can be placed in CurveUtils. - */ - - let c0 = 0, c1 = 0, c2 = 0, c3 = 0; - - /* - * Compute coefficients for a cubic polynomial - * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 - * such that - * p(0) = x0, p(1) = x1 - * and - * p'(0) = t0, p'(1) = t1. - */ - function init( x0, x1, t0, t1 ) { - - c0 = x0; - c1 = t0; - c2 = -3 * x0 + 3 * x1 - 2 * t0 - t1; - c3 = 2 * x0 - 2 * x1 + t0 + t1; - - } - - return { - - initCatmullRom: function ( x0, x1, x2, x3, tension ) { - - init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); - - }, - - initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { - - // compute tangents when parameterized in [t1,t2] - let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; - let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - - // rescale tangents for parametrization in [0,1] - t1 *= dt1; - t2 *= dt1; - - init( x1, x2, t1, t2 ); - - }, - - calc: function ( t ) { - - const t2 = t * t; - const t3 = t2 * t; - return c0 + c1 * t + c2 * t2 + c3 * t3; - - } - - }; - -} - -// - -const tmp = /*@__PURE__*/ new Vector3(); -const tmp2 = /*@__PURE__*/ new Vector3(); -const px = /*@__PURE__*/ new CubicPoly(); -const py = /*@__PURE__*/ new CubicPoly(); -const pz = /*@__PURE__*/ new CubicPoly(); - -/** - * A curve representing a Catmull-Rom spline. - * - * ```js - * //Create a closed wavey loop - * const curve = new THREE.CatmullRomCurve3( [ - * new THREE.Vector3( -10, 0, 10 ), - * new THREE.Vector3( -5, 5, 5 ), - * new THREE.Vector3( 0, 0, 0 ), - * new THREE.Vector3( 5, -5, 5 ), - * new THREE.Vector3( 10, 0, 10 ) - * ] ); - * - * const points = curve.getPoints( 50 ); - * const geometry = new THREE.BufferGeometry().setFromPoints( points ); - * - * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - * - * // Create the final object to add to the scene - * const curveObject = new THREE.Line( geometry, material ); - * ``` - * - * @augments Curve - */ -class CatmullRomCurve3 extends Curve { - - /** - * Constructs a new Catmull-Rom curve. - * - * @param {Array} [points] - An array of 3D points defining the curve. - * @param {boolean} [closed=false] - Whether the curve is closed or not. - * @param {('centripetal'|'chordal'|'catmullrom')} [curveType='centripetal'] - The curve type. - * @param {number} [tension=0.5] - Tension of the curve. - */ - constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCatmullRomCurve3 = true; - - this.type = 'CatmullRomCurve3'; - - /** - * An array of 3D points defining the curve. - * - * @type {Array} - */ - this.points = points; - - /** - * Whether the curve is closed or not. - * - * @type {boolean} - * @default false - */ - this.closed = closed; - - /** - * The curve type. - * - * @type {('centripetal'|'chordal'|'catmullrom')} - * @default 'centripetal' - */ - this.curveType = curveType; - - /** - * Tension of the curve. - * - * @type {number} - * @default 0.5 - */ - this.tension = tension; - - } - - /** - * Returns a point on the curve. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector3} The position on the curve. - */ - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const points = this.points; - const l = points.length; - - const p = ( l - ( this.closed ? 0 : 1 ) ) * t; - let intPoint = Math.floor( p ); - let weight = p - intPoint; - - if ( this.closed ) { - - intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; - - } else if ( weight === 0 && intPoint === l - 1 ) { - - intPoint = l - 2; - weight = 1; - - } - - let p0, p3; // 4 points (p1 & p2 defined below) - - if ( this.closed || intPoint > 0 ) { - - p0 = points[ ( intPoint - 1 ) % l ]; - - } else { - - // extrapolate first point - tmp2.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); - p0 = tmp2; - - } - - const p1 = points[ intPoint % l ]; - const p2 = points[ ( intPoint + 1 ) % l ]; - - if ( this.closed || intPoint + 2 < l ) { - - p3 = points[ ( intPoint + 2 ) % l ]; - - } else { - - // extrapolate last point - tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); - p3 = tmp; - - } - - if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { - - // init Centripetal / Chordal Catmull-Rom - const pow = this.curveType === 'chordal' ? 0.5 : 0.25; - let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); - let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); - let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - - // safety check for repeated points - if ( dt1 < 1e-4 ) dt1 = 1.0; - if ( dt0 < 1e-4 ) dt0 = dt1; - if ( dt2 < 1e-4 ) dt2 = dt1; - - px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); - py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); - pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); - - } else if ( this.curveType === 'catmullrom' ) { - - px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); - py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); - pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); - - } - - point.set( - px.calc( weight ), - py.calc( weight ), - pz.calc( weight ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.points = []; - - for ( let i = 0, l = source.points.length; i < l; i ++ ) { - - const point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - this.closed = source.closed; - this.curveType = source.curveType; - this.tension = source.tension; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.points = []; - - for ( let i = 0, l = this.points.length; i < l; i ++ ) { - - const point = this.points[ i ]; - data.points.push( point.toArray() ); - - } - - data.closed = this.closed; - data.curveType = this.curveType; - data.tension = this.tension; - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; - - for ( let i = 0, l = json.points.length; i < l; i ++ ) { - - const point = json.points[ i ]; - this.points.push( new Vector3().fromArray( point ) ); - - } - - this.closed = json.closed; - this.curveType = json.curveType; - this.tension = json.tension; - - return this; - - } - -} - -/** - * Interpolations contains spline and Bézier functions internally used by concrete curve classes. - * - * Bezier Curves formulas obtained from: https://en.wikipedia.org/wiki/B%C3%A9zier_curve - * - * @module Interpolations - */ - -/** - * Computes a point on a Catmull-Rom spline. - * - * @param {number} t - The interpolation factor. - * @param {number} p0 - The first control point. - * @param {number} p1 - The second control point. - * @param {number} p2 - The third control point. - * @param {number} p3 - The fourth control point. - * @return {number} The calculated point on a Catmull-Rom spline. - */ -function CatmullRom( t, p0, p1, p2, p3 ) { - - const v0 = ( p2 - p0 ) * 0.5; - const v1 = ( p3 - p1 ) * 0.5; - const t2 = t * t; - const t3 = t * t2; - return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( -3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; - -} - -// - -function QuadraticBezierP0( t, p ) { - - const k = 1 - t; - return k * k * p; - -} - -function QuadraticBezierP1( t, p ) { - - return 2 * ( 1 - t ) * t * p; - -} - -function QuadraticBezierP2( t, p ) { - - return t * t * p; - -} - -/** - * Computes a point on a Quadratic Bezier curve. - * - * @param {number} t - The interpolation factor. - * @param {number} p0 - The first control point. - * @param {number} p1 - The second control point. - * @param {number} p2 - The third control point. - * @return {number} The calculated point on a Quadratic Bezier curve. - */ -function QuadraticBezier( t, p0, p1, p2 ) { - - return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + - QuadraticBezierP2( t, p2 ); - -} - -// - -function CubicBezierP0( t, p ) { - - const k = 1 - t; - return k * k * k * p; - -} - -function CubicBezierP1( t, p ) { - - const k = 1 - t; - return 3 * k * k * t * p; - -} - -function CubicBezierP2( t, p ) { - - return 3 * ( 1 - t ) * t * t * p; - -} - -function CubicBezierP3( t, p ) { - - return t * t * t * p; - -} - -/** - * Computes a point on a Cubic Bezier curve. - * - * @param {number} t - The interpolation factor. - * @param {number} p0 - The first control point. - * @param {number} p1 - The second control point. - * @param {number} p2 - The third control point. - * @param {number} p3 - The fourth control point. - * @return {number} The calculated point on a Cubic Bezier curve. - */ -function CubicBezier( t, p0, p1, p2, p3 ) { - - return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + - CubicBezierP3( t, p3 ); - -} - -/** - * A curve representing a 2D Cubic Bezier curve. - * - * ```js - * const curve = new THREE.CubicBezierCurve( - * new THREE.Vector2( - 0, 0 ), - * new THREE.Vector2( - 5, 15 ), - * new THREE.Vector2( 20, 15 ), - * new THREE.Vector2( 10, 0 ) - * ); - * - * const points = curve.getPoints( 50 ); - * const geometry = new THREE.BufferGeometry().setFromPoints( points ); - * - * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - * - * // Create the final object to add to the scene - * const curveObject = new THREE.Line( geometry, material ); - * ``` - * - * @augments Curve - */ -class CubicBezierCurve extends Curve { - - /** - * Constructs a new Cubic Bezier curve. - * - * @param {Vector2} [v0] - The start point. - * @param {Vector2} [v1] - The first control point. - * @param {Vector2} [v2] - The second control point. - * @param {Vector2} [v3] - The end point. - */ - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCubicBezierCurve = true; - - this.type = 'CubicBezierCurve'; - - /** - * The start point. - * - * @type {Vector2} - */ - this.v0 = v0; - - /** - * The first control point. - * - * @type {Vector2} - */ - this.v1 = v1; - - /** - * The second control point. - * - * @type {Vector2} - */ - this.v2 = v2; - - /** - * The end point. - * - * @type {Vector2} - */ - this.v3 = v3; - - } - - /** - * Returns a point on the curve. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector2} The position on the curve. - */ - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); - - return this; - - } - -} - -/** - * A curve representing a 3D Cubic Bezier curve. - * - * @augments Curve - */ -class CubicBezierCurve3 extends Curve { - - /** - * Constructs a new Cubic Bezier curve. - * - * @param {Vector3} [v0] - The start point. - * @param {Vector3} [v1] - The first control point. - * @param {Vector3} [v2] - The second control point. - * @param {Vector3} [v3] - The end point. - */ - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isCubicBezierCurve3 = true; - - this.type = 'CubicBezierCurve3'; - - /** - * The start point. - * - * @type {Vector3} - */ - this.v0 = v0; - - /** - * The first control point. - * - * @type {Vector3} - */ - this.v1 = v1; - - /** - * The second control point. - * - * @type {Vector3} - */ - this.v2 = v2; - - /** - * The end point. - * - * @type {Vector3} - */ - this.v3 = v3; - - } - - /** - * Returns a point on the curve. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector3} The position on the curve. - */ - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), - CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); - - return this; - - } - -} - -/** - * A curve representing a 2D line segment. - * - * @augments Curve - */ -class LineCurve extends Curve { - - /** - * Constructs a new line curve. - * - * @param {Vector2} [v1] - The start point. - * @param {Vector2} [v2] - The end point. - */ - constructor( v1 = new Vector2(), v2 = new Vector2() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isLineCurve = true; - - this.type = 'LineCurve'; - - /** - * The start point. - * - * @type {Vector2} - */ - this.v1 = v1; - - /** - * The end point. - * - * @type {Vector2} - */ - this.v2 = v2; - - } - - /** - * Returns a point on the line. - * - * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. - * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector2} The position on the line. - */ - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - - } - - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - - } - - getTangent( t, optionalTarget = new Vector2() ) { - - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - - } - - getTangentAt( u, optionalTarget ) { - - return this.getTangent( u, optionalTarget ); - - } - - copy( source ) { - - super.copy( source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -/** - * A curve representing a 3D line segment. - * - * @augments Curve - */ -class LineCurve3 extends Curve { - - /** - * Constructs a new line curve. - * - * @param {Vector3} [v1] - The start point. - * @param {Vector3} [v2] - The end point. - */ - constructor( v1 = new Vector3(), v2 = new Vector3() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isLineCurve3 = true; - - this.type = 'LineCurve3'; - - /** - * The start point. - * - * @type {Vector3} - */ - this.v1 = v1; - - /** - * The end point. - * - * @type {Vector2} - */ - this.v2 = v2; - - } - - /** - * Returns a point on the line. - * - * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. - * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector3} The position on the line. - */ - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - - } - - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - - } - - getTangent( t, optionalTarget = new Vector3() ) { - - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - - } - - getTangentAt( u, optionalTarget ) { - - return this.getTangent( u, optionalTarget ); - - } - - copy( source ) { - - super.copy( source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -/** - * A curve representing a 2D Quadratic Bezier curve. - * - * ```js - * const curve = new THREE.QuadraticBezierCurve( - * new THREE.Vector2( - 10, 0 ), - * new THREE.Vector2( 20, 15 ), - * new THREE.Vector2( 10, 0 ) - * ) - * - * const points = curve.getPoints( 50 ); - * const geometry = new THREE.BufferGeometry().setFromPoints( points ); - * - * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - * - * // Create the final object to add to the scene - * const curveObject = new THREE.Line( geometry, material ); - * ``` - * - * @augments Curve - */ -class QuadraticBezierCurve extends Curve { - - /** - * Constructs a new Quadratic Bezier curve. - * - * @param {Vector2} [v0] - The start point. - * @param {Vector2} [v1] - The control point. - * @param {Vector2} [v2] - The end point. - */ - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isQuadraticBezierCurve = true; - - this.type = 'QuadraticBezierCurve'; - - /** - * The start point. - * - * @type {Vector2} - */ - this.v0 = v0; - - /** - * The control point. - * - * @type {Vector2} - */ - this.v1 = v1; - - /** - * The end point. - * - * @type {Vector2} - */ - this.v2 = v2; - - } - - /** - * Returns a point on the curve. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector2} The position on the curve. - */ - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -/** - * A curve representing a 3D Quadratic Bezier curve. - * - * @augments Curve - */ -class QuadraticBezierCurve3 extends Curve { - - /** - * Constructs a new Quadratic Bezier curve. - * - * @param {Vector3} [v0] - The start point. - * @param {Vector3} [v1] - The control point. - * @param {Vector3} [v2] - The end point. - */ - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isQuadraticBezierCurve3 = true; - - this.type = 'QuadraticBezierCurve3'; - - /** - * The start point. - * - * @type {Vector3} - */ - this.v0 = v0; - - /** - * The control point. - * - * @type {Vector3} - */ - this.v1 = v1; - - /** - * The end point. - * - * @type {Vector3} - */ - this.v2 = v2; - - } - - /** - * Returns a point on the curve. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector3} The position on the curve. - */ - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ), - QuadraticBezier( t, v0.z, v1.z, v2.z ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -/** - * A curve representing a 2D spline curve. - * - * ```js - * // Create a sine-like wave - * const curve = new THREE.SplineCurve( [ - * new THREE.Vector2( -10, 0 ), - * new THREE.Vector2( -5, 5 ), - * new THREE.Vector2( 0, 0 ), - * new THREE.Vector2( 5, -5 ), - * new THREE.Vector2( 10, 0 ) - * ] ); - * - * const points = curve.getPoints( 50 ); - * const geometry = new THREE.BufferGeometry().setFromPoints( points ); - * - * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - * - * // Create the final object to add to the scene - * const splineObject = new THREE.Line( geometry, material ); - * ``` - * - * @augments Curve - */ -class SplineCurve extends Curve { - - /** - * Constructs a new 2D spline curve. - * - * @param {Array} [points] - An array of 2D points defining the curve. - */ - constructor( points = [] ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isSplineCurve = true; - - this.type = 'SplineCurve'; - - /** - * An array of 2D points defining the curve. - * - * @type {Array} - */ - this.points = points; - - } - - /** - * Returns a point on the curve. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. - * @return {Vector2} The position on the curve. - */ - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const points = this.points; - const p = ( points.length - 1 ) * t; - - const intPoint = Math.floor( p ); - const weight = p - intPoint; - - const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; - const p1 = points[ intPoint ]; - const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; - const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - - point.set( - CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), - CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.points = []; - - for ( let i = 0, l = source.points.length; i < l; i ++ ) { - - const point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.points = []; - - for ( let i = 0, l = this.points.length; i < l; i ++ ) { - - const point = this.points[ i ]; - data.points.push( point.toArray() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; - - for ( let i = 0, l = json.points.length; i < l; i ++ ) { - - const point = json.points[ i ]; - this.points.push( new Vector2().fromArray( point ) ); - - } - - return this; - - } - -} - -var Curves = /*#__PURE__*/Object.freeze({ - __proto__: null, - ArcCurve: ArcCurve, - CatmullRomCurve3: CatmullRomCurve3, - CubicBezierCurve: CubicBezierCurve, - CubicBezierCurve3: CubicBezierCurve3, - EllipseCurve: EllipseCurve, - LineCurve: LineCurve, - LineCurve3: LineCurve3, - QuadraticBezierCurve: QuadraticBezierCurve, - QuadraticBezierCurve3: QuadraticBezierCurve3, - SplineCurve: SplineCurve -}); - -/** - * A base class extending {@link Curve}. `CurvePath` is simply an - * array of connected curves, but retains the API of a curve. - * - * @augments Curve - */ -class CurvePath extends Curve { - - /** - * Constructs a new curve path. - */ - constructor() { - - super(); - - this.type = 'CurvePath'; - - /** - * An array of curves defining the - * path. - * - * @type {Array} - */ - this.curves = []; - - /** - * Whether the path should automatically be closed - * by a line curve. - * - * @type {boolean} - * @default false - */ - this.autoClose = false; - - } - - /** - * Adds a curve to this curve path. - * - * @param {Curve} curve - The curve to add. - */ - add( curve ) { - - this.curves.push( curve ); - - } - - /** - * Adds a line curve to close the path. - * - * @return {CurvePath} A reference to this curve path. - */ - closePath() { - - // Add a line curve if start and end of lines are not connected - const startPoint = this.curves[ 0 ].getPoint( 0 ); - const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); - - if ( ! startPoint.equals( endPoint ) ) { - - const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; - this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); - - } - - return this; - - } - - /** - * This method returns a vector in 2D or 3D space (depending on the curve definitions) - * for the given interpolation factor. - * - * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. - * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. - * @return {?(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. - */ - getPoint( t, optionalTarget ) { - - // To get accurate point with reference to - // entire path distance at time t, - // following has to be done: - - // 1. Length of each sub path have to be known - // 2. Locate and identify type of curve - // 3. Get t for the curve - // 4. Return curve.getPointAt(t') - - const d = t * this.getLength(); - const curveLengths = this.getCurveLengths(); - let i = 0; - - // To think about boundaries points. - - while ( i < curveLengths.length ) { - - if ( curveLengths[ i ] >= d ) { - - const diff = curveLengths[ i ] - d; - const curve = this.curves[ i ]; - - const segmentLength = curve.getLength(); - const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; - - return curve.getPointAt( u, optionalTarget ); - - } - - i ++; - - } - - return null; - - // loop where sum != 0, sum > d , sum+1 } The curve lengths. - */ - getCurveLengths() { - - // Compute lengths and cache them - // We cannot overwrite getLengths() because UtoT mapping uses it. - // We use cache values if curves and cache array are same length - - if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) { - - return this.cacheLengths; - - } - - // Get length of sub-curve - // Push sums into cached array - - const lengths = []; - let sums = 0; - - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { - - sums += this.curves[ i ].getLength(); - lengths.push( sums ); - - } - - this.cacheLengths = lengths; - - return lengths; - - } - - getSpacedPoints( divisions = 40 ) { - - const points = []; - - for ( let i = 0; i <= divisions; i ++ ) { - - points.push( this.getPoint( i / divisions ) ); - - } - - if ( this.autoClose ) { - - points.push( points[ 0 ] ); - - } - - return points; - - } - - getPoints( divisions = 12 ) { - - const points = []; - let last; - - for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { - - const curve = curves[ i ]; - const resolution = curve.isEllipseCurve ? divisions * 2 - : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 - : curve.isSplineCurve ? divisions * curve.points.length - : divisions; - - const pts = curve.getPoints( resolution ); - - for ( let j = 0; j < pts.length; j ++ ) { - - const point = pts[ j ]; - - if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates - - points.push( point ); - last = point; - - } - - } - - if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { - - points.push( points[ 0 ] ); - - } - - return points; - - } - - copy( source ) { - - super.copy( source ); - - this.curves = []; - - for ( let i = 0, l = source.curves.length; i < l; i ++ ) { - - const curve = source.curves[ i ]; - - this.curves.push( curve.clone() ); - - } - - this.autoClose = source.autoClose; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.autoClose = this.autoClose; - data.curves = []; - - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { - - const curve = this.curves[ i ]; - data.curves.push( curve.toJSON() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.autoClose = json.autoClose; - this.curves = []; - - for ( let i = 0, l = json.curves.length; i < l; i ++ ) { - - const curve = json.curves[ i ]; - this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); - - } - - return this; - - } - -} - -/** - * A 2D path representation. The class provides methods for creating paths - * and contours of 2D shapes similar to the 2D Canvas API. - * - * ```js - * const path = new THREE.Path(); - * - * path.lineTo( 0, 0.8 ); - * path.quadraticCurveTo( 0, 1, 0.2, 1 ); - * path.lineTo( 1, 1 ); - * - * const points = path.getPoints(); - * - * const geometry = new THREE.BufferGeometry().setFromPoints( points ); - * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); - * - * const line = new THREE.Line( geometry, material ); - * scene.add( line ); - * ``` - * - * @augments CurvePath - */ -let Path$1 = class Path extends CurvePath { - - /** - * Constructs a new path. - * - * @param {Array} [points] - An array of 2D points defining the path. - */ - constructor( points ) { - - super(); - - this.type = 'Path'; - - /** - * The current offset of the path. Any new curve added will start here. - * - * @type {Vector2} - */ - this.currentPoint = new Vector2(); - - if ( points ) { - - this.setFromPoints( points ); - - } - - } - - /** - * Creates a path from the given list of points. The points are added - * to the path as instances of {@link LineCurve}. - * - * @param {Array} points - An array of 2D points. - * @return {Path} A reference to this path. - */ - setFromPoints( points ) { - - this.moveTo( points[ 0 ].x, points[ 0 ].y ); - - for ( let i = 1, l = points.length; i < l; i ++ ) { - - this.lineTo( points[ i ].x, points[ i ].y ); - - } - - return this; - - } - - /** - * Moves {@link Path#currentPoint} to the given point. - * - * @param {number} x - The x coordinate. - * @param {number} y - The y coordinate. - * @return {Path} A reference to this path. - */ - moveTo( x, y ) { - - this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? - - return this; - - } - - /** - * Adds an instance of {@link LineCurve} to the path by connecting - * the current point with the given one. - * - * @param {number} x - The x coordinate of the end point. - * @param {number} y - The y coordinate of the end point. - * @return {Path} A reference to this path. - */ - lineTo( x, y ) { - - const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); - this.curves.push( curve ); - - this.currentPoint.set( x, y ); - - return this; - - } - - /** - * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting - * the current point with the given one. - * - * @param {number} aCPx - The x coordinate of the control point. - * @param {number} aCPy - The y coordinate of the control point. - * @param {number} aX - The x coordinate of the end point. - * @param {number} aY - The y coordinate of the end point. - * @return {Path} A reference to this path. - */ - quadraticCurveTo( aCPx, aCPy, aX, aY ) { - - const curve = new QuadraticBezierCurve( - this.currentPoint.clone(), - new Vector2( aCPx, aCPy ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - return this; - - } - - /** - * Adds an instance of {@link CubicBezierCurve} to the path by connecting - * the current point with the given one. - * - * @param {number} aCP1x - The x coordinate of the first control point. - * @param {number} aCP1y - The y coordinate of the first control point. - * @param {number} aCP2x - The x coordinate of the second control point. - * @param {number} aCP2y - The y coordinate of the second control point. - * @param {number} aX - The x coordinate of the end point. - * @param {number} aY - The y coordinate of the end point. - * @return {Path} A reference to this path. - */ - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - - const curve = new CubicBezierCurve( - this.currentPoint.clone(), - new Vector2( aCP1x, aCP1y ), - new Vector2( aCP2x, aCP2y ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - return this; - - } - - /** - * Adds an instance of {@link SplineCurve} to the path by connecting - * the current point with the given list of points. - * - * @param {Array} pts - An array of points in 2D space. - * @return {Path} A reference to this path. - */ - splineThru( pts ) { - - const npts = [ this.currentPoint.clone() ].concat( pts ); - - const curve = new SplineCurve( npts ); - this.curves.push( curve ); - - this.currentPoint.copy( pts[ pts.length - 1 ] ); - - return this; - - } - - /** - * Adds an arc as an instance of {@link EllipseCurve} to the path, positioned relative - * to the current point. - * - * @param {number} [aX=0] - The x coordinate of the center of the arc offsetted from the previous curve. - * @param {number} [aY=0] - The y coordinate of the center of the arc offsetted from the previous curve. - * @param {number} [aRadius=1] - The radius of the arc. - * @param {number} [aStartAngle=0] - The start angle in radians. - * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. - * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. - * @return {Path} A reference to this path. - */ - arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; - - this.absarc( aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise ); - - return this; - - } - - /** - * Adds an absolutely positioned arc as an instance of {@link EllipseCurve} to the path. - * - * @param {number} [aX=0] - The x coordinate of the center of the arc. - * @param {number} [aY=0] - The y coordinate of the center of the arc. - * @param {number} [aRadius=1] - The radius of the arc. - * @param {number} [aStartAngle=0] - The start angle in radians. - * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. - * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. - * @return {Path} A reference to this path. - */ - absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - return this; - - } - - /** - * Adds an ellipse as an instance of {@link EllipseCurve} to the path, positioned relative - * to the current point - * - * @param {number} [aX=0] - The x coordinate of the center of the ellipse offsetted from the previous curve. - * @param {number} [aY=0] - The y coordinate of the center of the ellipse offsetted from the previous curve. - * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. - * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. - * @param {number} [aStartAngle=0] - The start angle in radians. - * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. - * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. - * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. - * @return {Path} A reference to this path. - */ - ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; - - this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - return this; - - } - - /** - * Adds an absolutely positioned ellipse as an instance of {@link EllipseCurve} to the path. - * - * @param {number} [aX=0] - The x coordinate of the absolute center of the ellipse. - * @param {number} [aY=0] - The y coordinate of the absolute center of the ellipse. - * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. - * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. - * @param {number} [aStartAngle=0] - The start angle in radians. - * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. - * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. - * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. - * @return {Path} A reference to this path. - */ - absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - if ( this.curves.length > 0 ) { - - // if a previous curve is present, attempt to join - const firstPoint = curve.getPoint( 0 ); - - if ( ! firstPoint.equals( this.currentPoint ) ) { - - this.lineTo( firstPoint.x, firstPoint.y ); - - } - - } - - this.curves.push( curve ); - - const lastPoint = curve.getPoint( 1 ); - this.currentPoint.copy( lastPoint ); - - return this; - - } - - copy( source ) { - - super.copy( source ); - - this.currentPoint.copy( source.currentPoint ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.currentPoint = this.currentPoint.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.currentPoint.fromArray( json.currentPoint ); - - return this; - - } - -}; - -/** - * Defines an arbitrary 2d shape plane using paths with optional holes. It - * can be used with {@link ExtrudeGeometry}, {@link ShapeGeometry}, to get - * points, or to get triangulated faces. - * - * ```js - * const heartShape = new THREE.Shape(); - * - * heartShape.moveTo( 25, 25 ); - * heartShape.bezierCurveTo( 25, 25, 20, 0, 0, 0 ); - * heartShape.bezierCurveTo( - 30, 0, - 30, 35, - 30, 35 ); - * heartShape.bezierCurveTo( - 30, 55, - 10, 77, 25, 95 ); - * heartShape.bezierCurveTo( 60, 77, 80, 55, 80, 35 ); - * heartShape.bezierCurveTo( 80, 35, 80, 0, 50, 0 ); - * heartShape.bezierCurveTo( 35, 0, 25, 25, 25, 25 ); - * - * const extrudeSettings = { - * depth: 8, - * bevelEnabled: true, - * bevelSegments: 2, - * steps: 2, - * bevelSize: 1, - * bevelThickness: 1 - * }; - * - * const geometry = new THREE.ExtrudeGeometry( heartShape, extrudeSettings ); - * const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() ); - * ``` - * - * @augments Path - */ -class Shape extends Path$1 { - - /** - * Constructs a new shape. - * - * @param {Array} [points] - An array of 2D points defining the shape. - */ - constructor( points ) { - - super( points ); - - /** - * The UUID of the shape. - * - * @type {string} - * @readonly - */ - this.uuid = generateUUID(); - - this.type = 'Shape'; - - /** - * Defines the holes in the shape. Hole definitions must use the - * opposite winding order (CW/CCW) than the outer shape. - * - * @type {Array} - * @readonly - */ - this.holes = []; - - } - - /** - * Returns an array representing each contour of the holes - * as a list of 2D points. - * - * @param {number} divisions - The fineness of the result. - * @return {Array>} The holes as a series of 2D points. - */ - getPointsHoles( divisions ) { - - const holesPts = []; - - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - - holesPts[ i ] = this.holes[ i ].getPoints( divisions ); - - } - - return holesPts; - - } - - // get points of shape and holes (keypoints based on segments parameter) - - /** - * Returns an object that holds contour data for the shape and its holes as - * arrays of 2D points. - * - * @param {number} divisions - The fineness of the result. - * @return {{shape:Array,holes:Array>}} An object with contour data. - */ - extractPoints( divisions ) { - - return { - - shape: this.getPoints( divisions ), - holes: this.getPointsHoles( divisions ) - - }; - - } - - copy( source ) { - - super.copy( source ); - - this.holes = []; - - for ( let i = 0, l = source.holes.length; i < l; i ++ ) { - - const hole = source.holes[ i ]; - - this.holes.push( hole.clone() ); - - } - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.uuid = this.uuid; - data.holes = []; - - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - - const hole = this.holes[ i ]; - data.holes.push( hole.toJSON() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.uuid = json.uuid; - this.holes = []; - - for ( let i = 0, l = json.holes.length; i < l; i ++ ) { - - const hole = json.holes[ i ]; - this.holes.push( new Path$1().fromJSON( hole ) ); - - } - - return this; - - } - -} - -/* eslint-disable */ -// copy of mapbox/earcut version 3.0.2 -// https://github.com/mapbox/earcut/tree/v3.0.2 - -function earcut(data, holeIndices, dim = 2) { - - const hasHoles = holeIndices && holeIndices.length; - const outerLen = hasHoles ? holeIndices[0] * dim : data.length; - let outerNode = linkedList(data, 0, outerLen, dim, true); - const triangles = []; - - if (!outerNode || outerNode.next === outerNode.prev) return triangles; - - let minX, minY, invSize; - - if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); - - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - if (data.length > 80 * dim) { - minX = data[0]; - minY = data[1]; - let maxX = minX; - let maxY = minY; - - for (let i = dim; i < outerLen; i += dim) { - const x = data[i]; - const y = data[i + 1]; - if (x < minX) minX = x; - if (y < minY) minY = y; - if (x > maxX) maxX = x; - if (y > maxY) maxY = y; - } - - // minX, minY and invSize are later used to transform coords into integers for z-order calculation - invSize = Math.max(maxX - minX, maxY - minY); - invSize = invSize !== 0 ? 32767 / invSize : 0; - } - - earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); - - return triangles; -} - -// create a circular doubly linked list from polygon points in the specified winding order -function linkedList(data, start, end, dim, clockwise) { - let last; - - if (clockwise === (signedArea(data, start, end, dim) > 0)) { - for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); - } else { - for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); - } - - if (last && equals(last, last.next)) { - removeNode(last); - last = last.next; - } - - return last; -} - -// eliminate colinear or duplicate points -function filterPoints(start, end) { - if (!start) return start; - if (!end) end = start; - - let p = start, - again; - do { - again = false; - - if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { - removeNode(p); - p = end = p.prev; - if (p === p.next) break; - again = true; - - } else { - p = p.next; - } - } while (again || p !== end); - - return end; -} - -// main ear slicing loop which triangulates a polygon (given as a linked list) -function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { - if (!ear) return; - - // interlink polygon nodes in z-order - if (!pass && invSize) indexCurve(ear, minX, minY, invSize); - - let stop = ear; - - // iterate through ears, slicing them one by one - while (ear.prev !== ear.next) { - const prev = ear.prev; - const next = ear.next; - - if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { - triangles.push(prev.i, ear.i, next.i); // cut off the triangle - - removeNode(ear); - - // skipping the next vertex leads to less sliver triangles - ear = next.next; - stop = next.next; - - continue; - } - - ear = next; - - // if we looped through the whole remaining polygon and can't find any more ears - if (ear === stop) { - // try filtering points and slicing again - if (!pass) { - earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); - - // if this didn't work, try curing all small self-intersections locally - } else if (pass === 1) { - ear = cureLocalIntersections(filterPoints(ear), triangles); - earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); - - // as a last resort, try splitting the remaining polygon into two - } else if (pass === 2) { - splitEarcut(ear, triangles, dim, minX, minY, invSize); - } - - break; - } - } -} - -// check whether a polygon node forms a valid ear with adjacent nodes -function isEar(ear) { - const a = ear.prev, - b = ear, - c = ear.next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - // now make sure we don't have other points inside the potential ear - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; - - // triangle bbox - const x0 = Math.min(ax, bx, cx), - y0 = Math.min(ay, by, cy), - x1 = Math.max(ax, bx, cx), - y1 = Math.max(ay, by, cy); - - let p = c.next; - while (p !== a) { - if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && - pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && - area(p.prev, p, p.next) >= 0) return false; - p = p.next; - } - - return true; -} - -function isEarHashed(ear, minX, minY, invSize) { - const a = ear.prev, - b = ear, - c = ear.next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; - - // triangle bbox - const x0 = Math.min(ax, bx, cx), - y0 = Math.min(ay, by, cy), - x1 = Math.max(ax, bx, cx), - y1 = Math.max(ay, by, cy); - - // z-order range for the current triangle bbox; - const minZ = zOrder(x0, y0, minX, minY, invSize), - maxZ = zOrder(x1, y1, minX, minY, invSize); - - let p = ear.prevZ, - n = ear.nextZ; - - // look for points inside the triangle in both directions - while (p && p.z >= minZ && n && n.z <= maxZ) { - if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; - p = p.prevZ; - - if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; - n = n.nextZ; - } - - // look for remaining points in decreasing z-order - while (p && p.z >= minZ) { - if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; - p = p.prevZ; - } - - // look for remaining points in increasing z-order - while (n && n.z <= maxZ) { - if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; - n = n.nextZ; - } - - return true; -} - -// go through all polygon nodes and cure small local self-intersections -function cureLocalIntersections(start, triangles) { - let p = start; - do { - const a = p.prev, - b = p.next.next; - - if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { - - triangles.push(a.i, p.i, b.i); - - // remove two nodes involved - removeNode(p); - removeNode(p.next); - - p = start = b; - } - p = p.next; - } while (p !== start); - - return filterPoints(p); -} - -// try splitting polygon into two and triangulate them independently -function splitEarcut(start, triangles, dim, minX, minY, invSize) { - // look for a valid diagonal that divides the polygon into two - let a = start; - do { - let b = a.next.next; - while (b !== a.prev) { - if (a.i !== b.i && isValidDiagonal(a, b)) { - // split the polygon in two by the diagonal - let c = splitPolygon(a, b); - - // filter colinear points around the cuts - a = filterPoints(a, a.next); - c = filterPoints(c, c.next); - - // run earcut on each half - earcutLinked(a, triangles, dim, minX, minY, invSize, 0); - earcutLinked(c, triangles, dim, minX, minY, invSize, 0); - return; - } - b = b.next; - } - a = a.next; - } while (a !== start); -} - -// link every hole into the outer loop, producing a single-ring polygon without holes -function eliminateHoles(data, holeIndices, outerNode, dim) { - const queue = []; - - for (let i = 0, len = holeIndices.length; i < len; i++) { - const start = holeIndices[i] * dim; - const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; - const list = linkedList(data, start, end, dim, false); - if (list === list.next) list.steiner = true; - queue.push(getLeftmost(list)); - } - - queue.sort(compareXYSlope); - - // process holes from left to right - for (let i = 0; i < queue.length; i++) { - outerNode = eliminateHole(queue[i], outerNode); - } - - return outerNode; -} - -function compareXYSlope(a, b) { - let result = a.x - b.x; - // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find - // the bridge to the outer shell is always the point that they meet at. - if (result === 0) { - result = a.y - b.y; - if (result === 0) { - const aSlope = (a.next.y - a.y) / (a.next.x - a.x); - const bSlope = (b.next.y - b.y) / (b.next.x - b.x); - result = aSlope - bSlope; - } - } - return result; -} - -// find a bridge between vertices that connects hole with an outer ring and link it -function eliminateHole(hole, outerNode) { - const bridge = findHoleBridge(hole, outerNode); - if (!bridge) { - return outerNode; - } - - const bridgeReverse = splitPolygon(bridge, hole); - - // filter collinear points around the cuts - filterPoints(bridgeReverse, bridgeReverse.next); - return filterPoints(bridge, bridge.next); -} - -// David Eberly's algorithm for finding a bridge between hole and outer polygon -function findHoleBridge(hole, outerNode) { - let p = outerNode; - const hx = hole.x; - const hy = hole.y; - let qx = -Infinity; - let m; - - // find a segment intersected by a ray from the hole's leftmost point to the left; - // segment's endpoint with lesser x will be potential connection point - // unless they intersect at a vertex, then choose the vertex - if (equals(hole, p)) return p; - do { - if (equals(hole, p.next)) return p.next; - else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { - const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); - if (x <= hx && x > qx) { - qx = x; - m = p.x < p.next.x ? p : p.next; - if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint - } - } - p = p.next; - } while (p !== outerNode); - - if (!m) return null; - - // look for points inside the triangle of hole point, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the point of the minimum angle with the ray as connection point - - const stop = m; - const mx = m.x; - const my = m.y; - let tanMin = Infinity; - - p = m; - - do { - if (hx >= p.x && p.x >= mx && hx !== p.x && - pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { - - const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential - - if (locallyInside(p, hole) && - (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { - m = p; - tanMin = tan; - } - } - - p = p.next; - } while (p !== stop); - - return m; -} - -// whether sector in vertex m contains sector in vertex p in the same coordinates -function sectorContainsSector(m, p) { - return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; -} - -// interlink polygon nodes in z-order -function indexCurve(start, minX, minY, invSize) { - let p = start; - do { - if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; - } while (p !== start); - - p.prevZ.nextZ = null; - p.prevZ = null; - - sortLinked(p); -} - -// Simon Tatham's linked list merge sort algorithm -// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -function sortLinked(list) { - let numMerges; - let inSize = 1; - - do { - let p = list; - let e; - list = null; - let tail = null; - numMerges = 0; - - while (p) { - numMerges++; - let q = p; - let pSize = 0; - for (let i = 0; i < inSize; i++) { - pSize++; - q = q.nextZ; - if (!q) break; - } - let qSize = inSize; - - while (pSize > 0 || (qSize > 0 && q)) { - - if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { - e = p; - p = p.nextZ; - pSize--; - } else { - e = q; - q = q.nextZ; - qSize--; - } - - if (tail) tail.nextZ = e; - else list = e; - - e.prevZ = tail; - tail = e; - } - - p = q; - } - - tail.nextZ = null; - inSize *= 2; - - } while (numMerges > 1); - - return list; -} - -// z-order of a point given coords and inverse of the longer side of data bbox -function zOrder(x, y, minX, minY, invSize) { - // coords are transformed into non-negative 15-bit integer range - x = (x - minX) * invSize | 0; - y = (y - minY) * invSize | 0; - - x = (x | (x << 8)) & 0x00FF00FF; - x = (x | (x << 4)) & 0x0F0F0F0F; - x = (x | (x << 2)) & 0x33333333; - x = (x | (x << 1)) & 0x55555555; - - y = (y | (y << 8)) & 0x00FF00FF; - y = (y | (y << 4)) & 0x0F0F0F0F; - y = (y | (y << 2)) & 0x33333333; - y = (y | (y << 1)) & 0x55555555; - - return x | (y << 1); -} - -// find the leftmost node of a polygon ring -function getLeftmost(start) { - let p = start, - leftmost = start; - do { - if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; - p = p.next; - } while (p !== start); - - return leftmost; -} - -// check if a point lies within a convex triangle -function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { - return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && - (ax - px) * (by - py) >= (bx - px) * (ay - py) && - (bx - px) * (cy - py) >= (cx - px) * (by - py); -} - -// check if a point lies within a convex triangle but false if its equal to the first point of the triangle -function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) { - return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py); -} - -// check if a diagonal between two polygon nodes is valid (lies in polygon interior) -function isValidDiagonal(a, b) { - return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges - (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible - (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors - equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case -} - -// signed area of a triangle -function area(p, q, r) { - return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); -} - -// check if two points are equal -function equals(p1, p2) { - return p1.x === p2.x && p1.y === p2.y; -} - -// check if two segments intersect -function intersects(p1, q1, p2, q2) { - const o1 = sign(area(p1, q1, p2)); - const o2 = sign(area(p1, q1, q2)); - const o3 = sign(area(p2, q2, p1)); - const o4 = sign(area(p2, q2, q1)); - - if (o1 !== o2 && o3 !== o4) return true; // general case - - if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 - - return false; -} - -// for collinear points p, q, r, check if point q lies on segment pr -function onSegment(p, q, r) { - return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); -} - -function sign(num) { - return num > 0 ? 1 : num < 0 ? -1 : 0; -} - -// check if a polygon diagonal intersects any polygon segments -function intersectsPolygon(a, b) { - let p = a; - do { - if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && - intersects(p, p.next, a, b)) return true; - p = p.next; - } while (p !== a); - - return false; -} - -// check if a polygon diagonal is locally inside the polygon -function locallyInside(a, b) { - return area(a.prev, a, a.next) < 0 ? - area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : - area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; -} - -// check if the middle point of a polygon diagonal is inside the polygon -function middleInside(a, b) { - let p = a; - let inside = false; - const px = (a.x + b.x) / 2; - const py = (a.y + b.y) / 2; - do { - if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && - (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) - inside = !inside; - p = p.next; - } while (p !== a); - - return inside; -} - -// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; -// if one belongs to the outer ring and another to a hole, it merges it into a single ring -function splitPolygon(a, b) { - const a2 = createNode(a.i, a.x, a.y), - b2 = createNode(b.i, b.x, b.y), - an = a.next, - bp = b.prev; - - a.next = b; - b.prev = a; - - a2.next = an; - an.prev = a2; - - b2.next = a2; - a2.prev = b2; - - bp.next = b2; - b2.prev = bp; - - return b2; -} - -// create a node and optionally link it with previous one (in a circular doubly linked list) -function insertNode(i, x, y, last) { - const p = createNode(i, x, y); - - if (!last) { - p.prev = p; - p.next = p; - - } else { - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; - } - return p; -} - -function removeNode(p) { - p.next.prev = p.prev; - p.prev.next = p.next; - - if (p.prevZ) p.prevZ.nextZ = p.nextZ; - if (p.nextZ) p.nextZ.prevZ = p.prevZ; -} - -function createNode(i, x, y) { - return { - i, // vertex index in coordinates array - x, y, // vertex coordinates - prev: null, // previous and next vertex nodes in a polygon ring - next: null, - z: 0, // z-order curve value - prevZ: null, // previous and next nodes in z-order - nextZ: null, - steiner: false // indicates whether this is a steiner point - }; -} - -function signedArea(data, start, end, dim) { - let sum = 0; - for (let i = start, j = end - dim; i < end; i += dim) { - sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); - j = i; - } - return sum; -} - -/** - * An implementation of the earcut polygon triangulation algorithm. - * The code is a port of [mapbox/earcut](https://github.com/mapbox/earcut). - * - * @see https://github.com/mapbox/earcut - */ -class Earcut { - - /** - * Triangulates the given shape definition by returning an array of triangles. - * - * @param {Array} data - An array with 2D points. - * @param {Array} holeIndices - An array with indices defining holes. - * @param {number} [dim=2] - The number of coordinates per vertex in the input array. - * @return {Array} An array representing the triangulated faces. Each face is defined by three consecutive numbers - * representing vertex indices. - */ - static triangulate( data, holeIndices, dim = 2 ) { - - return earcut( data, holeIndices, dim ); - - } - -} - -/** - * A class containing utility functions for shapes. - * - * @hideconstructor - */ -class ShapeUtils { - - /** - * Calculate area of a ( 2D ) contour polygon. - * - * @param {Array} contour - An array of 2D points. - * @return {number} The area. - */ - static area( contour ) { - - const n = contour.length; - let a = 0.0; - - for ( let p = n - 1, q = 0; q < n; p = q ++ ) { - - a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; - - } - - return a * 0.5; - - } - - /** - * Returns `true` if the given contour uses a clockwise winding order. - * - * @param {Array} pts - An array of 2D points defining a polygon. - * @return {boolean} Whether the given contour uses a clockwise winding order or not. - */ - static isClockWise( pts ) { - - return ShapeUtils.area( pts ) < 0; - - } - - /** - * Triangulates the given shape definition. - * - * @param {Array} contour - An array of 2D points defining the contour. - * @param {Array>} holes - An array that holds arrays of 2D points defining the holes. - * @return {Array>} An array that holds for each face definition an array with three indices. - */ - static triangulateShape( contour, holes ) { - - const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] - const holeIndices = []; // array of hole indices - const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] - - removeDupEndPts( contour ); - addContour( vertices, contour ); - - // - - let holeIndex = contour.length; - - holes.forEach( removeDupEndPts ); - - for ( let i = 0; i < holes.length; i ++ ) { - - holeIndices.push( holeIndex ); - holeIndex += holes[ i ].length; - addContour( vertices, holes[ i ] ); - - } - - // - - const triangles = Earcut.triangulate( vertices, holeIndices ); - - // - - for ( let i = 0; i < triangles.length; i += 3 ) { - - faces.push( triangles.slice( i, i + 3 ) ); - - } - - return faces; - - } - -} - -function removeDupEndPts( points ) { - - const l = points.length; - - if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { - - points.pop(); - - } - -} - -function addContour( vertices, contour ) { - - for ( let i = 0; i < contour.length; i ++ ) { - - vertices.push( contour[ i ].x ); - vertices.push( contour[ i ].y ); - - } - -} - -/** - * Creates extruded geometry from a path shape. - * - * ```js - * const length = 12, width = 8; - * - * const shape = new THREE.Shape(); - * shape.moveTo( 0,0 ); - * shape.lineTo( 0, width ); - * shape.lineTo( length, width ); - * shape.lineTo( length, 0 ); - * shape.lineTo( 0, 0 ); - * - * const geometry = new THREE.ExtrudeGeometry( shape ); - * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - * const mesh = new THREE.Mesh( geometry, material ) ; - * scene.add( mesh ); - * ``` - * - * @augments BufferGeometry - * @demo scenes/geometry-browser.html#ExtrudeGeometry - */ -class ExtrudeGeometry extends BufferGeometry { - - /** - * Constructs a new extrude geometry. - * - * @param {Shape|Array} [shapes] - A shape or an array of shapes. - * @param {ExtrudeGeometry~Options} [options] - The extrude settings. - */ - constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( -0.5, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), options = {} ) { - - super(); - - this.type = 'ExtrudeGeometry'; - - /** - * Holds the constructor parameters that have been - * used to generate the geometry. Any modification - * after instantiation does not change the geometry. - * - * @type {Object} - */ - this.parameters = { - shapes: shapes, - options: options - }; - - shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; - - const scope = this; - - const verticesArray = []; - const uvArray = []; - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - addShape( shape ); - - } - - // build geometry - - this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); - - this.computeVertexNormals(); - - // functions - - function addShape( shape ) { - - const placeholder = []; - - // options - - const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; - const steps = options.steps !== undefined ? options.steps : 1; - const depth = options.depth !== undefined ? options.depth : 1; - - let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; - let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; - let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; - let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; - let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; - - const extrudePath = options.extrudePath; - - const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; - - // - - let extrudePts, extrudeByPath = false; - let splineTube, binormal, normal, position2; - - if ( extrudePath ) { - - extrudePts = extrudePath.getSpacedPoints( steps ); - - extrudeByPath = true; - bevelEnabled = false; // bevels not supported for path extrusion - - // SETUP TNB variables - - const isClosed = extrudePath.isCatmullRomCurve3 ? extrudePath.closed : false; - - splineTube = extrudePath.computeFrenetFrames( steps, isClosed ); - - // log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); - - binormal = new Vector3(); - normal = new Vector3(); - position2 = new Vector3(); - - } - - // Safeguards if bevels are not enabled - - if ( ! bevelEnabled ) { - - bevelSegments = 0; - bevelThickness = 0; - bevelSize = 0; - bevelOffset = 0; - - } - - // Variables initialization - - const shapePoints = shape.extractPoints( curveSegments ); - - let vertices = shapePoints.shape; - const holes = shapePoints.holes; - - const reverse = ! ShapeUtils.isClockWise( vertices ); - - if ( reverse ) { - - vertices = vertices.reverse(); - - // Maybe we should also check if holes are in the opposite direction, just to be safe ... - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - - if ( ShapeUtils.isClockWise( ahole ) ) { - - holes[ h ] = ahole.reverse(); - - } - - } - - } - - /**Merges index-adjacent points that are within a threshold distance of each other. Array is modified in-place. Threshold distance is empirical, and scaled based on the magnitude of point coordinates. - * @param {Array} points - */ - function mergeOverlappingPoints( points ) { - - const THRESHOLD = 1e-10; - const THRESHOLD_SQ = THRESHOLD * THRESHOLD; - let prevPos = points[ 0 ]; - for ( let i = 1; i <= points.length; i ++ ) { - - const currentIndex = i % points.length; - const currentPos = points[ currentIndex ]; - const dx = currentPos.x - prevPos.x; - const dy = currentPos.y - prevPos.y; - const distSq = dx * dx + dy * dy; - - const scalingFactorSqrt = Math.max( - Math.abs( currentPos.x ), - Math.abs( currentPos.y ), - Math.abs( prevPos.x ), - Math.abs( prevPos.y ) - ); - const thresholdSqScaled = THRESHOLD_SQ * scalingFactorSqrt * scalingFactorSqrt; - if ( distSq <= thresholdSqScaled ) { - - points.splice( currentIndex, 1 ); - i --; - continue; - - } - - prevPos = currentPos; - - } - - } - - mergeOverlappingPoints( vertices ); - holes.forEach( mergeOverlappingPoints ); - - const numHoles = holes.length; - - /* Vertices */ - - const contour = vertices; // vertices has all points but contour has only points of circumference - - for ( let h = 0; h < numHoles; h ++ ) { - - const ahole = holes[ h ]; - - vertices = vertices.concat( ahole ); - - } - - - function scalePt2( pt, vec, size ) { - - if ( ! vec ) error( 'ExtrudeGeometry: vec does not exist' ); - - return pt.clone().addScaledVector( vec, size ); - - } - - const vlen = vertices.length; - - - // Find directions for point movement - - - function getBevelVec( inPt, inPrev, inNext ) { - - // computes for inPt the corresponding point inPt' on a new contour - // shifted by 1 unit (length of normalized vector) to the left - // if we walk along contour clockwise, this new contour is outside the old one - // - // inPt' is the intersection of the two lines parallel to the two - // adjacent edges of inPt at a distance of 1 unit on the left side. - - let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt - - // good reading for geometry algorithms (here: line-line intersection) - // http://geomalgorithms.com/a05-_intersect-1.html - - const v_prev_x = inPt.x - inPrev.x, - v_prev_y = inPt.y - inPrev.y; - const v_next_x = inNext.x - inPt.x, - v_next_y = inNext.y - inPt.y; - - const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); - - // check for collinear edges - const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - - if ( Math.abs( collinear0 ) > Number.EPSILON ) { - - // not collinear - - // length of vectors for normalizing - - const v_prev_len = Math.sqrt( v_prev_lensq ); - const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); - - // shift adjacent points by unit vectors to the left - - const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); - const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); - - const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); - const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); - - // scaling factor for v_prev to intersection point - - const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - - ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / - ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - - // vector from inPt to intersection point - - v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); - v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); - - // Don't normalize!, otherwise sharp corners become ugly - // but prevent crazy spikes - const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); - if ( v_trans_lensq <= 2 ) { - - return new Vector2( v_trans_x, v_trans_y ); - - } else { - - shrink_by = Math.sqrt( v_trans_lensq / 2 ); - - } - - } else { - - // handle special case of collinear edges - - let direction_eq = false; // assumes: opposite - - if ( v_prev_x > Number.EPSILON ) { - - if ( v_next_x > Number.EPSILON ) { - - direction_eq = true; - - } - - } else { - - if ( v_prev_x < - Number.EPSILON ) { - - if ( v_next_x < - Number.EPSILON ) { - - direction_eq = true; - - } - - } else { - - if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { - - direction_eq = true; - - } - - } - - } - - if ( direction_eq ) { - - // log("Warning: lines are a straight sequence"); - v_trans_x = - v_prev_y; - v_trans_y = v_prev_x; - shrink_by = Math.sqrt( v_prev_lensq ); - - } else { - - // log("Warning: lines are a straight spike"); - v_trans_x = v_prev_x; - v_trans_y = v_prev_y; - shrink_by = Math.sqrt( v_prev_lensq / 2 ); - - } - - } - - return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); - - } - - - const contourMovements = []; - - for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - - if ( j === il ) j = 0; - if ( k === il ) k = 0; - - // (j)---(i)---(k) - // log('i,j,k', i, j , k) - - contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); - - } - - const holesMovements = []; - let oneHoleMovements, verticesMovements = contourMovements.concat(); - - for ( let h = 0, hl = numHoles; h < hl; h ++ ) { - - const ahole = holes[ h ]; - - oneHoleMovements = []; - - for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - - if ( j === il ) j = 0; - if ( k === il ) k = 0; - - // (j)---(i)---(k) - oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); - - } - - holesMovements.push( oneHoleMovements ); - verticesMovements = verticesMovements.concat( oneHoleMovements ); - - } - - let faces; - - if ( bevelSegments === 0 ) { - - faces = ShapeUtils.triangulateShape( contour, holes ); - - } else { - - const contractedContourVertices = []; - const expandedHoleVertices = []; - - // Loop bevelSegments, 1 for the front, 1 for the back - - for ( let b = 0; b < bevelSegments; b ++ ) { - - //for ( b = bevelSegments; b > 0; b -- ) { - - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - - // contract shape - - for ( let i = 0, il = contour.length; i < il; i ++ ) { - - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - - v( vert.x, vert.y, - z ); - if ( t === 0 ) contractedContourVertices.push( vert ); - - } - - // expand holes - - for ( let h = 0, hl = numHoles; h < hl; h ++ ) { - - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; - const oneHoleVertices = []; - for ( let i = 0, il = ahole.length; i < il; i ++ ) { - - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - - v( vert.x, vert.y, - z ); - if ( t === 0 ) oneHoleVertices.push( vert ); - - } - - if ( t === 0 ) expandedHoleVertices.push( oneHoleVertices ); - - } - - } - - faces = ShapeUtils.triangulateShape( contractedContourVertices, expandedHoleVertices ); - - } - - const flen = faces.length; - - const bs = bevelSize + bevelOffset; - - // Back facing vertices - - for ( let i = 0; i < vlen; i ++ ) { - - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, 0 ); - - } else { - - // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); - - normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); - - position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); - - v( position2.x, position2.y, position2.z ); - - } - - } - - // Add stepped vertices... - // Including front facing vertices - - for ( let s = 1; s <= steps; s ++ ) { - - for ( let i = 0; i < vlen; i ++ ) { - - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, depth / steps * s ); - - } else { - - // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); - - normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); - - position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); - - v( position2.x, position2.y, position2.z ); - - } - - } - - } - - - // Add bevel segments planes - - //for ( b = 1; b <= bevelSegments; b ++ ) { - for ( let b = bevelSegments - 1; b >= 0; b -- ) { - - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - - // contract shape - - for ( let i = 0, il = contour.length; i < il; i ++ ) { - - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - v( vert.x, vert.y, depth + z ); - - } - - // expand holes - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; - - for ( let i = 0, il = ahole.length; i < il; i ++ ) { - - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, depth + z ); - - } else { - - v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); - - } - - } - - } - - } - - /* Faces */ - - // Top and bottom faces - - buildLidFaces(); - - // Sides faces - - buildSideFaces(); - - - ///// Internal functions - - function buildLidFaces() { - - const start = verticesArray.length / 3; - - if ( bevelEnabled ) { - - let layer = 0; // steps + 1 - let offset = vlen * layer; - - // Bottom faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); - - } - - layer = steps + bevelSegments * 2; - offset = vlen * layer; - - // Top faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); - - } - - } else { - - // Bottom faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 2 ], face[ 1 ], face[ 0 ] ); - - } - - // Top faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); - - } - - } - - scope.addGroup( start, verticesArray.length / 3 - start, 0 ); - - } - - // Create faces for the z-sides of the shape - - function buildSideFaces() { - - const start = verticesArray.length / 3; - let layeroffset = 0; - sidewalls( contour, layeroffset ); - layeroffset += contour.length; - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - sidewalls( ahole, layeroffset ); - - //, true - layeroffset += ahole.length; - - } - - - scope.addGroup( start, verticesArray.length / 3 - start, 1 ); - - - } - - function sidewalls( contour, layeroffset ) { - - let i = contour.length; - - while ( -- i >= 0 ) { - - const j = i; - let k = i - 1; - if ( k < 0 ) k = contour.length - 1; - - //log('b', i,j, i-1, k,vertices.length); - - for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { - - const slen1 = vlen * s; - const slen2 = vlen * ( s + 1 ); - - const a = layeroffset + j + slen1, - b = layeroffset + k + slen1, - c = layeroffset + k + slen2, - d = layeroffset + j + slen2; - - f4( a, b, c, d ); - - } - - } - - } - - function v( x, y, z ) { - - placeholder.push( x ); - placeholder.push( y ); - placeholder.push( z ); - - } - - - function f3( a, b, c ) { - - addVertex( a ); - addVertex( b ); - addVertex( c ); - - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - - } - - function f4( a, b, c, d ) { - - addVertex( a ); - addVertex( b ); - addVertex( d ); - - addVertex( b ); - addVertex( c ); - addVertex( d ); - - - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 3 ] ); - - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - addUV( uvs[ 3 ] ); - - } - - function addVertex( index ) { - - verticesArray.push( placeholder[ index * 3 + 0 ] ); - verticesArray.push( placeholder[ index * 3 + 1 ] ); - verticesArray.push( placeholder[ index * 3 + 2 ] ); - - } - - - function addUV( vector2 ) { - - uvArray.push( vector2.x ); - uvArray.push( vector2.y ); - - } - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - const shapes = this.parameters.shapes; - const options = this.parameters.options; - - return toJSON$1( shapes, options, data ); - - } - - /** - * Factory method for creating an instance of this class from the given - * JSON object. - * - * @param {Object} data - A JSON object representing the serialized geometry. - * @param {Array} shapes - An array of shapes. - * @return {ExtrudeGeometry} A new instance. - */ - static fromJSON( data, shapes ) { - - const geometryShapes = []; - - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { - - const shape = shapes[ data.shapes[ j ] ]; - - geometryShapes.push( shape ); - - } - - const extrudePath = data.options.extrudePath; - - if ( extrudePath !== undefined ) { - - data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); - - } - - return new ExtrudeGeometry( geometryShapes, data.options ); - - } - -} - -const WorldUVGenerator = { - - generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { - - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; - - return [ - new Vector2( a_x, a_y ), - new Vector2( b_x, b_y ), - new Vector2( c_x, c_y ) - ]; - - }, - - generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { - - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const a_z = vertices[ indexA * 3 + 2 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const b_z = vertices[ indexB * 3 + 2 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; - const c_z = vertices[ indexC * 3 + 2 ]; - const d_x = vertices[ indexD * 3 ]; - const d_y = vertices[ indexD * 3 + 1 ]; - const d_z = vertices[ indexD * 3 + 2 ]; - - if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { - - return [ - new Vector2( a_x, 1 - a_z ), - new Vector2( b_x, 1 - b_z ), - new Vector2( c_x, 1 - c_z ), - new Vector2( d_x, 1 - d_z ) - ]; - - } else { - - return [ - new Vector2( a_y, 1 - a_z ), - new Vector2( b_y, 1 - b_z ), - new Vector2( c_y, 1 - c_z ), - new Vector2( d_y, 1 - d_z ) - ]; - - } - - } - -}; - -function toJSON$1( shapes, options, data ) { - - data.shapes = []; - - if ( Array.isArray( shapes ) ) { - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - - data.shapes.push( shape.uuid ); - - } - - } else { - - data.shapes.push( shapes.uuid ); - - } - - data.options = Object.assign( {}, options ); - - if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); - - return data; - -} - -/** - * A class for generating a sphere geometry. - * - * ```js - * const geometry = new THREE.SphereGeometry( 15, 32, 16 ); - * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); - * const sphere = new THREE.Mesh( geometry, material ); - * scene.add( sphere ); - * ``` - * - * @augments BufferGeometry - * @demo scenes/geometry-browser.html#SphereGeometry - */ -class SphereGeometry extends BufferGeometry { - - /** - * Constructs a new sphere geometry. - * - * @param {number} [radius=1] - The sphere radius. - * @param {number} [widthSegments=32] - The number of horizontal segments. Minimum value is `3`. - * @param {number} [heightSegments=16] - The number of vertical segments. Minimum value is `2`. - * @param {number} [phiStart=0] - The horizontal starting angle in radians. - * @param {number} [phiLength=Math.PI*2] - The horizontal sweep angle size. - * @param {number} [thetaStart=0] - The vertical starting angle in radians. - * @param {number} [thetaLength=Math.PI] - The vertical sweep angle size. - */ - constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { - - super(); - - this.type = 'SphereGeometry'; - - /** - * Holds the constructor parameters that have been - * used to generate the geometry. Any modification - * after instantiation does not change the geometry. - * - * @type {Object} - */ - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - widthSegments = Math.max( 3, Math.floor( widthSegments ) ); - heightSegments = Math.max( 2, Math.floor( heightSegments ) ); - - const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); - - let index = 0; - const grid = []; - - const vertex = new Vector3(); - const normal = new Vector3(); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // generate vertices, normals and uvs - - for ( let iy = 0; iy <= heightSegments; iy ++ ) { - - const verticesRow = []; - - const v = iy / heightSegments; - const theta = thetaStart + v * thetaLength; - - const y = radius * Math.cos( theta ); - const ringRadius = Math.sqrt( radius * radius - y * y ); - - // special case for the poles - - let uOffset = 0; - - if ( iy === 0 && thetaStart === 0 ) { - - uOffset = 0.5 / widthSegments; - - } else if ( iy === heightSegments && thetaEnd === Math.PI ) { - - uOffset = -0.5 / widthSegments; - - } - - for ( let ix = 0; ix <= widthSegments; ix ++ ) { - - const u = ix / widthSegments; - const phi = phiStart + u * phiLength; - - // vertex - - vertex.x = - ringRadius * Math.cos( phi ); - vertex.y = y; - vertex.z = ringRadius * Math.sin( phi ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normal.copy( vertex ).normalize(); - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( u + uOffset, 1 - v ); - - verticesRow.push( index ++ ); - - } - - grid.push( verticesRow ); - - } - - // indices - - for ( let iy = 0; iy < heightSegments; iy ++ ) { - - for ( let ix = 0; ix < widthSegments; ix ++ ) { - - const a = grid[ iy ][ ix + 1 ]; - const b = grid[ iy ][ ix ]; - const c = grid[ iy + 1 ][ ix ]; - const d = grid[ iy + 1 ][ ix + 1 ]; - - if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); - if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - /** - * Factory method for creating an instance of this class from the given - * JSON object. - * - * @param {Object} data - A JSON object representing the serialized geometry. - * @return {SphereGeometry} A new instance. - */ - static fromJSON( data ) { - - return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); - - } - -} - -/** - * A standard physically based material, using Metallic-Roughness workflow. - * - * Physically based rendering (PBR) has recently become the standard in many - * 3D applications, such as [Unity](https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/), - * [Unreal](https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/PhysicallyBased/) and - * [3D Studio Max](http://area.autodesk.com/blogs/the-3ds-max-blog/what039s-new-for-rendering-in-3ds-max-2017). - * - * This approach differs from older approaches in that instead of using - * approximations for the way in which light interacts with a surface, a - * physically correct model is used. The idea is that, instead of tweaking - * materials to look good under specific lighting, a material can be created - * that will react 'correctly' under all lighting scenarios. - * - * In practice this gives a more accurate and realistic looking result than - * the {@link MeshLambertMaterial} or {@link MeshPhongMaterial}, at the cost of - * being somewhat more computationally expensive. `MeshStandardMaterial` uses per-fragment - * shading. - * - * Note that for best results you should always specify an environment map when using this material. - * - * For a non-technical introduction to the concept of PBR and how to set up a - * PBR material, check out these articles by the people at [marmoset](https://www.marmoset.co): - * - * - [Basic Theory of Physically Based Rendering](https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/) - * - [Physically Based Rendering and You Can Too](https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/) - * - * Technical details of the approach used in three.js (and most other PBR systems) can be found is this - * [paper from Disney](https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf) - * (pdf), by Brent Burley. - * - * @augments Material - * @demo scenes/material-browser.html#MeshStandardMaterial - */ -class MeshStandardMaterial extends Material { - - /** - * Constructs a new mesh standard material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshStandardMaterial = true; - - this.type = 'MeshStandardMaterial'; - - this.defines = { 'STANDARD': '' }; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); // diffuse - - /** - * How rough the material appears. `0.0` means a smooth mirror reflection, `1.0` - * means fully diffuse. If `roughnessMap` is also provided, - * both values are multiplied. - * - * @type {number} - * @default 1 - */ - this.roughness = 1.0; - - /** - * How much the material is like a metal. Non-metallic materials such as wood - * or stone use `0.0`, metallic use `1.0`, with nothing (usually) in between. - * A value between `0.0` and `1.0` could be used for a rusty metal look. - * If `metalnessMap` is also provided, both values are multiplied. - * - * @type {number} - * @default 0 - */ - this.metalness = 0.0; - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The light map. Requires a second set of UVs. - * - * `lightMap` represents pre-baked illuminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `lightMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.lightMap = null; - - /** - * Intensity of the baked light. - * - * @type {number} - * @default 1 - */ - this.lightMapIntensity = 1.0; - - /** - * The red channel of this texture is used as the ambient occlusion map. - * Requires a second set of UVs. - * - * `aoMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.aoMap = null; - - /** - * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` - * disables ambient occlusion. Where intensity is `1` and the AO map's - * red channel is also `1`, ambient light is fully occluded on a surface. - * - * @type {number} - * @default 1 - */ - this.aoMapIntensity = 1.0; - - /** - * Emissive (light) color of the material, essentially a solid color - * unaffected by other lighting. - * - * @type {Color} - * @default (0,0,0) - */ - this.emissive = new Color( 0x000000 ); - - /** - * Intensity of the emissive light. Modulates the emissive color. - * - * @type {number} - * @default 1 - */ - this.emissiveIntensity = 1.0; - - /** - * Set emissive (glow) map. The emissive map color is modulated by the - * emissive color and the emissive intensity. If you have an emissive map, - * be sure to set the emissive color to something other than black. - * - * `emissiveMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `emissiveMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.emissiveMap = null; - - /** - * The texture to create a bump map. The black and white values map to the - * perceived depth in relation to the lights. Bump doesn't actually affect - * the geometry of the object, only the lighting. If a normal map is defined - * this will be ignored. - * - * `bumpMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.bumpMap = null; - - /** - * How much the bump map affects the material. Typical range is `[0,1]`. - * - * @type {number} - * @default 1 - */ - this.bumpScale = 1; - - /** - * The texture to create a normal map. The RGB values affect the surface - * normal for each pixel fragment and change the way the color is lit. Normal - * maps do not change the actual shape of the surface, only the lighting. In - * case the material has a normal map authored using the left handed - * convention, the `y` component of `normalScale` should be negated to compensate - * for the different handedness. - * - * `normalMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.normalMap = null; - - /** - * The type of normal map. - * - * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} - * @default TangentSpaceNormalMap - */ - this.normalMapType = TangentSpaceNormalMap; - - /** - * How much the normal map affects the material. Typical value range is `[0,1]`. - * - * @type {Vector2} - * @default (1,1) - */ - this.normalScale = new Vector2( 1, 1 ); - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. For best results, pair a - * displacement map with a matching normal map, since the renderer can - * not recompute surface normals from the displaced vertices. - * - * `displacementMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - /** - * The green channel of this texture is used to alter the roughness of the - * material. - * - * `roughnessMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.roughnessMap = null; - - /** - * The blue channel of this texture is used to alter the metalness of the - * material. - * - * `metalnessMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.metalnessMap = null; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * The environment map. To ensure a physically correct rendering, environment maps - * are internally pre-processed with {@link PMREMGenerator}. - * - * `envMap` represents luminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `envMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.envMap = null; - - /** - * The rotation of the environment map in radians. - * - * @type {Euler} - * @default (0,0,0) - */ - this.envMapRotation = new Euler(); - - /** - * Scales the effect of the environment map by multiplying its color. - * - * @type {number} - * @default 1 - */ - this.envMapIntensity = 1.0; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Defines appearance of wireframe ends. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinecap = 'round'; - - /** - * Defines appearance of wireframe joints. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinejoin = 'round'; - - /** - * Whether the material is rendered with flat shading or not. - * - * @type {boolean} - * @default false - */ - this.flatShading = false; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.defines = { 'STANDARD': '' }; - - this.color.copy( source.color ); - this.roughness = source.roughness; - this.metalness = source.metalness; - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.roughnessMap = source.roughnessMap; - - this.metalnessMap = source.metalnessMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.envMapIntensity = source.envMapIntensity; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - -} - -/** - * An extension of the {@link MeshStandardMaterial}, providing more advanced - * physically-based rendering properties: - * - * - Anisotropy: Ability to represent the anisotropic property of materials - * as observable with brushed metals. - * - Clearcoat: Some materials — like car paints, carbon fiber, and wet surfaces — require - * a clear, reflective layer on top of another layer that may be irregular or rough. - * Clearcoat approximates this effect, without the need for a separate transparent surface. - * - Iridescence: Allows to render the effect where hue varies depending on the viewing - * angle and illumination angle. This can be seen on soap bubbles, oil films, or on the - * wings of many insects. - * - Physically-based transparency: One limitation of {@link Material#opacity} is that highly - * transparent materials are less reflective. Physically-based transmission provides a more - * realistic option for thin, transparent surfaces like glass. - * - Advanced reflectivity: More flexible reflectivity for non-metallic materials. - * - Retroreflection: Redirects specular light back toward the light source for - * safety materials like road markings and reflective tape. - * - Sheen: Can be used for representing cloth and fabric materials. - * - * As a result of these complex shading features, `MeshPhysicalMaterial` has a - * higher performance cost, per pixel, than other three.js materials. Most - * effects are disabled by default, and add cost as they are enabled. For - * best results, always specify an environment map when using this material. - * - * @augments MeshStandardMaterial - * @demo scenes/material-browser.html#MeshPhysicalMaterial - */ -class MeshPhysicalMaterial extends MeshStandardMaterial { - - /** - * Constructs a new mesh physical material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshPhysicalMaterial = true; - - this.defines = { - - 'STANDARD': '', - 'PHYSICAL': '' - - }; - - this.type = 'MeshPhysicalMaterial'; - - /** - * The rotation of the anisotropy in tangent, bitangent space, measured in radians - * counter-clockwise from the tangent. When `anisotropyMap` is present, this - * property provides additional rotation to the vectors in the texture. - * - * @type {number} - * @default 1 - */ - this.anisotropyRotation = 0; - - /** - * Red and green channels represent the anisotropy direction in `[-1, 1]` tangent, - * bitangent space, to be rotated by `anisotropyRotation`. The blue channel - * contains strength as `[0, 1]` to be multiplied by `anisotropy`. - * - * `anisotropyMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.anisotropyMap = null; - - /** - * The red channel of this texture is multiplied against `clearcoat`, - * for per-pixel control over a coating's intensity. - * - * `clearcoatMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.clearcoatMap = null; - - /** - * Roughness of the clear coat layer, from `0.0` to `1.0`. - * - * @type {number} - * @default 0 - */ - this.clearcoatRoughness = 0.0; - - /** - * The green channel of this texture is multiplied against - * `clearcoatRoughness`, for per-pixel control over a coating's roughness. - * - * `clearcoatRoughnessMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.clearcoatRoughnessMap = null; - - /** - * How much `clearcoatNormalMap` affects the clear coat layer, from - * `(0,0)` to `(1,1)`. - * - * @type {Vector2} - * @default (1,1) - */ - this.clearcoatNormalScale = new Vector2( 1, 1 ); - - /** - * Can be used to enable independent normals for the clear coat layer. - * - * `clearcoatNormalMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.clearcoatNormalMap = null; - - /** - * Index-of-refraction for non-metallic materials, from `1.0` to `2.333`. - * - * @type {number} - * @default 1.5 - */ - this.ior = 1.5; - - /** - * Degree of reflectivity, from `0.0` to `1.0`. Default is `0.5`, which - * corresponds to an index-of-refraction of `1.5`. - * - * This models the reflectivity of non-metallic materials. It has no effect - * when `metalness` is `1.0` - * - * @name MeshPhysicalMaterial#reflectivity - * @type {number} - * @default 0.5 - */ - Object.defineProperty( this, 'reflectivity', { - get: function () { - - return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); - - }, - set: function ( reflectivity ) { - - this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); - - } - } ); - - /** - * The red channel of this texture is multiplied against `iridescence`, for per-pixel - * control over iridescence. - * - * `iridescenceMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.iridescenceMap = null; - - /** - * Strength of the iridescence RGB color shift effect, represented by an index-of-refraction. - * Between `1.0` to `2.333`. - * - * @type {number} - * @default 1.3 - */ - this.iridescenceIOR = 1.3; - - /** - *Array of exactly 2 elements, specifying minimum and maximum thickness of the iridescence layer. - Thickness of iridescence layer has an equivalent effect of the one `thickness` has on `ior`. - * - * @type {Array} - * @default [100,400] - */ - this.iridescenceThicknessRange = [ 100, 400 ]; - - /** - * A texture that defines the thickness of the iridescence layer, stored in the green channel. - * Minimum and maximum values of thickness are defined by `iridescenceThicknessRange` array: - * - `0.0` in the green channel will result in thickness equal to first element of the array. - * - `1.0` in the green channel will result in thickness equal to second element of the array. - * - Values in-between will linearly interpolate between the elements of the array. - * - * `iridescenceThicknessMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.iridescenceThicknessMap = null; - - /** - * The sheen tint. - * - * @type {Color} - * @default (0,0,0) - */ - this.sheenColor = new Color( 0x000000 ); - - /** - * The RGB channels of this texture are multiplied against `sheenColor`, for per-pixel control - * over sheen tint. - * - * `sheenColorMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `sheenColorMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.sheenColorMap = null; - - /** - * Roughness of the sheen layer, from `0.0` to `1.0`. - * - * @type {number} - * @default 1 - */ - this.sheenRoughness = 1.0; - - /** - * The alpha channel of this texture is multiplied against `sheenRoughness`, for per-pixel control - * over sheen roughness. - * - * `sheenRoughnessMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.sheenRoughnessMap = null; - - /** - * The red channel of this texture is multiplied against `transmission`, for per-pixel control over - * optical transparency. - * - * `transmissionMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.transmissionMap = null; - - /** - * The thickness of the volume beneath the surface. The value is given in the - * coordinate space of the mesh. If the value is `0` the material is - * thin-walled. Otherwise the material is a volume boundary. - * - * @type {number} - * @default 0 - */ - this.thickness = 0; - - /** - * A texture that defines the thickness, stored in the green channel. This will - * be multiplied by `thickness`. - * - * `thicknessMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.thicknessMap = null; - - /** - * Density of the medium given as the average distance that light travels in - * the medium before interacting with a particle. The value is given in world - * space units, and must be greater than zero. - * - * @type {number} - * @default Infinity - */ - this.attenuationDistance = Infinity; - - /** - * The color that white light turns into due to absorption when reaching the - * attenuation distance. - * - * @type {Color} - * @default (1,1,1) - */ - this.attenuationColor = new Color( 1, 1, 1 ); - - /** - * A float that scales the amount of specular reflection for non-metals only. - * When set to zero, the model is effectively Lambertian. From `0.0` to `1.0`. - * - * @type {number} - * @default 1 - */ - this.specularIntensity = 1.0; - - /** - * The alpha channel of this texture is multiplied against `specularIntensity`, - * for per-pixel control over specular intensity. - * - * `specularIntensityMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.specularIntensityMap = null; - - /** - * Tints the specular reflection at normal incidence for non-metals only. - * - * @type {Color} - * @default (1,1,1) - */ - this.specularColor = new Color( 1, 1, 1 ); - - /** - * The RGB channels of this texture are multiplied against `specularColor`, - * for per-pixel control over specular color. - * - * `specularColorMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `specularColorMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.specularColorMap = null; - - this._anisotropy = 0; - this._clearcoat = 0; - this._dispersion = 0; - this._iridescence = 0; - this._retroreflectivity = 0; - this._sheen = 0.0; - this._transmission = 0; - - this.setValues( parameters ); - - } - - /** - * The anisotropy strength, from `0.0` to `1.0`. - * - * @type {number} - * @default 0 - */ - get anisotropy() { - - return this._anisotropy; - - } - - set anisotropy( value ) { - - if ( this._anisotropy > 0 !== value > 0 ) { - - this.version ++; - - } - - this._anisotropy = value; - - } - - /** - * Represents the intensity of the clear coat layer, from `0.0` to `1.0`. Use - * clear coat related properties to enable multilayer materials that have a - * thin translucent layer over the base layer. - * - * @type {number} - * @default 0 - */ - get clearcoat() { - - return this._clearcoat; - - } - - set clearcoat( value ) { - - if ( this._clearcoat > 0 !== value > 0 ) { - - this.version ++; - - } - - this._clearcoat = value; - - } - /** - * The intensity of the iridescence layer, simulating RGB color shift based on the angle between - * the surface and the viewer, from `0.0` to `1.0`. - * - * @type {number} - * @default 0 - */ - get iridescence() { - - return this._iridescence; - - } - - set iridescence( value ) { - - if ( this._iridescence > 0 !== value > 0 ) { - - this.version ++; - - } - - this._iridescence = value; - - } - - /** - * Defines the strength of the angular separation of colors (chromatic aberration) transmitting - * through a relatively clear volume. Any value zero or larger is valid, the typical range of - * realistic values is `[0, 1]`. This property can be only be used with transmissive objects. - * - * @type {number} - * @default 0 - */ - get dispersion() { - - return this._dispersion; - - } - - set dispersion( value ) { - - if ( this._dispersion > 0 !== value > 0 ) { - - this.version ++; - - } - - this._dispersion = value; - - } - - /** - * The strength of retroreflection, from `0.0` to `1.0`. A value of `1.0` - * evaluates the material's microfacet reflection with the view direction - * reflected about the surface normal, redirecting the specular lobe back - * toward the light source. - * - * @type {number} - * @default 0 - */ - get retroreflectivity() { - - return this._retroreflectivity; - - } - - set retroreflectivity( value ) { - - if ( this._retroreflectivity > 0 !== value > 0 ) { - - this.version ++; - - } - - this._retroreflectivity = value; - - } - - /** - * The intensity of the sheen layer, from `0.0` to `1.0`. - * - * @type {number} - * @default 0 - */ - get sheen() { - - return this._sheen; - - } - - set sheen( value ) { - - if ( this._sheen > 0 !== value > 0 ) { - - this.version ++; - - } - - this._sheen = value; - - } - - /** - * Degree of transmission (or optical transparency), from `0.0` to `1.0`. - * - * Thin, transparent or semitransparent, plastic or glass materials remain - * largely reflective even if they are fully transmissive. The transmission - * property can be used to model these materials. - * - * When transmission is non-zero, `opacity` should be set to `1`. - * - * @type {number} - * @default 0 - */ - get transmission() { - - return this._transmission; - - } - - set transmission( value ) { - - if ( this._transmission > 0 !== value > 0 ) { - - this.version ++; - - } - - this._transmission = value; - - } - - copy( source ) { - - super.copy( source ); - - this.defines = { - - 'STANDARD': '', - 'PHYSICAL': '' - - }; - - this.anisotropy = source.anisotropy; - this.anisotropyRotation = source.anisotropyRotation; - this.anisotropyMap = source.anisotropyMap; - - this.clearcoat = source.clearcoat; - this.clearcoatMap = source.clearcoatMap; - this.clearcoatRoughness = source.clearcoatRoughness; - this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; - this.clearcoatNormalMap = source.clearcoatNormalMap; - this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); - - this.dispersion = source.dispersion; - this.ior = source.ior; - - this.iridescence = source.iridescence; - this.iridescenceMap = source.iridescenceMap; - this.iridescenceIOR = source.iridescenceIOR; - this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; - this.iridescenceThicknessMap = source.iridescenceThicknessMap; - - this.retroreflectivity = source.retroreflectivity; - - this.sheen = source.sheen; - this.sheenColor.copy( source.sheenColor ); - this.sheenColorMap = source.sheenColorMap; - this.sheenRoughness = source.sheenRoughness; - this.sheenRoughnessMap = source.sheenRoughnessMap; - - this.transmission = source.transmission; - this.transmissionMap = source.transmissionMap; - - this.thickness = source.thickness; - this.thicknessMap = source.thicknessMap; - this.attenuationDistance = source.attenuationDistance; - this.attenuationColor.copy( source.attenuationColor ); - - this.specularIntensity = source.specularIntensity; - this.specularIntensityMap = source.specularIntensityMap; - this.specularColor.copy( source.specularColor ); - this.specularColorMap = source.specularColorMap; - - return this; - - } - -} - -/** - * A material for shiny surfaces with specular highlights. - * - * The material uses a non-physically based [Blinn-Phong](https://en.wikipedia.org/wiki/Blinn-Phong_shading_model) - * model for calculating reflectance. Unlike the Lambertian model used in the - * {@link MeshLambertMaterial} this can simulate shiny surfaces with specular - * highlights (such as varnished wood). `MeshPhongMaterial` uses per-fragment shading. - * - * Performance will generally be greater when using this material over the - * {@link MeshStandardMaterial} or {@link MeshPhysicalMaterial}, at the cost of - * some graphical accuracy. - * - * @augments Material - * @demo scenes/material-browser.html#MeshPhongMaterial - */ -class MeshPhongMaterial extends Material { - - /** - * Constructs a new mesh phong material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshPhongMaterial = true; - - this.type = 'MeshPhongMaterial'; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); // diffuse - - /** - * Specular color of the material. The default color is set to `0x111111` (very dark grey) - * - * This defines how shiny the material is and the color of its shine. - * - * @type {Color} - */ - this.specular = new Color( 0x111111 ); - - /** - * How shiny the specular highlight is; a higher value gives a sharper highlight. - * - * @type {number} - * @default 30 - */ - this.shininess = 30; - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The light map. Requires a second set of UVs. - * - * `lightMap` represents pre-baked illuminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `lightMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.lightMap = null; - - /** - * Intensity of the baked light. - * - * @type {number} - * @default 1 - */ - this.lightMapIntensity = 1.0; - - /** - * The red channel of this texture is used as the ambient occlusion map. - * Requires a second set of UVs. - * - * `aoMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.aoMap = null; - - /** - * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` - * disables ambient occlusion. Where intensity is `1` and the AO map's - * red channel is also `1`, ambient light is fully occluded on a surface. - * - * @type {number} - * @default 1 - */ - this.aoMapIntensity = 1.0; - - /** - * Emissive (light) color of the material, essentially a solid color - * unaffected by other lighting. - * - * @type {Color} - * @default (0,0,0) - */ - this.emissive = new Color( 0x000000 ); - - /** - * Intensity of the emissive light. Modulates the emissive color. - * - * @type {number} - * @default 1 - */ - this.emissiveIntensity = 1.0; - - /** - * Set emissive (glow) map. The emissive map color is modulated by the - * emissive color and the emissive intensity. If you have an emissive map, - * be sure to set the emissive color to something other than black. - * - * `emissiveMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `emissiveMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.emissiveMap = null; - - /** - * The texture to create a bump map. The black and white values map to the - * perceived depth in relation to the lights. Bump doesn't actually affect - * the geometry of the object, only the lighting. If a normal map is defined - * this will be ignored. - * - * `bumpMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.bumpMap = null; - - /** - * How much the bump map affects the material. Typical range is `[0,1]`. - * - * @type {number} - * @default 1 - */ - this.bumpScale = 1; - - /** - * The texture to create a normal map. The RGB values affect the surface - * normal for each pixel fragment and change the way the color is lit. Normal - * maps do not change the actual shape of the surface, only the lighting. In - * case the material has a normal map authored using the left handed - * convention, the `y` component of `normalScale` should be negated to compensate - * for the different handedness. - * - * `normalMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.normalMap = null; - - /** - * The type of normal map. - * - * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} - * @default TangentSpaceNormalMap - */ - this.normalMapType = TangentSpaceNormalMap; - - /** - * How much the normal map affects the material. Typical value range is `[0,1]`. - * - * @type {Vector2} - * @default (1,1) - */ - this.normalScale = new Vector2( 1, 1 ); - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. For best results, pair a - * displacement map with a matching normal map, since the renderer can - * not recompute surface normals from the displaced vertices. - * - * `displacementMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - /** - * The specular map value affects both how much the specular surface - * highlight contributes and how much of the environment map affects the - * surface. - * - * `specularMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `specularMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.specularMap = null; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * The environment map. - * - * `envMap` represents luminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `envMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.envMap = null; - - /** - * The rotation of the environment map in radians. - * - * @type {Euler} - * @default (0,0,0) - */ - this.envMapRotation = new Euler(); - - /** - * How to combine the result of the surface's color with the environment map, if any. - * - * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to - * blend between the two colors. - * - * @type {(MultiplyOperation|MixOperation|AddOperation)} - * @default MultiplyOperation - */ - this.combine = MultiplyOperation; - - /** - * How much the environment map affects the surface. - * The valid range is between `0` (no reflections) and `1` (full reflections). - * - * @type {number} - * @default 1 - */ - this.reflectivity = 1; - - /** - * Scales the effect of the environment map by multiplying its color. - * - * @type {number} - * @default 1 - */ - this.envMapIntensity = 1.0; - - /** - * The index of refraction (IOR) of air (approximately 1) divided by the - * index of refraction of the material. It is used with environment mapping - * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. - * The refraction ratio should not exceed `1`. - * - * @type {number} - * @default 0.98 - */ - this.refractionRatio = 0.98; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Defines appearance of wireframe ends. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinecap = 'round'; - - /** - * Defines appearance of wireframe joints. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinejoin = 'round'; - - /** - * Whether the material is rendered with flat shading or not. - * - * @type {boolean} - * @default false - */ - this.flatShading = false; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - this.specular.copy( source.specular ); - this.shininess = source.shininess; - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.envMapIntensity = source.envMapIntensity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - -} - -/** - * A material implementing toon shading. - * - * @augments Material - * @demo scenes/material-browser.html#MeshToonMaterial - */ -class MeshToonMaterial extends Material { - - /** - * Constructs a new mesh toon material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshToonMaterial = true; - - this.defines = { 'TOON': '' }; - - this.type = 'MeshToonMaterial'; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * Gradient map for toon shading. It's required to set - * {@link Texture#minFilter} and {@link Texture#magFilter} to {@link NearestFilter} - * when using this type of texture. - * - * `gradientMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.gradientMap = null; - - /** - * The light map. Requires a second set of UVs. - * - * `lightMap` represents pre-baked illuminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `lightMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.lightMap = null; - - /** - * Intensity of the baked light. - * - * @type {number} - * @default 1 - */ - this.lightMapIntensity = 1.0; - - /** - * The red channel of this texture is used as the ambient occlusion map. - * Requires a second set of UVs. - * - * `aoMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.aoMap = null; - - /** - * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` - * disables ambient occlusion. Where intensity is `1` and the AO map's - * red channel is also `1`, ambient light is fully occluded on a surface. - * - * @type {number} - * @default 1 - */ - this.aoMapIntensity = 1.0; - - /** - * Emissive (light) color of the material, essentially a solid color - * unaffected by other lighting. - * - * @type {Color} - * @default (0,0,0) - */ - this.emissive = new Color( 0x000000 ); - - /** - * Intensity of the emissive light. Modulates the emissive color. - * - * @type {number} - * @default 1 - */ - this.emissiveIntensity = 1.0; - - /** - * Set emissive (glow) map. The emissive map color is modulated by the - * emissive color and the emissive intensity. If you have an emissive map, - * be sure to set the emissive color to something other than black. - * - * `emissiveMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `emissiveMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.emissiveMap = null; - - /** - * The texture to create a bump map. The black and white values map to the - * perceived depth in relation to the lights. Bump doesn't actually affect - * the geometry of the object, only the lighting. If a normal map is defined - * this will be ignored. - * - * `bumpMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.bumpMap = null; - - /** - * How much the bump map affects the material. Typical range is `[0,1]`. - * - * @type {number} - * @default 1 - */ - this.bumpScale = 1; - - /** - * The texture to create a normal map. The RGB values affect the surface - * normal for each pixel fragment and change the way the color is lit. Normal - * maps do not change the actual shape of the surface, only the lighting. In - * case the material has a normal map authored using the left handed - * convention, the `y` component of `normalScale` should be negated to compensate - * for the different handedness. - * - * `normalMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.normalMap = null; - - /** - * The type of normal map. - * - * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} - * @default TangentSpaceNormalMap - */ - this.normalMapType = TangentSpaceNormalMap; - - /** - * How much the normal map affects the material. Typical value range is `[0,1]`. - * - * @type {Vector2} - * @default (1,1) - */ - this.normalScale = new Vector2( 1, 1 ); - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. For best results, pair a - * displacement map with a matching normal map, since the renderer can - * not recompute surface normals from the displaced vertices. - * - * `displacementMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Defines appearance of wireframe ends. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinecap = 'round'; - - /** - * Defines appearance of wireframe joints. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinejoin = 'round'; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - this.gradientMap = source.gradientMap; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.alphaMap = source.alphaMap; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.fog = source.fog; - - return this; - - } - -} - -/** - * A material that maps the normal vectors to RGB colors. - * - * @augments Material - * @demo scenes/material-browser.html#MeshNormalMaterial - */ -class MeshNormalMaterial extends Material { - - /** - * Constructs a new mesh normal material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshNormalMaterial = true; - - this.type = 'MeshNormalMaterial'; - - /** - * The texture to create a bump map. The black and white values map to the - * perceived depth in relation to the lights. Bump doesn't actually affect - * the geometry of the object, only the lighting. If a normal map is defined - * this will be ignored. - * - * `bumpMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.bumpMap = null; - - /** - * How much the bump map affects the material. Typical range is `[0,1]`. - * - * @type {number} - * @default 1 - */ - this.bumpScale = 1; - - /** - * The texture to create a normal map. The RGB values affect the surface - * normal for each pixel fragment and change the way the color is lit. Normal - * maps do not change the actual shape of the surface, only the lighting. In - * case the material has a normal map authored using the left handed - * convention, the `y` component of `normalScale` should be negated to compensate - * for the different handedness. - * - * `normalMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.normalMap = null; - - /** - * The type of normal map. - * - * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} - * @default TangentSpaceNormalMap - */ - this.normalMapType = TangentSpaceNormalMap; - - /** - * How much the normal map affects the material. Typical value range is `[0,1]`. - * - * @type {Vector2} - * @default (1,1) - */ - this.normalScale = new Vector2( 1, 1 ); - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. For best results, pair a - * displacement map with a matching normal map, since the renderer can - * not recompute surface normals from the displaced vertices. - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * WebGL and WebGPU ignore this property and always render - * 1 pixel wide lines. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Whether the material is rendered with flat shading or not. - * - * @type {boolean} - * @default false - */ - this.flatShading = false; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.flatShading = source.flatShading; - - return this; - - } - -} - -/** - * A material for non-shiny surfaces, without specular highlights. - * - * The material uses a non-physically based [Lambertian](https://en.wikipedia.org/wiki/Lambertian_reflectance) - * model for calculating reflectance. This can simulate some surfaces (such - * as untreated wood or stone) well, but cannot simulate shiny surfaces with - * specular highlights (such as varnished wood). `MeshLambertMaterial` uses per-fragment - * shading. - * - * Due to the simplicity of the reflectance and illumination models, - * performance will be greater when using this material over the - * {@link MeshPhongMaterial}, {@link MeshStandardMaterial} or - * {@link MeshPhysicalMaterial}, at the cost of some graphical accuracy. - * - * @augments Material - * @demo scenes/material-browser.html#MeshLambertMaterial - */ -class MeshLambertMaterial extends Material { - - /** - * Constructs a new mesh lambert material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshLambertMaterial = true; - - this.type = 'MeshLambertMaterial'; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); // diffuse - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The light map. Requires a second set of UVs. - * - * `lightMap` represents pre-baked illuminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `lightMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.lightMap = null; - - /** - * Intensity of the baked light. - * - * @type {number} - * @default 1 - */ - this.lightMapIntensity = 1.0; - - /** - * The red channel of this texture is used as the ambient occlusion map. - * Requires a second set of UVs. - * - * `aoMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.aoMap = null; - - /** - * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` - * disables ambient occlusion. Where intensity is `1` and the AO map's - * red channel is also `1`, ambient light is fully occluded on a surface. - * - * @type {number} - * @default 1 - */ - this.aoMapIntensity = 1.0; - - /** - * Emissive (light) color of the material, essentially a solid color - * unaffected by other lighting. - * - * @type {Color} - * @default (0,0,0) - */ - this.emissive = new Color( 0x000000 ); - - /** - * Intensity of the emissive light. Modulates the emissive color. - * - * @type {number} - * @default 1 - */ - this.emissiveIntensity = 1.0; - - /** - * Set emissive (glow) map. The emissive map color is modulated by the - * emissive color and the emissive intensity. If you have an emissive map, - * be sure to set the emissive color to something other than black. - * - * `emissiveMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `emissiveMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.emissiveMap = null; - - /** - * The texture to create a bump map. The black and white values map to the - * perceived depth in relation to the lights. Bump doesn't actually affect - * the geometry of the object, only the lighting. If a normal map is defined - * this will be ignored. - * - * `bumpMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.bumpMap = null; - - /** - * How much the bump map affects the material. Typical range is `[0,1]`. - * - * @type {number} - * @default 1 - */ - this.bumpScale = 1; - - /** - * The texture to create a normal map. The RGB values affect the surface - * normal for each pixel fragment and change the way the color is lit. Normal - * maps do not change the actual shape of the surface, only the lighting. In - * case the material has a normal map authored using the left handed - * convention, the `y` component of `normalScale` should be negated to compensate - * for the different handedness. - * - * `normalMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.normalMap = null; - - /** - * The type of normal map. - * - * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} - * @default TangentSpaceNormalMap - */ - this.normalMapType = TangentSpaceNormalMap; - - /** - * How much the normal map affects the material. Typical value range is `[0,1]`. - * - * @type {Vector2} - * @default (1,1) - */ - this.normalScale = new Vector2( 1, 1 ); - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. For best results, pair a - * displacement map with a matching normal map, since the renderer can - * not recompute surface normals from the displaced vertices. - * - * `displacementMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - /** - * Specular map used by the material. - * - * `specularMap` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `specularMap` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.specularMap = null; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * The environment map. - * - * `envMap` represents luminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. Most `envMap` textures set - * `texture.colorSpace = LinearSRGBColorSpace` and use float-type formats - * such as `.exr` or `.hdr`. - * - * @type {?Texture} - * @default null - */ - this.envMap = null; - - /** - * The rotation of the environment map in radians. - * - * @type {Euler} - * @default (0,0,0) - */ - this.envMapRotation = new Euler(); - - /** - * How to combine the result of the surface's color with the environment map, if any. - * - * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to - * blend between the two colors. - * - * @type {(MultiplyOperation|MixOperation|AddOperation)} - * @default MultiplyOperation - */ - this.combine = MultiplyOperation; - - /** - * How much the environment map affects the surface. - * The valid range is between `0` (no reflections) and `1` (full reflections). - * - * @type {number} - * @default 1 - */ - this.reflectivity = 1; - - /** - * Scales the effect of the environment map by multiplying its color. - * - * @type {number} - * @default 1 - */ - this.envMapIntensity = 1.0; - - /** - * The index of refraction (IOR) of air (approximately 1) divided by the - * index of refraction of the material. It is used with environment mapping - * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. - * The refraction ratio should not exceed `1`. - * - * @type {number} - * @default 0.98 - */ - this.refractionRatio = 0.98; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Defines appearance of wireframe ends. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinecap = 'round'; - - /** - * Defines appearance of wireframe joints. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {('round'|'bevel'|'miter')} - * @default 'round' - */ - this.wireframeLinejoin = 'round'; - - /** - * Whether the material is rendered with flat shading or not. - * - * @type {boolean} - * @default false - */ - this.flatShading = false; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.envMapRotation.copy( source.envMapRotation ); - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.envMapIntensity = source.envMapIntensity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - -} - -/** - * This material is defined by a MatCap (or Lit Sphere) texture, which encodes the - * material color and shading. - * - * `MeshMatcapMaterial` does not respond to lights since the matcap image file encodes - * baked lighting. It will cast a shadow onto an object that receives shadows - * (and shadow clipping works), but it will not self-shadow or receive - * shadows. - * - * @augments Material - * @demo scenes/material-browser.html#MeshMatcapMaterial - */ -class MeshMatcapMaterial extends Material { - - /** - * Constructs a new mesh matcap material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isMeshMatcapMaterial = true; - - this.defines = { 'MATCAP': '' }; - - this.type = 'MeshMatcapMaterial'; - - /** - * Color of the material. - * - * @type {Color} - * @default (1,1,1) - */ - this.color = new Color( 0xffffff ); // diffuse - - /** - * The matcap map. - * - * `matcap` represents luminance data, and the texture must be assigned - * a {@link Texture#colorSpace}. HDR `matcap` textures (e.g. `.exr`) - * typically set `texture.colorSpace = LinearSRGBColorSpace`, while LDR - * `matcap` textures (e.g. `.png`, `.jpg`, `.webp`) typically set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.matcap = null; - - /** - * The color map. May optionally include an alpha channel, typically combined - * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map - * color is modulated by the diffuse `color`. - * - * `map` represents color data, and the texture must be assigned a - * {@link Texture#colorSpace}. Most `map` textures set - * `texture.colorSpace = SRGBColorSpace`. - * - * @type {?Texture} - * @default null - */ - this.map = null; - - /** - * The texture to create a bump map. The black and white values map to the - * perceived depth in relation to the lights. Bump doesn't actually affect - * the geometry of the object, only the lighting. If a normal map is defined - * this will be ignored. - * - * `bumpMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.bumpMap = null; - - /** - * How much the bump map affects the material. Typical range is `[0,1]`. - * - * @type {number} - * @default 1 - */ - this.bumpScale = 1; - - /** - * The texture to create a normal map. The RGB values affect the surface - * normal for each pixel fragment and change the way the color is lit. Normal - * maps do not change the actual shape of the surface, only the lighting. In - * case the material has a normal map authored using the left handed - * convention, the `y` component of `normalScale` should be negated to compensate - * for the different handedness. - * - * `normalMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.normalMap = null; - - /** - * The type of normal map. - * - * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} - * @default TangentSpaceNormalMap - */ - this.normalMapType = TangentSpaceNormalMap; - - /** - * How much the normal map affects the material. Typical value range is `[0,1]`. - * - * @type {Vector2} - * @default (1,1) - */ - this.normalScale = new Vector2( 1, 1 ); - - /** - * The displacement map affects the position of the mesh's vertices. Unlike - * other maps which only affect the light and shade of the material the - * displaced vertices can cast shadows, block other objects, and otherwise - * act as real geometry. The displacement texture is an image where the value - * of each pixel (white being the highest) is mapped against, and - * repositions, the vertices of the mesh. For best results, pair a - * displacement map with a matching normal map, since the renderer can - * not recompute surface normals from the displaced vertices. - * - * `displacementMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.displacementMap = null; - - /** - * How much the displacement map affects the mesh (where black is no - * displacement, and white is maximum displacement). Without a displacement - * map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementScale = 1; - - /** - * The offset of the displacement map's values on the mesh's vertices. - * The bias is added to the scaled sample of the displacement map. - * Without a displacement map set, this value is not applied. - * - * @type {number} - * @default 0 - */ - this.displacementBias = 0; - - /** - * The alpha map is a grayscale texture that controls the opacity across the - * surface (black: fully transparent; white: fully opaque). - * - * Only the color of the texture is used, ignoring the alpha channel if one - * exists. For RGB and RGBA textures, the renderer will use the green channel - * when sampling this texture due to the extra bit of precision provided for - * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and - * luminance/alpha textures will also still work as expected. - * - * `alphaMap` represents non-color data. Any texture assigned must have - * `texture.colorSpace = NoColorSpace` (default). - * - * @type {?Texture} - * @default null - */ - this.alphaMap = null; - - /** - * Renders the geometry as a wireframe. - * - * @type {boolean} - * @default false - */ - this.wireframe = false; - - /** - * Controls the thickness of the wireframe. - * - * Can only be used with {@link SVGRenderer}. - * - * @type {number} - * @default 1 - */ - this.wireframeLinewidth = 1; - - /** - * Whether the material is rendered with flat shading or not. - * - * @type {boolean} - * @default false - */ - this.flatShading = false; - - /** - * Whether the material is affected by fog or not. - * - * @type {boolean} - * @default true - */ - this.fog = true; - - this.setValues( parameters ); - - } - - - copy( source ) { - - super.copy( source ); - - this.defines = { 'MATCAP': '' }; - - this.color.copy( source.color ); - - this.matcap = source.matcap; - - this.map = source.map; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.alphaMap = source.alphaMap; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - -} - -/** - * A material for rendering line primitives. - * - * Materials define the appearance of renderable 3D objects. - * - * ```js - * const material = new THREE.LineDashedMaterial( { - * color: 0xffffff, - * scale: 1, - * dashSize: 3, - * gapSize: 1, - * } ); - * ``` - * - * @augments LineBasicMaterial - */ -class LineDashedMaterial extends LineBasicMaterial { - - /** - * Constructs a new line dashed material. - * - * @param {Object} [parameters] - An object with one or more properties - * defining the material's appearance. Any property of the material - * (including any property from inherited materials) can be passed - * in here. Color values can be passed any type of value accepted - * by {@link Color#set}. - */ - constructor( parameters ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isLineDashedMaterial = true; - this.type = 'LineDashedMaterial'; - - /** - * The scale of the dashed part of a line. - * - * @type {number} - * @default 1 - */ - this.scale = 1; - - /** - * The size of the dash. This is both the gap with the stroke. - * - * @type {number} - * @default 3 - */ - this.dashSize = 3; - - /** - * The size of the gap. - * - * @type {number} - * @default 1 - */ - this.gapSize = 1; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.scale = source.scale; - this.dashSize = source.dashSize; - this.gapSize = source.gapSize; - - return this; - - } - -} - -/** - * @class - * @classdesc A simple caching system, used internally by {@link FileLoader}. - * To enable caching across all loaders that use {@link FileLoader}, add `THREE.Cache.enabled = true.` once in your app. - * @hideconstructor - */ -const Cache = { - - /** - * Whether caching is enabled or not. - * - * @static - * @type {boolean} - * @default false - */ - enabled: false, - - /** - * A dictionary that holds cached files. - * - * @static - * @type {Object} - */ - files: {}, - - /** - * Adds a cache entry with a key to reference the file. If this key already - * holds a file, it is overwritten. - * - * @static - * @param {string} key - The key to reference the cached file. - * @param {Object} file - The file to be cached. - */ - add: function ( key, file ) { - - if ( this.enabled === false ) return; - - if ( isBlobURL( key ) ) return; - - // log( 'Cache', 'Adding key:', key ); - - this.files[ key ] = file; - - }, - - /** - * Gets the cached value for the given key. - * - * @static - * @param {string} key - The key to reference the cached file. - * @return {Object|undefined} The cached file. If the key does not exist `undefined` is returned. - */ - get: function ( key ) { - - if ( this.enabled === false ) return; - - if ( isBlobURL( key ) ) return; - - // log( 'Cache', 'Checking key:', key ); - - return this.files[ key ]; - - }, - - /** - * Removes the cached file associated with the given key. - * - * @static - * @param {string} key - The key to reference the cached file. - */ - remove: function ( key ) { - - delete this.files[ key ]; - - }, - - /** - * Remove all values from the cache. - * - * @static - */ - clear: function () { - - this.files = {}; - - } - -}; - -/** - * Returns true if the given cache key contains the blob: scheme. - * - * @private - * @param {string} key - The cache key. - * @return {boolean} Whether the given cache key contains the blob: scheme or not. - */ -function isBlobURL( key ) { - - try { - - const urlString = key.slice( key.indexOf( ':' ) + 1 ); // remove type identifier - - const url = new URL( urlString ); - return url.protocol === 'blob:'; - - } catch ( e ) { - - // If the string is not a valid URL, it throws an error - return false; - - } - -} - -/** - * Handles and keeps track of loaded and pending data. A default global - * instance of this class is created and used by loaders if not supplied - * manually. - * - * In general that should be sufficient, however there are times when it can - * be useful to have separate loaders - for example if you want to show - * separate loading bars for objects and textures. - * - * ```js - * const manager = new THREE.LoadingManager(); - * manager.onLoad = () => console.log( 'Loading complete!' ); - * - * const loader1 = new OBJLoader( manager ); - * const loader2 = new ColladaLoader( manager ); - * ``` - */ -class LoadingManager { - - /** - * Constructs a new loading manager. - * - * @param {Function} [onLoad] - Executes when all items have been loaded. - * @param {Function} [onProgress] - Executes when single items have been loaded. - * @param {Function} [onError] - Executes when an error occurs. - */ - constructor( onLoad, onProgress, onError ) { - - const scope = this; - - let isLoading = false; - let itemsLoaded = 0; - let itemsTotal = 0; - let urlModifier = undefined; - const handlers = []; - - // Refer to #5689 for the reason why we don't set .onStart - // in the constructor - - /** - * Executes when an item starts loading. - * - * @type {Function|undefined} - * @default undefined - */ - this.onStart = undefined; - - /** - * Executes when all items have been loaded. - * - * @type {Function|undefined} - * @default undefined - */ - this.onLoad = onLoad; - - /** - * Executes when single items have been loaded. - * - * @type {Function|undefined} - * @default undefined - */ - this.onProgress = onProgress; - - /** - * Executes when an error occurs. - * - * @type {Function|undefined} - * @default undefined - */ - this.onError = onError; - - /** - * Used for aborting ongoing requests in loaders using this manager. - * - * @private - * @type {AbortController | null} - */ - this._abortController = null; - - /** - * This should be called by any loader using the manager when the loader - * starts loading an item. - * - * @param {string} url - The URL to load. - */ - this.itemStart = function ( url ) { - - itemsTotal ++; - - if ( isLoading === false ) { - - if ( scope.onStart !== undefined ) { - - scope.onStart( url, itemsLoaded, itemsTotal ); - - } - - } - - isLoading = true; - - }; - - /** - * This should be called by any loader using the manager when the loader - * ended loading an item. - * - * @param {string} url - The URL of the loaded item. - */ - this.itemEnd = function ( url ) { - - itemsLoaded ++; - - if ( scope.onProgress !== undefined ) { - - scope.onProgress( url, itemsLoaded, itemsTotal ); - - } - - if ( itemsLoaded === itemsTotal ) { - - isLoading = false; - - if ( scope.onLoad !== undefined ) { - - scope.onLoad(); - - } - - } - - }; - - /** - * This should be called by any loader using the manager when the loader - * encounters an error when loading an item. - * - * @param {string} url - The URL of the item that produces an error. - */ - this.itemError = function ( url ) { - - if ( scope.onError !== undefined ) { - - scope.onError( url ); - - } - - }; - - /** - * Given a URL, uses the URL modifier callback (if any) and returns a - * resolved URL. If no URL modifier is set, returns the original URL. - * - * @param {string} url - The URL to load. - * @return {string} The resolved URL. - */ - this.resolveURL = function ( url ) { - - // Normalize to NFC so that Unicode URIs (e.g. from glTF) - // are percent-encoded correctly per RFC 3987. - - url = url.normalize( 'NFC' ); - - if ( urlModifier ) { - - return urlModifier( url ); - - } - - return url; - - }; - - /** - * If provided, the callback will be passed each resource URL before a - * request is sent. The callback may return the original URL, or a new URL to - * override loading behavior. This behavior can be used to load assets from - * .ZIP files, drag-and-drop APIs, and Data URIs. - * - * ```js - * const blobs = {'fish.gltf': blob1, 'diffuse.png': blob2, 'normal.png': blob3}; - * - * const manager = new THREE.LoadingManager(); - * - * // Initialize loading manager with URL callback. - * const objectURLs = []; - * manager.setURLModifier( ( url ) => { - * - * url = URL.createObjectURL( blobs[ url ] ); - * objectURLs.push( url ); - * return url; - * - * } ); - * - * // Load as usual, then revoke the blob URLs. - * const loader = new GLTFLoader( manager ); - * loader.load( 'fish.gltf', (gltf) => { - * - * scene.add( gltf.scene ); - * objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) ); - * - * } ); - * ``` - * - * @param {function(string):string} transform - URL modifier callback. Called with an URL and must return a resolved URL. - * @return {LoadingManager} A reference to this loading manager. - */ - this.setURLModifier = function ( transform ) { - - urlModifier = transform; - - return this; - - }; - - /** - * Registers a loader with the given regular expression. Can be used to - * define what loader should be used in order to load specific files. A - * typical use case is to overwrite the default loader for textures. - * - * ```js - * // add handler for TGA textures - * manager.addHandler( /\.tga$/i, new TGALoader() ); - * ``` - * - * @param {string} regex - A regular expression. - * @param {Loader} loader - A loader that should handle matched cases. - * @return {LoadingManager} A reference to this loading manager. - */ - this.addHandler = function ( regex, loader ) { - - handlers.push( regex, loader ); - - return this; - - }; - - /** - * Removes the loader for the given regular expression. - * - * @param {string} regex - A regular expression. - * @return {LoadingManager} A reference to this loading manager. - */ - this.removeHandler = function ( regex ) { - - const index = handlers.indexOf( regex ); - - if ( index !== -1 ) { - - handlers.splice( index, 2 ); - - } - - return this; - - }; - - /** - * Can be used to retrieve the registered loader for the given file path. - * - * @param {string} file - The file path. - * @return {?Loader} The registered loader. Returns `null` if no loader was found. - */ - this.getHandler = function ( file ) { - - for ( let i = 0, l = handlers.length; i < l; i += 2 ) { - - const regex = handlers[ i ]; - const loader = handlers[ i + 1 ]; - - if ( regex.global ) regex.lastIndex = 0; // see #17920 - - if ( regex.test( file ) ) { - - return loader; - - } - - } - - return null; - - }; - - /** - * Can be used to abort ongoing loading requests in loaders using this manager. - * The abort only works if the loaders implement {@link Loader#abort} and `AbortSignal.any()` - * is supported in the browser. - * - * @return {LoadingManager} A reference to this loading manager. - */ - this.abort = function () { - - - this.abortController.abort(); - this._abortController = null; - - return this; - - }; - - } - - // TODO: Revert this back to a single member variable once this issue has been fixed - // https://github.com/cloudflare/workerd/issues/3657 - - /** - * Used for aborting ongoing requests in loaders using this manager. - * - * @type {AbortController} - */ - get abortController() { - - if ( ! this._abortController ) { - - this._abortController = new AbortController(); - - } - - return this._abortController; - - } - -} - -/** - * The global default loading manager. - * - * @constant - * @type {LoadingManager} - */ -const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); - -/** - * Abstract base class for loaders. - * - * @abstract - */ -class Loader { - - /** - * Constructs a new loader. - * - * @param {LoadingManager} [manager] - The loading manager. - */ - constructor( manager ) { - - /** - * The loading manager. - * - * @type {LoadingManager} - * @default DefaultLoadingManager - */ - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - - /** - * The crossOrigin string to implement CORS for loading the url from a - * different domain that allows CORS. - * - * @type {string} - * @default 'anonymous' - */ - this.crossOrigin = 'anonymous'; - - /** - * Whether the XMLHttpRequest uses credentials. - * - * @type {boolean} - * @default false - */ - this.withCredentials = false; - - /** - * The base path from which the asset will be loaded. - * - * @type {string} - */ - this.path = ''; - - /** - * The base path from which additional resources like textures will be loaded. - * - * @type {string} - */ - this.resourcePath = ''; - - /** - * The [request header](https://developer.mozilla.org/en-US/docs/Glossary/Request_header) - * used in HTTP request. - * - * @type {Object} - */ - this.requestHeader = {}; - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - - } - - } - - /** - * This method needs to be implemented by all concrete loaders. It holds the - * logic for loading assets from the backend. - * - * @abstract - * @param {string} url - The path/URL of the file to be loaded. - * @param {Function} onLoad - Executed when the loading process has been finished. - * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. - * @param {onErrorCallback} [onError] - Executed when errors occur. - */ - load( /* url, onLoad, onProgress, onError */ ) {} - - /** - * A async version of {@link Loader#load}. - * - * @param {string} url - The path/URL of the file to be loaded. - * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. - * @return {Promise} A Promise that resolves when the asset has been loaded. - */ - loadAsync( url, onProgress ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.load( url, resolve, onProgress, reject ); - - } ); - - } - - /** - * This method needs to be implemented by all concrete loaders. It holds the - * logic for parsing the asset into three.js entities. - * - * @abstract - * @param {any} data - The data to parse. - */ - parse( /* data */ ) {} - - /** - * Sets the `crossOrigin` String to implement CORS for loading the URL - * from a different domain that allows CORS. - * - * @param {string} crossOrigin - The `crossOrigin` value. - * @return {Loader} A reference to this instance. - */ - setCrossOrigin( crossOrigin ) { - - this.crossOrigin = crossOrigin; - return this; - - } - - /** - * Whether the XMLHttpRequest uses credentials such as cookies, authorization - * headers or TLS client certificates, see [XMLHttpRequest.withCredentials](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials). - * - * Note: This setting has no effect if you are loading files locally or from the same domain. - * - * @param {boolean} value - The `withCredentials` value. - * @return {Loader} A reference to this instance. - */ - setWithCredentials( value ) { - - this.withCredentials = value; - return this; - - } - - /** - * Sets the base path for the asset. - * - * @param {string} path - The base path. - * @return {Loader} A reference to this instance. - */ - setPath( path ) { - - this.path = path; - return this; - - } - - /** - * Sets the base path for dependent resources like textures. - * - * @param {string} resourcePath - The resource path. - * @return {Loader} A reference to this instance. - */ - setResourcePath( resourcePath ) { - - this.resourcePath = resourcePath; - return this; - - } - - /** - * Sets the given request header. - * - * @param {Object} requestHeader - A [request header](https://developer.mozilla.org/en-US/docs/Glossary/Request_header) - * for configuring the HTTP request. - * @return {Loader} A reference to this instance. - */ - setRequestHeader( requestHeader ) { - - this.requestHeader = requestHeader; - return this; - - } - - /** - * This method can be implemented in loaders for aborting ongoing requests. - * - * @abstract - * @return {Loader} A reference to this instance. - */ - abort() { - - return this; - - } - -} - -/** - * Callback for onProgress in loaders. - * - * @callback onProgressCallback - * @param {ProgressEvent} event - An instance of `ProgressEvent` that represents the current loading status. - */ - -/** - * Callback for onError in loaders. - * - * @callback onErrorCallback - * @param {Error} error - The error which occurred during the loading process. - */ - -/** - * The default material name that is used by loaders - * when creating materials for loaded 3D objects. - * - * Note: Not all loaders might honor this setting. - * - * @static - * @type {string} - * @default '__DEFAULT' - */ -Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; - -const _loading = new WeakMap(); - -/** - * A loader for loading images. The class loads images with the HTML `Image` API. - * - * ```js - * const loader = new THREE.ImageLoader(); - * const image = await loader.loadAsync( 'image.png' ); - * ``` - * Please note that `ImageLoader` has dropped support for progress - * events in `r84`. For an `ImageLoader` that supports progress events, see - * [this thread](https://github.com/mrdoob/three.js/issues/10439#issuecomment-275785639). - * - * @augments Loader - */ -class ImageLoader extends Loader { - - /** - * Constructs a new image loader. - * - * @param {LoadingManager} [manager] - The loading manager. - */ - constructor( manager ) { - - super( manager ); - - } - - /** - * Starts loading from the given URL and passes the loaded image - * to the `onLoad()` callback. The method also returns a new `Image` object which can - * directly be used for texture creation. If you do it this way, the texture - * may pop up in your scene once the respective loading process is finished. - * - * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. - * @param {function(Image)} onLoad - Executed when the loading process has been finished. - * @param {onProgressCallback} onProgress - Unsupported in this loader. - * @param {onErrorCallback} onError - Executed when errors occur. - * @return {Image} The image. - */ - load( url, onLoad, onProgress, onError ) { - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - const scope = this; - - const cached = Cache.get( `image:${url}` ); - - if ( cached !== undefined ) { - - if ( cached.complete === true ) { - - scope.manager.itemStart( url ); - - setTimeout( function () { - - if ( onLoad ) onLoad( cached ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - } else { - - let arr = _loading.get( cached ); - - if ( arr === undefined ) { - - arr = []; - _loading.set( cached, arr ); - - } - - arr.push( { onLoad, onError } ); - - } - - return cached; - - } - - const image = createElementNS( 'img' ); - - function onImageLoad() { - - removeEventListeners(); - - if ( onLoad ) onLoad( this ); - - // - - const callbacks = _loading.get( this ) || []; - - for ( let i = 0; i < callbacks.length; i ++ ) { - - const callback = callbacks[ i ]; - if ( callback.onLoad ) callback.onLoad( this ); - - } - - _loading.delete( this ); - - scope.manager.itemEnd( url ); - - } - - function onImageError( event ) { - - removeEventListeners(); - - if ( onError ) onError( event ); - - Cache.remove( `image:${url}` ); - - // - - const callbacks = _loading.get( this ) || []; - - for ( let i = 0; i < callbacks.length; i ++ ) { - - const callback = callbacks[ i ]; - if ( callback.onError ) callback.onError( event ); - - } - - _loading.delete( this ); - - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - } - - function removeEventListeners() { - - image.removeEventListener( 'load', onImageLoad, false ); - image.removeEventListener( 'error', onImageError, false ); - - } - - image.addEventListener( 'load', onImageLoad, false ); - image.addEventListener( 'error', onImageError, false ); - - if ( url.slice( 0, 5 ) !== 'data:' ) { - - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; - - } - - Cache.add( `image:${url}`, image ); - scope.manager.itemStart( url ); - - image.src = url; - - return image; - - } - -} - -/** - * Class for loading textures. Images are internally - * loaded via {@link ImageLoader}. - * - * ```js - * const loader = new THREE.TextureLoader(); - * const texture = await loader.loadAsync( 'textures/land_ocean_ice_cloud_2048.jpg' ); - * - * const material = new THREE.MeshBasicMaterial( { map:texture } ); - * ``` - * Please note that `TextureLoader` has dropped support for progress - * events in `r84`. For a `TextureLoader` that supports progress events, see - * [this thread](https://github.com/mrdoob/three.js/issues/10439#issuecomment-293260145). - * - * @augments Loader - */ -class TextureLoader extends Loader { - - /** - * Constructs a new texture loader. - * - * @param {LoadingManager} [manager] - The loading manager. - */ - constructor( manager ) { - - super( manager ); - - } - - /** - * Starts loading from the given URL and pass the fully loaded texture - * to the `onLoad()` callback. The method also returns a new texture object which can - * directly be used for material creation. If you do it this way, the texture - * may pop up in your scene once the respective loading process is finished. - * - * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. - * @param {function(Texture)} onLoad - Executed when the loading process has been finished. - * @param {onProgressCallback} onProgress - Unsupported in this loader. - * @param {onErrorCallback} onError - Executed when errors occur. - * @return {Texture} The texture. - */ - load( url, onLoad, onProgress, onError ) { - - const texture = new Texture(); - - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); - - loader.load( url, function ( image ) { - - texture.image = image; - texture.needsUpdate = true; - - if ( onLoad !== undefined ) { - - onLoad( texture ); - - } - - }, onProgress, onError ); - - return texture; - - } - -} - -/** - * Abstract base class for lights - all other light types inherit the - * properties and methods described here. - * - * @abstract - * @augments Object3D - */ -class Light extends Object3D { - - /** - * Constructs a new light. - * - * @param {(number|Color|string)} [color=0xffffff] - The light's color. - * @param {number} [intensity=1] - The light's strength/intensity. - */ - constructor( color, intensity = 1 ) { - - super(); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isLight = true; - - this.type = 'Light'; - - /** - * The light's color. - * - * @type {Color} - */ - this.color = new Color( color ); - - /** - * The light's intensity. - * - * @type {number} - * @default 1 - */ - this.intensity = intensity; - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.color.copy( source.color ); - this.intensity = source.intensity; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; - - return data; - - } - -} - -/** - * A light source positioned directly above the scene, with color fading from - * the sky color to the ground color. - * - * This light cannot be used to cast shadows. - * - * ```js - * const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); - * scene.add( light ); - * ``` - * - * @augments Light - */ -class HemisphereLight extends Light { - - /** - * Constructs a new hemisphere light. - * - * @param {(number|Color|string)} [skyColor=0xffffff] - The light's sky color. - * @param {(number|Color|string)} [groundColor=0xffffff] - The light's ground color. - * @param {number} [intensity=1] - The light's strength/intensity. - */ - constructor( skyColor, groundColor, intensity ) { - - super( skyColor, intensity ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isHemisphereLight = true; - - this.type = 'HemisphereLight'; - - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); - - /** - * The light's ground color. - * - * @type {Color} - */ - this.groundColor = new Color( groundColor ); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.groundColor.copy( source.groundColor ); - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.groundColor = this.groundColor.getHex(); - - return data; - - } - -} - -const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); -const _lightPositionWorld = /*@__PURE__*/ new Vector3(); -const _lookTarget = /*@__PURE__*/ new Vector3(); - -/** - * Abstract base class for light shadow classes. These classes - * represent the shadow configuration for different light types. - * - * @abstract - */ -class LightShadow { - - /** - * Constructs a new light shadow. - * - * @param {Camera} camera - The light's view of the world. - */ - constructor( camera ) { - - /** - * The light's view of the world. - * - * @type {Camera} - */ - this.camera = camera; - - /** - * The intensity of the shadow. The default is `1`. - * Valid values are in the range `[0, 1]`. - * - * @type {number} - * @default 1 - */ - this.intensity = 1; - - /** - * Shadow map bias, how much to add or subtract from the normalized depth - * when deciding whether a surface is in shadow. - * - * The default is `0`. Very tiny adjustments here (in the order of `0.0001`) - * may help reduce artifacts in shadows. - * - * @type {number} - * @default 0 - */ - this.bias = 0; - - /** - * A node version of `bias`. Only supported with `WebGPURenderer`. - * - * If a bias node is defined, `bias` has no effect. - * - * @type {?Node} - * @default null - */ - this.biasNode = null; - - /** - * Defines how much the position used to query the shadow map is offset along - * the object normal. The default is `0`. Increasing this value can be used to - * reduce shadow acne especially in large scenes where light shines onto - * geometry at a shallow angle. The cost is that shadows may appear distorted. - * - * @type {number} - * @default 0 - */ - this.normalBias = 0; - - /** - * Setting this to values greater than 1 will blur the edges of the shadow. - * High values will cause unwanted banding effects in the shadows - a greater - * map size will allow for a higher value to be used here before these effects - * become visible. - * - * The property has no effect when the shadow map type is `BasicShadowMap`. - * - * @type {number} - * @default 1 - */ - this.radius = 1; - - /** - * The amount of samples to use when blurring a VSM shadow map. - * - * @type {number} - * @default 8 - */ - this.blurSamples = 8; - - /** - * Defines the width and height of the shadow map. Higher values give better quality - * shadows at the cost of computation time. Values must be powers of two. - * - * @type {Vector2} - * @default (512,512) - */ - this.mapSize = new Vector2( 512, 512 ); - - /** - * The type of shadow texture. The default is `UnsignedByteType`. - * - * @type {number} - * @default UnsignedByteType - */ - this.mapType = UnsignedByteType; - - /** - * The depth map generated using the internal camera; a location beyond a - * pixel's depth is in shadow. Computed internally during rendering. - * - * @type {?RenderTarget} - * @default null - */ - this.map = null; - - /** - * The distribution map generated using the internal camera; an occlusion is - * calculated based on the distribution of depths. Computed internally during - * rendering. - * - * @type {?RenderTarget} - * @default null - */ - this.mapPass = null; - - /** - * Model to shadow camera space, to compute location and depth in shadow map. - * This is computed internally during rendering. - * - * @type {Matrix4} - */ - this.matrix = new Matrix4(); - - /** - * Enables automatic updates of the light's shadow. If you do not require dynamic - * lighting / shadows, you may set this to `false`. - * - * @type {boolean} - * @default true - */ - this.autoUpdate = true; - - /** - * When set to `true`, shadow maps will be updated in the next `render` call. - * If you have set {@link LightShadow#autoUpdate} to `false`, you will need to - * set this property to `true` and then make a render call to update the light's shadow. - * - * @type {boolean} - * @default false - */ - this.needsUpdate = false; - - this._frustum = new Frustum(); - this._frameExtents = new Vector2( 1, 1 ); - - this._viewportCount = 1; - - this._viewports = [ - - new Vector4( 0, 0, 1, 1 ) - - ]; - - } - - /** - * Used internally by the renderer to get the number of viewports that need - * to be rendered for this shadow. - * - * @return {number} The viewport count. - */ - getViewportCount() { - - return this._viewportCount; - - } - - /** - * Used internally by the renderer to get the camera that renders the given viewport. - * - * @param {number} [viewportIndex=0] - The viewport index. - * @return {Camera} The shadow camera. - */ - getCamera( /* viewportIndex */ ) { - - return this.camera; - - } - - /** - * Gets the shadow cameras frustum. Used internally by the renderer to cull objects. - * - * @return {Frustum} The shadow camera frustum. - */ - getFrustum() { - - return this._frustum; - - } - - /** - * Update the matrices for the camera and shadow, used internally by the renderer. - * - * @param {Light} light - The light for which the shadow is being rendered. - */ - updateMatrices( light ) { - - const shadowCamera = this.camera; - _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); - shadowCamera.position.copy( _lightPositionWorld ); - - _lookTarget.setFromMatrixPosition( light.target.matrixWorld ); - shadowCamera.lookAt( _lookTarget ); - shadowCamera.updateMatrixWorld(); - this._updateMatrix( shadowCamera, this.matrix, this._frustum ); - - } - - /** - * Updates a shadow projection matrix and its corresponding frustum. - * - * @private - * @param {Camera} shadowCamera - The shadow camera. - * @param {Matrix4} shadowMatrix - The target shadow matrix. - * @param {Frustum} frustum - The target frustum. - * @param {Vector4} [viewport] - The viewport within the shadow atlas. - */ - _updateMatrix( shadowCamera, shadowMatrix, frustum, viewport ) { - - _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); - frustum.setFromProjectionMatrix( _projScreenMatrix, shadowCamera.coordinateSystem, shadowCamera.reversedDepth ); - - const frameExtents = this._frameExtents; - const scaleX = viewport ? viewport.z / frameExtents.x : 1; - const scaleY = viewport ? viewport.w / frameExtents.y : 1; - const offsetX = viewport ? viewport.x / frameExtents.x : 0; - const offsetY = viewport ? viewport.y / frameExtents.y : 0; - - if ( shadowCamera.coordinateSystem === WebGPUCoordinateSystem || shadowCamera.reversedDepth ) { - - shadowMatrix.set( - 0.5 * scaleX, 0.0, 0.0, 0.5 * scaleX + offsetX, - 0.0, 0.5 * scaleY, 0.0, 0.5 * scaleY + offsetY, - 0.0, 0.0, 1.0, 0.0, // Identity Z (preserving the correct [0, 1] range from the projection matrix) - 0.0, 0.0, 0.0, 1.0 - ); - - } else { - - shadowMatrix.set( - 0.5 * scaleX, 0.0, 0.0, 0.5 * scaleX + offsetX, - 0.0, 0.5 * scaleY, 0.0, 0.5 * scaleY + offsetY, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - - } - - shadowMatrix.multiply( _projScreenMatrix ); - - } - - /** - * Returns a viewport definition for the given viewport index. - * - * @param {number} viewportIndex - The viewport index. - * @return {Vector4} The viewport. - */ - getViewport( viewportIndex ) { - - return this._viewports[ viewportIndex ]; - - } - - /** - * Returns the frame extends. - * - * @return {Vector2} The frame extends. - */ - getFrameExtents() { - - return this._frameExtents; - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - */ - dispose() { - - if ( this.map ) { - - this.map.dispose(); - - } - - if ( this.mapPass ) { - - this.mapPass.dispose(); - - } - - } - - /** - * Copies the values of the given light shadow instance to this instance. - * - * @param {LightShadow} source - The light shadow to copy. - * @return {LightShadow} A reference to this light shadow instance. - */ - copy( source ) { - - this.camera = source.camera.clone(); - - this.intensity = source.intensity; - - this.bias = source.bias; - this.radius = source.radius; - - this.autoUpdate = source.autoUpdate; - this.needsUpdate = source.needsUpdate; - this.normalBias = source.normalBias; - this.blurSamples = source.blurSamples; - - this.mapSize.copy( source.mapSize ); - - this.biasNode = source.biasNode; - - return this; - - } - - /** - * Returns a new light shadow instance with copied values from this instance. - * - * @return {LightShadow} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Serializes the light shadow into JSON. - * - * @return {Object} A JSON object representing the serialized light shadow. - * @see {@link ObjectLoader#parse} - */ - toJSON() { - - const object = {}; - - object.intensity = this.intensity; - object.bias = this.bias; - object.normalBias = this.normalBias; - object.radius = this.radius; - object.blurSamples = this.blurSamples; - object.mapSize = this.mapSize.toArray(); - - object.camera = this.camera.toJSON( false ).object; - delete object.camera.matrix; - - return object; - - } - -} - -/** - * Represents the shadow configuration of directional lights. - * - * @augments LightShadow - */ -class DirectionalLightShadow extends LightShadow { - - /** - * Constructs a new directional light shadow. - */ - constructor() { - - super( new OrthographicCamera( -5, 5, 5, -5, 0.5, 500 ) ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isDirectionalLightShadow = true; - - } - -} - -/** - * A light that gets emitted in a specific direction. This light will behave - * as though it is infinitely far away and the rays produced from it are all - * parallel. The common use case for this is to simulate daylight; the sun is - * far enough away that its position can be considered to be infinite, and - * all light rays coming from it are parallel. - * - * A common point of confusion for directional lights is that setting the - * rotation has no effect. This is because three.js's DirectionalLight is the - * equivalent to what is often called a 'Target Direct Light' in other - * applications. - * - * This means that its direction is calculated as pointing from the light's - * {@link Object3D#position} to the {@link DirectionalLight#target} position - * (as opposed to a 'Free Direct Light' that just has a rotation - * component). - * - * This light can cast shadows - see the {@link DirectionalLightShadow} for details. - * - * ```js - * // White directional light at half intensity shining from the top. - * const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); - * scene.add( directionalLight ); - * ``` - * - * @augments Light - */ -class DirectionalLight extends Light { - - /** - * Constructs a new directional light. - * - * @param {(number|Color|string)} [color=0xffffff] - The light's color. - * @param {number} [intensity=1] - The light's strength/intensity. - */ - constructor( color, intensity ) { - - super( color, intensity ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isDirectionalLight = true; - - this.type = 'DirectionalLight'; - - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); - - /** - * The directional light points from its position to the - * target's position. - * - * For the target's position to be changed to anything other - * than the default, it must be added to the scene. - * - * It is also possible to set the target to be another 3D object - * in the scene. The light will now track the target object. - * - * @type {Object3D} - */ - this.target = new Object3D(); - - /** - * This property holds the light's shadow configuration. - * - * @type {DirectionalLightShadow} - */ - this.shadow = new DirectionalLightShadow(); - - } - - dispose() { - - super.dispose(); - - this.shadow.dispose(); - - } - - copy( source ) { - - super.copy( source ); - - this.target = source.target.clone(); - this.shadow = source.shadow.clone(); - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.shadow = this.shadow.toJSON(); - data.object.target = this.target.uuid; - - return data; - - } - -} - -/** - * This light globally illuminates all objects in the scene equally. - * - * It cannot be used to cast shadows as it does not have a direction. - * - * ```js - * const light = new THREE.AmbientLight( 0x404040 ); // soft white light - * scene.add( light ); - * ``` - * - * @augments Light - */ -class AmbientLight extends Light { - - /** - * Constructs a new ambient light. - * - * @param {(number|Color|string)} [color=0xffffff] - The light's color. - * @param {number} [intensity=1] - The light's strength/intensity. - */ - constructor( color, intensity ) { - - super( color, intensity ); - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isAmbientLight = true; - - this.type = 'AmbientLight'; - - } - -} - -const _matrix = /*@__PURE__*/ new Matrix4(); - -/** - * This class is designed to assist with raycasting. Raycasting is used for - * mouse picking (working out what objects in the 3d space the mouse is over) - * amongst other things. - */ -class Raycaster { - - /** - * Constructs a new raycaster. - * - * @param {Vector3} origin - The origin vector where the ray casts from. - * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. - * @param {number} [near=0] - All results returned are further away than near. Near can't be negative. - * @param {number} [far=Infinity] - All results returned are closer than far. Far can't be lower than near. - */ - constructor( origin, direction, near = 0, far = Infinity ) { - - /** - * The ray used for raycasting. - * - * @type {Ray} - */ - this.ray = new Ray( origin, direction ); - - /** - * All results returned are further away than near. Near can't be negative. - * - * @type {number} - * @default 0 - */ - this.near = near; - - /** - * All results returned are closer than far. Far can't be lower than near. - * - * @type {number} - * @default Infinity - */ - this.far = far; - - /** - * The camera to use when raycasting against view-dependent objects such as - * billboarded objects like sprites. This field can be set manually or - * is set when calling `setFromCamera()`. - * - * @type {?Camera} - * @default null - */ - this.camera = null; - - /** - * Allows to selectively ignore 3D objects when performing intersection tests. - * The following code example ensures that only 3D objects on layer `1` will be - * honored by raycaster. - * ```js - * raycaster.layers.set( 1 ); - * object.layers.enable( 1 ); - * ``` - * - * @type {Layers} - */ - this.layers = new Layers(); - - - /** - * A parameter object that configures the raycasting. It has the structure: - * - * ``` - * { - * Mesh: {}, - * Line: { threshold: 1 }, - * LOD: {}, - * Points: { threshold: 1 }, - * Sprite: {} - * } - * ``` - * Where `threshold` is the precision of the raycaster when intersecting objects, in world units. - * - * @type {Object} - */ - this.params = { - Mesh: {}, - Line: { threshold: 1 }, - LOD: {}, - Points: { threshold: 1 }, - Sprite: {} - }; - - } - - /** - * Updates the ray with a new origin and direction by copying the values from the arguments. - * - * @param {Vector3} origin - The origin vector where the ray casts from. - * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. - */ - set( origin, direction ) { - - // direction is assumed to be normalized (for accurate distance calculations) - - this.ray.set( origin, direction ); - - } - - /** - * Uses the given coordinates and camera to compute a new origin and direction for the internal ray. - * - * @param {Vector2} coords - 2D coordinates of the mouse, in normalized device coordinates (NDC). - * X and Y components should be between `-1` and `1`. - * @param {Camera} camera - The camera from which the ray should originate. - */ - setFromCamera( coords, camera ) { - - if ( camera.isPerspectiveCamera ) { - - this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); - this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); - this.camera = camera; - - } else if ( camera.isOrthographicCamera ) { - - this.ray.origin.set( coords.x, coords.y, camera.projectionMatrix.elements[ 14 ] ).unproject( camera ); // set origin in plane of camera - this.ray.direction.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ); - this.camera = camera; - - } else { - - error( 'Raycaster: Unsupported camera type: ' + camera.type ); - - } - - } - - /** - * Uses the given WebXR controller to compute a new origin and direction for the internal ray. - * - * @param {WebXRController} controller - The controller to copy the position and direction from. - * @return {Raycaster} A reference to this raycaster. - */ - setFromXRController( controller ) { - - _matrix.identity().extractRotation( controller.matrixWorld ); - - this.ray.origin.setFromMatrixPosition( controller.matrixWorld ); - this.ray.direction.set( 0, 0, -1 ).applyMatrix4( _matrix ); - - return this; - - } - - /** - * The intersection point of a raycaster intersection test. - * @typedef {Object} Raycaster~Intersection - * @property {number} distance - The distance from the ray's origin to the intersection point. - * @property {number} distanceToRay - Some 3D objects e.g. {@link Points} provide the distance of the - * intersection to the nearest point on the ray. For other objects it will be `undefined`. - * @property {Vector3} point - The intersection point, in world coordinates. - * @property {Object} face - The face that has been intersected. - * @property {number} faceIndex - The face index. - * @property {Object3D} object - The 3D object that has been intersected. - * @property {Vector2} uv - U,V coordinates at point of intersection. - * @property {Vector2} uv1 - Second set of U,V coordinates at point of intersection. - * @property {Vector3} normal - Interpolated normal vector at point of intersection. - * @property {number} instanceId - The index number of the instance where the ray - * intersects the {@link InstancedMesh}. - */ - - /** - * Checks all intersection between the ray and the object with or without the - * descendants. Intersections are returned sorted by distance, closest first. - * - * `Raycaster` delegates to the `raycast()` method of the passed 3D object, when - * evaluating whether the ray intersects the object or not. This allows meshes to respond - * differently to ray casting than lines or points. - * - * Note that for meshes, faces must be pointed towards the origin of the ray in order - * to be detected; intersections of the ray passing through the back of a face will not - * be detected. To raycast against both faces of an object, you'll want to set {@link Material#side} - * to `THREE.DoubleSide`. - * - * Note that a ray hitting a triangle mesh exactly along an edge shared by two faces may be - * reported by both faces, resulting in two coincident intersections (identical point and - * distance) in the returned array. - * - * @param {Object3D} object - The 3D object to check for intersection with the ray. - * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. - * Otherwise it only checks intersection with the object. - * @param {Array} [intersects=[]] The target array that holds the result of the method. - * @return {Array} An array holding the intersection points. - */ - intersectObject( object, recursive = true, intersects = [] ) { - - intersect( object, this, intersects, recursive ); - - intersects.sort( ascSort ); - - return intersects; - - } - - /** - * Checks all intersection between the ray and the objects with or without - * the descendants. Intersections are returned sorted by distance, closest first. - * - * @param {Array} objects - The 3D objects to check for intersection with the ray. - * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. - * Otherwise it only checks intersection with the object. - * @param {Array} [intersects=[]] The target array that holds the result of the method. - * @return {Array} An array holding the intersection points. - */ - intersectObjects( objects, recursive = true, intersects = [] ) { - - for ( let i = 0, l = objects.length; i < l; i ++ ) { - - intersect( objects[ i ], this, intersects, recursive ); - - } - - intersects.sort( ascSort ); - - return intersects; - - } - -} - -function ascSort( a, b ) { - - return a.distance - b.distance; - -} - -function intersect( object, raycaster, intersects, recursive ) { - - let propagate = true; - - if ( object.layers.test( raycaster.layers ) ) { - - const result = object.raycast( raycaster, intersects ); - - if ( result === false ) propagate = false; - - } - - if ( propagate === true && recursive === true ) { - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - intersect( children[ i ], raycaster, intersects, true ); - - } - - } - -} - -/** - * This class is an alternative to {@link Clock} with a different API design and behavior. - * The goal is to avoid the conceptual flaws that became apparent in `Clock` over time. - * - * - `Timer` has an `update()` method that updates its internal state. That makes it possible to - * call `getDelta()` and `getElapsed()` multiple times per simulation step without getting different values. - * - The class can make use of the Page Visibility API to avoid large time delta values when the app - * is inactive (e.g. tab switched or browser hidden). - * - * ```js - * const timer = new Timer(); - * timer.connect( document ); // use Page Visibility API - * ``` - */ -class Timer { - - /** - * Constructs a new timer. - */ - constructor() { - - this._previousTime = 0; - this._currentTime = 0; - this._startTime = performance.now(); - - this._delta = 0; - this._elapsed = 0; - - this._timescale = 1; - - this._document = null; - this._pageVisibilityHandler = null; - - } - - /** - * Connect the timer to the given document.Calling this method is not mandatory to - * use the timer but enables the usage of the Page Visibility API to avoid large time - * delta values. - * - * @param {Document} document - The document. - */ - connect( document ) { - - this._document = document; - - // use Page Visibility API to avoid large time delta values - - if ( document.hidden !== undefined ) { - - this._pageVisibilityHandler = handleVisibilityChange.bind( this ); - - document.addEventListener( 'visibilitychange', this._pageVisibilityHandler, false ); - - } - - } - - /** - * Disconnects the timer from the DOM and also disables the usage of the Page Visibility API. - */ - disconnect() { - - if ( this._pageVisibilityHandler !== null ) { - - this._document.removeEventListener( 'visibilitychange', this._pageVisibilityHandler ); - this._pageVisibilityHandler = null; - - } - - this._document = null; - - } - - /** - * Returns the time delta in seconds. - * - * @return {number} The time delta in second. - */ - getDelta() { - - return this._delta / 1000; - - } - - /** - * Returns the elapsed time in seconds. - * - * @return {number} The elapsed time in second. - */ - getElapsed() { - - return this._elapsed / 1000; - - } - - /** - * Returns the timescale. - * - * @return {number} The timescale. - */ - getTimescale() { - - return this._timescale; - - } - - /** - * Sets the given timescale which scale the time delta computation - * in `update()`. - * - * @param {number} timescale - The timescale to set. - * @return {Timer} A reference to this timer. - */ - setTimescale( timescale ) { - - this._timescale = timescale; - - return this; - - } - - /** - * Resets the time computation for the current simulation step. - * - * @return {Timer} A reference to this timer. - */ - reset() { - - this._currentTime = performance.now() - this._startTime; - - return this; - - } - - /** - * Can be used to free all internal resources. Usually called when - * the timer instance isn't required anymore. - */ - dispose() { - - this.disconnect(); - - } - - /** - * Updates the internal state of the timer. This method should be called - * once per simulation step and before you perform queries against the timer - * (e.g. via `getDelta()`). - * - * @param {number} timestamp - The current time in milliseconds. Can be obtained - * from the `requestAnimationFrame` callback argument. If not provided, the current - * time will be determined with `performance.now`. - * @return {Timer} A reference to this timer. - */ - update( timestamp ) { - - if ( this._pageVisibilityHandler !== null && this._document.hidden === true ) { - - this._delta = 0; - - } else { - - this._previousTime = this._currentTime; - this._currentTime = ( timestamp !== undefined ? timestamp : performance.now() ) - this._startTime; - - this._delta = ( this._currentTime - this._previousTime ) * this._timescale; - this._elapsed += this._delta; // _elapsed is the accumulation of all previous deltas - - } - - return this; - - } - -} - -function handleVisibilityChange() { - - if ( this._document.hidden === false ) this.reset(); - -} - -/** - * This class can be used to represent points in 3D space as - * [Spherical coordinates](https://en.wikipedia.org/wiki/Spherical_coordinate_system). - */ -class Spherical { - - /** - * Constructs a new spherical. - * - * @param {number} [radius=1] - The radius, or the Euclidean distance (straight-line distance) from the point to the origin. - * @param {number} [phi=0] - The polar angle in radians from the y (up) axis. - * @param {number} [theta=0] - The equator/azimuthal angle in radians around the y (up) axis. - */ - constructor( radius = 1, phi = 0, theta = 0 ) { - - /** - * The radius, or the Euclidean distance (straight-line distance) from the point to the origin. - * - * @type {number} - * @default 1 - */ - this.radius = radius; - - /** - * The polar angle in radians from the y (up) axis. - * - * @type {number} - * @default 0 - */ - this.phi = phi; - - /** - * The equator/azimuthal angle in radians around the y (up) axis. - * - * @type {number} - * @default 0 - */ - this.theta = theta; - - } - - /** - * Sets the spherical components by copying the given values. - * - * @param {number} radius - The radius. - * @param {number} phi - The polar angle. - * @param {number} theta - The azimuthal angle. - * @return {Spherical} A reference to this spherical. - */ - set( radius, phi, theta ) { - - this.radius = radius; - this.phi = phi; - this.theta = theta; - - return this; - - } - - /** - * Copies the values of the given spherical to this instance. - * - * @param {Spherical} other - The spherical to copy. - * @return {Spherical} A reference to this spherical. - */ - copy( other ) { - - this.radius = other.radius; - this.phi = other.phi; - this.theta = other.theta; - - return this; - - } - - /** - * Restricts the polar angle [page:.phi phi] to be between `0.000001` and pi - - * `0.000001`. - * - * @return {Spherical} A reference to this spherical. - */ - makeSafe() { - - const EPS = 0.000001; - this.phi = clamp( this.phi, EPS, Math.PI - EPS ); - - return this; - - } - - /** - * Sets the spherical components from the given vector which is assumed to hold - * Cartesian coordinates. - * - * @param {Vector3} v - The vector to set. - * @return {Spherical} A reference to this spherical. - */ - setFromVector3( v ) { - - return this.setFromCartesianCoords( v.x, v.y, v.z ); - - } - - /** - * Sets the spherical components from the given Cartesian coordinates. - * - * @param {number} x - The x value. - * @param {number} y - The y value. - * @param {number} z - The z value. - * @return {Spherical} A reference to this spherical. - */ - setFromCartesianCoords( x, y, z ) { - - this.radius = Math.sqrt( x * x + y * y + z * z ); - - if ( this.radius === 0 ) { - - this.theta = 0; - this.phi = 0; - - } else { - - this.theta = Math.atan2( x, z ); - this.phi = Math.acos( clamp( y / this.radius, -1, 1 ) ); - - } - - return this; - - } - - /** - * Returns a new spherical with copied values from this instance. - * - * @return {Spherical} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - -} - -const _vector = /*@__PURE__*/ new Vector2(); - -/** - * Represents an axis-aligned bounding box (AABB) in 2D space. - */ -class Box2 { - - /** - * Constructs a new bounding box. - * - * @param {Vector2} [min=(Infinity,Infinity)] - A vector representing the lower boundary of the box. - * @param {Vector2} [max=(-Infinity,-Infinity)] - A vector representing the upper boundary of the box. - */ - constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isBox2 = true; - - /** - * The lower boundary of the box. - * - * @type {Vector2} - */ - this.min = min; - - /** - * The upper boundary of the box. - * - * @type {Vector2} - */ - this.max = max; - - } - - /** - * Sets the lower and upper boundaries of this box. - * Please note that this method only copies the values from the given objects. - * - * @param {Vector2} min - The lower boundary of the box. - * @param {Vector2} max - The upper boundary of the box. - * @return {Box2} A reference to this bounding box. - */ - set( min, max ) { - - this.min.copy( min ); - this.max.copy( max ); - - return this; - - } - - /** - * Sets the upper and lower bounds of this box so it encloses the position data - * in the given array. - * - * @param {Array} points - An array holding 2D position data as instances of {@link Vector2}. - * @return {Box2} A reference to this bounding box. - */ - setFromPoints( points ) { - - this.makeEmpty(); - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - this.expandByPoint( points[ i ] ); - - } - - return this; - - } - - /** - * Centers this box on the given center vector and sets this box's width, height and - * depth to the given size values. - * - * @param {Vector2} center - The center of the box. - * @param {Vector2} size - The x and y dimensions of the box. - * @return {Box2} A reference to this bounding box. - */ - setFromCenterAndSize( center, size ) { - - const halfSize = _vector.copy( size ).multiplyScalar( 0.5 ); - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); - - return this; - - } - - /** - * Returns a new box with copied values from this instance. - * - * @return {Box2} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - - /** - * Copies the values of the given box to this instance. - * - * @param {Box2} box - The box to copy. - * @return {Box2} A reference to this bounding box. - */ - copy( box ) { - - this.min.copy( box.min ); - this.max.copy( box.max ); - - return this; - - } - - /** - * Makes this box empty which means in encloses a zero space in 2D. - * - * @return {Box2} A reference to this bounding box. - */ - makeEmpty() { - - this.min.x = this.min.y = + Infinity; - this.max.x = this.max.y = - Infinity; - - return this; - - } - - /** - * Returns true if this box includes zero points within its bounds. - * Note that a box with equal lower and upper bounds still includes one - * point, the one both bounds share. - * - * @return {boolean} Whether this box is empty or not. - */ - isEmpty() { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); - - } - - /** - * Returns the center point of this box. - * - * @param {Vector2} target - The target vector that is used to store the method's result. - * @return {Vector2} The center point. - */ - getCenter( target ) { - - return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - - } - - /** - * Returns the dimensions of this box. - * - * @param {Vector2} target - The target vector that is used to store the method's result. - * @return {Vector2} The size. - */ - getSize( target ) { - - return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); - - } - - /** - * Expands the boundaries of this box to include the given point. - * - * @param {Vector2} point - The point that should be included by the bounding box. - * @return {Box2} A reference to this bounding box. - */ - expandByPoint( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; - - } - - /** - * Expands this box equilaterally by the given vector. The width of this - * box will be expanded by the x component of the vector in both - * directions. The height of this box will be expanded by the y component of - * the vector in both directions. - * - * @param {Vector2} vector - The vector that should expand the bounding box. - * @return {Box2} A reference to this bounding box. - */ - expandByVector( vector ) { - - this.min.sub( vector ); - this.max.add( vector ); - - return this; - - } - - /** - * Expands each dimension of the box by the given scalar. If negative, the - * dimensions of the box will be contracted. - * - * @param {number} scalar - The scalar value that should expand the bounding box. - * @return {Box2} A reference to this bounding box. - */ - expandByScalar( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); - - return this; - - } - - /** - * Returns `true` if the given point lies within or on the boundaries of this box. - * - * @param {Vector2} point - The point to test. - * @return {boolean} Whether the bounding box contains the given point or not. - */ - containsPoint( point ) { - - return point.x >= this.min.x && point.x <= this.max.x && - point.y >= this.min.y && point.y <= this.max.y; - - } - - /** - * Returns `true` if this bounding box includes the entirety of the given bounding box. - * If this box and the given one are identical, this function also returns `true`. - * - * @param {Box2} box - The bounding box to test. - * @return {boolean} Whether the bounding box contains the given bounding box or not. - */ - containsBox( box ) { - - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y; - - } - - /** - * Returns a point as a proportion of this box's width and height. - * - * @param {Vector2} point - A point in 2D space. - * @param {Vector2} target - The target vector that is used to store the method's result. - * @return {Vector2} A point as a proportion of this box's width and height. - */ - getParameter( point, target ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ) - ); - - } - - /** - * Returns `true` if the given bounding box intersects with this bounding box. - * - * @param {Box2} box - The bounding box to test. - * @return {boolean} Whether the given bounding box intersects with this bounding box. - */ - intersectsBox( box ) { - - // using 4 splitting planes to rule out intersections - - return box.max.x >= this.min.x && box.min.x <= this.max.x && - box.max.y >= this.min.y && box.min.y <= this.max.y; - - } - - /** - * Clamps the given point within the bounds of this box. - * - * @param {Vector2} point - The point to clamp. - * @param {Vector2} target - The target vector that is used to store the method's result. - * @return {Vector2} The clamped point. - */ - clampPoint( point, target ) { - - return target.copy( point ).clamp( this.min, this.max ); - - } - - /** - * Returns the euclidean distance from any edge of this box to the specified point. If - * the given point lies inside of this box, the distance will be `0`. - * - * @param {Vector2} point - The point to compute the distance to. - * @return {number} The euclidean distance. - */ - distanceToPoint( point ) { - - return this.clampPoint( point, _vector ).distanceTo( point ); - - } - - /** - * Computes the intersection of this bounding box and the given one, setting the upper - * bound of this box to the lesser of the two boxes' upper bounds and the - * lower bound of this box to the greater of the two boxes' lower bounds. If - * there's no overlap, makes this box empty. - * - * @param {Box2} box - The bounding box to intersect with. - * @return {Box2} A reference to this bounding box. - */ - intersect( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); - - if ( this.isEmpty() ) this.makeEmpty(); - - return this; - - } - - /** - * Computes the union of this box and another and the given one, setting the upper - * bound of this box to the greater of the two boxes' upper bounds and the - * lower bound of this box to the lesser of the two boxes' lower bounds. - * - * @param {Box2} box - The bounding box that will be unioned with this instance. - * @return {Box2} A reference to this bounding box. - */ - union( box ) { - - this.min.min( box.min ); - this.max.max( box.max ); - - return this; - - } - - /** - * Adds the given offset to both the upper and lower bounds of this bounding box, - * effectively moving it in 2D space. - * - * @param {Vector2} offset - The offset that should be used to translate the bounding box. - * @return {Box2} A reference to this bounding box. - */ - translate( offset ) { - - this.min.add( offset ); - this.max.add( offset ); - - return this; - - } - - /** - * Returns `true` if this bounding box is equal with the given one. - * - * @param {Box2} box - The box to test for equality. - * @return {boolean} Whether this bounding box is equal with the given one. - */ - equals( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - -} - -const _startP = /*@__PURE__*/ new Vector3(); -const _startEnd = /*@__PURE__*/ new Vector3(); - -const _d1 = /*@__PURE__*/ new Vector3(); -const _d2 = /*@__PURE__*/ new Vector3(); -const _r = /*@__PURE__*/ new Vector3(); -const _c1 = /*@__PURE__*/ new Vector3(); -const _c2 = /*@__PURE__*/ new Vector3(); - -/** - * An analytical line segment in 3D space represented by a start and end point. - */ -class Line3 { - - /** - * Constructs a new line segment. - * - * @param {Vector3} [start=(0,0,0)] - Start of the line segment. - * @param {Vector3} [end=(0,0,0)] - End of the line segment. - */ - constructor( start = new Vector3(), end = new Vector3() ) { - - /** - * Start of the line segment. - * - * @type {Vector3} - */ - this.start = start; - - /** - * End of the line segment. - * - * @type {Vector3} - */ - this.end = end; - - } - - /** - * Sets the start and end values by copying the given vectors. - * - * @param {Vector3} start - The start point. - * @param {Vector3} end - The end point. - * @return {Line3} A reference to this line segment. - */ - set( start, end ) { - - this.start.copy( start ); - this.end.copy( end ); - - return this; - - } - - /** - * Copies the values of the given line segment to this instance. - * - * @param {Line3} line - The line segment to copy. - * @return {Line3} A reference to this line segment. - */ - copy( line ) { - - this.start.copy( line.start ); - this.end.copy( line.end ); - - return this; - - } - - /** - * Returns the center of the line segment. - * - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The center point. - */ - getCenter( target ) { - - return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); - - } - - /** - * Returns the delta vector of the line segment's start and end point. - * - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The delta vector. - */ - delta( target ) { - - return target.subVectors( this.end, this.start ); - - } - - /** - * Returns the squared Euclidean distance between the line' start and end point. - * - * @return {number} The squared Euclidean distance. - */ - distanceSq() { - - return this.start.distanceToSquared( this.end ); - - } - - /** - * Returns the Euclidean distance between the line' start and end point. - * - * @return {number} The Euclidean distance. - */ - distance() { - - return this.start.distanceTo( this.end ); - - } - - /** - * Returns a vector at a certain position along the line segment. - * - * @param {number} t - A value between `[0,1]` to represent a position along the line segment. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The delta vector. - */ - at( t, target ) { - - return this.delta( target ).multiplyScalar( t ).add( this.start ); - - } - - /** - * Returns a point parameter based on the closest point as projected on the line segment. - * - * @param {Vector3} point - The point for which to return a point parameter. - * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. - * @return {number} The point parameter. - */ - closestPointToPointParameter( point, clampToLine ) { - - _startP.subVectors( point, this.start ); - _startEnd.subVectors( this.end, this.start ); - - const startEnd2 = _startEnd.dot( _startEnd ); - - if ( startEnd2 === 0 ) return 0; - - const startEnd_startP = _startEnd.dot( _startP ); - - let t = startEnd_startP / startEnd2; - - if ( clampToLine ) { - - t = clamp( t, 0, 1 ); - - } - - return t; - - } - - /** - * Returns the closest point on the line for a given point. - * - * @param {Vector3} point - The point to compute the closest point on the line for. - * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. - * @param {Vector3} target - The target vector that is used to store the method's result. - * @return {Vector3} The closest point on the line. - */ - closestPointToPoint( point, clampToLine, target ) { - - const t = this.closestPointToPointParameter( point, clampToLine ); - - return this.delta( target ).multiplyScalar( t ).add( this.start ); - - } - - /** - * Returns the closest squared distance between this line segment and the given one. - * - * @param {Line3} line - The line segment to compute the closest squared distance to. - * @param {Vector3} [c1] - The closest point on this line segment. - * @param {Vector3} [c2] - The closest point on the given line segment. - * @return {number} The squared distance between this line segment and the given one. - */ - distanceSqToLine3( line, c1 = _c1, c2 = _c2 ) { - - // from Real-Time Collision Detection by Christer Ericson, chapter 5.1.9 - - // Computes closest points C1 and C2 of S1(s)=P1+s*(Q1-P1) and - // S2(t)=P2+t*(Q2-P2), returning s and t. Function result is squared - // distance between between S1(s) and S2(t) - - const EPSILON = 1e-8 * 1e-8; // must be squared since we compare squared length - let s, t; - - const p1 = this.start; - const p2 = line.start; - const q1 = this.end; - const q2 = line.end; - - _d1.subVectors( q1, p1 ); // Direction vector of segment S1 - _d2.subVectors( q2, p2 ); // Direction vector of segment S2 - _r.subVectors( p1, p2 ); - - const a = _d1.dot( _d1 ); // Squared length of segment S1, always nonnegative - const e = _d2.dot( _d2 ); // Squared length of segment S2, always nonnegative - const f = _d2.dot( _r ); - - // Check if either or both segments degenerate into points - - if ( a <= EPSILON && e <= EPSILON ) { - - // Both segments degenerate into points - - c1.copy( p1 ); - c2.copy( p2 ); - - c1.sub( c2 ); - - return c1.dot( c1 ); - - } - - if ( a <= EPSILON ) { - - // First segment degenerates into a point - - s = 0; - t = f / e; // s = 0 => t = (b*s + f) / e = f / e - t = clamp( t, 0, 1 ); - - - } else { - - const c = _d1.dot( _r ); - - if ( e <= EPSILON ) { - - // Second segment degenerates into a point - - t = 0; - s = clamp( - c / a, 0, 1 ); // t = 0 => s = (b*t - c) / a = -c / a - - } else { - - // The general nondegenerate case starts here - - const b = _d1.dot( _d2 ); - const denom = a * e - b * b; // Always nonnegative - - // If segments not parallel, compute closest point on L1 to L2 and - // clamp to segment S1. Else pick arbitrary s (here 0) - - if ( denom !== 0 ) { - - s = clamp( ( b * f - c * e ) / denom, 0, 1 ); - - } else { - - s = 0; - - } - - // Compute point on L2 closest to S1(s) using - // t = Dot((P1 + D1*s) - P2,D2) / Dot(D2,D2) = (b*s + f) / e - - t = ( b * s + f ) / e; - - // If t in [0,1] done. Else clamp t, recompute s for the new value - // of t using s = Dot((P2 + D2*t) - P1,D1) / Dot(D1,D1)= (t*b - c) / a - // and clamp s to [0, 1] - - if ( t < 0 ) { - - t = 0.; - s = clamp( - c / a, 0, 1 ); - - } else if ( t > 1 ) { - - t = 1; - s = clamp( ( b - c ) / a, 0, 1 ); - - } - - } - - } - - c1.copy( p1 ).addScaledVector( _d1, s ); - c2.copy( p2 ).addScaledVector( _d2, t ); - - return c1.distanceToSquared( c2 ); - - } - - /** - * Applies a 4x4 transformation matrix to this line segment. - * - * @param {Matrix4} matrix - The transformation matrix. - * @return {Line3} A reference to this line segment. - */ - applyMatrix4( matrix ) { - - this.start.applyMatrix4( matrix ); - this.end.applyMatrix4( matrix ); - - return this; - - } - - /** - * Returns `true` if this line segment is equal with the given one. - * - * @param {Line3} line - The line segment to test for equality. - * @return {boolean} Whether this line segment is equal with the given one. - */ - equals( line ) { - - return line.start.equals( this.start ) && line.end.equals( this.end ); - - } - - /** - * Returns a new line segment with copied values from this instance. - * - * @return {Line3} A clone of this instance. - */ - clone() { - - return new this.constructor().copy( this ); - - } - -} - -/** - * A helper object to visualize an instance of {@link Plane}. - * - * ```js - * const plane = new THREE.Plane( new THREE.Vector3( 1, 1, 0.2 ), 3 ); - * const helper = new THREE.PlaneHelper( plane, 1, 0xffff00 ); - * scene.add( helper ); - * ``` - * - * @augments Line - */ -class PlaneHelper extends Line$1 { - - /** - * Constructs a new plane helper. - * - * @param {Plane} plane - The plane to be visualized. - * @param {number} [size=1] - The side length of plane helper. - * @param {number|Color|string} [hex=0xffff00] - The helper's color. - */ - constructor( plane, size = 1, hex = 0xffff00 ) { - - const color = hex; - - const positions = [ 1, -1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0 ]; - - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - geometry.computeBoundingSphere(); - - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - - this.type = 'PlaneHelper'; - - /** - * The plane being visualized. - * - * @type {Plane} - */ - this.plane = plane; - - /** - * The side length of plane helper. - * - * @type {number} - * @default 1 - */ - this.size = size; - - const positions2 = [ 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0 ]; - - const geometry2 = new BufferGeometry(); - geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); - geometry2.computeBoundingSphere(); - - this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); - - } - - updateMatrixWorld( force ) { - - this.position.set( 0, 0, 0 ); - - this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); - - this.lookAt( this.plane.normal ); - - this.translateZ( - this.plane.constant ); - - super.updateMatrixWorld( force ); - - } - - /** - * Updates the helper to match the position and direction of the - * light being visualized. - */ - dispose() { - - super.dispose(); - - this.geometry.dispose(); - this.material.dispose(); - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); - - } - -} - -/** - * This class is used to convert a series of paths to an array of - * shapes. It is specifically used in context of fonts and SVG. - */ -class ShapePath { - - /** - * Constructs a new shape path. - */ - constructor() { - - this.type = 'ShapePath'; - - /** - * The color of the shape. - * - * @type {Color} - */ - this.color = new Color(); - - /** - * The paths that have been generated for this shape. - * - * @type {Array} - * @default null - */ - this.subPaths = []; - - /** - * The current path that is being generated. - * - * @type {?Path} - * @default null - */ - this.currentPath = null; - - /** - * An object that can be used to store custom data about the shape path. - * Mainly used by SVGLoader to store style information. - * - * @type {Object} - */ - this.userData = {}; - - } - - /** - * Creates a new path and moves it current point to the given one. - * - * @param {number} x - The x coordinate. - * @param {number} y - The y coordinate. - * @return {ShapePath} A reference to this shape path. - */ - moveTo( x, y ) { - - this.currentPath = new Path$1(); - this.subPaths.push( this.currentPath ); - this.currentPath.moveTo( x, y ); - - return this; - - } - - /** - * Adds an instance of {@link LineCurve} to the path by connecting - * the current point with the given one. - * - * @param {number} x - The x coordinate of the end point. - * @param {number} y - The y coordinate of the end point. - * @return {ShapePath} A reference to this shape path. - */ - lineTo( x, y ) { - - this.currentPath.lineTo( x, y ); - - return this; - - } - - /** - * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting - * the current point with the given one. - * - * @param {number} aCPx - The x coordinate of the control point. - * @param {number} aCPy - The y coordinate of the control point. - * @param {number} aX - The x coordinate of the end point. - * @param {number} aY - The y coordinate of the end point. - * @return {ShapePath} A reference to this shape path. - */ - quadraticCurveTo( aCPx, aCPy, aX, aY ) { - - this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); - - return this; - - } - - /** - * Adds an instance of {@link CubicBezierCurve} to the path by connecting - * the current point with the given one. - * - * @param {number} aCP1x - The x coordinate of the first control point. - * @param {number} aCP1y - The y coordinate of the first control point. - * @param {number} aCP2x - The x coordinate of the second control point. - * @param {number} aCP2y - The y coordinate of the second control point. - * @param {number} aX - The x coordinate of the end point. - * @param {number} aY - The y coordinate of the end point. - * @return {ShapePath} A reference to this shape path. - */ - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - - this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); - - return this; - - } - - /** - * Adds an instance of {@link SplineCurve} to the path by connecting - * the current point with the given list of points. - * - * @param {Array} pts - An array of points in 2D space. - * @return {ShapePath} A reference to this shape path. - */ - splineThru( pts ) { - - this.currentPath.splineThru( pts ); - - return this; - - } - - /** - * Converts the paths into an array of shapes. - * - * @return {Array} An array of shapes. - */ - toShapes() { - - // Point-in-polygon test using the even-odd ray-casting rule. Valid for - // simple (non self-intersecting) polygons. - function pointInPolygon( p, polygon ) { - - let inside = false; - const n = polygon.length; - - for ( let i = 0, j = n - 1; i < n; j = i ++ ) { - - const a = polygon[ i ]; - const b = polygon[ j ]; - - if ( ( a.y > p.y ) !== ( b.y > p.y ) && - p.x < ( b.x - a.x ) * ( p.y - a.y ) / ( b.y - a.y ) + a.x ) { - - inside = ! inside; - - } - - } - - return inside; - - } - - // Returns a point guaranteed to be strictly inside the given simple - // polygon. First tries the bounding-box center; if that falls outside - // the polygon, casts a horizontal ray at the center's y and picks the - // midpoint between the first two sorted intercepts. - // - // Port of paper.js' Path#getInteriorPoint() - // https://github.com/paperjs/paper.js/blob/develop/src/path/PathItem.Boolean.js - function getInteriorPoint( polygon, boundingBox ) { - - const point = boundingBox.getCenter( new Vector2() ); - - if ( pointInPolygon( point, polygon ) ) return point; - - const y = point.y; - const intercepts = []; - const n = polygon.length; - - for ( let i = 0; i < n; i ++ ) { - - const a = polygon[ i ]; - const b = polygon[ ( i + 1 ) % n ]; - - // Half-open crossing rule — counts each vertex exactly once and - // skips horizontal edges. - if ( ( a.y > y ) !== ( b.y > y ) ) { - - const x = a.x + ( y - a.y ) * ( b.x - a.x ) / ( b.y - a.y ); - intercepts.push( x ); - - } - - } - - if ( intercepts.length > 1 ) { - - intercepts.sort( ( a, b ) => a - b ); - point.x = ( intercepts[ 0 ] + intercepts[ 1 ] ) / 2; - - } - - return point; - - } - - // Resolve fill-rule. Defaults to 'nonzero'. - let fillRule = ( this.userData.style && this.userData.style.fillRule ) || 'nonzero'; - - if ( fillRule !== 'nonzero' && fillRule !== 'evenodd' ) { - - warn( 'Fill-rule "' + fillRule + '" is not supported, falling back to "nonzero".' ); - fillRule = 'nonzero'; - - } - - // Predicate that decides whether a winding number falls inside the fill - // region, per the SVG fill-rule spec. Works for negative windings too, - // because JavaScript's bitwise AND preserves odd/even under two's - // complement. - const isInside = fillRule === 'nonzero' - ? ( w => w !== 0 ) - : ( w => ( w & 1 ) !== 0 ); - - // Build an entry per usable subpath. Self-winding follows the standard - // convention used by ShapeUtils: counter-clockwise (signed area > 0) - // contributes +1 to the winding number at an interior point, - // clockwise contributes -1. - const entries = []; - - for ( const subPath of this.subPaths ) { - - const points = subPath.getPoints(); - if ( points.length < 3 ) continue; - - const area = ShapeUtils.area( points ); - if ( area === 0 ) continue; - - const boundingBox = new Box2(); - for ( let i = 0; i < points.length; i ++ ) boundingBox.expandByPoint( points[ i ] ); - - entries.push( { - subPath: subPath, - points: points, - boundingBox: boundingBox, - interiorPoint: getInteriorPoint( points, boundingBox ), - absArea: Math.abs( area ), - winding: area < 0 ? -1 : 1, - container: null, - exclude: false, - role: null - } ); - - } - - // Sort by area descending. This guarantees that any subpath that could - // contain `entries[i]` is located at a smaller index and has already - // been processed when it's entries[i]'s turn. Port of paper.js' - // reorientPaths() algorithm. - entries.sort( ( a, b ) => b.absArea - a.absArea ); - - // Walk already-processed entries from closest-in-size to largest, - // stopping at the innermost container. Accumulate the container's - // cumulative winding into this entry's winding so that the final value - // equals the winding number at this entry's interior point. - // - // A subpath only contributes to the fill boundary when crossing it - // actually flips the "insideness" per the fill rule; otherwise it's a - // redundant overlap and gets excluded to avoid double-counting. - for ( let i = 0; i < entries.length; i ++ ) { - - const entry = entries[ i ]; - let containerWinding = 0; - - for ( let j = i - 1; j >= 0; j -- ) { - - const candidate = entries[ j ]; - - if ( ! candidate.boundingBox.containsBox( entry.boundingBox ) ) continue; - if ( ! pointInPolygon( entry.interiorPoint, candidate.points ) ) continue; - - entry.container = candidate.exclude ? candidate.container : candidate; - containerWinding = candidate.winding; - entry.winding += containerWinding; - break; - - } - - if ( isInside( entry.winding ) === isInside( containerWinding ) ) { - - entry.exclude = true; - - } - - } - - // Classify retained entries. An entry is an outer shape if it has no - // container or if its container is itself a hole (a solid nested inside - // a hole becomes a new top-level shape); otherwise it's a hole in its - // container. Entries were already sorted outermost-first, so each - // container's role is known by the time we look at it. - for ( const entry of entries ) { - - if ( entry.exclude ) continue; - entry.role = ( entry.container === null || entry.container.role === 'hole' ) ? 'outer' : 'hole'; - - } - - // Build Shapes for outers first, then attach holes to their container's - // Shape. - const shapes = []; - const shapeByEntry = new Map(); - - for ( const entry of entries ) { - - if ( entry.exclude || entry.role !== 'outer' ) continue; - - const shape = new Shape(); - shape.curves = entry.subPath.curves; - shapes.push( shape ); - shapeByEntry.set( entry, shape ); - - } - - for ( const entry of entries ) { - - if ( entry.exclude || entry.role !== 'hole' ) continue; - - const shape = shapeByEntry.get( entry.container ); - if ( ! shape ) continue; - - const hole = new Path$1(); - hole.curves = entry.subPath.curves; - shape.holes.push( hole ); - - } - - return shapes; - - - } - -} - -/** - * Abstract base class for controls. - * - * @abstract - * @augments EventDispatcher - */ -class Controls extends EventDispatcher { - - /** - * Constructs a new controls instance. - * - * @param {Object3D} object - The object that is managed by the controls. - * @param {?HTMLElement} domElement - The HTML element used for event listeners. - */ - constructor( object, domElement = null ) { - - super(); - - /** - * The object that is managed by the controls. - * - * @type {Object3D} - */ - this.object = object; - - /** - * The HTML element used for event listeners. - * - * @type {?HTMLElement} - * @default null - */ - this.domElement = domElement; - - /** - * Whether the controls responds to user input or not. - * - * @type {boolean} - * @default true - */ - this.enabled = true; - - /** - * The internal state of the controls. - * - * @type {number} - * @default -1 - */ - this.state = -1; - - /** - * This object defines the keyboard input of the controls. - * - * @type {Object} - */ - this.keys = {}; - - /** - * This object defines what type of actions are assigned to the available mouse buttons. - * It depends on the control implementation what kind of mouse buttons and actions are supported. - * - * @type {{LEFT: ?number, MIDDLE: ?number, RIGHT: ?number}} - */ - this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null }; - - /** - * This object defines what type of actions are assigned to what kind of touch interaction. - * It depends on the control implementation what kind of touch interaction and actions are supported. - * - * @type {{ONE: ?number, TWO: ?number}} - */ - this.touches = { ONE: null, TWO: null }; - - } - - /** - * Connects the controls to the DOM. This method has so called "side effects" since - * it adds the module's event listeners to the DOM. - * - * @param {HTMLElement} element - The DOM element to connect to. - */ - connect( element ) { - - if ( this.domElement !== null ) this.disconnect(); - - this.domElement = element; - - } - - /** - * Disconnects the controls from the DOM. - */ - disconnect() {} - - /** - * Call this method if you no longer want use to the controls. It frees all internal - * resources and removes all event listeners. - */ - dispose() {} - - /** - * Controls should implement this method if they have to update their internal state - * per simulation step. - * - * @param {number} [delta] - The time delta in seconds. - */ - update( /* delta */ ) {} - -} - -// export * from './Three.Legacy.js'; - -if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - /* eslint-disable no-undef */ - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { - revision: REVISION, - } } ) ); - /* eslint-enable no-undef */ - -} - -if ( typeof window !== 'undefined' ) { - - if ( window.__THREE__ ) { - - console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); - - } else { - - window.__THREE__ = REVISION; - - } - -} - -/** - * @license - * Copyright 2010-2025 Three.js Authors - * SPDX-License-Identifier: MIT - */ - -/** - * Class representing a font. - */ -class Font { - - /** - * Constructs a new font. - * - * @param {Object} data - The font data as JSON. - */ - constructor( data ) { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isFont = true; - - this.type = 'Font'; - - /** - * The font data as JSON. - * - * @type {Object} - */ - this.data = data; - - } - - /** - * Generates geometry shapes from the given text and size. The result of this method - * should be used with {@link ShapeGeometry} to generate the actual geometry data. - * - * @param {string} text - The text. - * @param {number} [size=100] - The text size. - * @param {string} [direction='ltr'] - Char direction: ltr(left to right), rtl(right to left) & tb(top bottom). - * @return {Array} An array of shapes representing the text. - */ - generateShapes( text, size = 100, direction = 'ltr' ) { - - const shapes = []; - const paths = createPaths( text, size, this.data, direction ); - - for ( let p = 0, pl = paths.length; p < pl; p ++ ) { - - shapes.push( ...paths[ p ].toShapes() ); - - } - - return shapes; - - } - -} - -function createPaths( text, size, data, direction ) { - - const chars = Array.from( text ); - const scale = size / data.resolution; - const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; - - const paths = []; - - let offsetX = 0, offsetY = 0; - - if ( direction == 'rtl' || direction == 'tb' ) { - - chars.reverse(); - - } - - for ( let i = 0; i < chars.length; i ++ ) { - - const char = chars[ i ]; - - if ( char === '\n' ) { - - offsetX = 0; - offsetY -= line_height; - - } else { - - const ret = createPath( char, scale, offsetX, offsetY, data ); - - if ( direction == 'tb' ) { - - offsetX = 0; - offsetY += data.ascender * scale; - - } else { - - offsetX += ret.offsetX; - - } - - paths.push( ret.path ); - - } - - } - - return paths; - -} - -function createPath( char, scale, offsetX, offsetY, data ) { - - const glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; - - if ( ! glyph ) { - - console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' ); - - return; - - } - - const path = new ShapePath(); - - let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; - - if ( glyph.o ) { - - const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); - - for ( let i = 0, l = outline.length; i < l; ) { - - const action = outline[ i ++ ]; - - switch ( action ) { - - case 'm': // moveTo - - x = outline[ i ++ ] * scale + offsetX; - y = outline[ i ++ ] * scale + offsetY; - - path.moveTo( x, y ); - - break; - - case 'l': // lineTo - - x = outline[ i ++ ] * scale + offsetX; - y = outline[ i ++ ] * scale + offsetY; - - path.lineTo( x, y ); - - break; - - case 'q': // quadraticCurveTo - - cpx = outline[ i ++ ] * scale + offsetX; - cpy = outline[ i ++ ] * scale + offsetY; - cpx1 = outline[ i ++ ] * scale + offsetX; - cpy1 = outline[ i ++ ] * scale + offsetY; - - path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); - - break; - - case 'b': // bezierCurveTo - - cpx = outline[ i ++ ] * scale + offsetX; - cpy = outline[ i ++ ] * scale + offsetY; - cpx1 = outline[ i ++ ] * scale + offsetX; - cpy1 = outline[ i ++ ] * scale + offsetY; - cpx2 = outline[ i ++ ] * scale + offsetX; - cpy2 = outline[ i ++ ] * scale + offsetY; - - path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); - - break; - - } - - } - - } - - return { offsetX: glyph.ha * scale, path: path }; - -} - -/** - * A class for generating text as a single geometry. It is constructed by providing a string of text, and a set of - * parameters consisting of a loaded font and extrude settings. - * - * See the {@link FontLoader} page for additional details. - * - * `TextGeometry` uses [typeface.json](http://gero3.github.io/facetype.js/) generated fonts. - * Some existing fonts can be found located in `/examples/fonts`. - * - * ```js - * const loader = new FontLoader(); - * const font = await loader.loadAsync( 'fonts/helvetiker_regular.typeface.json' ); - * const geometry = new TextGeometry( 'Hello three.js!', { - * font: font, - * size: 80, - * depth: 5, - * curveSegments: 12 - * } ); - * ``` - * - * @augments ExtrudeGeometry - * @three_import import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; - */ -class TextGeometry extends ExtrudeGeometry { - - /** - * Constructs a new text geometry. - * - * @param {string} text - The text that should be transformed into a geometry. - * @param {TextGeometry~Options} [parameters] - The text settings. - */ - constructor( text, parameters = {} ) { - - const font = parameters.font; - - if ( font === undefined ) { - - super(); // generate default extrude geometry - - } else { - - const shapes = font.generateShapes( text, parameters.size, parameters.direction ); - - // defaults - - if ( parameters.depth === undefined ) parameters.depth = 50; - if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; - if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; - if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; - - super( shapes, parameters ); - - } - - this.type = 'TextGeometry'; - - } - - toJSON() { - - const data = super.toJSON(); - return data; - - } - - static fromJSON( data ) { - - const options = data.options; - - options.font = new Font( options.font.data ); - return new TextGeometry( options.text, options ); - - } - -} - -/** - * Fires when the camera has been transformed by the controls. - * - * @event OrbitControls#change - * @type {Object} - */ -const _changeEvent = { type: 'change' }; - -/** - * Fires when an interaction was initiated. - * - * @event OrbitControls#start - * @type {Object} - */ -const _startEvent = { type: 'start' }; - -/** - * Fires when an interaction has finished. - * - * @event OrbitControls#end - * @type {Object} - */ -const _endEvent = { type: 'end' }; - -const _ray = new Ray(); -const _plane = new Plane(); -const _TILT_LIMIT = Math.cos( 70 * MathUtils$1.DEG2RAD ); - -const _v = new Vector3(); -const _twoPI = 2 * Math.PI; - -const _STATE = { - NONE: -1, - ROTATE: 0, - DOLLY: 1, - PAN: 2, - TOUCH_ROTATE: 3, - TOUCH_PAN: 4, - TOUCH_DOLLY_PAN: 5, - TOUCH_DOLLY_ROTATE: 6 -}; -const _EPS = 0.000001; - - -/** - * Orbit controls allow the camera to orbit around a target. - * - * OrbitControls performs orbiting, dollying (zooming), and panning. Unlike {@link TrackballControls}, - * it maintains the "up" direction `object.up` (+Y by default). - * - * - Orbit: Left mouse / touch: one-finger move. - * - Zoom: Middle mouse, or mousewheel / touch: two-finger spread or squish. - * - Pan: Right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move. - * - * ```js - * const controls = new OrbitControls( camera, renderer.domElement ); - * - * // controls.update() must be called after any manual changes to the camera's transform - * camera.position.set( 0, 20, 100 ); - * controls.update(); - * - * function animate() { - * - * // required if controls.enableDamping or controls.autoRotate are set to true - * controls.update(); - * - * renderer.render( scene, camera ); - * - * } - * ``` - * - * @augments Controls - * @three_import import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - */ -class OrbitControls extends Controls { - - /** - * Constructs a new controls instance. - * - * @param {Object3D} object - The object that is managed by the controls. - * @param {?HTMLElement} domElement - The HTML element used for event listeners. - */ - constructor( object, domElement = null ) { - - super( object, domElement ); - - this.state = _STATE.NONE; - - /** - * The focus point of the controls, the `object` orbits around this. - * It can be updated manually at any point to change the focus of the controls. - * - * @type {Vector3} - */ - this.target = new Vector3(); - - /** - * The focus point of the `minTargetRadius` and `maxTargetRadius` limits. - * It can be updated manually at any point to change the center of interest - * for the `target`. - * - * @type {Vector3} - */ - this.cursor = new Vector3(); - - /** - * How far you can dolly in (perspective camera only). - * - * @type {number} - * @default 0 - */ - this.minDistance = 0; - - /** - * How far you can dolly out (perspective camera only). - * - * @type {number} - * @default Infinity - */ - this.maxDistance = Infinity; - - /** - * How far you can zoom in (orthographic camera only). - * - * @type {number} - * @default 0 - */ - this.minZoom = 0; - - /** - * How far you can zoom out (orthographic camera only). - * - * @type {number} - * @default Infinity - */ - this.maxZoom = Infinity; - - /** - * How close you can get the target to the 3D `cursor`. - * - * @type {number} - * @default 0 - */ - this.minTargetRadius = 0; - - /** - * How far you can move the target from the 3D `cursor`. - * - * @type {number} - * @default Infinity - */ - this.maxTargetRadius = Infinity; - - /** - * How far you can orbit vertically, lower limit. Range is `[0, Math.PI]` radians. - * - * @type {number} - * @default 0 - */ - this.minPolarAngle = 0; - - /** - * How far you can orbit vertically, upper limit. Range is `[0, Math.PI]` radians. - * - * @type {number} - * @default Math.PI - */ - this.maxPolarAngle = Math.PI; - - /** - * How far you can orbit horizontally, lower limit. If set, the interval `[ min, max ]` - * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. - * - * @type {number} - * @default -Infinity - */ - this.minAzimuthAngle = - Infinity; - - /** - * How far you can orbit horizontally, upper limit. If set, the interval `[ min, max ]` - * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. - * - * @type {number} - * @default -Infinity - */ - this.maxAzimuthAngle = Infinity; - - /** - * Set to `true` to enable damping (inertia), which can be used to give a sense of weight - * to the controls. Note that if this is enabled, you must call `update()` in your animation - * loop. - * - * @type {boolean} - * @default false - */ - this.enableDamping = false; - - /** - * The damping inertia used if `enableDamping` is set to `true`. - * - * Note that for this to work, you must call `update()` in your animation loop. - * - * @type {number} - * @default 0.05 - */ - this.dampingFactor = 0.05; - - /** - * Enable or disable zooming (dollying) of the camera. - * - * @type {boolean} - * @default true - */ - this.enableZoom = true; - - /** - * Speed of zooming / dollying. - * - * @type {number} - * @default 1 - */ - this.zoomSpeed = 1.0; - - /** - * Enable or disable horizontal and vertical rotation of the camera. - * - * Note that it is possible to disable a single axis by setting the min and max of the - * `minPolarAngle` or `minAzimuthAngle` to the same value, which will cause the vertical - * or horizontal rotation to be fixed at that value. - * - * @type {boolean} - * @default true - */ - this.enableRotate = true; - - /** - * Speed of rotation. - * - * @type {number} - * @default 1 - */ - this.rotateSpeed = 1.0; - - /** - * How fast to rotate the camera when the keyboard is used. - * - * @type {number} - * @default 1 - */ - this.keyRotateSpeed = 1.0; - - /** - * Enable or disable camera panning. - * - * @type {boolean} - * @default true - */ - this.enablePan = true; - - /** - * Speed of panning. - * - * @type {number} - * @default 1 - */ - this.panSpeed = 1.0; - - /** - * Defines how the camera's position is translated when panning. If `true`, the camera pans - * in screen space. Otherwise, the camera pans in the plane orthogonal to the camera's up - * direction. - * - * @type {boolean} - * @default true - */ - this.screenSpacePanning = true; - - /** - * How fast to pan the camera when the keyboard is used in - * pixels per keypress. - * - * @type {number} - * @default 7 - */ - this.keyPanSpeed = 7.0; - - /** - * Setting this property to `true` allows to zoom to the cursor's position. - * - * @type {boolean} - * @default false - */ - this.zoomToCursor = false; - - /** - * Set to true to automatically rotate around the target - * - * Note that if this is enabled, you must call `update()` in your animation loop. - * If you want the auto-rotate speed to be independent of the frame rate (the refresh - * rate of the display), you must pass the time `deltaTime`, in seconds, to `update()`. - * - * @type {boolean} - * @default false - */ - this.autoRotate = false; - - /** - * How fast to rotate around the target if `autoRotate` is `true`. The default equates to 30 seconds - * per orbit at 60fps. - * - * Note that if `autoRotate` is enabled, you must call `update()` in your animation loop. - * - * @type {number} - * @default 2 - */ - this.autoRotateSpeed = 2.0; - - /** - * This object contains references to the keycodes for controlling camera panning. - * - * ```js - * controls.keys = { - * LEFT: 'ArrowLeft', //left arrow - * UP: 'ArrowUp', // up arrow - * RIGHT: 'ArrowRight', // right arrow - * BOTTOM: 'ArrowDown' // down arrow - * } - * ``` - * @type {Object} - */ - this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; - - /** - * This object contains references to the mouse actions used by the controls. - * - * ```js - * controls.mouseButtons = { - * LEFT: THREE.MOUSE.ROTATE, - * MIDDLE: THREE.MOUSE.DOLLY, - * RIGHT: THREE.MOUSE.PAN - * } - * ``` - * @type {Object} - */ - this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - - /** - * This object contains references to the touch actions used by the controls. - * - * ```js - * controls.mouseButtons = { - * ONE: THREE.TOUCH.ROTATE, - * TWO: THREE.TOUCH.DOLLY_PAN - * } - * ``` - * @type {Object} - */ - this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; - - /** - * Used internally by `saveState()` and `reset()`. - * - * @type {Vector3} - */ - this.target0 = this.target.clone(); - - /** - * Used internally by `saveState()` and `reset()`. - * - * @type {Vector3} - */ - this.position0 = this.object.position.clone(); - - /** - * Used internally by `saveState()` and `reset()`. - * - * @type {number} - */ - this.zoom0 = this.object.zoom; - - this._cursorStyle = 'auto'; - - // the target DOM element for key events - this._domElementKeyEvents = null; - - // internals - - this._lastPosition = new Vector3(); - this._lastQuaternion = new Quaternion(); - this._lastTargetPosition = new Vector3(); - - // so camera.up is the orbit axis - this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); - this._quatInverse = this._quat.clone().invert(); - - // current position in spherical coordinates - this._spherical = new Spherical(); - this._sphericalDelta = new Spherical(); - - this._scale = 1; - this._panOffset = new Vector3(); - - this._rotateStart = new Vector2(); - this._rotateEnd = new Vector2(); - this._rotateDelta = new Vector2(); - - this._panStart = new Vector2(); - this._panEnd = new Vector2(); - this._panDelta = new Vector2(); - - this._dollyStart = new Vector2(); - this._dollyEnd = new Vector2(); - this._dollyDelta = new Vector2(); - - this._dollyDirection = new Vector3(); - this._mouse = new Vector2(); - this._performCursorZoom = false; - - this._pointers = []; - this._pointerPositions = {}; - - this._controlActive = false; - - // event listeners - - this._onPointerMove = onPointerMove.bind( this ); - this._onPointerDown = onPointerDown.bind( this ); - this._onPointerUp = onPointerUp.bind( this ); - this._onContextMenu = onContextMenu.bind( this ); - this._onMouseWheel = onMouseWheel.bind( this ); - this._onKeyDown = onKeyDown.bind( this ); - - this._onTouchStart = onTouchStart.bind( this ); - this._onTouchMove = onTouchMove.bind( this ); - - this._onMouseDown = onMouseDown.bind( this ); - this._onMouseMove = onMouseMove.bind( this ); - - this._interceptControlDown = interceptControlDown.bind( this ); - this._interceptControlUp = interceptControlUp.bind( this ); - - // - - if ( this.domElement !== null ) { - - this.connect( this.domElement ); - - } - - this.update(); - - } - - /** - * Defines the visual representation of the cursor. - * - * @type {('auto'|'grab')} - * @default 'auto' - */ - set cursorStyle( type ) { - - this._cursorStyle = type; - - if ( type === 'grab' ) { - - this.domElement.style.cursor = 'grab'; - - } else { - - this.domElement.style.cursor = 'auto'; - - } - - } - - get cursorStyle() { - - return this._cursorStyle; - - } - - connect( element ) { - - super.connect( element ); - - this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.addEventListener( 'pointercancel', this._onPointerUp ); - - this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); - this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } ); - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } ); - - this.domElement.style.touchAction = 'none'; // Disable touch scroll - - } - - disconnect() { - - this.state = _STATE.NONE; - - this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.ownerDocument.removeEventListener( 'pointermove', this._onPointerMove ); - this.domElement.ownerDocument.removeEventListener( 'pointerup', this._onPointerUp ); - this.domElement.removeEventListener( 'pointercancel', this._onPointerUp ); - - this.domElement.removeEventListener( 'wheel', this._onMouseWheel ); - this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - - this.stopListenToKeyEvents(); - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } ); - document.removeEventListener( 'keyup', this._interceptControlUp, { capture: true } ); - - this._controlActive = false; - - this._pointers.length = 0; - this._pointerPositions = {}; - - this.domElement.style.touchAction = ''; // Restore touch scroll - this.domElement.style.cursor = 'auto'; - - } - - dispose() { - - this.disconnect(); - - } - - /** - * Get the current vertical rotation, in radians. - * - * @return {number} The current vertical rotation, in radians. - */ - getPolarAngle() { - - return this._spherical.phi; - - } - - /** - * Get the current horizontal rotation, in radians. - * - * @return {number} The current horizontal rotation, in radians. - */ - getAzimuthalAngle() { - - return this._spherical.theta; - - } - - /** - * Returns the distance from the camera to the target. - * - * @return {number} The distance from the camera to the target. - */ - getDistance() { - - return this.object.position.distanceTo( this.target ); - - } - - /** - * Adds key event listeners to the given DOM element. - * `window` is a recommended argument for using this method. - * - * @param {HTMLElement} domElement - The DOM element - */ - listenToKeyEvents( domElement ) { - - domElement.addEventListener( 'keydown', this._onKeyDown ); - this._domElementKeyEvents = domElement; - - } - - /** - * Removes the key event listener previously defined with `listenToKeyEvents()`. - */ - stopListenToKeyEvents() { - - if ( this._domElementKeyEvents !== null ) { - - this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown ); - this._domElementKeyEvents = null; - - } - - } - - /** - * Save the current state of the controls. This can later be recovered with `reset()`. - */ - saveState() { - - this.target0.copy( this.target ); - this.position0.copy( this.object.position ); - this.zoom0 = this.object.zoom; - - } - - /** - * Reset the controls to their state from either the last time the `saveState()` - * was called, or the initial state. - */ - reset() { - - this.target.copy( this.target0 ); - this.object.position.copy( this.position0 ); - this.object.zoom = this.zoom0; - - this.object.updateProjectionMatrix(); - this.dispatchEvent( _changeEvent ); - - this.update(); - - this.state = _STATE.NONE; - - } - - /** - * Programmatically pan the camera. - * - * @param {number} deltaX - The horizontal pan amount in pixels. - * @param {number} deltaY - The vertical pan amount in pixels. - */ - pan( deltaX, deltaY ) { - - this._pan( deltaX, deltaY ); - this.update(); - - } - - /** - * Programmatically dolly in (zoom in for perspective camera). - * - * @param {number} dollyScale - The dolly scale factor. - */ - dollyIn( dollyScale ) { - - this._dollyIn( dollyScale ); - this.update(); - - } - - /** - * Programmatically dolly out (zoom out for perspective camera). - * - * @param {number} dollyScale - The dolly scale factor. - */ - dollyOut( dollyScale ) { - - this._dollyOut( dollyScale ); - this.update(); - - } - - /** - * Programmatically rotate the camera left (around the vertical axis). - * - * @param {number} angle - The rotation angle in radians. - */ - rotateLeft( angle ) { - - this._rotateLeft( angle ); - this.update(); - - } - - /** - * Programmatically rotate the camera up (around the horizontal axis). - * - * @param {number} angle - The rotation angle in radians. - */ - rotateUp( angle ) { - - this._rotateUp( angle ); - this.update(); - - } - - update( deltaTime = null ) { - - const position = this.object.position; - - _v.copy( position ).sub( this.target ); - - // rotate offset to "y-axis-is-up" space - _v.applyQuaternion( this._quat ); - - // angle from z-axis around y-axis - this._spherical.setFromVector3( _v ); - - if ( this.autoRotate && this.state === _STATE.NONE ) { - - this._rotateLeft( this._getAutoRotationAngle( deltaTime ) ); - - } - - if ( this.enableDamping ) { - - this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor; - this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor; - - } else { - - this._spherical.theta += this._sphericalDelta.theta; - this._spherical.phi += this._sphericalDelta.phi; - - } - - // restrict theta to be between desired limits - - let min = this.minAzimuthAngle; - let max = this.maxAzimuthAngle; - - if ( isFinite( min ) && isFinite( max ) ) { - - if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI; - - if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI; - - if ( min <= max ) { - - this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) ); - - } else { - - this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ? - Math.max( min, this._spherical.theta ) : - Math.min( max, this._spherical.theta ); - - } - - } - - // restrict phi to be between desired limits - this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) ); - - this._spherical.makeSafe(); - - - // move target to panned location - - if ( this.enableDamping === true ) { - - this.target.addScaledVector( this._panOffset, this.dampingFactor ); - - } else { - - this.target.add( this._panOffset ); - - } - - // Limit the target distance from the cursor to create a sphere around the center of interest - this.target.sub( this.cursor ); - this.target.clampLength( this.minTargetRadius, this.maxTargetRadius ); - this.target.add( this.cursor ); - - let zoomChanged = false; - // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera - // we adjust zoom later in these cases - if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) { - - this._spherical.radius = this._clampDistance( this._spherical.radius ); - - } else { - - const prevRadius = this._spherical.radius; - this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale ); - zoomChanged = prevRadius != this._spherical.radius; - - } - - _v.setFromSpherical( this._spherical ); - - // rotate offset back to "camera-up-vector-is-up" space - _v.applyQuaternion( this._quatInverse ); - - position.copy( this.target ).add( _v ); - - this.object.lookAt( this.target ); - - if ( this.enableDamping === true ) { - - this._sphericalDelta.theta *= ( 1 - this.dampingFactor ); - this._sphericalDelta.phi *= ( 1 - this.dampingFactor ); - - this._panOffset.multiplyScalar( 1 - this.dampingFactor ); - - } else { - - this._sphericalDelta.set( 0, 0, 0 ); - - this._panOffset.set( 0, 0, 0 ); - - } - - // adjust camera position - if ( this.zoomToCursor && this._performCursorZoom ) { - - let newRadius = null; - if ( this.object.isPerspectiveCamera ) { - - // move the camera down the pointer ray - // this method avoids floating point error - const prevRadius = _v.length(); - newRadius = this._clampDistance( prevRadius * this._scale ); - - const radiusDelta = prevRadius - newRadius; - this.object.position.addScaledVector( this._dollyDirection, radiusDelta ); - this.object.updateMatrixWorld(); - - zoomChanged = !! radiusDelta; - - } else if ( this.object.isOrthographicCamera ) { - - // adjust the ortho camera position based on zoom changes - const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 ); - mouseBefore.unproject( this.object ); - - const prevZoom = this.object.zoom; - this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); - this.object.updateProjectionMatrix(); - - zoomChanged = prevZoom !== this.object.zoom; - - const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 ); - mouseAfter.unproject( this.object ); - - this.object.position.sub( mouseAfter ).add( mouseBefore ); - this.object.updateMatrixWorld(); - - newRadius = _v.length(); - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); - this.zoomToCursor = false; - - } - - // handle the placement of the target - if ( newRadius !== null ) { - - if ( this.screenSpacePanning ) { - - // position the orbit target in front of the new camera position - this.target.set( 0, 0, -1 ) - .transformDirection( this.object.matrix ) - .multiplyScalar( newRadius ) - .add( this.object.position ); - - } else { - - // get the ray and translation plane to compute target - _ray.origin.copy( this.object.position ); - _ray.direction.set( 0, 0, -1 ).transformDirection( this.object.matrix ); - - // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid - // extremely large values - if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) { - - this.object.lookAt( this.target ); - - } else { - - _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target ); - _ray.intersectPlane( _plane, this.target ); - - } - - } - - } - - } else if ( this.object.isOrthographicCamera ) { - - const prevZoom = this.object.zoom; - this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); - - if ( prevZoom !== this.object.zoom ) { - - this.object.updateProjectionMatrix(); - zoomChanged = true; - - } - - } - - this._scale = 1; - this._performCursorZoom = false; - - // update condition is: - // min(camera displacement, camera rotation in radians)^2 > EPS - // using small-angle approximation cos(x/2) = 1 - x^2 / 8 - - if ( zoomChanged || - this._lastPosition.distanceToSquared( this.object.position ) > _EPS || - 8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS || - this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) { - - this.dispatchEvent( _changeEvent ); - - this._lastPosition.copy( this.object.position ); - this._lastQuaternion.copy( this.object.quaternion ); - this._lastTargetPosition.copy( this.target ); - - return true; - - } - - return false; - - } - - _getAutoRotationAngle( deltaTime ) { - - if ( deltaTime !== null ) { - - return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime; - - } else { - - return _twoPI / 60 / 60 * this.autoRotateSpeed; - - } - - } - - _getZoomScale( delta ) { - - const normalizedDelta = Math.abs( delta * 0.01 ); - return Math.pow( 0.95, this.zoomSpeed * normalizedDelta ); - - } - - _rotateLeft( angle ) { - - this._sphericalDelta.theta -= angle; - - } - - _rotateUp( angle ) { - - this._sphericalDelta.phi -= angle; - - } - - _panLeft( distance, objectMatrix ) { - - _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix - _v.multiplyScalar( - distance ); - - this._panOffset.add( _v ); - - } - - _panUp( distance, objectMatrix ) { - - if ( this.screenSpacePanning === true ) { - - _v.setFromMatrixColumn( objectMatrix, 1 ); - - } else { - - _v.setFromMatrixColumn( objectMatrix, 0 ); - _v.crossVectors( this.object.up, _v ); - - } - - _v.multiplyScalar( distance ); - - this._panOffset.add( _v ); - - } - - // deltaX and deltaY are in pixels; right and down are positive - _pan( deltaX, deltaY ) { - - const element = this.domElement; - - if ( this.object.isPerspectiveCamera ) { - - // perspective - const position = this.object.position; - _v.copy( position ).sub( this.target ); - let targetDistance = _v.length(); - - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 ); - - // we use only clientHeight here so aspect ratio does not distort speed - this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix ); - this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix ); - - } else if ( this.object.isOrthographicCamera ) { - - // orthographic - this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix ); - this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix ); - - } else { - - // camera neither orthographic nor perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - this.enablePan = false; - - } - - } - - _dollyOut( dollyScale ) { - - if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - - this._scale /= dollyScale; - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - this.enableZoom = false; - - } - - } - - _dollyIn( dollyScale ) { - - if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { - - this._scale *= dollyScale; - - } else { - - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - this.enableZoom = false; - - } - - } - - _updateZoomParameters( x, y ) { - - if ( ! this.zoomToCursor ) { - - return; - - } - - this._performCursorZoom = true; - - const rect = this.domElement.getBoundingClientRect(); - const dx = x - rect.left; - const dy = y - rect.top; - const w = rect.width; - const h = rect.height; - - this._mouse.x = ( dx / w ) * 2 - 1; - this._mouse.y = - ( dy / h ) * 2 + 1; - - this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize(); - - } - - _clampDistance( dist ) { - - return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) ); - - } - - // - // event callbacks - update the object state - // - - _handleMouseDownRotate( event ) { - - this._rotateStart.set( event.clientX, event.clientY ); - - } - - _handleMouseDownDolly( event ) { - - this._updateZoomParameters( event.clientX, event.clientX ); - this._dollyStart.set( event.clientX, event.clientY ); - - } - - _handleMouseDownPan( event ) { - - this._panStart.set( event.clientX, event.clientY ); - - } - - _handleMouseMoveRotate( event ) { - - this._rotateEnd.set( event.clientX, event.clientY ); - - this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - - const element = this.domElement; - - this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - - this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - - this._rotateStart.copy( this._rotateEnd ); - - this.update(); - - } - - _handleMouseMoveDolly( event ) { - - this._dollyEnd.set( event.clientX, event.clientY ); - - this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart ); - - if ( this._dollyDelta.y > 0 ) { - - this._dollyOut( this._getZoomScale( this._dollyDelta.y ) ); - - } else if ( this._dollyDelta.y < 0 ) { - - this._dollyIn( this._getZoomScale( this._dollyDelta.y ) ); - - } - - this._dollyStart.copy( this._dollyEnd ); - - this.update(); - - } - - _handleMouseMovePan( event ) { - - this._panEnd.set( event.clientX, event.clientY ); - - this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - - this._pan( this._panDelta.x, this._panDelta.y ); - - this._panStart.copy( this._panEnd ); - - this.update(); - - } - - _handleMouseWheel( event ) { - - this._updateZoomParameters( event.clientX, event.clientY ); - - if ( event.deltaY < 0 ) { - - this._dollyIn( this._getZoomScale( event.deltaY ) ); - - } else if ( event.deltaY > 0 ) { - - this._dollyOut( this._getZoomScale( event.deltaY ) ); - - } - - this.update(); - - } - - _handleKeyDown( event ) { - - let needsUpdate = false; - - switch ( event.code ) { - - case this.keys.UP: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( 0, this.keyPanSpeed ); - - } - - } - - needsUpdate = true; - break; - - case this.keys.BOTTOM: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( 0, - this.keyPanSpeed ); - - } - - } - - needsUpdate = true; - break; - - case this.keys.LEFT: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( this.keyPanSpeed, 0 ); - - } - - } - - needsUpdate = true; - break; - - case this.keys.RIGHT: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate ) { - - this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - - } - - } else { - - if ( this.enablePan ) { - - this._pan( - this.keyPanSpeed, 0 ); - - } - - } - - needsUpdate = true; - break; - - } - - if ( needsUpdate ) { - - // prevent the browser from scrolling on cursor keys - event.preventDefault(); - - this.update(); - - } - - - } - - _handleTouchStartRotate( event ) { - - if ( this._pointers.length === 1 ) { - - this._rotateStart.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._rotateStart.set( x, y ); - - } - - } - - _handleTouchStartPan( event ) { - - if ( this._pointers.length === 1 ) { - - this._panStart.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._panStart.set( x, y ); - - } - - } - - _handleTouchStartDolly( event ) { - - const position = this._getSecondPointerPosition( event ); - - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; - - const distance = Math.sqrt( dx * dx + dy * dy ); - - this._dollyStart.set( 0, distance ); - - } - - _handleTouchStartDollyPan( event ) { - - if ( this.enableZoom ) this._handleTouchStartDolly( event ); - - if ( this.enablePan ) this._handleTouchStartPan( event ); - - } - - _handleTouchStartDollyRotate( event ) { - - if ( this.enableZoom ) this._handleTouchStartDolly( event ); - - if ( this.enableRotate ) this._handleTouchStartRotate( event ); - - } - - _handleTouchMoveRotate( event ) { - - if ( this._pointers.length == 1 ) { - - this._rotateEnd.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._rotateEnd.set( x, y ); - - } - - this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - - const element = this.domElement; - - this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - - this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - - this._rotateStart.copy( this._rotateEnd ); - - } - - _handleTouchMovePan( event ) { - - if ( this._pointers.length === 1 ) { - - this._panEnd.set( event.pageX, event.pageY ); - - } else { - - const position = this._getSecondPointerPosition( event ); - - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); - - this._panEnd.set( x, y ); - - } - - this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - - this._pan( this._panDelta.x, this._panDelta.y ); - - this._panStart.copy( this._panEnd ); - - } - - _handleTouchMoveDolly( event ) { - - const position = this._getSecondPointerPosition( event ); - - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; - - const distance = Math.sqrt( dx * dx + dy * dy ); - - this._dollyEnd.set( 0, distance ); - - this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) ); - - this._dollyOut( this._dollyDelta.y ); - - this._dollyStart.copy( this._dollyEnd ); - - const centerX = ( event.pageX + position.x ) * 0.5; - const centerY = ( event.pageY + position.y ) * 0.5; - - this._updateZoomParameters( centerX, centerY ); - - } - - _handleTouchMoveDollyPan( event ) { - - if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - - if ( this.enablePan ) this._handleTouchMovePan( event ); - - } - - _handleTouchMoveDollyRotate( event ) { - - if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - - if ( this.enableRotate ) this._handleTouchMoveRotate( event ); - - } - - // pointers - - _addPointer( event ) { - - this._pointers.push( event.pointerId ); - - } - - _removePointer( event ) { - - delete this._pointerPositions[ event.pointerId ]; - - for ( let i = 0; i < this._pointers.length; i ++ ) { - - if ( this._pointers[ i ] == event.pointerId ) { - - this._pointers.splice( i, 1 ); - return; - - } - - } - - } - - _isTrackingPointer( event ) { - - for ( let i = 0; i < this._pointers.length; i ++ ) { - - if ( this._pointers[ i ] == event.pointerId ) return true; - - } - - return false; - - } - - _trackPointer( event ) { - - let position = this._pointerPositions[ event.pointerId ]; - - if ( position === undefined ) { - - position = new Vector2(); - this._pointerPositions[ event.pointerId ] = position; - - } - - position.set( event.pageX, event.pageY ); - - } - - _getSecondPointerPosition( event ) { - - const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ]; - - return this._pointerPositions[ pointerId ]; - - } - - // - - _customWheelEvent( event ) { - - const mode = event.deltaMode; - - // minimal wheel event altered to meet delta-zoom demand - const newEvent = { - clientX: event.clientX, - clientY: event.clientY, - deltaY: event.deltaY, - }; - - switch ( mode ) { - - case 1: // LINE_MODE - newEvent.deltaY *= 16; - break; - - case 2: // PAGE_MODE - newEvent.deltaY *= 100; - break; - - } - - // detect if event was triggered by pinching - if ( event.ctrlKey && ! this._controlActive ) { - - newEvent.deltaY *= 10; - - } - - return newEvent; - - } - -} - -function onPointerDown( event ) { - - if ( this.enabled === false ) return; - - if ( this._pointers.length === 0 ) { - - this.domElement.setPointerCapture( event.pointerId ); - - this.domElement.ownerDocument.addEventListener( 'pointermove', this._onPointerMove ); - this.domElement.ownerDocument.addEventListener( 'pointerup', this._onPointerUp ); - - } - - // - - if ( this._isTrackingPointer( event ) ) return; - - // - - this._addPointer( event ); - - if ( event.pointerType === 'touch' ) { - - this._onTouchStart( event ); - - } else { - - this._onMouseDown( event ); - - } - - if ( this._cursorStyle === 'grab' ) { - - this.domElement.style.cursor = 'grabbing'; - - } - -} - -function onPointerMove( event ) { - - if ( this.enabled === false ) return; - - if ( event.pointerType === 'touch' ) { - - this._onTouchMove( event ); - - } else { - - this._onMouseMove( event ); - - } - -} - -function onPointerUp( event ) { - - this._removePointer( event ); - - switch ( this._pointers.length ) { - - case 0: - - this.domElement.releasePointerCapture( event.pointerId ); - - this.domElement.ownerDocument.removeEventListener( 'pointermove', this._onPointerMove ); - this.domElement.ownerDocument.removeEventListener( 'pointerup', this._onPointerUp ); - - this.dispatchEvent( _endEvent ); - - this.state = _STATE.NONE; - - if ( this._cursorStyle === 'grab' ) { - - this.domElement.style.cursor = 'grab'; - - } - - break; - - case 1: - - const pointerId = this._pointers[ 0 ]; - const position = this._pointerPositions[ pointerId ]; - - // minimal placeholder event - allows state correction on pointer-up - this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); - - break; - - } - -} - -function onMouseDown( event ) { - - let mouseAction; - - switch ( event.button ) { - - case 0: - - mouseAction = this.mouseButtons.LEFT; - break; - - case 1: - - mouseAction = this.mouseButtons.MIDDLE; - break; - - case 2: - - mouseAction = this.mouseButtons.RIGHT; - break; - - default: - - mouseAction = -1; - - } - - switch ( mouseAction ) { - - case MOUSE.DOLLY: - - if ( this.enableZoom === false ) return; - - this._handleMouseDownDolly( event ); - - this.state = _STATE.DOLLY; - - break; - - case MOUSE.ROTATE: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enablePan === false ) return; - - this._handleMouseDownPan( event ); - - this.state = _STATE.PAN; - - } else { - - if ( this.enableRotate === false ) return; - - this._handleMouseDownRotate( event ); - - this.state = _STATE.ROTATE; - - } - - break; - - case MOUSE.PAN: - - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - - if ( this.enableRotate === false ) return; - - this._handleMouseDownRotate( event ); - - this.state = _STATE.ROTATE; - - } else { - - if ( this.enablePan === false ) return; - - this._handleMouseDownPan( event ); - - this.state = _STATE.PAN; - - } - - break; - - default: - - this.state = _STATE.NONE; - - } - - if ( this.state !== _STATE.NONE ) { - - this.dispatchEvent( _startEvent ); - - } - -} - -function onMouseMove( event ) { - - switch ( this.state ) { - - case _STATE.ROTATE: - - if ( this.enableRotate === false ) return; - - this._handleMouseMoveRotate( event ); - - break; - - case _STATE.DOLLY: - - if ( this.enableZoom === false ) return; - - this._handleMouseMoveDolly( event ); - - break; - - case _STATE.PAN: - - if ( this.enablePan === false ) return; - - this._handleMouseMovePan( event ); - - break; - - } - -} - -function onMouseWheel( event ) { - - if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return; - - event.preventDefault(); - - this.dispatchEvent( _startEvent ); - - this._handleMouseWheel( this._customWheelEvent( event ) ); - - this.dispatchEvent( _endEvent ); - -} - -function onKeyDown( event ) { - - if ( this.enabled === false ) return; - - this._handleKeyDown( event ); - -} - -function onTouchStart( event ) { - - this._trackPointer( event ); - - switch ( this._pointers.length ) { - - case 1: - - switch ( this.touches.ONE ) { - - case TOUCH.ROTATE: - - if ( this.enableRotate === false ) return; - - this._handleTouchStartRotate( event ); - - this.state = _STATE.TOUCH_ROTATE; - - break; - - case TOUCH.PAN: - - if ( this.enablePan === false ) return; - - this._handleTouchStartPan( event ); - - this.state = _STATE.TOUCH_PAN; - - break; - - default: - - this.state = _STATE.NONE; - - } - - break; - - case 2: - - switch ( this.touches.TWO ) { - - case TOUCH.DOLLY_PAN: - - if ( this.enableZoom === false && this.enablePan === false ) return; - - this._handleTouchStartDollyPan( event ); - - this.state = _STATE.TOUCH_DOLLY_PAN; - - break; - - case TOUCH.DOLLY_ROTATE: - - if ( this.enableZoom === false && this.enableRotate === false ) return; - - this._handleTouchStartDollyRotate( event ); - - this.state = _STATE.TOUCH_DOLLY_ROTATE; - - break; - - default: - - this.state = _STATE.NONE; - - } - - break; - - default: - - this.state = _STATE.NONE; - - } - - if ( this.state !== _STATE.NONE ) { - - this.dispatchEvent( _startEvent ); - - } - -} - -function onTouchMove( event ) { - - this._trackPointer( event ); - - switch ( this.state ) { - - case _STATE.TOUCH_ROTATE: - - if ( this.enableRotate === false ) return; - - this._handleTouchMoveRotate( event ); - - this.update(); - - break; - - case _STATE.TOUCH_PAN: - - if ( this.enablePan === false ) return; - - this._handleTouchMovePan( event ); - - this.update(); - - break; - - case _STATE.TOUCH_DOLLY_PAN: - - if ( this.enableZoom === false && this.enablePan === false ) return; - - this._handleTouchMoveDollyPan( event ); - - this.update(); - - break; - - case _STATE.TOUCH_DOLLY_ROTATE: - - if ( this.enableZoom === false && this.enableRotate === false ) return; - - this._handleTouchMoveDollyRotate( event ); - - this.update(); - - break; - - default: - - this.state = _STATE.NONE; - - } - -} - -function onContextMenu( event ) { - - if ( this.enabled === false ) return; - - event.preventDefault(); - -} - -function interceptControlDown( event ) { - - if ( event.key === 'Control' ) { - - this._controlActive = true; - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - - document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); - - } - -} - -function interceptControlUp( event ) { - - if ( event.key === 'Control' ) { - - this._controlActive = false; - - const document = this.domElement.getRootNode(); // offscreen canvas compatibility - - document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); - - } - -} - -/** - * @module CopyShader - * @three_import import { CopyShader } from 'three/addons/shaders/CopyShader.js'; - */ - -/** - * Full-screen copy shader pass. - * - * @constant - * @type {ShaderMaterial~Shader} - */ -const CopyShader = { - - name: 'CopyShader', - - uniforms: { - - 'tDiffuse': { value: null }, - 'opacity': { value: 1.0 } - - }, - - vertexShader: /* glsl */` - - varying vec2 vUv; - - void main() { - - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - uniform float opacity; - - uniform sampler2D tDiffuse; - - varying vec2 vUv; - - void main() { - - vec4 texel = texture2D( tDiffuse, vUv ); - gl_FragColor = opacity * texel; - - - }` - -}; - -/** - * Abstract base class for all post processing passes. - * - * This module is only relevant for post processing with {@link WebGLRenderer}. - * - * @abstract - * @three_import import { Pass } from 'three/addons/postprocessing/Pass.js'; - */ -class Pass { - - /** - * Constructs a new pass. - */ - constructor() { - - /** - * This flag can be used for type testing. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isPass = true; - - /** - * If set to `true`, the pass is processed by the composer. - * - * @type {boolean} - * @default true - */ - this.enabled = true; - - /** - * If set to `true`, the pass indicates to swap read and write buffer after rendering. - * - * @type {boolean} - * @default true - */ - this.needsSwap = true; - - /** - * If set to `true`, the pass clears its buffer before rendering - * - * @type {boolean} - * @default false - */ - this.clear = false; - - /** - * If set to `true`, the result of the pass is rendered to screen. The last pass in the composers - * pass chain gets automatically rendered to screen, no matter how this property is configured. - * - * @type {boolean} - * @default false - */ - this.renderToScreen = false; - - } - - /** - * Sets the size of the pass. - * - * @abstract - * @param {number} width - The width to set. - * @param {number} height - The height to set. - */ - setSize( /* width, height */ ) {} - - /** - * This method holds the render logic of a pass. It must be implemented in all derived classes. - * - * @abstract - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering - * destination for the pass. - * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the - * previous pass from this buffer. - * @param {number} deltaTime - The delta time in seconds. - * @param {boolean} maskActive - Whether masking is active or not. - */ - render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { - - console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever the pass is no longer used in your app. - * - * @abstract - */ - dispose() {} - -} - -// Helper for passes that need to fill the viewport with a single quad. - -const _camera = new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); - -// https://github.com/mrdoob/three.js/pull/21358 - -class FullscreenTriangleGeometry extends BufferGeometry { - - constructor() { - - super(); - - this.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); - - } - -} - -const _geometry = new FullscreenTriangleGeometry(); - - -/** - * This module is a helper for passes which need to render a full - * screen effect which is quite common in context of post processing. - * - * The intended usage is to reuse a single full screen quad for rendering - * subsequent passes by just reassigning the `material` reference. - * - * This module can only be used with {@link WebGLRenderer}. - * - * @augments Mesh - * @three_import import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js'; - */ -class FullScreenQuad { - - /** - * Constructs a new full screen quad. - * - * @param {?Material} material - The material to render te full screen quad with. - */ - constructor( material ) { - - this._mesh = new Mesh( _geometry, material ); - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever the instance is no longer used in your app. - */ - dispose() { - - this._mesh.geometry.dispose(); - - } - - /** - * Renders the full screen quad. - * - * @param {WebGLRenderer} renderer - The renderer. - */ - render( renderer ) { - - renderer.render( this._mesh, _camera ); - - } - - /** - * The quad's material. - * - * @type {?Material} - */ - get material() { - - return this._mesh.material; - - } - - set material( value ) { - - this._mesh.material = value; - - } - -} - -/** - * This pass can be used to create a post processing effect - * with a raw GLSL shader object. Useful for implementing custom - * effects. - * - * ```js - * const fxaaPass = new ShaderPass( FXAAShader ); - * composer.addPass( fxaaPass ); - * ``` - * - * @augments Pass - * @three_import import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - */ -class ShaderPass extends Pass { - - /** - * Constructs a new shader pass. - * - * @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as - * defines and uniforms. It's also valid to pass a custom shader material. - * @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample - * the read buffer. - */ - constructor( shader, textureID = 'tDiffuse' ) { - - super(); - - /** - * The name of the texture uniform that should sample the read buffer. - * - * @type {string} - * @default 'tDiffuse' - */ - this.textureID = textureID; - - /** - * The pass uniforms. - * - * @type {?Object} - */ - this.uniforms = null; - - /** - * The pass material. - * - * @type {?ShaderMaterial} - */ - this.material = null; - - if ( shader instanceof ShaderMaterial ) { - - this.uniforms = shader.uniforms; - - this.material = shader; - - } else if ( shader ) { - - this.uniforms = UniformsUtils.clone( shader.uniforms ); - - this.material = new ShaderMaterial( { - - name: ( shader.name !== undefined ) ? shader.name : 'unspecified', - defines: Object.assign( {}, shader.defines ), - uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader - - } ); - - } - - // internals - - this._fsQuad = new FullScreenQuad( this.material ); - - } - - /** - * Performs the shader pass. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering - * destination for the pass. - * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the - * previous pass from this buffer. - * @param {number} deltaTime - The delta time in seconds. - * @param {boolean} maskActive - Whether masking is active or not. - */ - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - - if ( this.uniforms[ this.textureID ] ) { - - this.uniforms[ this.textureID ].value = readBuffer.texture; - - } - - this._fsQuad.material = this.material; - - if ( this.renderToScreen ) { - - renderer.setRenderTarget( null ); - this._fsQuad.render( renderer ); - - } else { - - renderer.setRenderTarget( writeBuffer ); - // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 - if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - this._fsQuad.render( renderer ); - - } - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever the pass is no longer used in your app. - */ - dispose() { - - this.material.dispose(); - - this._fsQuad.dispose(); - - } - -} - -/** - * This pass can be used to define a mask during post processing. - * Meaning only areas of subsequent post processing are affected - * which lie in the masking area of this pass. Internally, the masking - * is implemented with the stencil buffer. - * - * ```js - * const maskPass = new MaskPass( scene, camera ); - * composer.addPass( maskPass ); - * ``` - * - * @augments Pass - * @three_import import { MaskPass } from 'three/addons/postprocessing/MaskPass.js'; - */ -class MaskPass extends Pass { - - /** - * Constructs a new mask pass. - * - * @param {Scene} scene - The 3D objects in this scene will define the mask. - * @param {Camera} camera - The camera. - */ - constructor( scene, camera ) { - - super(); - - /** - * The scene that defines the mask. - * - * @type {Scene} - */ - this.scene = scene; - - /** - * The camera. - * - * @type {Camera} - */ - this.camera = camera; - - /** - * Overwritten to perform a clear operation by default. - * - * @type {boolean} - * @default true - */ - this.clear = true; - - /** - * Overwritten to disable the swap. - * - * @type {boolean} - * @default false - */ - this.needsSwap = false; - - /** - * Whether to inverse the mask or not. - * - * @type {boolean} - * @default false - */ - this.inverse = false; - - } - - /** - * Performs a mask pass with the configured scene and camera. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering - * destination for the pass. - * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the - * previous pass from this buffer. - * @param {number} deltaTime - The delta time in seconds. - * @param {boolean} maskActive - Whether masking is active or not. - */ - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - - const context = renderer.getContext(); - const state = renderer.state; - - // don't update color or depth - - state.buffers.color.setMask( false ); - state.buffers.depth.setMask( false ); - - // lock buffers - - state.buffers.color.setLocked( true ); - state.buffers.depth.setLocked( true ); - - // set up stencil - - let writeValue, clearValue; - - if ( this.inverse ) { - - writeValue = 0; - clearValue = 1; - - } else { - - writeValue = 1; - clearValue = 0; - - } - - state.buffers.stencil.setTest( true ); - state.buffers.stencil.setOp( context.REPLACE, context.REPLACE, context.REPLACE ); - state.buffers.stencil.setFunc( context.ALWAYS, writeValue, 0xffffffff ); - state.buffers.stencil.setClear( clearValue ); - state.buffers.stencil.setLocked( true ); - - // draw into the stencil buffer - - renderer.setRenderTarget( readBuffer ); - if ( this.clear ) renderer.clear(); - renderer.render( this.scene, this.camera ); - - renderer.setRenderTarget( writeBuffer ); - if ( this.clear ) renderer.clear(); - renderer.render( this.scene, this.camera ); - - // unlock color and depth buffer and make them writable for subsequent rendering/clearing - - state.buffers.color.setLocked( false ); - state.buffers.depth.setLocked( false ); - - state.buffers.color.setMask( true ); - state.buffers.depth.setMask( true ); - - // only render where stencil is set to 1 - - state.buffers.stencil.setLocked( false ); - state.buffers.stencil.setFunc( context.EQUAL, 1, 0xffffffff ); // draw if == 1 - state.buffers.stencil.setOp( context.KEEP, context.KEEP, context.KEEP ); - state.buffers.stencil.setLocked( true ); - - } - -} - -/** - * This pass can be used to clear a mask previously defined with {@link MaskPass}. - * - * ```js - * const clearPass = new ClearMaskPass(); - * composer.addPass( clearPass ); - * ``` - * - * @augments Pass - */ -class ClearMaskPass extends Pass { - - /** - * Constructs a new clear mask pass. - */ - constructor() { - - super(); - - /** - * Overwritten to disable the swap. - * - * @type {boolean} - * @default false - */ - this.needsSwap = false; - - } - - /** - * Performs the clear of the currently defined mask. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering - * destination for the pass. - * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the - * previous pass from this buffer. - * @param {number} deltaTime - The delta time in seconds. - * @param {boolean} maskActive - Whether masking is active or not. - */ - render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { - - renderer.state.buffers.stencil.setLocked( false ); - renderer.state.buffers.stencil.setTest( false ); - - } - -} - -/** - * Used to implement post-processing effects in three.js. - * The class manages a chain of post-processing passes to produce the final visual result. - * Post-processing passes are executed in order of their addition/insertion. - * The last pass is automatically rendered to screen. - * - * This module can only be used with {@link WebGLRenderer}. - * - * ```js - * const composer = new EffectComposer( renderer ); - * - * // adding some passes - * const renderPass = new RenderPass( scene, camera ); - * composer.addPass( renderPass ); - * - * const glitchPass = new GlitchPass(); - * composer.addPass( glitchPass ); - * - * const outputPass = new OutputPass() - * composer.addPass( outputPass ); - * - * function animate() { - * - * composer.render(); // instead of renderer.render() - * - * } - * ``` - * - * @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - */ -class EffectComposer { - - /** - * Constructs a new effect composer. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will - * be used as the internal read and write buffers. If not given, the composer creates - * the buffers automatically. - */ - constructor( renderer, renderTarget ) { - - /** - * The renderer. - * - * @type {WebGLRenderer} - */ - this.renderer = renderer; - - this._pixelRatio = renderer.getPixelRatio(); - - if ( renderTarget === undefined ) { - - const size = renderer.getSize( new Vector2() ); - this._width = size.width; - this._height = size.height; - - renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); - renderTarget.texture.name = 'EffectComposer.rt1'; - - } else { - - this._width = renderTarget.width; - this._height = renderTarget.height; - - } - - this.renderTarget1 = renderTarget; - this.renderTarget2 = renderTarget.clone(); - this.renderTarget2.texture.name = 'EffectComposer.rt2'; - - /** - * A reference to the internal write buffer. Passes usually write - * their result into this buffer. - * - * @type {WebGLRenderTarget} - */ - this.writeBuffer = this.renderTarget1; - - /** - * A reference to the internal read buffer. Passes usually read - * the previous render result from this buffer. - * - * @type {WebGLRenderTarget} - */ - this.readBuffer = this.renderTarget2; - - /** - * Whether the final pass is rendered to the screen (default framebuffer) or not. - * - * @type {boolean} - * @default true - */ - this.renderToScreen = true; - - /** - * An array representing the (ordered) chain of post-processing passes. - * - * @type {Array} - */ - this.passes = []; - - /** - * A copy pass used for internal swap operations. - * - * @private - * @type {ShaderPass} - */ - this.copyPass = new ShaderPass( CopyShader ); - this.copyPass.material.blending = NoBlending; - - /** - * The internal timer for managing time data. - * - * @private - * @type {Timer} - */ - this.timer = new Timer(); - - } - - /** - * Swaps the internal read/write buffers. - */ - swapBuffers() { - - const tmp = this.readBuffer; - this.readBuffer = this.writeBuffer; - this.writeBuffer = tmp; - - } - - /** - * Adds the given pass to the pass chain. - * - * @param {Pass} pass - The pass to add. - */ - addPass( pass ) { - - this.passes.push( pass ); - pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); - - } - - /** - * Inserts the given pass at a given index. - * - * @param {Pass} pass - The pass to insert. - * @param {number} index - The index into the pass chain. - */ - insertPass( pass, index ) { - - this.passes.splice( index, 0, pass ); - pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); - - } - - /** - * Removes the given pass from the pass chain. - * - * @param {Pass} pass - The pass to remove. - */ - removePass( pass ) { - - const index = this.passes.indexOf( pass ); - - if ( index !== -1 ) { - - this.passes.splice( index, 1 ); - - } - - } - - /** - * Returns `true` if the pass for the given index is the last enabled pass in the pass chain. - * - * @param {number} passIndex - The pass index. - * @return {boolean} Whether the pass for the given index is the last pass in the pass chain. - */ - isLastEnabledPass( passIndex ) { - - for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { - - if ( this.passes[ i ].enabled ) { - - return false; - - } - - } - - return true; - - } - - /** - * Executes all enabled post-processing passes in order to produce the final frame. - * - * @param {number} deltaTime - The delta time in seconds. If not given, the composer computes - * its own time delta value. - */ - render( deltaTime ) { - - // deltaTime value is in seconds - - this.timer.update(); - - if ( deltaTime === undefined ) { - - deltaTime = this.timer.getDelta(); - - } - - const currentRenderTarget = this.renderer.getRenderTarget(); - - let maskActive = false; - - for ( let i = 0, il = this.passes.length; i < il; i ++ ) { - - const pass = this.passes[ i ]; - - if ( pass.enabled === false ) continue; - - pass.renderToScreen = ( this.renderToScreen && this.isLastEnabledPass( i ) ); - pass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime, maskActive ); - - if ( pass.needsSwap ) { - - if ( maskActive ) { - - const context = this.renderer.getContext(); - const stencil = this.renderer.state.buffers.stencil; - - //context.stencilFunc( context.NOTEQUAL, 1, 0xffffffff ); - stencil.setFunc( context.NOTEQUAL, 1, 0xffffffff ); - - this.copyPass.render( this.renderer, this.writeBuffer, this.readBuffer, deltaTime ); - - //context.stencilFunc( context.EQUAL, 1, 0xffffffff ); - stencil.setFunc( context.EQUAL, 1, 0xffffffff ); - - } - - this.swapBuffers(); - - } - - if ( MaskPass !== undefined ) { - - if ( pass instanceof MaskPass ) { - - maskActive = true; - - } else if ( pass instanceof ClearMaskPass ) { - - maskActive = false; - - } - - } - - } - - this.renderer.setRenderTarget( currentRenderTarget ); - - } - - /** - * Resets the internal state of the EffectComposer. - * - * @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like - * the one from the constructor. If set, it is used to setup the read and write buffers. - */ - reset( renderTarget ) { - - if ( renderTarget === undefined ) { - - const size = this.renderer.getSize( new Vector2() ); - this._pixelRatio = this.renderer.getPixelRatio(); - this._width = size.width; - this._height = size.height; - - renderTarget = this.renderTarget1.clone(); - renderTarget.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio ); - - } - - this.renderTarget1.dispose(); - this.renderTarget2.dispose(); - this.renderTarget1 = renderTarget; - this.renderTarget2 = renderTarget.clone(); - - this.writeBuffer = this.renderTarget1; - this.readBuffer = this.renderTarget2; - - } - - /** - * Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize}, - * this method honors the current pixel ration. - * - * @param {number} width - The width in logical pixels. - * @param {number} height - The height in logical pixels. - */ - setSize( width, height ) { - - this._width = width; - this._height = height; - - const effectiveWidth = this._width * this._pixelRatio; - const effectiveHeight = this._height * this._pixelRatio; - - this.renderTarget1.setSize( effectiveWidth, effectiveHeight ); - this.renderTarget2.setSize( effectiveWidth, effectiveHeight ); - - for ( let i = 0; i < this.passes.length; i ++ ) { - - this.passes[ i ].setSize( effectiveWidth, effectiveHeight ); - - } - - } - - /** - * Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output. - * Setting the pixel ratio will automatically resize the composer. - * - * @param {number} pixelRatio - The pixel ratio to set. - */ - setPixelRatio( pixelRatio ) { - - this._pixelRatio = pixelRatio; - - this.setSize( this._width, this._height ); - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever the composer is no longer used in your app. - */ - dispose() { - - this.renderTarget1.dispose(); - this.renderTarget2.dispose(); - - this.copyPass.dispose(); - - } - -} - -/** - * This class represents a render pass. It takes a camera and a scene and produces - * a beauty pass for subsequent post processing effects. - * - * ```js - * const renderPass = new RenderPass( scene, camera ); - * composer.addPass( renderPass ); - * ``` - * - * @augments Pass - * @three_import import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - */ -class RenderPass extends Pass { - - /** - * Constructs a new render pass. - * - * @param {Scene} scene - The scene to render. - * @param {Camera} camera - The camera. - * @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used - * for all objects in the scene. - * @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass. - * @param {?number} [clearAlpha=null] - The clear alpha of the render pass. - */ - constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) { - - super(); - - /** - * The scene to render. - * - * @type {Scene} - */ - this.scene = scene; - - /** - * The camera. - * - * @type {Camera} - */ - this.camera = camera; - - /** - * The override material. If set, this material is used - * for all objects in the scene. - * - * @type {?Material} - * @default null - */ - this.overrideMaterial = overrideMaterial; - - /** - * The clear color of the render pass. - * - * @type {?(number|Color|string)} - * @default null - */ - this.clearColor = clearColor; - - /** - * The clear alpha of the render pass. - * - * @type {?number} - * @default null - */ - this.clearAlpha = clearAlpha; - - /** - * Overwritten to perform a clear operation by default. - * - * @type {boolean} - * @default true - */ - this.clear = true; - - /** - * If set to `true`, only the depth can be cleared when `clear` is to `false`. - * - * @type {boolean} - * @default false - */ - this.clearDepth = false; - - /** - * Overwritten to disable the swap. - * - * @type {boolean} - * @default false - */ - this.needsSwap = false; - - /** - * This flag indicates that this pass renders the scene itself. - * - * @type {boolean} - * @readonly - * @default true - */ - this.isRenderPass = true; - - this._oldClearColor = new Color(); - - } - - /** - * Performs a beauty pass with the configured scene and camera. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering - * destination for the pass. - * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the - * previous pass from this buffer. - * @param {number} deltaTime - The delta time in seconds. - * @param {boolean} maskActive - Whether masking is active or not. - */ - render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; - - let oldClearAlpha, oldOverrideMaterial; - - if ( this.overrideMaterial !== null ) { - - oldOverrideMaterial = this.scene.overrideMaterial; - - this.scene.overrideMaterial = this.overrideMaterial; - - } - - if ( this.clearColor !== null ) { - - renderer.getClearColor( this._oldClearColor ); - renderer.setClearColor( this.clearColor, renderer.getClearAlpha() ); - - } - - if ( this.clearAlpha !== null ) { - - oldClearAlpha = renderer.getClearAlpha(); - renderer.setClearAlpha( this.clearAlpha ); - - } - - if ( this.clearDepth == true ) { - - renderer.clearDepth(); - - } - - renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); - - if ( this.clear === true ) { - - // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - - } - - renderer.render( this.scene, this.camera ); - - // restore - - if ( this.clearColor !== null ) { - - renderer.setClearColor( this._oldClearColor ); - - } - - if ( this.clearAlpha !== null ) { - - renderer.setClearAlpha( oldClearAlpha ); - - } - - if ( this.overrideMaterial !== null ) { - - this.scene.overrideMaterial = oldOverrideMaterial; - - } - - renderer.autoClear = oldAutoClear; - - } - -} - -/** - * @module LuminosityHighPassShader - * @three_import import { LuminosityHighPassShader } from 'three/addons/shaders/LuminosityHighPassShader.js'; - */ - -/** - * Luminosity high pass shader. - * - * @constant - * @type {ShaderMaterial~Shader} - */ -const LuminosityHighPassShader = { - - uniforms: { - - 'tDiffuse': { value: null }, - 'luminosityThreshold': { value: 1.0 }, - 'smoothWidth': { value: 1.0 }, - 'defaultColor': { value: new Color( 0x000000 ) }, - 'defaultOpacity': { value: 0.0 } - - }, - - vertexShader: /* glsl */` - - varying vec2 vUv; - - void main() { - - vUv = uv; - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - uniform sampler2D tDiffuse; - uniform vec3 defaultColor; - uniform float defaultOpacity; - uniform float luminosityThreshold; - uniform float smoothWidth; - - varying vec2 vUv; - - void main() { - - vec4 texel = texture2D( tDiffuse, vUv ); - - float v = luminance( texel.xyz ); - - vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity ); - - float alpha = smoothstep( luminosityThreshold, luminosityThreshold + smoothWidth, v ); - - gl_FragColor = mix( outputColor, texel, alpha ); - - }` - -}; - -/** - * This pass is inspired by the bloom pass of Unreal Engine. It creates a - * mip map chain of bloom textures and blurs them with different radii. Because - * of the weighted combination of mips, and because larger blurs are done on - * higher mips, this effect provides good quality and performance. - * - * When using this pass, tone mapping must be enabled in the renderer settings. - * - * Reference: - * - [Bloom in Unreal Engine](https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/) - * - * ```js - * const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); - * const bloomPass = new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ); - * composer.addPass( bloomPass ); - * ``` - * - * @augments Pass - * @three_import import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; - */ -class UnrealBloomPass extends Pass { - - /** - * Constructs a new Unreal Bloom pass. - * - * @param {Vector2} [resolution] - The effect's resolution. - * @param {number} [strength=1] - The Bloom strength. - * @param {number} radius - The Bloom radius. - * @param {number} threshold - The luminance threshold limits which bright areas contribute to the Bloom effect. - */ - constructor( resolution, strength = 1, radius, threshold ) { - - super(); - - /** - * The Bloom strength. - * - * @type {number} - * @default 1 - */ - this.strength = strength; - - /** - * The Bloom radius. Must be in the range `[0,1]`. - * - * @type {number} - */ - this.radius = radius; - - /** - * The luminance threshold limits which bright areas contribute to the Bloom effect. - * - * @type {number} - */ - this.threshold = threshold; - - /** - * The effect's resolution. - * - * @type {Vector2} - * @default (256,256) - */ - this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - - /** - * The effect's clear color - * - * @type {Color} - * @default (0,0,0) - */ - this.clearColor = new Color( 0, 0, 0 ); - - /** - * Overwritten to disable the swap. - * - * @type {boolean} - * @default false - */ - this.needsSwap = false; - - // internals - - // render targets - this.renderTargetsHorizontal = []; - this.renderTargetsVertical = []; - this.nMips = 5; - let resx = Math.round( this.resolution.x / 2 ); - let resy = Math.round( this.resolution.y / 2 ); - - this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType, depthBuffer: false } ); - this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; - this.renderTargetBright.texture.generateMipmaps = false; - - for ( let i = 0; i < this.nMips; i ++ ) { - - const renderTargetHorizontal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType, depthBuffer: false } ); - - renderTargetHorizontal.texture.name = 'UnrealBloomPass.h' + i; - renderTargetHorizontal.texture.generateMipmaps = false; - - this.renderTargetsHorizontal.push( renderTargetHorizontal ); - - const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType, depthBuffer: false } ); - - renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; - renderTargetVertical.texture.generateMipmaps = false; - - this.renderTargetsVertical.push( renderTargetVertical ); - - resx = Math.round( resx / 2 ); - - resy = Math.round( resy / 2 ); - - } - - // luminosity high pass material - - const highPassShader = LuminosityHighPassShader; - this.highPassUniforms = UniformsUtils.clone( highPassShader.uniforms ); - - this.highPassUniforms[ 'luminosityThreshold' ].value = threshold; - this.highPassUniforms[ 'smoothWidth' ].value = 0.01; - - this.materialHighPassFilter = new ShaderMaterial( { - uniforms: this.highPassUniforms, - vertexShader: highPassShader.vertexShader, - fragmentShader: highPassShader.fragmentShader - } ); - - // gaussian blur materials - - this.separableBlurMaterials = []; - // These sizes have been changed to account for the altered coefficients-calculation to avoid blockiness, - // while retaining the same blur-strength. For details see https://github.com/mrdoob/three.js/pull/31528 - const kernelSizeArray = [ 6, 10, 14, 18, 22 ]; - resx = Math.round( this.resolution.x / 2 ); - resy = Math.round( this.resolution.y / 2 ); - - for ( let i = 0; i < this.nMips; i ++ ) { - - this.separableBlurMaterials.push( this._getSeparableBlurMaterial( kernelSizeArray[ i ] ) ); - - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - - resx = Math.round( resx / 2 ); - - resy = Math.round( resy / 2 ); - - } - - // composite material - - this.compositeMaterial = this._getCompositeMaterial( this.nMips ); - this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture4' ].value = this.renderTargetsVertical[ 3 ].texture; - this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; - - const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; - this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; - this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - - // blend material - - this.copyUniforms = UniformsUtils.clone( CopyShader.uniforms ); - - this.blendMaterial = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - premultipliedAlpha: true, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - transparent: true - } ); - - this._oldClearColor = new Color(); - this._oldClearAlpha = 1; - - this._basic = new MeshBasicMaterial(); - - this._fsQuad = new FullScreenQuad( null ); - - } - - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever the pass is no longer used in your app. - */ - dispose() { - - for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { - - this.renderTargetsHorizontal[ i ].dispose(); - - } - - for ( let i = 0; i < this.renderTargetsVertical.length; i ++ ) { - - this.renderTargetsVertical[ i ].dispose(); - - } - - this.renderTargetBright.dispose(); - - // - - for ( let i = 0; i < this.separableBlurMaterials.length; i ++ ) { - - this.separableBlurMaterials[ i ].dispose(); - - } - - this.compositeMaterial.dispose(); - this.blendMaterial.dispose(); - this._basic.dispose(); - - // - - this._fsQuad.dispose(); - - } - - /** - * Sets the size of the pass. - * - * @param {number} width - The width to set. - * @param {number} height - The height to set. - */ - setSize( width, height ) { - - let resx = Math.round( width / 2 ); - let resy = Math.round( height / 2 ); - - this.renderTargetBright.setSize( resx, resy ); - - for ( let i = 0; i < this.nMips; i ++ ) { - - this.renderTargetsHorizontal[ i ].setSize( resx, resy ); - this.renderTargetsVertical[ i ].setSize( resx, resy ); - - this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); - - resx = Math.round( resx / 2 ); - resy = Math.round( resy / 2 ); - - } - - } - - /** - * Performs the Bloom pass. - * - * @param {WebGLRenderer} renderer - The renderer. - * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering - * destination for the pass. - * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the - * previous pass from this buffer. - * @param {number} deltaTime - The delta time in seconds. - * @param {boolean} maskActive - Whether masking is active or not. - */ - render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { - - renderer.getClearColor( this._oldClearColor ); - this._oldClearAlpha = renderer.getClearAlpha(); - const oldAutoClear = renderer.autoClear; - renderer.autoClear = false; - - renderer.setClearColor( this.clearColor, 0 ); - - if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - - // Render input to screen - - if ( this.renderToScreen ) { - - this._fsQuad.material = this._basic; - this._basic.map = readBuffer.texture; - - renderer.setRenderTarget( null ); - renderer.clear(); - this._fsQuad.render( renderer ); - - } - - // 1. Extract Bright Areas - - this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; - this._fsQuad.material = this.materialHighPassFilter; - - renderer.setRenderTarget( this.renderTargetBright ); - renderer.clear(); - this._fsQuad.render( renderer ); - - // 2. Blur All the mips progressively - - let inputRenderTarget = this.renderTargetBright; - - for ( let i = 0; i < this.nMips; i ++ ) { - - this._fsQuad.material = this.separableBlurMaterials[ i ]; - - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; - renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); - renderer.clear(); - this._fsQuad.render( renderer ); - - this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; - this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; - renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); - renderer.clear(); - this._fsQuad.render( renderer ); - - inputRenderTarget = this.renderTargetsVertical[ i ]; - - } - - // Composite All the mips - - this._fsQuad.material = this.compositeMaterial; - this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; - this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; - this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - - renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); - renderer.clear(); - this._fsQuad.render( renderer ); - - // Blend it additively over the input texture - - this._fsQuad.material = this.blendMaterial; - this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; - - if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); - - if ( this.renderToScreen ) { - - renderer.setRenderTarget( null ); - this._fsQuad.render( renderer ); - - } else { - - renderer.setRenderTarget( readBuffer ); - this._fsQuad.render( renderer ); - - } - - // Restore renderer settings - - renderer.setClearColor( this._oldClearColor, this._oldClearAlpha ); - renderer.autoClear = oldAutoClear; - - } - - // internals - - _getSeparableBlurMaterial( kernelRadius ) { - - const coefficients = []; - const sigma = kernelRadius / 3; - - for ( let i = 0; i < kernelRadius; i ++ ) { - - coefficients.push( 0.39894 * Math.exp( -0.5 * i * i / ( sigma * sigma ) ) / sigma ); - - } - - // merge adjacent taps into single bilinear fetches (linear sampling) - - const offsets = []; - const weights = []; - - for ( let i = 1; i < kernelRadius; i += 2 ) { - - const wa = coefficients[ i ]; - const wb = ( i + 1 < kernelRadius ) ? coefficients[ i + 1 ] : 0; - const w = wa + wb; - - offsets.push( ( i * wa + ( i + 1 ) * wb ) / w ); - weights.push( w ); - - } - - return new ShaderMaterial( { - - defines: { - 'KERNEL_PAIRS': offsets.length - }, - - uniforms: { - 'colorTexture': { value: null }, - 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size - 'direction': { value: new Vector2( 0.5, 0.5 ) }, - 'centerWeight': { value: coefficients[ 0 ] }, - 'gaussianOffsets': { value: offsets }, - 'gaussianWeights': { value: weights } - }, - - vertexShader: /* glsl */` - - varying vec2 vUv; - - void main() { - - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - #include - - varying vec2 vUv; - - uniform sampler2D colorTexture; - uniform vec2 invSize; - uniform vec2 direction; - uniform float centerWeight; - uniform float gaussianOffsets[KERNEL_PAIRS]; - uniform float gaussianWeights[KERNEL_PAIRS]; - - void main() { - - vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * centerWeight; - - for ( int i = 0; i < KERNEL_PAIRS; i ++ ) { - - vec2 uvOffset = direction * invSize * gaussianOffsets[ i ]; - vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; - vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; - diffuseSum += ( sample1 + sample2 ) * gaussianWeights[ i ]; - - } - - gl_FragColor = vec4( diffuseSum, 1.0 ); - - }` - } ); - - } - - _getCompositeMaterial( nMips ) { - - return new ShaderMaterial( { - - defines: { - 'NUM_MIPS': nMips - }, - - uniforms: { - 'blurTexture1': { value: null }, - 'blurTexture2': { value: null }, - 'blurTexture3': { value: null }, - 'blurTexture4': { value: null }, - 'blurTexture5': { value: null }, - 'bloomStrength': { value: 1.0 }, - 'bloomFactors': { value: null }, - 'bloomTintColors': { value: null }, - 'bloomRadius': { value: 0.0 } - }, - - vertexShader: /* glsl */` - - varying vec2 vUv; - - void main() { - - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - varying vec2 vUv; - - uniform sampler2D blurTexture1; - uniform sampler2D blurTexture2; - uniform sampler2D blurTexture3; - uniform sampler2D blurTexture4; - uniform sampler2D blurTexture5; - uniform float bloomStrength; - uniform float bloomRadius; - uniform float bloomFactors[NUM_MIPS]; - uniform vec3 bloomTintColors[NUM_MIPS]; - - float lerpBloomFactor( const in float factor ) { - - float mirrorFactor = 1.2 - factor; - return mix( factor, mirrorFactor, bloomRadius ); - - } - - void main() { - - // 3.0 for backwards compatibility with previous alpha-based intensity - vec3 bloom = 3.0 * bloomStrength * ( - lerpBloomFactor( bloomFactors[ 0 ] ) * bloomTintColors[ 0 ] * texture2D( blurTexture1, vUv ).rgb + - lerpBloomFactor( bloomFactors[ 1 ] ) * bloomTintColors[ 1 ] * texture2D( blurTexture2, vUv ).rgb + - lerpBloomFactor( bloomFactors[ 2 ] ) * bloomTintColors[ 2 ] * texture2D( blurTexture3, vUv ).rgb + - lerpBloomFactor( bloomFactors[ 3 ] ) * bloomTintColors[ 3 ] * texture2D( blurTexture4, vUv ).rgb + - lerpBloomFactor( bloomFactors[ 4 ] ) * bloomTintColors[ 4 ] * texture2D( blurTexture5, vUv ).rgb - ); - - float bloomAlpha = max( bloom.r, max( bloom.g, bloom.b ) ); - gl_FragColor = vec4( bloom, bloomAlpha ); - - }` - } ); - - } - -} - -UnrealBloomPass.BlurDirectionX = new Vector2( 1.0, 0.0 ); -UnrealBloomPass.BlurDirectionY = new Vector2( 0.0, 1.0 ); - -class RenderableObject { - - constructor() { - - this.id = 0; - - this.object = null; - this.z = 0; - this.renderOrder = 0; - - } - -} - -// - -class RenderableFace { - - constructor() { - - this.id = 0; - - this.v1 = new RenderableVertex(); - this.v2 = new RenderableVertex(); - this.v3 = new RenderableVertex(); - - this.normalModel = new Vector3(); - - this.vertexNormalsModel = [ new Vector3(), new Vector3(), new Vector3() ]; - this.vertexNormalsLength = 0; - - this.color = new Color(); - this.material = null; - this.uvs = [ new Vector2(), new Vector2(), new Vector2() ]; - - this.z = 0; - this.renderOrder = 0; - - } - -} - -// - -class RenderableVertex { - - constructor() { - - this.position = new Vector3(); - this.positionWorld = new Vector3(); - this.positionScreen = new Vector4(); - - this.visible = true; - - } - - copy( vertex ) { - - this.positionWorld.copy( vertex.positionWorld ); - this.positionScreen.copy( vertex.positionScreen ); - - } - -} - -// - -class RenderableLine { - - constructor() { - - this.id = 0; - - this.v1 = new RenderableVertex(); - this.v2 = new RenderableVertex(); - - this.vertexColors = [ new Color(), new Color() ]; - this.material = null; - - this.z = 0; - this.renderOrder = 0; - - } - -} - -// - -class RenderableSprite { - - constructor() { - - this.id = 0; - - this.object = null; - - this.x = 0; - this.y = 0; - this.z = 0; - - this.rotation = 0; - this.scale = new Vector2(); - - this.material = null; - this.renderOrder = 0; - - } - -} - -/** - * This class can project a given scene in 3D space into a 2D representation - * used for rendering with a 2D API. `Projector` is currently used by {@link SVGRenderer} - * and was previously used by the legacy `CanvasRenderer`. - * - * @three_import import { Projector } from 'three/addons/renderers/Projector.js'; - */ -class Projector { - - /** - * Constructs a new projector. - */ - constructor() { - - let _object, _objectCount, _objectPoolLength = 0, - _vertex, _vertexCount, _vertexPoolLength = 0, - _face, _faceCount, _facePoolLength = 0, - _line, _lineCount, _linePoolLength = 0, - _sprite, _spriteCount, _spritePoolLength = 0, - _modelMatrix, _clipInput = [], _clipOutput = []; - - const - - _renderData = { objects: [], lights: [], elements: [] }, - - _vector3 = new Vector3(), - _vector4 = new Vector4(), - - _clipBox = new Box3( new Vector3( -1, -1, -1 ), new Vector3( 1, 1, 1 ) ), - _boundingBox = new Box3(), - _points3 = new Array( 3 ), - - _viewMatrix = new Matrix4(), - _viewProjectionMatrix = new Matrix4(), - - _modelViewProjectionMatrix = new Matrix4(), - - _frustum = new Frustum(), - - _objectPool = [], _vertexPool = [], _facePool = [], _linePool = [], _spritePool = [], - - _clipVertexPool = [], - _clipPos1 = new Vector4(), - _clipPos2 = new Vector4(), - _clipPos3 = new Vector4(), - _screenVertexPool = [], - _clipInputVertices = [ null, null, null ], - - _clipPlanes = [ - { sign: 1 }, - { sign: -1 } - ]; - - // - - function RenderList() { - - const normals = []; - const colors = []; - const uvs = []; - - let object = null; - - const normalMatrix = new Matrix3(); - - function setObject( value ) { - - object = value; - - normalMatrix.getNormalMatrix( object.matrixWorld ); - - normals.length = 0; - colors.length = 0; - uvs.length = 0; - - } - - function projectVertex( vertex ) { - - const position = vertex.position; - const positionWorld = vertex.positionWorld; - const positionScreen = vertex.positionScreen; - - positionWorld.copy( position ).applyMatrix4( _modelMatrix ); - positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix ); - - const invW = 1 / positionScreen.w; - - positionScreen.x *= invW; - positionScreen.y *= invW; - positionScreen.z *= invW; - - vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 && - positionScreen.y >= -1 && positionScreen.y <= 1 && - positionScreen.z >= -1 && positionScreen.z <= 1; - - } - - function pushVertex( x, y, z ) { - - _vertex = getNextVertexInPool(); - _vertex.position.set( x, y, z ); - - projectVertex( _vertex ); - - } - - function pushNormal( x, y, z ) { - - normals.push( x, y, z ); - - } - - function pushColor( r, g, b ) { - - colors.push( r, g, b ); - - } - - function pushUv( x, y ) { - - uvs.push( x, y ); - - } - - function checkTriangleVisibility( v1, v2, v3 ) { - - if ( v1.visible === true || v2.visible === true || v3.visible === true ) return true; - - _points3[ 0 ] = v1.positionScreen; - _points3[ 1 ] = v2.positionScreen; - _points3[ 2 ] = v3.positionScreen; - - return _clipBox.intersectsBox( _boundingBox.setFromPoints( _points3 ) ); - - } - - function checkBackfaceCulling( v1, v2, v3 ) { - - return ( ( v3.positionScreen.x - v1.positionScreen.x ) * - ( v2.positionScreen.y - v1.positionScreen.y ) - - ( v3.positionScreen.y - v1.positionScreen.y ) * - ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0; - - } - - function pushLine( a, b ) { - - const v1 = _vertexPool[ a ]; - const v2 = _vertexPool[ b ]; - - // Clip - - v1.positionScreen.copy( v1.position ).applyMatrix4( _modelViewProjectionMatrix ); - v2.positionScreen.copy( v2.position ).applyMatrix4( _modelViewProjectionMatrix ); - - if ( clipLine( v1.positionScreen, v2.positionScreen ) === true ) { - - // Perform the perspective divide - v1.positionScreen.multiplyScalar( 1 / v1.positionScreen.w ); - v2.positionScreen.multiplyScalar( 1 / v2.positionScreen.w ); - - _line = getNextLineInPool(); - _line.id = object.id; - _line.v1.copy( v1 ); - _line.v2.copy( v2 ); - _line.z = Math.max( v1.positionScreen.z, v2.positionScreen.z ); - _line.renderOrder = object.renderOrder; - - _line.material = object.material; - - if ( object.material.vertexColors ) { - - _line.vertexColors[ 0 ].fromArray( colors, a * 3 ); - _line.vertexColors[ 1 ].fromArray( colors, b * 3 ); - - } - - _renderData.elements.push( _line ); - - } - - } - - function pushTriangle( a, b, c, material ) { - - const v1 = _vertexPool[ a ]; - const v2 = _vertexPool[ b ]; - const v3 = _vertexPool[ c ]; - - // Derive near/far clip distances from NDC z and stored clip-space w - // (projectVertex already computed positionScreen = clipPos / w, with w preserved) - const w1 = v1.positionScreen.w; - const w2 = v2.positionScreen.w; - const w3 = v3.positionScreen.w; - - const nearDist1 = w1 * ( v1.positionScreen.z + 1 ); - const nearDist2 = w2 * ( v2.positionScreen.z + 1 ); - const nearDist3 = w3 * ( v3.positionScreen.z + 1 ); - const farDist1 = w1 * ( 1 - v1.positionScreen.z ); - const farDist2 = w2 * ( 1 - v2.positionScreen.z ); - const farDist3 = w3 * ( 1 - v3.positionScreen.z ); - - // Check if completely outside - if ( ( nearDist1 < 0 && nearDist2 < 0 && nearDist3 < 0 ) || - ( farDist1 < 0 && farDist2 < 0 && farDist3 < 0 ) ) { - - return; // Triangle completely clipped - - } - - // Check if completely inside (no clipping needed) - if ( nearDist1 >= 0 && nearDist2 >= 0 && nearDist3 >= 0 && - farDist1 >= 0 && farDist2 >= 0 && farDist3 >= 0 ) { - - // No clipping needed - use original path - if ( checkTriangleVisibility( v1, v2, v3 ) === false ) return; - - if ( material.side === DoubleSide || checkBackfaceCulling( v1, v2, v3 ) === true ) { - - _face = getNextFaceInPool(); - - _face.id = object.id; - _face.v1.copy( v1 ); - _face.v2.copy( v2 ); - _face.v3.copy( v3 ); - _face.z = ( v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z ) / 3; - _face.renderOrder = object.renderOrder; - - // face normal - _vector3.subVectors( v3.position, v2.position ); - _vector4.subVectors( v1.position, v2.position ); - _vector3.cross( _vector4 ); - _face.normalModel.copy( _vector3 ); - _face.normalModel.applyMatrix3( normalMatrix ).normalize(); - - for ( let i = 0; i < 3; i ++ ) { - - const normal = _face.vertexNormalsModel[ i ]; - normal.fromArray( normals, arguments[ i ] * 3 ); - normal.applyMatrix3( normalMatrix ).normalize(); - - const uv = _face.uvs[ i ]; - uv.fromArray( uvs, arguments[ i ] * 2 ); - - } - - _face.vertexNormalsLength = 3; - - _face.material = material; - - if ( material.vertexColors ) { - - _face.color.fromArray( colors, a * 3 ); - - } - - _renderData.elements.push( _face ); - - } - - return; - - } - - // Triangle needs clipping - reconstruct clip-space positions from NDC + w - _clipPos1.set( v1.positionScreen.x * w1, v1.positionScreen.y * w1, v1.positionScreen.z * w1, w1 ); - _clipPos2.set( v2.positionScreen.x * w2, v2.positionScreen.y * w2, v2.positionScreen.z * w2, w2 ); - _clipPos3.set( v3.positionScreen.x * w3, v3.positionScreen.y * w3, v3.positionScreen.z * w3, w3 ); - _clipInputVertices[ 0 ] = _clipPos1; - _clipInputVertices[ 1 ] = _clipPos2; - _clipInputVertices[ 2 ] = _clipPos3; - const clippedCount = clipTriangle( _clipInputVertices ); - - if ( clippedCount < 3 ) return; // Triangle completely clipped - - // Perform perspective divide on clipped vertices and create screen vertices - for ( let i = 0; i < clippedCount; i ++ ) { - - const cv = _clipInput[ i ]; - - // Get or create renderable vertex from pool - let sv = _screenVertexPool[ i ]; - if ( ! sv ) { - - sv = new RenderableVertex(); - _screenVertexPool[ i ] = sv; - - } - - // Perform perspective divide - const invW = 1 / cv.w; - sv.positionScreen.set( cv.x * invW, cv.y * invW, cv.z * invW, 1 ); - - // Interpolate world position (simplified - using weighted average based on barycentric-like coords) - // For a proper implementation, we'd need to track interpolation weights - sv.positionWorld.copy( v1.positionWorld ); - - sv.visible = true; - - } - - // Triangulate the clipped polygon (simple fan triangulation) - for ( let i = 1; i < clippedCount - 1; i ++ ) { - - const tv1 = _screenVertexPool[ 0 ]; - const tv2 = _screenVertexPool[ i ]; - const tv3 = _screenVertexPool[ i + 1 ]; - - if ( material.side === DoubleSide || checkBackfaceCulling( tv1, tv2, tv3 ) === true ) { - - _face = getNextFaceInPool(); - - _face.id = object.id; - _face.v1.copy( tv1 ); - _face.v2.copy( tv2 ); - _face.v3.copy( tv3 ); - _face.z = ( tv1.positionScreen.z + tv2.positionScreen.z + tv3.positionScreen.z ) / 3; - _face.renderOrder = object.renderOrder; - - // face normal - use original triangle's normal - _vector3.subVectors( v3.position, v2.position ); - _vector4.subVectors( v1.position, v2.position ); - _vector3.cross( _vector4 ); - _face.normalModel.copy( _vector3 ); - _face.normalModel.applyMatrix3( normalMatrix ).normalize(); - - // Use original vertex normals and UVs (simplified - proper impl would interpolate) - for ( let j = 0; j < 3; j ++ ) { - - const normal = _face.vertexNormalsModel[ j ]; - normal.fromArray( normals, arguments[ j ] * 3 ); - normal.applyMatrix3( normalMatrix ).normalize(); - - const uv = _face.uvs[ j ]; - uv.fromArray( uvs, arguments[ j ] * 2 ); - - } - - _face.vertexNormalsLength = 3; - - _face.material = material; - - if ( material.vertexColors ) { - - _face.color.fromArray( colors, a * 3 ); - - } - - _renderData.elements.push( _face ); - - } - - } - - } - - return { - setObject: setObject, - projectVertex: projectVertex, - checkTriangleVisibility: checkTriangleVisibility, - checkBackfaceCulling: checkBackfaceCulling, - pushVertex: pushVertex, - pushNormal: pushNormal, - pushColor: pushColor, - pushUv: pushUv, - pushLine: pushLine, - pushTriangle: pushTriangle - }; - - } - - const renderList = new RenderList(); - - function projectObject( object ) { - - if ( object.visible === false ) return; - - if ( object.isLight ) { - - _renderData.lights.push( object ); - - } else if ( object.isMesh || object.isLine || object.isPoints ) { - - if ( object.material.visible === false ) return; - if ( object.frustumCulled === true && _frustum.intersectsObject( object ) === false ) return; - - addObject( object ); - - } else if ( object.isSprite ) { - - if ( object.material.visible === false ) return; - if ( object.frustumCulled === true && _frustum.intersectsSprite( object ) === false ) return; - - addObject( object ); - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - projectObject( children[ i ] ); - - } - - } - - function addObject( object ) { - - _object = getNextObjectInPool(); - _object.id = object.id; - _object.object = object; - - _vector3.setFromMatrixPosition( object.matrixWorld ); - _vector3.applyMatrix4( _viewProjectionMatrix ); - _object.z = _vector3.z; - _object.renderOrder = object.renderOrder; - - _renderData.objects.push( _object ); - - } - - /** - * Projects the given scene in 3D space into a 2D representation. The result - * is an object with renderable items. - * - * @param {Object3D} scene - A scene or any other type of 3D object. - * @param {Camera} camera - The camera. - * @param {boolean} sortObjects - Whether to sort objects or not. - * @param {boolean} sortElements - Whether to sort elements (faces, lines and sprites) or not. - * @return {{objects:Array,lights:Array,elements:Array}} The projected scene as renderable objects. - */ - this.projectScene = function ( scene, camera, sortObjects, sortElements ) { - - _faceCount = 0; - _lineCount = 0; - _spriteCount = 0; - - _renderData.elements.length = 0; - - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - - _viewMatrix.copy( camera.matrixWorldInverse ); - _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); - - _frustum.setFromProjectionMatrix( _viewProjectionMatrix ); - - // - - _objectCount = 0; - - _renderData.objects.length = 0; - _renderData.lights.length = 0; - - projectObject( scene ); - - if ( sortObjects === true ) { - - _renderData.objects.sort( painterSort ); - - } - - // - - const objects = _renderData.objects; - - for ( let o = 0, ol = objects.length; o < ol; o ++ ) { - - const object = objects[ o ].object; - const geometry = object.geometry; - - renderList.setObject( object ); - - _modelMatrix = object.matrixWorld; - - _vertexCount = 0; - - if ( object.isMesh ) { - - let material = object.material; - - const isMultiMaterial = Array.isArray( material ); - - const attributes = geometry.attributes; - const groups = geometry.groups; - - if ( attributes.position === undefined ) continue; - - const positions = attributes.position.array; - - for ( let i = 0, l = positions.length; i < l; i += 3 ) { - - let x = positions[ i ]; - let y = positions[ i + 1 ]; - let z = positions[ i + 2 ]; - - const morphTargets = geometry.morphAttributes.position; - - if ( morphTargets !== undefined ) { - - const morphTargetsRelative = geometry.morphTargetsRelative; - const morphInfluences = object.morphTargetInfluences; - - for ( let t = 0, tl = morphTargets.length; t < tl; t ++ ) { - - const influence = morphInfluences[ t ]; - - if ( influence === 0 ) continue; - - const target = morphTargets[ t ]; - - if ( morphTargetsRelative ) { - - x += target.getX( i / 3 ) * influence; - y += target.getY( i / 3 ) * influence; - z += target.getZ( i / 3 ) * influence; - - } else { - - x += ( target.getX( i / 3 ) - positions[ i ] ) * influence; - y += ( target.getY( i / 3 ) - positions[ i + 1 ] ) * influence; - z += ( target.getZ( i / 3 ) - positions[ i + 2 ] ) * influence; - - } - - } - - } - - renderList.pushVertex( x, y, z ); - - } - - if ( attributes.normal !== undefined ) { - - const normals = attributes.normal.array; - - for ( let i = 0, l = normals.length; i < l; i += 3 ) { - - renderList.pushNormal( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ); - - } - - } - - if ( attributes.color !== undefined ) { - - const colors = attributes.color.array; - - for ( let i = 0, l = colors.length; i < l; i += 3 ) { - - renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); - - } - - } - - if ( attributes.uv !== undefined ) { - - const uvs = attributes.uv.array; - - for ( let i = 0, l = uvs.length; i < l; i += 2 ) { - - renderList.pushUv( uvs[ i ], uvs[ i + 1 ] ); - - } - - } - - if ( geometry.index !== null ) { - - const indices = geometry.index.array; - - if ( groups.length > 0 ) { - - for ( let g = 0; g < groups.length; g ++ ) { - - const group = groups[ g ]; - - material = isMultiMaterial === true - ? object.material[ group.materialIndex ] - : object.material; - - if ( material === undefined ) continue; - - for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { - - renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); - - } - - } - - } else { - - for ( let i = 0, l = indices.length; i < l; i += 3 ) { - - renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], material ); - - } - - } - - } else { - - if ( groups.length > 0 ) { - - for ( let g = 0; g < groups.length; g ++ ) { - - const group = groups[ g ]; - - material = isMultiMaterial === true - ? object.material[ group.materialIndex ] - : object.material; - - if ( material === undefined ) continue; - - for ( let i = group.start, l = group.start + group.count; i < l; i += 3 ) { - - renderList.pushTriangle( i, i + 1, i + 2, material ); - - } - - } - - } else { - - for ( let i = 0, l = positions.length / 3; i < l; i += 3 ) { - - renderList.pushTriangle( i, i + 1, i + 2, material ); - - } - - } - - } - - } else if ( object.isLine ) { - - _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); - - const attributes = geometry.attributes; - - if ( attributes.position !== undefined ) { - - const positions = attributes.position.array; - - for ( let i = 0, l = positions.length; i < l; i += 3 ) { - - renderList.pushVertex( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); - - } - - if ( attributes.color !== undefined ) { - - const colors = attributes.color.array; - - for ( let i = 0, l = colors.length; i < l; i += 3 ) { - - renderList.pushColor( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ); - - } - - } - - if ( geometry.index !== null ) { - - const indices = geometry.index.array; - - for ( let i = 0, l = indices.length; i < l; i += 2 ) { - - renderList.pushLine( indices[ i ], indices[ i + 1 ] ); - - } - - } else { - - const step = object.isLineSegments ? 2 : 1; - - for ( let i = 0, l = ( positions.length / 3 ) - 1; i < l; i += step ) { - - renderList.pushLine( i, i + 1 ); - - } - - } - - } - - } else if ( object.isPoints ) { - - _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); - - const attributes = geometry.attributes; - - if ( attributes.position !== undefined ) { - - const positions = attributes.position.array; - - for ( let i = 0, l = positions.length; i < l; i += 3 ) { - - _vector4.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ], 1 ); - _vector4.applyMatrix4( _modelViewProjectionMatrix ); - - pushPoint( _vector4, object, camera ); - - } - - } - - } else if ( object.isSprite ) { - - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - _vector4.set( _modelMatrix.elements[ 12 ], _modelMatrix.elements[ 13 ], _modelMatrix.elements[ 14 ], 1 ); - _vector4.applyMatrix4( _viewProjectionMatrix ); - - pushPoint( _vector4, object, camera ); - - } - - } - - if ( sortElements === true ) { - - _renderData.elements.sort( painterSort ); - - } - - return _renderData; - - }; - - function pushPoint( _vector4, object, camera ) { - - const invW = 1 / _vector4.w; - - _vector4.z *= invW; - - if ( _vector4.z >= -1 && _vector4.z <= 1 ) { - - _sprite = getNextSpriteInPool(); - _sprite.id = object.id; - _sprite.x = _vector4.x * invW; - _sprite.y = _vector4.y * invW; - _sprite.z = _vector4.z; - _sprite.renderOrder = object.renderOrder; - _sprite.object = object; - - _sprite.rotation = object.rotation; - - _sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[ 0 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 12 ] ) ); - _sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[ 5 ] ) / ( _vector4.w + camera.projectionMatrix.elements[ 13 ] ) ); - - _sprite.material = object.material; - - _renderData.elements.push( _sprite ); - - } - - } - - // Pools - - function getNextObjectInPool() { - - if ( _objectCount === _objectPoolLength ) { - - const object = new RenderableObject(); - _objectPool.push( object ); - _objectPoolLength ++; - _objectCount ++; - return object; - - } - - return _objectPool[ _objectCount ++ ]; - - } - - function getNextVertexInPool() { - - if ( _vertexCount === _vertexPoolLength ) { - - const vertex = new RenderableVertex(); - _vertexPool.push( vertex ); - _vertexPoolLength ++; - _vertexCount ++; - return vertex; - - } - - return _vertexPool[ _vertexCount ++ ]; - - } - - function getNextFaceInPool() { - - if ( _faceCount === _facePoolLength ) { - - const face = new RenderableFace(); - _facePool.push( face ); - _facePoolLength ++; - _faceCount ++; - return face; - - } - - return _facePool[ _faceCount ++ ]; - - - } - - function getNextLineInPool() { - - if ( _lineCount === _linePoolLength ) { - - const line = new RenderableLine(); - _linePool.push( line ); - _linePoolLength ++; - _lineCount ++; - return line; - - } - - return _linePool[ _lineCount ++ ]; - - } - - function getNextSpriteInPool() { - - if ( _spriteCount === _spritePoolLength ) { - - const sprite = new RenderableSprite(); - _spritePool.push( sprite ); - _spritePoolLength ++; - _spriteCount ++; - return sprite; - - } - - return _spritePool[ _spriteCount ++ ]; - - } - - // - - function painterSort( a, b ) { - - if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.z !== b.z ) { - - return b.z - a.z; - - } else if ( a.id !== b.id ) { - - return a.id - b.id; - - } else { - - return 0; - - } - - } - - // Sutherland-Hodgman triangle clipping in homogeneous clip space - // Returns count of vertices in clipped polygon (0 if completely clipped, 3+ if partially clipped) - // Result vertices are in _clipInput array - function clipTriangle( vertices ) { - - // Initialize input with the three input vertices - _clipInput[ 0 ] = vertices[ 0 ]; - _clipInput[ 1 ] = vertices[ 1 ]; - _clipInput[ 2 ] = vertices[ 2 ]; - - let inputCount = 3; - let outputCount = 0; - - for ( let p = 0; p < _clipPlanes.length; p ++ ) { - - const plane = _clipPlanes[ p ]; - outputCount = 0; - - if ( inputCount === 0 ) break; - - for ( let i = 0; i < inputCount; i ++ ) { - - const v1 = _clipInput[ i ]; - const v2 = _clipInput[ ( i + 1 ) % inputCount ]; - - const d1 = plane.sign * v1.z + v1.w; - const d2 = plane.sign * v2.z + v2.w; - - const v1Inside = d1 >= 0; - const v2Inside = d2 >= 0; - - if ( v1Inside && v2Inside ) { - - // Both inside - add v1 - _clipOutput[ outputCount ++ ] = v1; - - } else if ( v1Inside && ! v2Inside ) { - - // v1 inside, v2 outside - add v1 and intersection - _clipOutput[ outputCount ++ ] = v1; - - const t = d1 / ( d1 - d2 ); - let intersection = _clipVertexPool[ outputCount ]; - if ( ! intersection ) { - - intersection = new Vector4(); - _clipVertexPool[ outputCount ] = intersection; - - } - - intersection.lerpVectors( v1, v2, t ); - _clipOutput[ outputCount ++ ] = intersection; - - } else if ( ! v1Inside && v2Inside ) { - - // v1 outside, v2 inside - add intersection only - const t = d1 / ( d1 - d2 ); - let intersection = _clipVertexPool[ outputCount ]; - if ( ! intersection ) { - - intersection = new Vector4(); - _clipVertexPool[ outputCount ] = intersection; - - } - - intersection.lerpVectors( v1, v2, t ); - _clipOutput[ outputCount ++ ] = intersection; - - } - - // Both outside - add nothing - - } - - // Swap input/output - const temp = _clipInput; - _clipInput = _clipOutput; - _clipOutput = temp; - inputCount = outputCount; - - } - - return inputCount; - - } - - function clipLine( s1, s2 ) { - - let alpha1 = 0, alpha2 = 1; - - // Calculate the boundary coordinate of each vertex for the near and far clip planes, - // Z = -1 and Z = +1, respectively. - - const bc1near = s1.z + s1.w, - bc2near = s2.z + s2.w, - bc1far = - s1.z + s1.w, - bc2far = - s2.z + s2.w; - - if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) { - - // Both vertices lie entirely within all clip planes. - return true; - - } else if ( ( bc1near < 0 && bc2near < 0 ) || ( bc1far < 0 && bc2far < 0 ) ) { - - // Both vertices lie entirely outside one of the clip planes. - return false; - - } else { - - // The line segment spans at least one clip plane. - - if ( bc1near < 0 ) { - - // v1 lies outside the near plane, v2 inside - alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) ); - - } else if ( bc2near < 0 ) { - - // v2 lies outside the near plane, v1 inside - alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) ); - - } - - if ( bc1far < 0 ) { - - // v1 lies outside the far plane, v2 inside - alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) ); - - } else if ( bc2far < 0 ) { - - // v2 lies outside the far plane, v2 inside - alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) ); - - } - - if ( alpha2 < alpha1 ) { - - // The line segment spans two boundaries, but is outside both of them. - // (This can't happen when we're only clipping against just near/far but good - // to leave the check here for future usage if other clip planes are added.) - return false; - - } else { - - // Update the s1 and s2 vertices to match the clipped line segment. - s1.lerp( s2, alpha1 ); - s2.lerp( s1, 1 - alpha2 ); - - return true; - - } - - } - - } - - } - -} - -/** - * This renderer an be used to render geometric data using SVG. The produced vector - * graphics are particular useful in the following use cases: - * - * - Animated logos or icons. - * - Interactive 2D/3D diagrams or graphs. - * - Interactive maps. - * - Complex or animated user interfaces. - * - * `SVGRenderer` has various advantages. It produces crystal-clear and sharp output which - * is independent of the actual viewport resolution.SVG elements can be styled via CSS. - * And they have good accessibility since it's possible to add metadata like title or description - * (useful for search engines or screen readers). - * - * There are, however, some important limitations: - * - No advanced shading. - * - No texture support. - * - No shadow support. - * - * @three_import import { SVGRenderer } from 'three/addons/renderers/SVGRenderer.js'; - */ -class SVGRenderer { - - /** - * Constructs a new SVG renderer. - */ - constructor() { - - let _renderData, _elements, _lights, - _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf, - - _v1, _v2, _v3, - - _svgNode, - _pathCount = 0, - _svgObjectCount = 0, - _renderListCount = 0, - - _precision = null, - _quality = 1, - - _currentPath, _currentStyle; - - const _this = this, - _clipBox = new Box2(), - _elemBox = new Box2(), - - _color = new Color(), - _diffuseColor = new Color(), - _ambientLight = new Color(), - _directionalLights = new Color(), - _pointLights = new Color(), - _clearColor = new Color(), - - _vector3 = new Vector3(), // Needed for PointLight - _centroid = new Vector3(), - _normal = new Vector3(), - _normalViewMatrix = new Matrix3(), - - _viewMatrix = new Matrix4(), - _viewProjectionMatrix = new Matrix4(), - - _svgPathPool = [], - _svgObjectsPool = [], - _renderListPool = [], - - _projector = new Projector(), - _svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); - - /** - * The DOM where the renderer appends its child-elements. - * - * @type {SVGSVGElement} - */ - this.domElement = _svg; - - /** - * Whether to automatically perform a clear before a render call or not. - * - * @type {boolean} - * @default true - */ - this.autoClear = true; - - /** - * Whether to sort 3D objects or not. - * - * @type {boolean} - * @default true - */ - this.sortObjects = true; - - /** - * Whether to sort elements or not. - * - * @type {boolean} - * @default true - */ - this.sortElements = true; - - /** - * Number of fractional pixels to enlarge polygons in order to - * prevent anti-aliasing gaps. Range is `[0,1]`. - * - * @type {number} - * @default 0.5 - */ - this.overdraw = 0.5; - - /** - * The output color space. - * - * @type {(SRGBColorSpace|LinearSRGBColorSpace)} - * @default SRGBColorSpace - */ - this.outputColorSpace = SRGBColorSpace; - - /** - * Provides information about the number of - * rendered vertices and faces. - * - * @type {Object} - */ - this.info = { - - render: { - - vertices: 0, - faces: 0 - - } - - }; - - /** - * Sets the render quality. Setting to `high` makes the browser improve SVG quality - * over rendering speed and geometric precision. - * - * @param {('low'|'high')} quality - The quality. - */ - this.setQuality = function ( quality ) { - - switch ( quality ) { - - case 'high': _quality = 1; break; - case 'low': _quality = 0; break; - - } - - }; - - /** - * Sets the clear color. - * - * @param {(number|Color|string)} color - The clear color to set. - */ - this.setClearColor = function ( color ) { - - _clearColor.set( color ); - - }; - - this.setPixelRatio = function () {}; - - /** - * Resizes the renderer to the given width and height. - * - * @param {number} width - The width of the renderer. - * @param {number} height - The height of the renderer. - */ - this.setSize = function ( width, height ) { - - _svgWidth = width; _svgHeight = height; - _svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2; - - _svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight ); - _svg.setAttribute( 'width', _svgWidth ); - _svg.setAttribute( 'height', _svgHeight ); - - _clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf ); - _clipBox.max.set( _svgWidthHalf, _svgHeightHalf ); - - }; - - /** - * Returns an object containing the width and height of the renderer. - * - * @return {{width:number,height:number}} The size of the renderer. - */ - this.getSize = function () { - - return { - width: _svgWidth, - height: _svgHeight - }; - - }; - - /** - * Sets the precision of the data used to create a paths. - * - * @param {number} precision - The precision to set. - */ - this.setPrecision = function ( precision ) { - - _precision = precision; - - }; - - function removeChildNodes() { - - _pathCount = 0; - - while ( _svg.childNodes.length > 0 ) { - - _svg.removeChild( _svg.childNodes[ 0 ] ); - - } - - } - - function convert( c ) { - - return _precision !== null ? c.toFixed( _precision ) : c; - - } - - function renderSort( a, b ) { - - const aOrder = a.data.renderOrder !== undefined ? a.data.renderOrder : 0; - const bOrder = b.data.renderOrder !== undefined ? b.data.renderOrder : 0; - - if ( aOrder !== bOrder ) { - - return aOrder - bOrder; - - } else { - - const aZ = a.data.z !== undefined ? a.data.z : 0; - const bZ = b.data.z !== undefined ? b.data.z : 0; - - return bZ - aZ; // Painter's algorithm: far to near - - } - - } - - /** - * Performs a manual clear with the defined clear color. - */ - this.clear = function () { - - removeChildNodes(); - _svg.style.backgroundColor = _clearColor.getStyle( _this.outputColorSpace ); - - }; - - /** - * Renders the given scene using the given camera. - * - * @param {Object3D} scene - A scene or any other type of 3D object. - * @param {Camera} camera - The camera. - */ - this.render = function ( scene, camera ) { - - if ( camera instanceof Camera === false ) { - - console.error( 'THREE.SVGRenderer.render: camera is not an instance of Camera.' ); - return; - - } - - const background = scene.background; - - if ( background && background.isColor ) { - - removeChildNodes(); - _svg.style.backgroundColor = background.getStyle( _this.outputColorSpace ); - - } else if ( this.autoClear === true ) { - - this.clear(); - - } - - _this.info.render.vertices = 0; - _this.info.render.faces = 0; - - _viewMatrix.copy( camera.matrixWorldInverse ); - _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); - - _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements ); - _elements = _renderData.elements; - _lights = _renderData.lights; - - _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse ); - - calculateLights( _lights ); - - _renderListCount = 0; - - for ( let e = 0, el = _elements.length; e < el; e ++ ) { - - const element = _elements[ e ]; - const material = element.material; - - if ( material === undefined || material.opacity === 0 ) continue; - - getRenderItem( _renderListCount ++, 'element', element, material ); - - } - - _svgObjectCount = 0; - - scene.traverseVisible( function ( object ) { - - if ( object.isSVGObject ) { - - _vector3.setFromMatrixPosition( object.matrixWorld ); - _vector3.applyMatrix4( _viewProjectionMatrix ); - - if ( _vector3.z < -1 || _vector3.z > 1 ) return; - - const x = _vector3.x * _svgWidthHalf; - const y = - _vector3.y * _svgHeightHalf; - - const svgObject = getSVGObjectData( _svgObjectCount ++ ); - - svgObject.node = object.node; - svgObject.x = x; - svgObject.y = y; - svgObject.z = _vector3.z; - svgObject.renderOrder = object.renderOrder; - - getRenderItem( _renderListCount ++, 'svgObject', svgObject, null ); - - } - - } ); - - _renderListPool.length = _renderListCount; - - if ( this.sortElements && _svgObjectCount > 0 ) { - - // Elements are already sorted by the Projector. - // Only re-sort when SVGObjects need depth-interleaving. - _renderListPool.sort( renderSort ); - - } - - // Reset accumulated path - _currentPath = ''; - _currentStyle = ''; - - // Render in sorted order - for ( let i = 0; i < _renderListCount; i ++ ) { - - const item = _renderListPool[ i ]; - - if ( item.type === 'svgObject' ) { - - flushPath(); // Flush any accumulated paths before inserting SVG node - - const svgObject = item.data; - const node = svgObject.node; - node.setAttribute( 'transform', 'translate(' + svgObject.x + ',' + svgObject.y + ')' ); - _svg.appendChild( node ); - - } else { - - const element = item.data; - const material = item.material; - - _elemBox.makeEmpty(); - - if ( element instanceof RenderableSprite ) { - - _v1 = element; - _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf; - - renderSprite( _v1, element, material ); - - } else if ( element instanceof RenderableLine ) { - - _v1 = element.v1; _v2 = element.v2; - - _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; - _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; - - _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] ); - - if ( _clipBox.intersectsBox( _elemBox ) === true ) { - - renderLine( _v1, _v2, material ); - - } - - } else if ( element instanceof RenderableFace ) { - - _v1 = element.v1; _v2 = element.v2; _v3 = element.v3; - - _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf; - _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf; - _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf; - - if ( this.overdraw > 0 ) { - - expand( _v1.positionScreen, _v2.positionScreen, this.overdraw ); - expand( _v2.positionScreen, _v3.positionScreen, this.overdraw ); - expand( _v3.positionScreen, _v1.positionScreen, this.overdraw ); - - } - - _elemBox.setFromPoints( [ - _v1.positionScreen, - _v2.positionScreen, - _v3.positionScreen - ] ); - - if ( _clipBox.intersectsBox( _elemBox ) === true ) { - - renderFace3( _v1, _v2, _v3, element, material ); - - } - - } - - } - - } - - flushPath(); // Flush any remaining paths - - }; - - function calculateLights( lights ) { - - _ambientLight.setRGB( 0, 0, 0 ); - _directionalLights.setRGB( 0, 0, 0 ); - _pointLights.setRGB( 0, 0, 0 ); - - for ( let l = 0, ll = lights.length; l < ll; l ++ ) { - - const light = lights[ l ]; - const lightColor = light.color; - - if ( light.isAmbientLight ) { - - _ambientLight.r += lightColor.r; - _ambientLight.g += lightColor.g; - _ambientLight.b += lightColor.b; - - } else if ( light.isDirectionalLight ) { - - _directionalLights.r += lightColor.r; - _directionalLights.g += lightColor.g; - _directionalLights.b += lightColor.b; - - } else if ( light.isPointLight ) { - - _pointLights.r += lightColor.r; - _pointLights.g += lightColor.g; - _pointLights.b += lightColor.b; - - } - - } - - } - - function calculateLight( lights, position, normal, color ) { - - for ( let l = 0, ll = lights.length; l < ll; l ++ ) { - - const light = lights[ l ]; - const lightColor = light.color; - - if ( light.isDirectionalLight ) { - - const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize(); - - let amount = normal.dot( lightPosition ); - - if ( amount <= 0 ) continue; - - amount *= light.intensity; - - color.r += lightColor.r * amount; - color.g += lightColor.g * amount; - color.b += lightColor.b * amount; - - } else if ( light.isPointLight ) { - - const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ); - - let amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() ); - - if ( amount <= 0 ) continue; - - amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 ); - - if ( amount == 0 ) continue; - - amount *= light.intensity; - - color.r += lightColor.r * amount; - color.g += lightColor.g * amount; - color.b += lightColor.b * amount; - - } - - } - - } - - function renderSprite( v1, element, material ) { - - let scaleX = element.scale.x * _svgWidthHalf; - let scaleY = element.scale.y * _svgHeightHalf; - - if ( material.isPointsMaterial ) { - - scaleX *= material.size; - scaleY *= material.size; - - } - - const path = 'M' + convert( v1.x - scaleX * 0.5 ) + ',' + convert( v1.y - scaleY * 0.5 ) + 'h' + convert( scaleX ) + 'v' + convert( scaleY ) + 'h' + convert( - scaleX ) + 'z'; - let style = ''; - - if ( material.isSpriteMaterial || material.isPointsMaterial ) { - - style = 'fill:' + material.color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; - - } - - addPath( style, path ); - - } - - function renderLine( v1, v2, material ) { - - const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ); - - if ( material.isLineBasicMaterial ) { - - let style = 'fill:none;stroke:' + material.color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap; - - if ( material.isLineDashedMaterial ) { - - style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize; - - } - - addPath( style, path ); - - } - - } - - function renderFace3( v1, v2, v3, element, material ) { - - _this.info.render.vertices += 3; - _this.info.render.faces ++; - - const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ) + 'L' + convert( v3.positionScreen.x ) + ',' + convert( v3.positionScreen.y ) + 'z'; - let style = ''; - - if ( material.isMeshBasicMaterial ) { - - _color.copy( material.color ); - - if ( material.vertexColors ) { - - _color.multiply( element.color ); - - } - - } else if ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) { - - _diffuseColor.copy( material.color ); - - if ( material.vertexColors ) { - - _diffuseColor.multiply( element.color ); - - } - - _color.copy( _ambientLight ); - - _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 ); - - calculateLight( _lights, _centroid, element.normalModel, _color ); - - _color.multiply( _diffuseColor ).add( material.emissive ); - - } else if ( material.isMeshNormalMaterial ) { - - _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ).normalize(); - - _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); - - } - - if ( material.wireframe ) { - - style = 'fill:none;stroke:' + _color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin; - - } else { - - style = 'fill:' + _color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; - - } - - addPath( style, path ); - - } - - // Hide anti-alias gaps - - function expand( v1, v2, pixels ) { - - let x = v2.x - v1.x, y = v2.y - v1.y; - const det = x * x + y * y; - - if ( det === 0 ) return; - - const idet = pixels / Math.sqrt( det ); - - x *= idet; y *= idet; - - v2.x += x; v2.y += y; - v1.x -= x; v1.y -= y; - - } - - function addPath( style, path ) { - - if ( _currentStyle === style ) { - - _currentPath += path; - - } else { - - flushPath(); - - _currentStyle = style; - _currentPath = path; - - } - - } - - function flushPath() { - - if ( _currentPath ) { - - _svgNode = getPathNode( _pathCount ++ ); - _svgNode.setAttribute( 'd', _currentPath ); - _svgNode.setAttribute( 'style', _currentStyle ); - _svg.appendChild( _svgNode ); - - } - - _currentPath = ''; - _currentStyle = ''; - - } - - function getPathNode( id ) { - - let path = _svgPathPool[ id ]; - - if ( path === undefined ) { - - path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' ); - - if ( _quality == 0 ) { - - path.setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed - - } - - _svgPathPool[ id ] = path; - - } - - return path; - - } - - function getSVGObjectData( id ) { - - let svgObject = _svgObjectsPool[ id ]; - - if ( svgObject === undefined ) { - - svgObject = { - node: null, - x: 0, - y: 0, - z: 0, - renderOrder: 0 - }; - - _svgObjectsPool[ id ] = svgObject; - - } - - return svgObject; - - } - - function getRenderItem( id, type, data, material ) { - - let item = _renderListPool[ id ]; - - if ( item === undefined ) { - - item = { - type: type, - data: data, - material: material - }; - - _renderListPool[ id ] = item; - - return item; - - } - - item.type = type; - item.data = data; - item.material = material; - - return item; - - } - - } - -} - -const originalTHREE = { - REVISION, DoubleSide, FrontSide, Object3D, Color, Vector2, Vector3, Matrix4, Line3, Raycaster, - WebGLRenderer, WebGLRenderTarget, - BufferGeometry, BufferAttribute, Float32BufferAttribute, Mesh, MeshBasicMaterial, MeshLambertMaterial, - LineSegments, LineDashedMaterial, LineBasicMaterial, Points, PointsMaterial, - Plane, Scene, PerspectiveCamera, OrthographicCamera, ShapeUtils, - Box3, InstancedMesh, MeshStandardMaterial, MeshNormalMaterial, - MeshPhysicalMaterial, MeshPhongMaterial, MeshDepthMaterial, MeshMatcapMaterial, MeshToonMaterial, - Group: Group$1, PlaneHelper, Euler, Quaternion, BoxGeometry, CircleGeometry, SphereGeometry, Fog, - AmbientLight, HemisphereLight, DirectionalLight, - CanvasTexture, TextureLoader, - - Font, OrbitControls, SVGRenderer, TextGeometry, EffectComposer, RenderPass, UnrealBloomPass -}, THREE = Object.assign({}, originalTHREE); - - -/** @summary Import proper three.js version - * @desc in node.js only r162 supports WebGL1 which can be emulated with "gl" package. - * Therefore only this version can be used for working in node.js - * @private */ -async function importThreeJs(original) { - - if (!isNodeJs() || (THREE.REVISION <= 162)) - return THREE; - return import('three').then(h1 => { - Object.assign(THREE, h1); - return import('three/addons'); - }).then(h2 => { - Object.assign(THREE, h2); - return THREE; - }); -} - -let _hfont; - -/** @summary Create three.js Helvetica Regular Font instance - * @private */ -function getHelveticaFont() { - if (_hfont && _hfont instanceof THREE.Font) - return _hfont; - - // eslint-disable-next-line - const glyphs={"0":{x_min:73,x_max:715,ha:792,o:"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"1":{x_min:215.671875,x_max:574,ha:792,o:"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"2":{x_min:59,x_max:731,ha:792,o:"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"3":{x_min:54,x_max:737,ha:792,o:"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"4":{x_min:48,x_max:742.453125,ha:792,o:"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"5":{x_min:54.171875,x_max:738,ha:792,o:"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"6":{x_min:53,x_max:739,ha:792,o:"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"7":{x_min:58.71875,x_max:730.953125,ha:792,o:"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"8":{x_min:55,x_max:736,ha:792,o:"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"9":{x_min:53,x_max:739,ha:792,o:"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"ο":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},S:{x_min:0,x_max:788,ha:890,o:"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{x_min:183.25,x_max:608.328125,ha:792,o:"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{x_min:-0.4375,x_max:777.453125,ha:839,o:"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},y:{x_min:0,x_max:684.78125,ha:771,o:"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{x_min:-111,x_max:339,ha:361,o:"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},g:{x_min:0,x_max:686,ha:838,o:"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{x_min:0,x_max:442,ha:539,o:"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{x_min:0,x_max:819.5625,ha:893,o:"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{x_min:-46.265625,x_max:392,ha:513,o:"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},e:{x_min:0,x_max:714,ha:813,o:"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},J:{x_min:0,x_max:588,ha:699,o:"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{x_min:-1,x_max:503,ha:601,o:"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{x_min:-3,x_max:1008,ha:1106,o:"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{x_min:0,x_max:922,ha:1030,o:"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{x_min:193.0625,x_max:598.609375,ha:792,o:"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{x_min:0,x_max:507.203125,ha:604,o:"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},D:{x_min:0,x_max:828,ha:935,o:"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{x_min:0,x_max:47,ha:125,o:"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},w:{x_min:0,x_max:1009.71875,ha:1100,o:"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},$:{x_min:0,x_max:700,ha:793,o:"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{x_min:-0.015625,x_max:425.0625,ha:522,o:"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{x_min:0,x_max:697.21875,ha:747,o:"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{x_min:42,x_max:181,ha:297,o:"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{x_min:0,x_max:1144.5,ha:1214,o:"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{x_min:0,x_max:801,ha:915,o:"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{x_min:8.71875,x_max:350.390625,ha:478,o:"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},Q:{x_min:0,x_max:968,ha:1072,o:"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{x_min:1,x_max:676.28125,ha:740,o:"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},M:{x_min:0,x_max:954,ha:1067,o:"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{x_min:0,x_max:1006,ha:1094,o:"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},C:{x_min:0,x_max:886,ha:944,o:"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{x_min:0,x_max:138,ha:236,o:"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{x_min:0,x_max:480.5625,ha:578,o:"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},X:{x_min:-0.015625,x_max:854.15625,ha:940,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{x_min:0,x_max:963.890625,ha:1061,o:"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{x_min:42,x_max:284,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{x_min:0,x_max:906.953125,ha:982,o:"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{x_min:0,x_max:318,ha:415,o:"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{x_min:0,x_max:952.78125,ha:1028,o:"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{x_min:0,x_max:481,ha:578,o:"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{x_min:-3,x_max:1672,ha:1821,o:"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},a:{x_min:0,x_max:698.609375,ha:794,o:"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{x_min:0,x_max:941.671875,ha:1039,o:"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{x_min:8.71875,x_max:780.953125,ha:792,o:"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},N:{x_min:0,x_max:801,ha:914,o:"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{x_min:0,x_max:712,ha:797,o:"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"¯":{x_min:0,x_max:941.671875,ha:938,o:"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},Z:{x_min:0,x_max:779,ha:849,o:"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},u:{x_min:0,x_max:617,ha:729,o:"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},k:{x_min:0,x_max:612.484375,ha:697,o:"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{x_min:0,x_max:906.953125,ha:985,o:"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},s:{x_min:0,x_max:604,ha:697,o:"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},B:{x_min:0,x_max:778,ha:876,o:"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{x_min:0,x_max:614,ha:708,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{x_min:0,x_max:607,ha:704,o:"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},H:{x_min:0,x_max:803,ha:915,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{x_min:0,x_max:675,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},c:{x_min:1,x_max:701.390625,ha:775,o:"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{x_min:0,x_max:566.671875,ha:678,o:"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{x_min:0,x_max:660,ha:745,o:"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{x_min:0,x_max:954,ha:1068,o:"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{x_min:0.109375,x_max:1120,ha:1217,o:"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{x_min:0,x_max:1158,ha:1275,o:"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{x_min:0,x_max:663.890625,ha:775,o:"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{x_min:0.1875,x_max:819.546875,ha:886,o:"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{x_min:0,x_max:318.0625,ha:415,o:"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},U:{x_min:0,x_max:796,ha:904,o:"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{x_min:0.5,x_max:744.953125,ha:822,o:"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},F:{x_min:0,x_max:683.328125,ha:717,o:"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{x_min:0,x_max:854.171875,ha:935,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{x_min:116,x_max:674,ha:792,o:"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{x_min:0,x_max:777,ha:835,o:"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{x_min:0,x_max:347,ha:444,o:"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},V:{x_min:0,x_max:862.71875,ha:940,o:"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{x_min:0,x_max:734.71875,ha:763,o:"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{x_min:0,x_max:0,ha:853},"Ϋ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"”":{x_min:0,x_max:347,ha:454,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{x_min:0,x_max:1260,ha:1357,o:"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{x_min:0,x_max:499,ha:613,o:"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},i:{x_min:14,x_max:136,ha:275,o:"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{x_min:0,x_max:778,ha:877,o:"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{x_min:0,x_max:275,ha:372,o:"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},m:{x_min:0,x_max:1019,ha:1128,o:"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{x_min:8.328125,x_max:780.5625,ha:815,o:"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"ί":{x_min:42,x_max:326.71875,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{x_min:0,x_max:779.171875,ha:850,o:"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},R:{x_min:0,x_max:781.953125,ha:907,o:"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},o:{x_min:0,x_max:713,ha:821,o:"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},K:{x_min:0,x_max:819.46875,ha:906,o:"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{x_min:0,x_max:142,ha:239,o:"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},d:{x_min:0,x_max:683,ha:796,o:"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{x_min:-109,x_max:247,ha:232,o:"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},E:{x_min:0,x_max:736.109375,ha:789,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},Y:{x_min:0,x_max:820,ha:886,o:"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{x_min:0,x_max:299,ha:396,o:"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{x_min:0,x_max:364,ha:467,o:"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{x_min:1,x_max:710,ha:810,o:"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{x_min:0,x_max:922,ha:1031,o:"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{x_min:0,x_max:96,ha:251,o:"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{x_min:11,x_max:781,ha:792,o:"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{x_min:0,x_max:617,ha:725,o:"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{x_min:0,x_max:593,ha:690,o:"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},b:{x_min:0,x_max:685,ha:783,o:"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},q:{x_min:0,x_max:683,ha:876,o:"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{x_min:-0.171875,x_max:969.5625,ha:1068,o:"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},z:{x_min:-0.015625,x_max:613.890625,ha:697,o:"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{x_min:0,x_max:894,ha:1000,o:"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{x_min:0,x_max:960,ha:1056,o:"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{x_min:-3,x_max:1008,ha:1106,o:"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{x_min:0,x_max:833,ha:931,o:"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{x_min:0,x_max:736.21875,ha:778,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{x_min:0,x_max:450,ha:547,o:"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{x_min:0,x_max:273.609375,ha:371,o:"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},L:{x_min:0,x_max:645.828125,ha:696,o:"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{x_min:0,x_max:803.390625,ha:894,o:"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{x_min:0,x_max:573,ha:642,o:"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{x_min:0,x_max:674,ha:778,o:"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{x_min:0,x_max:958,ha:1054,o:"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{x_min:0,x_max:705.28125,ha:749,o:"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{x_min:0,x_max:0,ha:375},"%":{x_min:-3,x_max:1089,ha:1186,o:"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},P:{x_min:0,x_max:726,ha:806,o:"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{x_min:0,x_max:1078.21875,ha:1118,o:"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{x_min:0.125,x_max:1136.546875,ha:1235,o:"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},_:{x_min:0,x_max:705.5625,ha:803,o:"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{x_min:-110,x_max:246,ha:275,o:"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{x_min:23,x_max:768,ha:792,o:"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{x_min:0,x_max:1050,ha:1149,o:"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{x_min:0,x_max:720,ha:783,o:"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{x_min:0,x_max:350,ha:397,o:"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{x_min:0,x_max:450,ha:553,o:"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},T:{x_min:0,x_max:777,ha:835,o:"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{x_min:0,x_max:915,ha:997,o:"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{x_min:0,x_max:0,ha:694},j:{x_min:-77.78125,x_max:167,ha:349,o:"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{x_min:0,x_max:756.953125,ha:819,o:"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"›":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{x_min:0,x_max:704.484375,ha:801,o:"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},t:{x_min:0,x_max:367,ha:458,o:"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{x_min:0,x_max:706,ha:803,o:"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{x_min:0,x_max:750,ha:803,o:"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},W:{x_min:0,x_max:1263.890625,ha:1351,o:"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},v:{x_min:0,x_max:675.15625,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{x_min:0.28125,x_max:644.5,ha:703,o:"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{x_min:0,x_max:624.9375,ha:699,o:"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{x_min:-3,x_max:894.25,ha:992,o:"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{x_min:0,x_max:862.5,ha:942,o:"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},I:{x_min:41,x_max:180,ha:293,o:"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},G:{x_min:0,x_max:921,ha:1011,o:"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{x_min:0,x_max:617,ha:725,o:"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{x_min:0,x_max:138.890625,ha:236,o:"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},r:{x_min:0,x_max:355.5625,ha:432,o:"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},x:{x_min:0,x_max:675,ha:764,o:"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{x_min:0,x_max:696.609375,ha:747,o:"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},h:{x_min:0,x_max:615,ha:724,o:"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{x_min:0,x_max:142,ha:239,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{x_min:-2,x_max:878,ha:974,o:"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},f:{x_min:0,x_max:378,ha:472,o:"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{x_min:1,x_max:348.21875,ha:454,o:"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},A:{x_min:0.03125,x_max:906.953125,ha:1008,o:"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"‘":{x_min:1,x_max:139.890625,ha:236,o:"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{x_min:-70,x_max:283,ha:361,o:"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{x_min:-0.21875,x_max:773.21875,ha:857,o:"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},O:{x_min:0,x_max:958,ha:1057,o:"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},n:{x_min:0,x_max:615,ha:724,o:"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},l:{x_min:41,x_max:166,ha:279,o:"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{x_min:40.09375,x_max:728.796875,ha:825,o:"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{x_min:0,x_max:632.328125,ha:679,o:"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},p:{x_min:0,x_max:685,ha:786,o:"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{x_min:0,x_max:777,ha:835,o:"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{x_min:0,x_max:808,ha:907,o:"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}}; - // eslint-disable-next-line - const cssFontWeight='normal', ascender=1189, underlinePosition=-100, cssFontStyle='normal', boundingBox={yMin:-334,xMin:-111,yMax:1189,xMax:1672}, resolution = 1000, original_font_information={postscript_name:"Helvetiker-Regular",version_string:"Version 1.00 2004 initial release",vendor_url:"http://www.magenta.gr/",full_font_name:"Helvetiker",font_family_name:"Helvetiker",copyright:"Copyright (c) Μagenta ltd, 2004",description:"",trademark:"",designer:"",designer_url:"",unique_font_identifier:"Μagenta ltd:Helvetiker:22-10-104",license_url:"http://www.ellak.gr/fonts/MgOpen/license.html",license_description:"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.",manufacturer_name:"Μagenta ltd",font_sub_family_name:"Regular"}, descender = -334, familyName = 'Helvetiker', lineHeight = 1522, underlineThickness = 50, helvetiker_regular_typeface = {glyphs, cssFontWeight, ascender, underlinePosition, cssFontStyle, boundingBox, resolution,original_font_information, descender, familyName, lineHeight, underlineThickness}; - - // segment in clockwise - holes, otherwise - solid - // provide infinity symbol - glyphs['\u221E'] = { x_min: 100, x_max: 700, ha: 800, o: 'm 150 200 l 350 300 l 150 400 l 150 200 m 450 300 l 650 200 l 650 400 l 450 300 m 100 100 l 100 500 l 400 350 l 700 500 l 700 100 l 400 250 l 100 100 ' }; - - _hfont = new THREE.Font({ - ascender, boundingBox, cssFontStyle, cssFontWeight, default: helvetiker_regular_typeface, descender, familyName, glyphs, - lineHeight, original_font_information, resolution, underlinePosition, underlineThickness - }); - - return _hfont; -} - -/** @summary Create three.js Color instance, handles optional opacity - * @private */ -function getMaterialArgs(color$1, args) { - if (!args || !isObject(args)) - args = {}; - - if (isStr(color$1) && (((color$1[0] === '#') && (color$1.length === 9)) || (color$1.indexOf('rgba') >= 0))) { - const col = color(color$1); - args.color = new THREE.Color(col.r / 255, col.g / 255, col.b / 255); - args.opacity = col.opacity ?? 1; - args.transparent = args.opacity < 1; - } else - args.color = new THREE.Color(color$1); - return args; -} - -function createSVGRenderer(as_is, precision, doc) { - - const excl_style1 = ';stroke-opacity:1;stroke-width:1;stroke-linecap:round', - excl_style2 = ';fill-opacity:1'; - /* eslint-disable-next-line one-var */ - const doc_wrapper = { - svg_attr: {}, - svg_style: {}, - path_attr: {}, - accPath: '', - createElementNS(ns, kind) { - if (kind === 'path') { - return { - _wrapper: this, - setAttribute(name, value) { - // cut useless fill-opacity:1 at the end of many SVG attributes - if ((name === 'style') && value) { - const pos1 = value.indexOf(excl_style1); - if ((pos1 >= 0) && (pos1 === value.length - excl_style1.length)) - value = value.slice(0, value.length - excl_style1.length); - const pos2 = value.indexOf(excl_style2); - if ((pos2 >= 0) && (pos2 === value.length - excl_style2.length)) - value = value.slice(0, value.length - excl_style2.length); - } - this._wrapper.path_attr[name] = value; - } - }; - } - - if (kind !== 'svg') { - console.error(`not supported element for SVGRenderer ${kind}`); - return null; - } - - return { - _wrapper: this, - childNodes: [], // may be accessed - make dummy - style: this.svg_style, // for background color - setAttribute(name, value) { - this._wrapper.svg_attr[name] = value; - }, - appendChild(/* node */) { - this._wrapper.accPath += ``; - this._wrapper.path_attr = {}; - }, - removeChild(/* node */) { - this.childNodes = []; - } - }; - } - }; - - let originalDocument; - - if (isNodeJs()) { - originalDocument = globalThis.document; - globalThis.document = doc_wrapper; - } - - const rndr = new THREE.SVGRenderer(); - - if (isNodeJs()) - globalThis.document = originalDocument; - - rndr.doc_wrapper = doc_wrapper; // use it to get final SVG code - - rndr.originalRender = rndr.render; - - rndr.render = function(scene, camera) { - const _doc = globalThis.document; - if (isNodeJs()) - globalThis.document = this.doc_wrapper; - - this.originalRender(scene, camera); - - if (isNodeJs()) - globalThis.document = _doc; - }; - - rndr.clearHTML = function() { - this.doc_wrapper.accPath = ''; - }; - - rndr.makeOuterHTML = function() { - const wrap = this.doc_wrapper, - _textSizeAttr = `viewBox="${wrap.svg_attr.viewBox}" width="${wrap.svg_attr.width}" height="${wrap.svg_attr.height}"`, - _textClearAttr = wrap.svg_style.backgroundColor ? ` style="background:${wrap.svg_style.backgroundColor}"` : ''; - - return `${wrap.accPath}`; - }; - - rndr.fillTargetSVG = function(svg) { - if (isNodeJs()) { - const wrap = this.doc_wrapper; - - svg.setAttribute('viewBox', wrap.svg_attr.viewBox); - svg.setAttribute('width', wrap.svg_attr.width); - svg.setAttribute('height', wrap.svg_attr.height); - svg.style.background = wrap.svg_style.backgroundColor || ''; - - svg.innerHTML = wrap.accPath; - } else { - const src = this.domElement; - - svg.setAttribute('viewBox', src.getAttribute('viewBox')); - svg.setAttribute('width', src.getAttribute('width')); - svg.setAttribute('height', src.getAttribute('height')); - svg.style.background = src.style.backgroundColor; - - while (src.firstChild) { - const elem = src.firstChild; - src.removeChild(elem); - svg.appendChild(elem); - } - } - }; - - rndr.setPrecision(precision); - - return rndr; -} - - -/** @summary Define rendering kind which will be used for rendering of 3D elements - * @param {value} [render3d] - pre-configured value, will be used if applicable - * @param {value} [is_batch] - is batch mode is configured - * @return {value} - rendering kind, see constants.Render3D - * @private */ -function getRender3DKind(render3d, is_batch) { - if (is_batch === undefined) - is_batch = isBatchMode(); - - if (!render3d) - render3d = is_batch ? settings.Render3DBatch : settings.Render3D; - const rc = constants$1.Render3D; - - if (render3d === rc.Default) - render3d = is_batch ? rc.WebGLImage : rc.WebGL; - if (is_batch && (render3d === rc.WebGL)) - render3d = rc.WebGLImage; - - return render3d; -} - -const Handling3DDrawings = { - - /** @summary Access current 3d mode - * @param {string} [new_value] - when specified, set new 3d mode - * @return current value - * @private */ - access3dKind(new_value) { - const svg = this.getPadPainter()?.getPadSvg(); - if (!svg || svg.empty()) - return -1; - - // returns kind of currently created 3d canvas - const kind = svg.property('can3d'); - if (new_value !== undefined) - svg.property('can3d', new_value); - return ((kind === null) || (kind === undefined)) ? -1 : kind; - }, - - /** @summary Returns size which available for 3D drawing. - * @desc One uses frame sizes for the 3D drawing - like TH2/TH3 objects - * @private */ - getSizeFor3d(can3d /* , render3d */) { - if (can3d === undefined) { - // analyze which render/embed mode can be used - can3d = getRender3DKind(); - // all non-webgl elements can be embedded into SVG as is - if (can3d !== constants$1.Render3D.WebGL) - can3d = constants$1.Embed3D.EmbedSVG; - else if (settings.Embed3D !== constants$1.Embed3D.Default) - can3d = settings.Embed3D; - else if (browser.isFirefox) - can3d = constants$1.Embed3D.Embed; - else if (browser.chromeVersion > 95) - // version 96 works partially, 97 works fine - can3d = constants$1.Embed3D.Embed; - else - can3d = constants$1.Embed3D.Overlay; - } - - const pp = this.getPadPainter(), - pad = pp?.getPadSvg(), - clname = 'draw3d_' + (pp?.getPadName() || 'canvas'); - - if (!pad || pad.empty()) { - // this is a case when object drawn without canvas - - const rect = getElementRect(this.selectDom()); - if ((rect.height < 10) && (rect.width > 10)) { - rect.height = Math.round(0.66 * rect.width); - this.selectDom().style('height', rect.height + 'px'); - } - rect.x = rect.y = 0; - rect.clname = clname; - rect.can3d = -1; - return rect; - } - - const fp = this.getFramePainter(); - let size; - - if (fp?.mode3d && (can3d > 0)) - size = fp.getFrameRect(); - else { - let elem = (can3d > 0) ? pad : this.getCanvSvg(); - size = { x: 0, y: 0, width: elem.property('draw_width'), height: elem.property('draw_height') }; - if (Number.isNaN(size.width) || Number.isNaN(size.height)) { - size.width = pp.getPadWidth(); - size.height = pp.getPadHeight(); - } else if (fp && !fp.mode3d) { - elem = pp.getFrameSvg(); - size.x = elem.property('draw_x'); - size.y = elem.property('draw_y'); - } - } - - size.clname = clname; - size.can3d = can3d; - - const rect = pp?.getPadRect(); - if (rect) { - // while 3D canvas uses area also for the axis labels, extend area relative to normal frame - const dx = Math.round(size.width * 0.07), dy = Math.round(size.height * 0.05); - - size.x = Math.max(0, size.x - dx); - size.y = Math.max(0, size.y - dy); - size.width = Math.min(size.width + 2 * dx, rect.width - size.x); - size.height = Math.min(size.height + 2 * dy, rect.height - size.y); - } - - if (can3d === constants$1.Embed3D.Overlay) { - size = getAbsPosInCanvas(pad, size); - const scale = this.getCanvPainter().getPadScale(); - if (scale && scale !== 1) { - size.x /= scale; - size.y /= scale; - size.width /= scale; - size.height /= scale; - } - } - - return size; - }, - - /** @summary Clear all 3D drawings - * @return can3d value - how webgl canvas was placed - * @private */ - clear3dCanvas() { - const can3d = this.access3dKind(null); - if (can3d < 0) { - // remove first child from main element - if it is canvas - const main = this.selectDom().node(); - let chld = main?.firstChild; - - while (chld && !chld.$jsroot) - chld = chld.nextSibling; - - if (chld?.$jsroot === '3d') { - delete chld.painter; - main.removeChild(chld); - } - return can3d; - } - - const size = this.getSizeFor3d(can3d); - if (size.can3d === 0) { - select(this.getCanvSvg().node().nextSibling).remove(); // remove html5 canvas - this.getCanvSvg().style('display', null); // show SVG canvas - } else { - const pp = this.getPadPainter(); - if (!pp || pp.getPadSvg().empty()) - return; - - this.apply3dSize(size).remove(); - - pp.getFrameSvg().style('display', null); // clear display property - } - return can3d; - }, - - /** @summary Add 3D canvas - * @private */ - add3dCanvas(size, canv, webgl) { - if (!canv || (size.can3d < -1)) - return; - - if (size.can3d === -1) { - // case when 3D object drawn without canvas - - const main = this.selectDom().node(); - if (main) { - main.appendChild(canv); - canv.painter = this; - canv.$jsroot = '3d'; // mark canvas as added by jsroot - } - - return; - } - - if ((size.can3d > 0) && !webgl) - size.can3d = constants$1.Embed3D.EmbedSVG; - - this.access3dKind(size.can3d); - - if (size.can3d === 0) { - this.getCanvSvg().style('display', 'none'); // hide SVG canvas - - this.getCanvSvg().node().parentNode.appendChild(canv); // add directly - } else { - const pp = this.getPadPainter(); - if (!pp || pp.getPadSvg().empty()) - return; - - // first hide normal frame - pp.getFrameSvg().style('display', 'none'); - - const elem = this.apply3dSize(size); - elem.attr('title', '').node().appendChild(canv); - } - }, - - /** @summary Apply size to 3D elements - * @private */ - apply3dSize(size, onlyget) { - if (size.can3d < 0) - return select(null); - - let elem; - - if (size.can3d > 1) { - const pp = this.getPadPainter(); - - elem = pp.getLayerSvg(size.clname); - if (onlyget) - return elem; - - const svg = pp.getPadSvg(); - - if (size.can3d === constants$1.Embed3D.EmbedSVG) { - // this is SVG mode or image mode - just create group to hold element - - if (elem.empty()) - elem = svg.insert('g', '.primitives_layer').attr('class', size.clname); - - makeTranslate(elem, size.x, size.y); - } else { - if (elem.empty()) - elem = svg.insert('foreignObject', '.primitives_layer').attr('class', size.clname); - - elem.attr('x', size.x) - .attr('y', size.y) - .attr('width', size.width) - .attr('height', size.height) - .attr('viewBox', `0 0 ${size.width} ${size.height}`) - .attr('preserveAspectRatio', 'xMidYMid'); - } - } else { - let prnt = this.getCanvSvg().node().parentNode; - - elem = select(prnt).select('.' + size.clname); - if (onlyget) - return elem; - - // force redraw by resize - this.getCanvSvg().property('redraw_by_resize', true); - - if (elem.empty()) { - elem = select(prnt).append('div').attr('class', size.clname) - .style('user-select', 'none'); - } - - // our position inside canvas, but to set 'absolute' position we should use - // canvas element offset relative to first parent with non-static position - // now try to use getBoundingClientRect - it should be more precise - - const pos0 = prnt.getBoundingClientRect(), doc = getDocument(); - - while (prnt) { - if (prnt === doc) { - prnt = null; - break; - } - try { - if (getComputedStyle(prnt).position !== 'static') - break; - } catch { - break; - } - prnt = prnt.parentNode; - } - - const pos1 = prnt?.getBoundingClientRect() ?? { top: 0, left: 0 }, - offx = Math.round(pos0.left - pos1.left), - offy = Math.round(pos0.top - pos1.top); - - elem.style('position', 'absolute').style('left', (size.x + offx) + 'px').style('top', (size.y + offy) + 'px').style('width', size.width + 'px').style('height', size.height + 'px'); - } - - return elem; - } - -}; // Handling3DDrawings - - -/** @summary Assigns method to handle 3D drawings inside SVG - * @private */ -function assign3DHandler(painter) { - Object.assign(painter, Handling3DDrawings); -} - - -/** @summary Creates renderer for the 3D drawings - * @param {value} width - rendering width - * @param {value} height - rendering height - * @param {value} render3d - render type, see {@link constants.Render3D} - * @param {object} args - different arguments for creating 3D renderer - * @return {Promise} with renderer object - * @private */ -async function createRender3D(width, height, render3d, args) { - const rc = constants$1.Render3D, doc = getDocument(); - - render3d = getRender3DKind(render3d); - - if (!args) - args = { antialias: true, alpha: true }; - - let promise; - - if (render3d === rc.None) - promise = Promise.resolve(null); - else if (render3d === rc.SVG) { - // SVG rendering - const r = createSVGRenderer(false, 0); - r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); - promise = Promise.resolve(r); - } else if (isNodeJs()) { - // try to use WebGL inside node.js - need to create headless context - promise = import('canvas').then(node_canvas => { - args.canvas = node_canvas.default.createCanvas(width, height); - args.canvas.addEventListener = () => {}; // dummy - args.canvas.removeEventListener = () => {}; // dummy - args.canvas.style = {}; - return internals._node_gl || import('gl'); - }).then(node_gl => { - internals._node_gl = node_gl; - const gl = node_gl?.default(width, height, { preserveDrawingBuffer: true }); - if (!gl) - throw Error('Fail to create headless-gl'); - args.context = gl; - gl.canvas = args.canvas; - - const r = new THREE.WebGLRenderer(args); - r.jsroot_output = new THREE.WebGLRenderTarget(width, height); - r.setRenderTarget(r.jsroot_output); - r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); - return r; - }).catch(() => { - console.log('gl module is not available - fallback to SVGRenderer'); - render3d = rc.SVG; - const r = createSVGRenderer(false, 0); - r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); - return r; - }); - } else if (render3d === rc.WebGL) { - // interactive WebGL Rendering - promise = Promise.resolve(new THREE.WebGLRenderer(args)); - } else { - // rendering with WebGL directly into svg image - const r = new THREE.WebGLRenderer(args); - r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); - promise = Promise.resolve(r); - } - - return promise.then(renderer => { - if (!renderer) - return renderer; - - if (!renderer.jsroot_dom) - renderer.jsroot_dom = renderer.domElement; - else - renderer.jsroot_custom_dom = true; - - // res.renderer.setClearColor('#000000', 1); - // res.renderer.setClearColor(0x0, 0); - renderer.jsroot_render3d = render3d; - - // which format used to convert into images - renderer.jsroot_image_format = 'png'; - - renderer.originalSetSize = renderer.setSize; - - // apply size to dom element - renderer.setSize = function(w, h, updateStyle) { - if (this.jsroot_custom_dom) { - this.jsroot_dom.setAttribute('width', w); - this.jsroot_dom.setAttribute('height', h); - } - - this.originalSetSize(w, h, updateStyle); - }; - - renderer.setSize(width, height); - - return renderer; - }); -} - - -/** @summary Cleanup created renderer object - * @private */ -function cleanupRender3D(renderer) { - if (!renderer) - return; - - if (isNodeJs()) { - const ctxt = isFunc(renderer.getContext) ? renderer.getContext() : null, - ext = ctxt?.getExtension('STACKGL_destroy_context'); - if (isFunc(ext?.destroy)) - ext.destroy(); - } else { - // suppress warnings in Chrome about lost webgl context, not required in firefox - if (browser.isChrome && isFunc(renderer.forceContextLoss)) - renderer.forceContextLoss(); - - if (isFunc(renderer.dispose)) - renderer.dispose(); - } -} - -/** @summary Cleanup previous renderings before doing next one - * @desc used together with SVG - * @private */ -function beforeRender3D(renderer) { - if (isFunc(renderer.clearHTML)) - renderer.clearHTML(); -} - -/** @summary Post-process result of rendering - * @desc used together with SVG or node.js image rendering - * @private */ -function afterRender3D(renderer) { - const rc = constants$1.Render3D; - - if (renderer.jsroot_render3d === rc.WebGL) - return; - - if (renderer.jsroot_render3d === rc.SVG) { - // case of SVGRenderer - renderer.fillTargetSVG(renderer.jsroot_dom); - } else if (isNodeJs()) { - // this is WebGL rendering in node.js - const canvas = renderer.domElement, - context = canvas.getContext('2d'), - pixels = new Uint8Array(4 * canvas.width * canvas.height); - - renderer.readRenderTargetPixels(renderer.jsroot_output, 0, 0, canvas.width, canvas.height, pixels); - - // small code to flip Y scale - let indx1 = 0, indx2 = (canvas.height - 1) * 4 * canvas.width, k, d; - while (indx1 < indx2) { - for (k = 0; k < 4 * canvas.width; ++k) { - d = pixels[indx1 + k]; - pixels[indx1 + k] = pixels[indx2 + k]; - pixels[indx2 + k] = d; - } - indx1 += 4 * canvas.width; - indx2 -= 4 * canvas.width; - } - - const imageData = context.createImageData(canvas.width, canvas.height); - imageData.data.set(pixels); - context.putImageData(imageData, 0, 0); - - const format = 'image/' + renderer.jsroot_image_format, - dataUrl = canvas.toDataURL(format); - - renderer.jsroot_dom.setAttribute('href', dataUrl); - } else { - const dataUrl = renderer.domElement.toDataURL('image/' + renderer.jsroot_image_format); - renderer.jsroot_dom.setAttribute('href', dataUrl); - } -} - -// ======================================================================================================== - -/** - * @summary Tooltip handler for 3D drawings - * - * @private - */ - -class TooltipFor3D { - - /** @summary constructor - * @param {object} prnt - parent tooltip object - * @param {object} canvas - canvas for 3D rendering */ - constructor(prnt, canvas) { - this.tt = null; - this.cont = null; - this.lastlbl = ''; - this.parent = prnt || getDocument().body; - this.canvas = canvas; // we need canvas to recalculate mouse events - this.abspos = !prnt; - this.scale = 1; - } - - /** @summary check parent */ - checkParent(prnt) { - if (prnt && (this.parent !== prnt)) { - this.hide(); - this.parent = prnt; - } - } - - /** @summary set scaling factor */ - setScale(v) { - this.scale = v; - } - - /** @summary extract position from event - * @desc can be used to process it later when event is gone */ - extract_pos(e) { - if (isObject(e) && (e.u !== undefined) && (e.l !== undefined)) - return e; - const res = { u: 0, l: 0 }; - if (this.abspos) { - res.l = e.pageX; - res.u = e.pageY; - } else { - res.l = e.offsetX; - res.u = e.offsetY; - } - res.l /= this.scale; - res.u /= this.scale; - return res; - } - - /** @summary Method used to define position of next tooltip - * @desc event is delivered from canvas, - * but position should be calculated relative to the element where tooltip is placed */ - pos(e) { - if (!this.tt) - return; - - const pos = this.extract_pos(e); - if (!this.abspos) { - const rect1 = this.parent.getBoundingClientRect(), - rect2 = this.canvas.getBoundingClientRect(); - - if ((rect1.left !== undefined) && (rect2.left !== undefined)) - pos.l += (rect2.left - rect1.left); - - if ((rect1.top !== undefined) && (rect2.top !== undefined)) - pos.u += rect2.top - rect1.top; - - if (pos.l + this.tt.offsetWidth + 3 >= this.parent.offsetWidth) - pos.l = this.parent.offsetWidth - this.tt.offsetWidth - 3; - - if (pos.u + this.tt.offsetHeight + 15 >= this.parent.offsetHeight) - pos.u = this.parent.offsetHeight - this.tt.offsetHeight - 15; - - // one should find parent with non-static position, - // all absolute coordinates calculated relative to such node - let abs_parent = this.parent; - while (abs_parent) { - const style = getComputedStyle(abs_parent); - if (!style || (style.position !== 'static')) - break; - if (!abs_parent.parentNode || (abs_parent.parentNode.nodeType !== 1)) - break; - abs_parent = abs_parent.parentNode; - } - - if (abs_parent && (abs_parent !== this.parent)) { - const rect0 = abs_parent.getBoundingClientRect(); - pos.l += (rect1.left - rect0.left); - pos.u += (rect1.top - rect0.top); - } - } - - this.tt.style.top = `${pos.u + 15}px`; - this.tt.style.left = `${pos.l + 3}px`; - } - - /** @summary Show tooltip */ - show(v /* , mouse_pos, status_func */) { - let lines; - if (v && isObject(v) && (v.lines || v.line)) { - if (!v.only_status) - lines = v.line ? [v.line] : v.lines; - } else if (isStr(v)) - lines = [v]; - - const doc = this.parent.ownerDocument; - - if (!lines || !doc) - return this.hide(); - - if (!this.tt) { - this.tt = doc.createElement('div'); - this.tt.setAttribute('style', 'opacity: 1; filter: alpha(opacity=1); position: absolute; display: block; width: auto; overflow: hidden; z-index: 101;'); - this.cont = doc.createElement('div'); - this.cont.setAttribute('style', 'display: block; padding: 5px; margin-left: 5px; font-size: 11px; line-height: 18px; background: #777; color: #fff;'); - this.tt.appendChild(this.cont); - this.parent.appendChild(this.tt); - } - - this.cont.innerText = ''; - lines.forEach(lbl => { - const p = doc.createElement('p'); - p.innerText = lbl; - p.setAttribute('style', 'padding: 0px; margin: 1px;'); - this.cont.appendChild(p); - }); - } - - /** @summary Hide tooltip */ - hide() { - if (this.tt) - this.parent.removeChild(this.tt); - - this.tt = this.cont = null; - } - -} // class TooltipFor3D - -/** @summary Create OrbitControls for painter - * @private */ -function createOrbitControl(painter, camera, scene, renderer, lookat) { - const enable_zoom = settings.Zooming && settings.ZoomMouse, - enable_wheel = settings.Zooming && settings.ZoomWheel, - enable_keys = settings.HandleKeys && !isNodeJs() && (typeof window !== 'undefined'), - enable_select = isFunc(painter.processMouseClick); - - let control = null; - - function control_mousedown(evnt) { - if (!control) - return; - - // function used to hide some events from orbit control and redirect them to zooming rect - if (control.mouse_zoom_mesh) { - evnt.stopImmediatePropagation(); - evnt.stopPropagation(); - return; - } - - // only left-button is considered - if ((evnt.button !== undefined) && (evnt.button !== 0)) - return; - if ((evnt.buttons !== undefined) && (evnt.buttons !== 1)) - return; - - if (control.enable_zoom) { - control.mouse_zoom_mesh = control.detectZoomMesh(evnt); - if (control.mouse_zoom_mesh) { - // just block orbit control - evnt.stopImmediatePropagation(); - evnt.stopPropagation(); - return; - } - } - - if (control.enable_select) - control.mouse_select_pnt = control.getMousePos(evnt, {}); - } - - function control_mouseup(evnt) { - if (!control) - return; - - if (control.mouse_zoom_mesh && control.mouse_zoom_mesh.point2 && control.painter.get3dZoomCoord) { - let kind = control.mouse_zoom_mesh.object.zoom, - pos1 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point, kind), - pos2 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point2, kind); - - if (pos1 > pos2) - [pos1, pos2] = [pos2, pos1]; - - if ((kind === 'z') && control.mouse_zoom_mesh.object.use_y_for_z) - kind = 'y'; - - // try to zoom - if ((pos1 < pos2) && control.painter.zoom(kind, pos1, pos2)) - control.mouse_zoom_mesh = null; - } - - // if selection was drawn, it should be removed and picture rendered again - if (control.enable_zoom) - control.removeZoomMesh(); - - if (control.enable_select && control.mouse_select_pnt) { - const pnt = control.getMousePos(evnt, {}), - same_pnt = (pnt.x === control.mouse_select_pnt.x) && (pnt.y === control.mouse_select_pnt.y); - delete control.mouse_select_pnt; - - if (same_pnt) { - const intersects = control.getMouseIntersects(pnt); - control.painter.processMouseClick(pnt, intersects, evnt); - } - } - } - - function render3DFired(_painter) { - if (_painter?.renderer === undefined) - return false; - // when timeout configured, object is prepared for rendering - return _painter.render_tmout !== undefined; - } - - function control_mousewheel(evnt) { - if (!control) - return; - - // try to handle zoom extra - if (render3DFired(control.painter) || control.mouse_zoom_mesh) { - evnt.preventDefault(); - evnt.stopPropagation(); - evnt.stopImmediatePropagation(); - return; // already fired redraw, do not react on the mouse wheel - } - - const intersect = control.detectZoomMesh(evnt); - if (!intersect) - return; - - evnt.preventDefault(); - evnt.stopPropagation(); - evnt.stopImmediatePropagation(); - - if (isFunc(control.painter?.analyzeMouseWheelEvent)) { - let kind = intersect.object.zoom, - position = intersect.point[kind]; - const item = { name: kind, ignore: false }; - - // z changes from 0..2*size_z3d, others -size_x3d..+size_x3d - switch (kind) { - case 'x': - position = (position + control.painter.size_x3d) / 2 / control.painter.size_x3d; - break; - case 'y': - position = (position + control.painter.size_y3d) / 2 / control.painter.size_y3d; - break; - case 'z': - position = position / 2 / control.painter.size_z3d; - break; - } - - control.painter.analyzeMouseWheelEvent(evnt, item, position, false); - - if ((kind === 'z') && intersect.object.use_y_for_z) - kind = 'y'; - - control.painter.zoom(kind, item.min, item.max); - } - } - - // assign own handler before creating OrbitControl - - if (enable_wheel) - renderer.domElement.addEventListener('wheel', control_mousewheel); - - if (enable_zoom || enable_select) { - renderer.domElement.addEventListener('pointerdown', control_mousedown); - renderer.domElement.addEventListener('pointerup', control_mouseup); - } - - control = new THREE.OrbitControls(camera, renderer.domElement); - - control.enableDamping = false; - control.dampingFactor = 1.0; - control.enableZoom = true; - if (enable_keys) - control.listenToKeyEvents(window); - - if (lookat) { - control.target.copy(lookat); - control.target0.copy(lookat); - control.update(); - } - - control.tooltip = new TooltipFor3D(painter.selectDom().node(), renderer.domElement); - - control.painter = painter; - control.camera = camera; - control.scene = scene; - control.renderer = renderer; - control.raycaster = new THREE.Raycaster(); - control.raycaster.params.Line.threshold = 10; - control.raycaster.params.Points.threshold = 5; - control.mouse_zoom_mesh = null; // zoom mesh, currently used in the zooming - control.block_ctxt = false; // require to block context menu command appearing after control ends, required in chrome which inject contextmenu when key released - control.block_mousemove = false; // when true, tooltip or cursor will not react on mouse move - control.cursor_changed = false; - control.control_changed = false; - control.control_active = false; - control.mouse_ctxt = { x: 0, y: 0, on: false }; - control.enable_zoom = enable_zoom; - control.enable_select = enable_select; - control.enable_wheel = enable_wheel; - control.enable_keys = enable_keys; - - control.cleanup = function() { - if (this.enable_wheel) - this.domElement.removeEventListener('wheel', control_mousewheel); - if (this.enable_zoom || this.enable_select) { - this.domElement.removeEventListener('pointerdown', control_mousedown); - this.domElement.removeEventListener('pointerup', control_mouseup); - } - if (this.enable_keys) - this.stopListenToKeyEvents(); - - this.domElement.removeEventListener('click', this.lstn_click); - this.domElement.removeEventListener('dblclick', this.lstn_dblclick); - this.domElement.removeEventListener('contextmenu', this.lstn_contextmenu); - this.domElement.removeEventListener('mousemove', this.lstn_mousemove); - this.domElement.removeEventListener('mouseleave', this.lstn_mouseleave); - - this.dispose(); // this is from OrbitControl itself - - this.tooltip.hide(); - delete this.tooltip; - delete this.painter; - delete this.camera; - delete this.scene; - delete this.renderer; - delete this.raycaster; - delete this.mouse_zoom_mesh; - }; - - control.hideTooltip = function() { - this.tooltip.hide(); - }; - - control.getMousePos = function(evnt, mouse) { - mouse.x = ('offsetX' in evnt) ? evnt.offsetX : evnt.layerX; - mouse.y = ('offsetY' in evnt) ? evnt.offsetY : evnt.layerY; - mouse.clientX = evnt.clientX; - mouse.clientY = evnt.clientY; - return mouse; - }; - - control.getOriginDirectionIntersects = function(origin, direction) { - this.raycaster.set(origin, direction); - let intersects = this.raycaster.intersectObjects(this.scene.children, true); - // painter may want to filter intersects - if (isFunc(this.painter.filterIntersects)) - intersects = this.painter.filterIntersects(intersects); - return intersects; - }; - - control.getMouseIntersects = function(mouse) { - // domElement gives correct coordinate with canvas render, but isn't always right for webgl renderer - if (!this.renderer) - return []; - - const sz = (this.renderer instanceof THREE.SVGRenderer) ? this.renderer.domElement : this.renderer.getSize(new THREE.Vector2()), - pnt = { x: mouse.x / sz.width * 2 - 1, y: -mouse.y / sz.height * 2 + 1 }; - - this.camera.updateMatrix(); - this.camera.updateMatrixWorld(); - this.raycaster.setFromCamera(pnt, this.camera); - let intersects = this.raycaster.intersectObjects(this.scene.children, true); - - // painter may want to filter intersects - if (isFunc(this.painter.filterIntersects)) - intersects = this.painter.filterIntersects(intersects); - - return intersects; - }; - - control.detectZoomMesh = function(evnt) { - const mouse = this.getMousePos(evnt, {}), - intersects = this.getMouseIntersects(mouse); - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) - return intersects[n]; - } - } - - return null; - }; - - control.getInfoAtMousePosition = function(mouse_pos) { - const intersects = this.getMouseIntersects(mouse_pos); - let tip = null, p = null; - - for (let i = 0; i < intersects.length; ++i) { - const obj3d = intersects[i].object; - if (isFunc(obj3d?.tooltip)) { - tip = obj3d.tooltip(intersects[i]); - p = obj3d.tip_painter || obj3d.painter || tip?.$painter; - break; - } - } - - if (tip && p) { - return { - obj: p.getObject(), - name: p.getObject().fName, - bin: tip.bin, cont: tip.value, - binx: tip.ix, biny: tip.iy, binz: tip.iz, - grx: (tip.x1 + tip.x2) / 2, gry: (tip.y1 + tip.y2) / 2, grz: (tip.z1 + tip.z2) / 2 - }; - } - }; - - control.processDblClick = function(evnt) { - // first check if zoom mesh clicked - const zoom_intersect = this.detectZoomMesh(evnt); - if (zoom_intersect && this.painter) { - this.painter.unzoom(zoom_intersect.object.use_y_for_z ? 'y' : zoom_intersect.object.zoom); - return; - } - - // then check if double-click handler assigned - const handler = this.painter?.getFramePainter()?.getDblclickHandler(); - - if (isFunc(handler)) { - const info = this.getInfoAtMousePosition(this.getMousePos(evnt, {})); - if (info) { - handler(info); - return; - } - } - - this.reset(); - }; - - control.changeEvent = function() { - this.mouse_ctxt.on = false; // disable context menu if any changes where done by orbit control - this.painter.render3D(0); - this.control_changed = true; - }; - - control.startEvent = function() { - this.control_active = true; - this.block_ctxt = false; - this.mouse_ctxt.on = false; - - this.tooltip.hide(); - - // do not reset here, problem of events sequence in orbitcontrol - // it issue change/start/stop event when do zooming - // control.control_changed = false; - }; - - control.endEvent = function() { - this.control_active = false; - if (this.mouse_ctxt.on) { - this.mouse_ctxt.on = false; - this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); - } /* else if (this.control_changed) { - // react on camera change when required - } */ - this.control_changed = false; - }; - - control.mainProcessContextMenu = function(evnt) { - evnt.preventDefault(); - this.getMousePos(evnt, this.mouse_ctxt); - if (this.control_active) - this.mouse_ctxt.on = true; - else if (this.block_ctxt) - this.block_ctxt = false; - else - this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); - }; - - control.contextMenu = function(/* pos, intersects */) { - // do nothing, function called when context menu want to be activated - }; - - control.setTooltipEnabled = function(on) { - this.block_mousemove = !on; - if (on === false) { - this.tooltip.hide(); - this.removeZoomMesh(); - } - }; - - control.removeZoomMesh = function() { - if (this.mouse_zoom_mesh?.object.showSelection()) - this.painter.render3D(); - this.mouse_zoom_mesh = null; // in any case clear mesh, enable orbit control again - }; - - control.mainProcessMouseMove = function(evnt) { - if (!this.painter) - return; // protect when cleanup - - if (this.control_active && evnt.buttons && (evnt.buttons & 2)) - this.block_ctxt = true; // if right button in control was active, block next context menu - - if (this.control_active || this.block_mousemove || !isFunc(this.processMouseMove)) - return; - - if (this.mouse_zoom_mesh) { - // when working with zoom mesh, need special handling - - const zoom2 = this.detectZoomMesh(evnt), - pnt2 = (zoom2?.object === this.mouse_zoom_mesh.object) ? zoom2.point : this.mouse_zoom_mesh.object.globalIntersect(this.raycaster); - - if (pnt2) - this.mouse_zoom_mesh.point2 = pnt2; - - if (pnt2 && this.painter.enable_highlight) { - if (this.mouse_zoom_mesh.object.showSelection(this.mouse_zoom_mesh.point, pnt2)) - this.painter.render3D(0); - } - - this.tooltip.hide(); - return; - } - - evnt.preventDefault(); - - // extract mouse position - this.tmout_mouse = this.getMousePos(evnt, {}); - this.tmout_ttpos = this.tooltip?.extract_pos(evnt); - - if (this.tmout_handle) { - clearTimeout(this.tmout_handle); - delete this.tmout_handle; - } - - if (!this.mouse_tmout) - this.delayedProcessMouseMove(); - else - this.tmout_handle = setTimeout(() => this.delayedProcessMouseMove(), this.mouse_tmout); - }; - - control.delayedProcessMouseMove = function() { - // remove handle - allow to trigger new timeout - delete this.tmout_handle; - if (!this.painter) - return; // protect when cleanup - - const mouse = this.tmout_mouse, - intersects = this.getMouseIntersects(mouse), - tip = this.processMouseMove(intersects); - - if (tip) { - let name = '', title = '', info = ''; - const coord = mouse ? mouse.x.toFixed(0) + ',' + mouse.y.toFixed(0) : ''; - if (isStr(tip)) - info = tip; - else { - name = tip.name; - title = tip.title; - if (tip.line) - info = tip.line; - else if (tip.lines) { - info = tip.lines.slice(1).join(' '); - name = tip.lines[0]; - } - } - this.painter.showObjectStatus(name, title, info, coord); - } - - this.cursor_changed = false; - if (tip && this.painter?.isTooltipAllowed()) { - this.tooltip.checkParent(this.painter.selectDom().node()); - this.tooltip.show(tip, mouse); - this.tooltip.pos(this.tmout_ttpos); - } else { - this.tooltip.hide(); - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) - this.cursor_changed = true; - } - } - } - - getDocument().body.style.cursor = this.cursor_changed ? 'pointer' : 'auto'; - }; - - control.mainProcessMouseLeave = function() { - if (!this.painter) - return; // protect when cleanup - - // do not enter main event at all - if (this.tmout_handle) { - clearTimeout(this.tmout_handle); - delete this.tmout_handle; - } - this.tooltip.hide(); - if (isFunc(this.processMouseLeave)) - this.processMouseLeave(); - if (this.cursor_changed) { - getDocument().body.style.cursor = 'auto'; - this.cursor_changed = false; - } - }; - - control.mainProcessDblClick = function(evnt) { - // suppress simple click handler if double click detected - if (this.single_click_tm) { - clearTimeout(this.single_click_tm); - delete this.single_click_tm; - } - this.processDblClick(evnt); - }; - - control.processClick = function(mouse_pos, kind) { - delete this.single_click_tm; - - if (kind === 1) { - const handler = this.painter?.getFramePainter()?.getClickHandler(); - if (isFunc(handler)) { - const info = this.getInfoAtMousePosition(mouse_pos); - if (info) { - handler(info); - return; - } - } - } - - // method assigned in the Eve7 and used for object selection - if ((kind === 2) && isFunc(this.processSingleClick)) { - const intersects = this.getMouseIntersects(mouse_pos); - this.processSingleClick(intersects); - } - - if (kind === 3) { - const intersects = this.getMouseIntersects(mouse_pos); - let objpainter = null; - for (let i = 0; !objpainter && (i < intersects.length); ++i) { - const obj3d = intersects[i].object; - objpainter = obj3d.painter || obj3d.parent?.painter; // check one top level - } - if (objpainter) { - // while axis painter not directly appears in the list of primitives, pad and canvas take from frame - const padp = this.painter?.getPadPainter(), - canvp = this.painter?.getCanvPainter(); - canvp?.producePadEvent('select', padp, objpainter); - } - } - }; - - control.lstn_click = function(evnt) { - // ignore right-mouse click - if (evnt.detail === 2) - return; - - if (this.single_click_tm) { - clearTimeout(this.single_click_tm); - delete this.single_click_tm; - } - - let kind = 0; - if (this.painter?.getFramePainter()?.getClickHandler()) - kind = 1; // user click handler - else if (this.processSingleClick && this.painter?.options?.mouse_click) - kind = 2; // eve7 click handler - else if (this.painter?.getCanvPainter()) - kind = 3; // select event for GED - - // if normal event, set longer timeout waiting if double click not detected - if (kind) - this.single_click_tm = setTimeout(this.processClick.bind(this, this.getMousePos(evnt, {}), kind), 300); - }.bind(control); - - control.addEventListener('change', () => control.changeEvent()); - control.addEventListener('start', () => control.startEvent()); - control.addEventListener('end', () => control.endEvent()); - - control.lstn_contextmenu = evnt => control.mainProcessContextMenu(evnt); - control.lstn_dblclick = evnt => control.mainProcessDblClick(evnt); - control.lstn_mousemove = evnt => control.mainProcessMouseMove(evnt); - control.lstn_mouseleave = () => control.mainProcessMouseLeave(); - - renderer.domElement.addEventListener('click', control.lstn_click); - renderer.domElement.addEventListener('dblclick', control.lstn_dblclick); - renderer.domElement.addEventListener('contextmenu', control.lstn_contextmenu); - renderer.domElement.addEventListener('mousemove', control.lstn_mousemove); - renderer.domElement.addEventListener('mouseleave', control.lstn_mouseleave); - - return control; -} - -/** @summary Method cleanup three.js object as much as possible. - * @desc Simplify JS engine to remove it from memory - * @private */ -function disposeThreejsObject(obj, only_childs) { - if (!obj) - return; - - if (obj.children) { - for (let i = 0; i < obj.children.length; i++) - disposeThreejsObject(obj.children[i]); - } - - if (only_childs) { - obj.children = []; - return; - } - - obj.children = undefined; - - if (obj.geometry) { - obj.geometry.dispose(); - obj.geometry = undefined; - } - if (obj.material) { - if (obj.material.map) { - obj.material.map.dispose(); - obj.material.map = undefined; - } - obj.material.dispose(); - obj.material = undefined; - } - - // cleanup jsroot fields to simplify browser cleanup job - delete obj.painter; - delete obj.bins_index; - delete obj.tooltip; - delete obj.stack; // used in geom painter - delete obj.drawn_highlight; // special highlight object - // used in lego tooltips - delete obj.face_to_bins_index; - delete obj.tip_painter; - delete obj.handle; -} - - -/** @summary Create LineSegments mesh (or only geometry) - * @desc If required, calculates lineDistance attribute for dashed geometries - * @private */ -function createLineSegments(arr, material, index = undefined, only_geometry = false) { - const geom = new THREE.BufferGeometry(); - - geom.setAttribute('position', arr instanceof Float32Array ? new THREE.BufferAttribute(arr, 3) : new THREE.Float32BufferAttribute(arr, 3)); - if (index) - geom.setIndex(new THREE.BufferAttribute(index, 1)); - - if (material.isLineDashedMaterial) { - const v1 = new THREE.Vector3(), - v2 = new THREE.Vector3(); - let d = 0, distances; - - if (index) { - distances = new Float32Array(index.length); - for (let n = 0; n < index.length; n += 2) { - const i1 = index[n], i2 = index[n + 1]; - v1.set(arr[i1], arr[i1 + 1], arr[i1 + 2]); - v2.set(arr[i2], arr[i2 + 1], arr[i2 + 2]); - distances[n] = d; - d += v2.distanceTo(v1); - distances[n + 1] = d; - } - } else { - distances = new Float32Array(arr.length / 3); - for (let n = 0; n < arr.length; n += 6) { - v1.set(arr[n], arr[n + 1], arr[n + 2]); - v2.set(arr[n + 3], arr[n + 4], arr[n + 5]); - distances[n / 3] = d; - d += v2.distanceTo(v1); - distances[n / 3 + 1] = d; - } - } - geom.setAttribute('lineDistance', new THREE.BufferAttribute(distances, 1)); - } - - return only_geometry ? geom : new THREE.LineSegments(geom, material); -} - -/** @summary Help structures for calculating Box mesh - * @private */ -const Box3D = { - Vertices: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 0), - new THREE.Vector3(1, 0, 1), new THREE.Vector3(1, 0, 0), - new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 1, 1), - new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1)], - Indexes: [0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 4, 5, 1, 5, 0, 1, - 7, 6, 2, 6, 3, 2, 5, 7, 0, 7, 2, 0, 1, 3, 4, 3, 6, 4], - Normals: [1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1], - Segments: [0, 2, 2, 7, 7, 5, 5, 0, 1, 3, 3, 6, 6, 4, 4, 1, 1, 0, 3, 2, 6, 7, 4, 5], // segments addresses Vertices - Crosses: [0, 7, 2, 5, 0, 3, 1, 2, 7, 3, 2, 6, 5, 6, 4, 7, 5, 1, 0, 4, 3, 4, 1, 6], // addresses Vertices - MeshSegments: undefined -}; - -// these segments address vertices from the mesh, we can use positions from box mesh -Box3D.MeshSegments = (function() { - const arr = new Int32Array(Box3D.Segments.length); - - for (let n = 0; n < arr.length; ++n) { - for (let k = 0; k < Box3D.Indexes.length; ++k) { - if (Box3D.Segments[n] === Box3D.Indexes[k]) { - arr[n] = k; - break; - } - } - } - return arr; -})(); - - -/** - * @summary Abstract interactive control interface for 3D objects - * - * @abstract - * @private - */ - -class InteractiveControl { - - cleanup() {} - extractIndex(/* intersect */) {} - setSelected(/* col, indx */) {} - setHighlight(/* col, indx */) {} - checkHighlightIndex(/* indx */) {} - -} // class InteractiveControl - - -/** - * @summary Class for creation of 3D points - * - * @private - */ - -class PointsCreator { - - /** @summary constructor - * @param {number} number - number of points - * @param {boolean} [iswebgl] - if WebGL is used - * @param {number} [scale] - scale factor */ - constructor(number, iswebgl = true, scale = 1) { - this.webgl = iswebgl; - this.scale = scale || 1; - - this.pos = new Float32Array(number * 3); - this.geom = new THREE.BufferGeometry(); - this.geom.setAttribute('position', new THREE.BufferAttribute(this.pos, 3)); - this.indx = 0; - } - - /** @summary Add point */ - addPoint(x, y, z) { - this.pos[this.indx] = x; - this.pos[this.indx + 1] = y; - this.pos[this.indx + 2] = z; - this.indx += 3; - } - - /** @summary Create points */ - createPoints(args) { - if (!isObject(args)) - args = { color: args }; - if (!args.color) - args.color = 'black'; - - let k = 1; - - // special dots - if (!args.style) - k = 1.1; - else if (args.style === 1) - k = 0.3; - else if (args.style === 6) - k = 0.5; - else if (args.style === 7) - k = 0.7; - - const makePoints = texture => { - const material_args = { size: 3 * this.scale * k }; - if (texture) { - material_args.map = texture; - material_args.transparent = true; - } else - material_args.color = args.color || 'black'; - - const pnts = new THREE.Points(this.geom, new THREE.PointsMaterial(material_args)); - pnts.nvertex = 1; - return pnts; - }; - - // this is plain creation of points, no need for texture loading - - if (k !== 1) { - const res = makePoints(); - return this.noPromise ? res : Promise.resolve(res); - } - - const handler = new TAttMarkerHandler({ style: args.style, color: args.color, size: 7 }), - w = handler.fill ? 1 : 7, - imgdata = `` + - `` + - '', - dataUrl = prSVG + (isNodeJs() ? imgdata : encodeURIComponent(imgdata)); - let promise; - - if (isNodeJs()) { - promise = import('canvas').then(handle => handle.default.loadImage(dataUrl).then(img => { - const canvas = handle.default.createCanvas(64, 64), - ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0, 64, 64); - return new THREE.CanvasTexture(canvas); - })); - } else if (this.noPromise) { - // only for v6 support - return makePoints(new THREE.TextureLoader().load(dataUrl)); - } else { - promise = new Promise((resolveFunc, rejectFunc) => { - const loader = new THREE.TextureLoader(); - loader.load(dataUrl, res => resolveFunc(res), undefined, () => rejectFunc(Error(`Fail to load ${dataUrl}`))); - }); - } - - return promise.then(makePoints); - } - -} // class PointsCreator - - -/** @summary Create material for 3D line - * @desc Takes into account dashed properties - * @private */ -function create3DLineMaterial(painter, arg, is_v7 = false) { - if (!painter || !arg) - return null; - - let color, lstyle, lwidth; - if (isStr(arg) || is_v7) { - color = painter.v7EvalColor(arg + 'color', 'black'); - lstyle = parseInt(painter.v7EvalAttr(arg + 'style', 0)); - lwidth = parseInt(painter.v7EvalAttr(arg + 'width', 1)); - } else { - color = painter.getColor(arg.fLineColor); - lstyle = arg.fLineStyle; - lwidth = arg.fLineWidth; - } - - const style = lstyle ? getSvgLineStyle(lstyle) : '', - dash = style ? style.split(',') : [], - material = (dash && dash.length >= 2) - ? new THREE.LineDashedMaterial({ color, dashSize: parseInt(dash[0]), gapSize: parseInt(dash[1]) }) - : new THREE.LineBasicMaterial({ color }); - - if (lwidth && (lwidth > 1)) - material.linewidth = lwidth; - - return material; -} - -/** @summary Create plain text geometry - * @private */ -function createTextGeometry(lbl, size) { - const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }; - if (THREE.REVISION > 162) - geom_args.depth = 0; - else - geom_args.height = 0; - - return new THREE.TextGeometry(lbl, geom_args); -} - -class TextParseWrapper { - - constructor(kind, parent, font_size) { - this.kind = kind ?? 'g'; - this.childs = []; - this.x = 0; - this.y = 0; - this.font_size = parent?.font_size ?? font_size; - this.stroke_width = parent?.stroke_width ?? 5; - parent?.childs.push(this); - } - - append(kind) { - if (kind === 'svg:g') - return new TextParseWrapper('g', this); - if (kind === 'svg:text') - return new TextParseWrapper('text', this); - if (kind === 'svg:path') - return new TextParseWrapper('path', this); - console.warn('missing handle for svg', kind); - } - - style(name, value) { - if ((name === 'stroke-width') && value) - this.stroke_width = Number.parseInt(value); - return this; - } - - property(name, value) { - if (value === undefined) - return this[name]; - this[name] = value; - return this; - } - - attr(name, value) { - const get = () => { - if (!value) - return ''; - const res = value[0]; - value = value.slice(1); - return res; - }, getN = skip => { - let p = 0; - while (((value[p] >= '0') && (value[p] <= '9')) || (value[p] === '-')) - p++; - const res = Number.parseInt(value.slice(0, p)); - value = value.slice(p); - if (skip) - get(); - return res; - }; - - if ((name === 'font-size') && value) - this.font_size = Number.parseInt(value); - else if ((name === 'transform') && isStr(value) && (value.indexOf('translate') === 0)) { - const arr = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')')).split(','); - this.x += arr[0] ? Number.parseInt(arr[0]) * 0.01 : 0; - this.y -= arr[1] ? Number.parseInt(arr[1]) * 0.01 : 0; - } else if ((name === 'x') && (this.kind === 'text')) - this.x += Number.parseInt(value) * 0.01; - else if ((name === 'y') && (this.kind === 'text')) - this.y -= Number.parseInt(value) * 0.01; - else if ((name === 'fill') && (this.kind === 'text')) - this.fill = value; - else if ((name === 'd') && (this.kind === 'path') && (value !== 'M0,0')) { - if (get() !== 'M') - return console.error('Not starts with M'); - let x1 = getN(true), y1 = getN(), next; - const pnts = [], add_line = (x2, y2) => { - const angle = Math.atan2(y2 - y1, x2 - x1), - dx = 0.5 * this.stroke_width * Math.sin(angle), - dy = -0.5 * this.stroke_width * Math.cos(angle); - // front side - pnts.push(x1 - dx, y1 - dy, 0, x2 - dx, y2 - dy, 0, x2 + dx, y2 + dy, 0, x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x1 + dx, y1 + dy, 0); - // back side - pnts.push(x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x2 - dx, y2 - dy, 0, x1 - dx, y1 - dy, 0, x1 + dx, y1 + dy, 0, x2 + dx, y2 + dy, 0); - x1 = x2; - y1 = y2; - }; - - while ((next = get())) { - switch (next) { - case 'L': - add_line(getN(true), getN()); - continue; - case 'l': - add_line(x1 + getN(true), y1 + getN()); - continue; - case 'H': - add_line(getN(), y1); - continue; - case 'h': - add_line(x1 + getN(), y1); - continue; - case 'V': - add_line(x1, getN()); - continue; - case 'v': - add_line(x1, y1 + getN()); - continue; - case 'a': { - const rx = getN(true), ry = getN(true), - angle = getN(true) / 180 * Math.PI, flag1 = getN(true); - getN(true); // skip unused flag2 - const x2 = x1 + getN(true), - y2 = y1 + getN(), - x0 = x1 + rx * Math.cos(angle), - y0 = y1 + ry * Math.sin(angle); - let angle2 = Math.atan2(y0 - y2, x0 - x2); - if (flag1 && (angle2 < angle)) - angle2 += 2 * Math.PI; - else if (!flag1 && (angle2 > angle)) - angle2 -= 2 * Math.PI; - - for (let cnt = 0; cnt < 10; ++cnt) { - const a = angle + (angle2 - angle) / 10 * (cnt + 1); - add_line(x0 - rx * Math.cos(a), y0 - ry * Math.sin(a)); - } - continue; - } - default: - console.log('not supported path operator', next); - } - } - - if (pnts.length) { - const pos = new Float32Array(pnts); - this.geom = new THREE.BufferGeometry(); - this.geom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); - this.geom.scale(0.01, -0.01, 0.01); - this.geom.computeVertexNormals(); - } - } - return this; - } - - text(v) { - if (this.kind === 'text') - this._text = v; - } - - collect(geoms, geom_args, as_array) { - if (this._text) { - geom_args.size = Math.round(0.01 * this.font_size); - const geom = new THREE.TextGeometry(this._text, geom_args); - if (as_array) { - // this is latex parsing - // while three.js uses full height, make it more like normal fonts - geom.scale(1, 0.9, 1); - geom.translate(0, 0.0005 * this.font_size, 0); - } - geom.translate(this.x, this.y, 0); - geom._fill = this.fill; - geoms.push(geom); - } - if (this.geom) { - this.geom.translate(this.x, this.y, 0); - this.geom._fill = this.fill; - geoms.push(this.geom); - } - - this.childs.forEach(chld => { - chld.x += this.x; - chld.y += this.y; - chld.collect(geoms, geom_args, as_array); - }); - } - -} // class TextParseWrapper - - -function createLatexGeometry(painter, lbl, size, as_array, use_latex = true) { - const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }, - font_size = size * 100, - node = new TextParseWrapper('g', null, font_size), - arg = { font_size, text: lbl, fast: true, font: { size: font_size, isMonospace: () => false, aver_width: 0.9 } }, - geoms = []; - - if (THREE.REVISION > 162) - geom_args.depth = 0; - else - geom_args.height = 0; - - if (!isPlainText(lbl)) { - produceLatex(painter, node, arg); - node.collect(geoms, geom_args, as_array); - } - - if (!geoms.length) { - geom_args.size = size; - const res = new THREE.TextGeometry(translateLaTeX(lbl), geom_args); - return as_array ? [res] : res; - } - - if (as_array) - return geoms; - - if (geoms.length === 1) - return geoms[0]; - - let total_size = 0; - geoms.forEach(geom => { total_size += geom.getAttribute('position').array.length; }); - - const pos = new Float32Array(total_size), - norm = new Float32Array(total_size); - let indx = 0; - - geoms.forEach(geom => { - const p1 = geom.getAttribute('position').array, - n1 = geom.getAttribute('normal').array; - for (let i = 0; i < p1.length; ++i, ++indx) { - pos[indx] = p1[i]; - norm[indx] = n1[i]; - } - }); - - const fullgeom = new THREE.BufferGeometry(); - fullgeom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); - fullgeom.setAttribute('normal', new THREE.BufferAttribute(norm, 3)); - return fullgeom; -} - - -/** @summary Build three.js object for the TLatex - * @private */ -function build3dlatex(obj, opt, painter, fp) { - if (!painter) - painter = new ObjectPainter(null, obj, opt); - const handle = painter.createAttText({ attr: obj }), - valign = handle.align % 10, - halign = (handle.align - valign) / 10, - text_size = handle.size > 1 ? handle.size : 2 * handle.size * (fp?.size_z3d || 100), - arr3d = createLatexGeometry(painter, obj.fTitle, text_size || 10, true, fp || (obj._typename === clTLatex)), - bb = new THREE.Box3().makeEmpty(); - - arr3d.forEach(geom => { - geom.computeBoundingBox(); - bb.expandByPoint(geom.boundingBox.max); - bb.expandByPoint(geom.boundingBox.min); - }); - - let dx = 0, dy = 0; - if (halign === 2) - dx = 0.5 * (bb.max.x + bb.min.x); - else if (halign === 3) - dx = bb.max.x; - - if (valign === 2) - dy = 0.5 * (bb.max.y + bb.min.y); - else if (valign === 3) - dy = bb.max.y; - - const obj3d = new THREE.Object3D(), - materials = [], - getMaterial = color => { - if (!color) - color = 'black'; - if (!materials[color]) - materials[color] = new THREE.MeshBasicMaterial(getMaterialArgs(color, { vertexColors: false })); - return materials[color]; - }; - - arr3d.forEach(geom => { - geom.translate(-dx, -dy, 0); - obj3d.add(new THREE.Mesh(geom, getMaterial(geom._fill || handle.color))); - }); - - return arr3d.length === 1 ? obj3d.children[0] : obj3d; -} - -var latex3d = /*#__PURE__*/Object.freeze({ -__proto__: null, -build3dlatex: build3dlatex, -createLatexGeometry: createLatexGeometry -}); - -/** - * A math namespace - all functions can be exported from base/math.mjs. - * Also all these functions can be used with TFormula calculations - * @namespace Math - */ - -/* eslint-disable curly */ -/* eslint-disable no-useless-assignment */ -/* eslint-disable no-use-before-define */ -/* eslint-disable no-else-return */ -/* eslint-disable no-shadow */ -/* eslint-disable operator-assignment */ -/* eslint-disable @stylistic/js/comma-spacing */ -/* eslint-disable @stylistic/js/no-floating-decimal */ -/* eslint-disable @stylistic/js/space-in-parens */ - -// this can be improved later - -/* eslint-disable eqeqeq */ - -const kMACHEP = 1.11022302462515654042363166809e-16, - kMINLOG = -708.3964185322641, - kMAXLOG = 709.782712893383973096206318587, - kMAXSTIR = 108.116855767857671821730036754, - kBig = 4.503599627370496e15, - kBiginv = 2.22044604925031308085e-16, - kSqrt2 = 1.41421356237309515, - M_PI = 3.14159265358979323846264338328; - -/** @summary Polynomialeval function - * @desc calculates a value of a polynomial of the form: - * a[0]x^N+a[1]x^(N-1) + ... + a[N] - * @memberof Math */ -function Polynomialeval(x, a, N) { - if (!N) - return a[0]; - - let pom = a[0]; - for (let i = 1; i <= N; ++i) - pom = pom * x + a[i]; - return pom; -} - -/** @summary Polynomial1eval function - * @desc calculates a value of a polynomial of the form: - * x^N+a[0]x^(N-1) + ... + a[N-1] - * @memberof Math */ -function Polynomial1eval(x, a, N) { - if (!N) - return a[0]; - - let pom = x + a[0]; - for (let i = 1; i < N; ++i) - pom = pom * x + a[i]; - return pom; -} - -/** @summary lgam function, logarithm from gamma - * @memberof Math */ -function lgam(x) { - let p, q, u, w, z; - const kMAXLGM = 2.556348e305, - LS2PI = 0.91893853320467274178, - A = [ - 8.11614167470508450300E-4, - -5950619042843014e-19, - 7.93650340457716943945E-4, - -0.002777777777300997, - 8.33333333333331927722E-2 - ], B = [ - -1378.2515256912086, - -38801.631513463784, - -331612.9927388712, - -1162370.974927623, - -1721737.0082083966, - -853555.6642457654 - ], C = [ - /* 1.00000000000000000000E0, */ - -351.81570143652345, - -17064.210665188115, - -220528.59055385445, - -1139334.4436798252, - -2532523.0717758294, - -2018891.4143353277 - ]; - - if ((x >= Number.MAX_VALUE) || (x == Number.POSITIVE_INFINITY)) - return Number.POSITIVE_INFINITY; - - if ( x < -34 ) { - q = -x; - w = lgam(q); - p = Math.floor(q); - if ( p === q ) // _unur_FP_same(p,q) - return Number.POSITIVE_INFINITY; - z = q - p; - if ( z > 0.5 ) { - p += 1.0; - z = p - q; - } - z = q * Math.sin( Math.PI * z ); - if ( z < 1e-300 ) - return Number.POSITIVE_INFINITY; - z = Math.log(Math.PI) - Math.log( z ) - w; - return z; - } - if ( x < 13.0 ) { - z = 1.0; - p = 0.0; - u = x; - while ( u >= 3.0 ) { - p -= 1.0; - u = x + p; - z *= u; - } - while ( u < 2.0 ) { - if ( u < 1e-300 ) - return Number.POSITIVE_INFINITY; - z /= u; - p += 1.0; - u = x + p; - } - if ( z < 0.0 ) { - z = -z; - } - if ( u === 2.0 ) - return Math.log(z); - p -= 2.0; - x = x + p; - p = x * Polynomialeval(x, B, 5 ) / Polynomial1eval( x, C, 6); - return Math.log(z) + p; - } - if ( x > kMAXLGM ) - return Number.POSITIVE_INFINITY; - - q = ( x - 0.5 ) * Math.log(x) - x + LS2PI; - if ( x > 1.0e8 ) - return q; - - p = 1.0 / (x * x); - if ( x >= 1000.0 ) - q += ((7.9365079365079365079365e-4 * p - - 2.7777777777777777777778e-3) * p - + 0.0833333333333333333333) / x; - else - q += Polynomialeval( p, A, 4 ) / x; - return q; -} - -/** @summary Stirling formula for the gamma function - * @memberof Math */ -function stirf(x) { - let y, w, v; - - const STIR = [ - 7.87311395793093628397E-4, - -22954996161337813e-20, - -0.0026813261780578124, - 3.47222221605458667310E-3, - 8.33333333333482257126E-2 - ], SQTPI = Math.sqrt(2 * Math.PI); - - w = 1.0 / x; - w = 1.0 + w * Polynomialeval( w, STIR, 4 ); - y = Math.exp(x); - - /* #define kMAXSTIR kMAXLOG/log(kMAXLOG) */ - - if ( x > kMAXSTIR ) - { /* Avoid overflow in pow() */ - v = Math.pow( x, 0.5 * x - 0.25 ); - y = v * (v / y); - } - else - { - y = Math.pow( x, x - 0.5 ) / y; - } - y = SQTPI * y * w; - return y; -} - -/** @summary complementary error function - * @memberof Math */ -function erfc(a) { - const erfP = [ - 2.46196981473530512524E-10, - 5.64189564831068821977E-1, - 7.46321056442269912687E0, - 4.86371970985681366614E1, - 1.96520832956077098242E2, - 5.26445194995477358631E2, - 9.34528527171957607540E2, - 1.02755188689515710272E3, - 5.57535335369399327526E2 - ], erfQ = [ - 1.32281951154744992508E1, - 8.67072140885989742329E1, - 3.54937778887819891062E2, - 9.75708501743205489753E2, - 1.82390916687909736289E3, - 2.24633760818710981792E3, - 1.65666309194161350182E3, - 5.57535340817727675546E2 - ], erfR = [ - 5.64189583547755073984E-1, - 1.27536670759978104416E0, - 5.01905042251180477414E0, - 6.16021097993053585195E0, - 7.40974269950448939160E0, - 2.97886665372100240670E0 - ], erfS = [ - 2.26052863220117276590E0, - 9.39603524938001434673E0, - 1.20489539808096656605E1, - 1.70814450747565897222E1, - 9.60896809063285878198E0, - 3.36907645100081516050E0 - ]; - - let p,q,x,y,z; - - if ( a < 0.0 ) - x = -a; - else - x = a; - - if ( x < 1.0 ) - return 1.0 - erf(a); - - z = -a * a; - - if (z < -kMAXLOG) - return (a < 0) ? 2.0 : 0.0; - - z = Math.exp(z); - - if ( x < 8.0 ) { - p = Polynomialeval( x, erfP, 8 ); - q = Polynomial1eval( x, erfQ, 8 ); - } else { - p = Polynomialeval( x, erfR, 5 ); - q = Polynomial1eval( x, erfS, 6 ); - } - y = (z * p) / q; - - if (a < 0) - y = 2.0 - y; - - if (y == 0) - return (a < 0) ? 2.0 : 0.0; - - return y; -} - -/** @summary error function - * @memberof Math */ -function erf(x) { - if (Math.abs(x) > 1.0) - return 1.0 - erfc(x); - - const erfT = [ - 9.60497373987051638749E0, - 9.00260197203842689217E1, - 2.23200534594684319226E3, - 7.00332514112805075473E3, - 5.55923013010394962768E4 - ], erfU = [ - 3.35617141647503099647E1, - 5.21357949780152679795E2, - 4.59432382970980127987E3, - 2.26290000613890934246E4, - 4.92673942608635921086E4 - ], z = x * x; - - return x * Polynomialeval(z, erfT, 4) / Polynomial1eval(z, erfU, 5); -} - -/** @summary lognormal_cdf_c function - * @memberof Math */ -function lognormal_cdf_c(x, m, s, x0) { - if (x0 === undefined) - x0 = 0; - const z = (Math.log((x - x0)) - m) / (s * kSqrt2); - if (z > 1.) - return 0.5 * erfc(z); - else - return 0.5 * (1.0 - erf(z)); -} - -/** @summary lognormal_cdf_c function - * @memberof Math */ -function lognormal_cdf(x, m, s, x0 = 0) { - const z = (Math.log((x - x0)) - m) / (s * kSqrt2); - if (z < -1) - return 0.5 * erfc(-z); - else - return 0.5 * (1.0 + erf(z)); -} - -/** @summary normal_cdf_c function - * @memberof Math */ -function normal_cdf_c(x, sigma, x0 = 0) { - const z = (x - x0) / (sigma * kSqrt2); - if (z > 1.) - return 0.5 * erfc(z); - else - return 0.5 * (1. - erf(z)); -} - -/** @summary normal_cdf function - * @memberof Math */ -function normal_cdf(x, sigma, x0 = 0) { - const z = (x - x0) / (sigma * kSqrt2); - if (z < -1) - return 0.5 * erfc(-z); - else - return 0.5 * (1.0 + erf(z)); -} - -/** @summary log normal pdf - * @memberof Math */ -function lognormal_pdf(x, m, s, x0 = 0) { - if ((x - x0) <= 0) - return 0.0; - const tmp = (Math.log((x - x0)) - m) / s; - return 1.0 / ((x - x0) * Math.abs(s) * Math.sqrt(2 * M_PI)) * Math.exp(-(tmp * tmp) / 2); -} - -/** @summary normal pdf - * @memberof Math */ -function normal_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x - x0) / sigma; - return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); -} - -/** @summary gamma calculation - * @memberof Math */ -function gamma(x) { - let p, q, z, i, sgngam = 1; - - if (x >= Number.MAX_VALUE) - return x; - - q = Math.abs(x); - - if ( q > 33.0 ) - { - if ( x < 0.0 ) - { - p = Math.floor(q); - if ( p == q ) - return Number.POSITIVE_INFINITY; - i = Math.round(p); - if ( (i & 1) == 0 ) - sgngam = -1; - z = q - p; - if ( z > 0.5 ) - { - p += 1.0; - z = q - p; - } - z = q * Math.sin(Math.PI * z); - if ( z == 0 ) - { - return sgngam > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; - } - z = Math.abs(z); - z = Math.PI / (z * stirf(q)); - } - else - { - z = stirf(x); - } - return sgngam * z; - } - - z = 1.0; - while ( x >= 3.0 ) { - x -= 1.0; - z *= x; - } - - let small = false; - - while (( x < 0.0 ) && !small) { - if ( x > -1e-9 ) - small = true; - else { - z /= x; - x += 1.0; - } - } - - while (( x < 2.0 ) && !small) { - if ( x < 1.e-9 ) - small = true; - else { - z /= x; - x += 1.0; - } - } - - if (small) { - if ( x == 0 ) - return Number.POSITIVE_INFINITY; - else - return z / ((1.0 + 0.5772156649015329 * x) * x); - } - - if ( x == 2.0 ) - return z; - - const P = [ - 1.60119522476751861407E-4, - 1.19135147006586384913E-3, - 1.04213797561761569935E-2, - 4.76367800457137231464E-2, - 2.07448227648435975150E-1, - 4.94214826801497100753E-1, - 9.99999999999999996796E-1 - ], Q = [ - -23158187332412014e-21, - 5.39605580493303397842E-4, - -0.004456419138517973, - 1.18139785222060435552E-2, - 3.58236398605498653373E-2, - -0.23459179571824335, - 7.14304917030273074085E-2, - 1.00000000000000000320E0]; - - x -= 2.0; - p = Polynomialeval( x, P, 6 ); - q = Polynomialeval( x, Q, 7 ); - return z * p / q; -} - -/** @summary ndtri function - * @memberof Math */ -function ndtri(y0) { - if ( y0 <= 0.0 ) - return Number.NEGATIVE_INFINITY; - if ( y0 >= 1.0 ) - return Number.POSITIVE_INFINITY; - - const P0 = [ - -59.96335010141079, - 9.80010754185999661536E1, - -56.67628574690703, - 1.39312609387279679503E1, - -1.2391658386738125 - ], Q0 = [ - 1.95448858338141759834E0, - 4.67627912898881538453E0, - 8.63602421390890590575E1, - -225.46268785411937, - 2.00260212380060660359E2, - -82.03722561683334, - 1.59056225126211695515E1, - -1.1833162112133 - ], P1 = [ - 4.05544892305962419923E0, - 3.15251094599893866154E1, - 5.71628192246421288162E1, - 4.40805073893200834700E1, - 1.46849561928858024014E1, - 2.18663306850790267539E0, - -0.1402560791713545, - -0.03504246268278482, - -8574567851546854e-19 - ], Q1 = [ - 1.57799883256466749731E1, - 4.53907635128879210584E1, - 4.13172038254672030440E1, - 1.50425385692907503408E1, - 2.50464946208309415979E0, - -0.14218292285478779, - -0.03808064076915783, - -9332594808954574e-19 - ], P2 = [ - 3.23774891776946035970E0, - 6.91522889068984211695E0, - 3.93881025292474443415E0, - 1.33303460815807542389E0, - 2.01485389549179081538E-1, - 1.23716634817820021358E-2, - 3.01581553508235416007E-4, - 2.65806974686737550832E-6, - 6.23974539184983293730E-9 - ], Q2 = [ - 6.02427039364742014255E0, - 3.67983563856160859403E0, - 1.37702099489081330271E0, - 2.16236993594496635890E-1, - 1.34204006088543189037E-2, - 3.28014464682127739104E-4, - 2.89247864745380683936E-6, - 6.79019408009981274425E-9 - ], s2pi = 2.50662827463100050242e0, dd = 0.13533528323661269189; - - let code = 1, y = y0, x, y2, x1; - - if (y > (1.0 - dd)) { - y = 1.0 - y; - code = 0; - } - if ( y > dd ) { - y = y - 0.5; - y2 = y * y; - x = y + y * (y2 * Polynomialeval( y2, P0, 4) / Polynomial1eval( y2, Q0, 8 )); - x = x * s2pi; - return x; - } - x = Math.sqrt(-2 * Math.log(y)); - const x0 = x - Math.log(x) / x, - z = 1.0 / x; - if ( x < 8.0 ) - x1 = z * Polynomialeval( z, P1, 8 ) / Polynomial1eval( z, Q1, 8 ); - else - x1 = z * Polynomialeval( z, P2, 8 ) / Polynomial1eval( z, Q2, 8 ); - x = x0 - x1; - if ( code != 0 ) - x = -x; - return x; -} - -/** @summary normal_quantile function - * @memberof Math */ -function normal_quantile(z, sigma) { - return sigma * ndtri(z); -} - -/** @summary normal_quantile_c function - * @memberof Math */ -function normal_quantile_c(z, sigma) { - return -sigma * ndtri(z); -} - -/** @summary igamc function - * @memberof Math */ -function igamc(a,x) { - // LM: for negative values returns 0.0 - // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) - return 0.0; - - if (x <= 0) - return 1.0; - - if ((x < 1.0) || (x < a)) - return (1.0 - igam(a,x)); - - let ax = a * Math.log(x) - x - lgam(a); - if ( ax < -kMAXLOG ) - return 0.0; - - ax = Math.exp(ax); - - /* continued fraction */ - let y = 1.0 - a, - z = x + y + 1.0, - c = 0.0, - pkm2 = 1.0, - qkm2 = x, - pkm1 = x + 1.0, - qkm1 = z * x, - ans = pkm1 / qkm1, - yc, r, t, pk, qk; - - do { - c += 1.0; - y += 1.0; - z += 2.0; - yc = y * c; - pk = pkm1 * z - pkm2 * yc; - qk = qkm1 * z - qkm2 * yc; - if (qk) - { - r = pk / qk; - t = Math.abs( (ans - r) / r ); - ans = r; - } - else - t = 1.0; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - if ( Math.abs(pk) > kBig ) - { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - } while ( t > kMACHEP ); - - return ans * ax; -} - -/** @summary igam function - * @memberof Math */ -function igam(a, x) { - // LM: for negative values returns 1.0 instead of zero - // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) - return 1.0; - - if (x <= 0) - return 0.0; - - if ((x > 1.0) && (x > a)) - return 1.0 - igamc(a,x); - - /* Compute x**a * exp(-x) / gamma(a) */ - let ax = a * Math.log(x) - x - lgam(a); - if ( ax < -kMAXLOG ) - return 0.0; - - ax = Math.exp(ax); - - /* power series */ - let r = a, c = 1.0, ans = 1.0; - - do { - r += 1.0; - c *= x / r; - ans += c; - } while ( c / ans > kMACHEP ); - - return ans * ax / a; -} - - -/** @summary igami function - * @memberof Math */ -function igami(a, y0) { - // check the domain - if (a <= 0) { - console.error(`igami : Wrong domain for parameter a = ${a} (must be > 0)`); - return 0; - } - if (y0 <= 0) { - return Number.POSITIVE_INFINITY; - } - if (y0 >= 1) { - return 0; - } - const kMAXNUM = Number.MAX_VALUE, dithresh = 5.0 * kMACHEP; - let x0 = kMAXNUM, x1 = 0, x, yl = 0, yh = 1, y, d, lgm, i, dir; - - /* approximation to inverse function */ - d = 1.0 / (9.0 * a); - y = 1.0 - d - ndtri(y0) * Math.sqrt(d); - x = a * y * y * y; - - lgm = lgam(a); - - for ( i = 0; i < 10; ++i ) { - if ( x > x0 || x < x1 ) - break; - y = igamc(a,x); - if ( y < yl || y > yh ) - break; - if ( y < y0 ) { - x0 = x; - yl = y; - } - else { - x1 = x; - yh = y; - } - /* compute the derivative of the function at this point */ - d = (a - 1.0) * Math.log(x) - x - lgm; - if ( d < -kMAXLOG ) - break; - d = -Math.exp(d); - /* compute the step to the next approximation of x */ - d = (y - y0) / d; - if ( Math.abs(d / x) < kMACHEP ) - return x; - x = x - d; - } - /* Resort to interval halving if Newton iteration did not converge. */ - d = 0.0625; - if ( x0 == kMAXNUM ) { - if ( x <= 0.0 ) - x = 1.0; - while ( x0 == kMAXNUM ) { - x = (1.0 + d) * x; - y = igamc( a, x ); - if ( y < y0 ) { - x0 = x; - yl = y; - break; - } - d = d + d; - } - } - d = 0.5; - dir = 0; - - for ( i = 0; i < 400; ++i ) { - x = x1 + d * (x0 - x1); - y = igamc( a, x ); - lgm = (x0 - x1) / (x1 + x0); - if ( Math.abs(lgm) < dithresh ) - break; - lgm = (y - y0) / y0; - if ( Math.abs(lgm) < dithresh ) - break; - if ( x <= 0.0 ) - break; - if ( y >= y0 ) { - x1 = x; - yh = y; - if ( dir < 0 ) { - dir = 0; - d = 0.5; - } - else if ( dir > 1 ) - d = 0.5 * d + 0.5; - else - d = (y0 - yl) / (yh - yl); - dir += 1; - } - else { - x0 = x; - yl = y; - if ( dir > 0 ) { - dir = 0; - d = 0.5; - } - else if ( dir < -1 ) - d = 0.5 * d; - else - d = (y0 - yl) / (yh - yl); - dir -= 1; - } - } - return x; -} - -/** @summary landau_pdf function - * @desc LANDAU pdf : algorithm from CERNLIB G110 denlan - * same algorithm is used in GSL - * @memberof Math */ -function landau_pdf(x, xi, x0 = 0) { - if (xi <= 0) - return 0; - const v = (x - x0) / xi; - let u, ue, us, denlan; - const p1 = [0.4259894875,-0.124976255, 0.03984243700, -0.006298287635, 0.001511162253], - q1 = [1.0 ,-0.3388260629, 0.09594393323, -0.01608042283, 0.003778942063], - p2 = [0.1788541609, 0.1173957403, 0.01488850518, -0.001394989411, 0.0001283617211], - q2 = [1.0 , 0.7428795082, 0.3153932961, 0.06694219548, 0.008790609714], - p3 = [0.1788544503, 0.09359161662,0.006325387654, 0.00006611667319,-2031049101e-15], - q3 = [1.0 , 0.6097809921, 0.2560616665, 0.04746722384, 0.006957301675], - p4 = [0.9874054407, 118.6723273, 849.2794360, -743.7792444, 427.0262186], - q4 = [1.0 , 106.8615961, 337.6496214, 2016.712389, 1597.063511], - p5 = [1.003675074, 167.5702434, 4789.711289, 21217.86767, -22324.9491], - q5 = [1.0 , 156.9424537, 3745.310488, 9834.698876, 66924.28357], - p6 = [1.000827619, 664.9143136, 62972.92665, 475554.6998, -5743609.109], - q6 = [1.0 , 651.4101098, 56974.73333, 165917.4725, -2815759.939], - a1 = [0.04166666667,-0.01996527778, 0.02709538966], - a2 = [-1.84556867,-4.284640743]; - - if (v < -5.5) { - u = Math.exp(v + 1.0); - if (u < 1e-10) - return 0.0; - ue = Math.exp(-1 / u); - us = Math.sqrt(u); - denlan = 0.3989422803 * (ue / us) * (1 + (a1[0] + (a1[1] + a1[2] * u) * u) * u); - } else if (v < -1) { - u = Math.exp(-v - 1); - denlan = Math.exp(-u) * Math.sqrt(u) * - (p1[0] + (p1[1] + (p1[2] + (p1[3] + p1[4] * v) * v) * v) * v) / - (q1[0] + (q1[1] + (q1[2] + (q1[3] + q1[4] * v) * v) * v) * v); - } else if (v < 1) { - denlan = (p2[0] + (p2[1] + (p2[2] + (p2[3] + p2[4] * v) * v) * v) * v) / - (q2[0] + (q2[1] + (q2[2] + (q2[3] + q2[4] * v) * v) * v) * v); - } else if (v < 5) { - denlan = (p3[0] + (p3[1] + (p3[2] + (p3[3] + p3[4] * v) * v) * v) * v) / - (q3[0] + (q3[1] + (q3[2] + (q3[3] + q3[4] * v) * v) * v) * v); - } else if (v < 12) { - u = 1 / v; - denlan = u * u * (p4[0] + (p4[1] + (p4[2] + (p4[3] + p4[4] * u) * u) * u) * u) / - (q4[0] + (q4[1] + (q4[2] + (q4[3] + q4[4] * u) * u) * u) * u); - } else if (v < 50) { - u = 1 / v; - denlan = u * u * (p5[0] + (p5[1] + (p5[2] + (p5[3] + p5[4] * u) * u) * u) * u) / - (q5[0] + (q5[1] + (q5[2] + (q5[3] + q5[4] * u) * u) * u) * u); - } else if (v < 300) { - u = 1 / v; - denlan = u * u * (p6[0] + (p6[1] + (p6[2] + (p6[3] + p6[4] * u) * u) * u) * u) / - (q6[0] + (q6[1] + (q6[2] + (q6[3] + q6[4] * u) * u) * u) * u); - } else { - u = 1 / (v - v * Math.log(v) / (v + 1)); - denlan = u * u * (1 + (a2[0] + a2[1] * u) * u); - } - return denlan / xi; -} - -/** @summary Landau function - * @memberof Math */ -function Landau(x, mpv, sigma, norm) { - if (sigma <= 0) - return 0; - const den = landau_pdf((x - mpv) / sigma, 1, 0); - if (!norm) - return den; - return den / sigma; -} - -/** @summary inc_gamma_c - * @memberof Math */ -function inc_gamma_c(a,x) { - return igamc(a,x); -} - -/** @summary inc_gamma - * @memberof Math */ -function inc_gamma(a,x) { - return igam(a,x); -} - -/** @summary lgamma - * @memberof Math */ -function lgamma(z) { - return lgam(z); -} - -/** @summary Probability density function of the beta distribution. - * @memberof Math */ -function beta_pdf(x, a, b) { - if (x < 0 || x > 1.0) - return 0; - if (x == 0 ) { - if (a < 1) - return Number.POSITIVE_INFINITY; - else if (a > 1) - return 0; - else if ( a == 1) - return b; // to avoid a nan from log(0)*0 - } - if (x == 1 ) { - if (b < 1) - return Number.POSITIVE_INFINITY; - else if (b > 1) - return 0; - else if ( b == 1) - return a; // to avoid a nan from log(0)*0 - } - return Math.exp(lgamma(a + b) - lgamma(a) - lgamma(b) + - Math.log(x) * (a - 1.) + Math.log1p(-x) * (b - 1.)); -} - -/** @summary beta - * @memberof Math */ -function beta(x,y) { - return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); -} - -/** @summary chisquared_cdf_c - * @memberof Math */ -function chisquared_cdf_c(x,r,x0 = 0) { - return inc_gamma_c(0.5 * r, 0.5 * (x - x0)); -} - -/** @summary Continued fraction expansion #1 for incomplete beta integral - * @memberof Math */ -function incbcf(a,b,x) { - let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, - k1, k2, k3, k4, k5, k6, k7, k8, - r, t, ans, n; - const thresh = 3.0 * kMACHEP; - - k1 = a; - k2 = a + b; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = b - 1.0; - k7 = k4; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; - - do { - xk = -( x * k1 * k2 ) / ( k3 * k4 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = ( x * k5 * k6 ) / ( k7 * k8 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if ( qk != 0 ) - r = pk / qk; - if ( r != 0 ) - { - t = Math.abs( (ans - r) / r ); - ans = r; - } - else - t = 1.0; - - if ( t < thresh ) - break; // goto cdone; - - k1 += 1.0; - k2 += 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 -= 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((Math.abs(qk) + Math.abs(pk)) > kBig) { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { - pkm2 *= kBig; - pkm1 *= kBig; - qkm2 *= kBig; - qkm1 *= kBig; - } - } - while ( ++n < 300 ); - - // cdone: - return ans; -} - -/** @summary Continued fraction expansion #2 for incomplete beta integral - * @memberof Math */ -function incbd(a,b,x) { - const z = x / (1.0 - x), - thresh = 3.0 * kMACHEP; - - let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, - k1, k2, k3, k4, k5, k6, k7, k8, - r, t, ans, n; - - k1 = a; - k2 = b - 1.0; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = a + b; - k7 = a + 1.0; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; - do { - xk = -( z * k1 * k2 ) / ( k3 * k4 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = ( z * k5 * k6 ) / ( k7 * k8 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if ( qk != 0 ) - r = pk / qk; - if ( r != 0 ) - { - t = Math.abs( (ans - r) / r ); - ans = r; - } - else - t = 1.0; - - if ( t < thresh ) - break; // goto cdone; - - k1 += 1.0; - k2 -= 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 += 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((Math.abs(qk) + Math.abs(pk)) > kBig) { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { - pkm2 *= kBig; - pkm1 *= kBig; - qkm2 *= kBig; - qkm1 *= kBig; - } - } - while ( ++n < 300 ); - // cdone: - return ans; -} - -/** @summary ROOT::Math::Cephes::pseries - * @memberof Math */ -function pseries(a,b,x) { - let s, t, u, v, n; - - const ai = 1.0 / a; - u = (1.0 - b) * x; - v = u / (a + 1.0); - const t1 = v; - t = u; - n = 2.0; - s = 0.0; - const z = kMACHEP * ai; - while ( Math.abs(v) > z ) { - u = (n - b) * x / n; - t *= u; - v = t / (a + n); - s += v; - n += 1.0; - } - s += t1; - s += ai; - - u = a * Math.log(x); - if ( (a + b) < kMAXSTIR && Math.abs(u) < kMAXLOG ) - { - t = gamma(a + b) / (gamma(a) * gamma(b)); - s = s * t * Math.pow(x,a); - } - else - { - t = lgam(a + b) - lgam(a) - lgam(b) + u + Math.log(s); - if ( t < kMINLOG ) - s = 0.0; - else - s = Math.exp(t); - } - return s; -} - -/** @summary ROOT::Math::Cephes::incbet - * @memberof Math */ -function incbet(aa,bb,xx) { - let a, b, t, x, xc, w, y, flag; - - if ( aa <= 0.0 || bb <= 0.0 ) - return 0.0; - - // LM: changed: for X > 1 return 1. - if (xx <= 0.0) - return 0.0; - if (xx >= 1.0) - return 1.0; - - flag = 0; - - /* - to test if that way is better for large b/ (comment out from Cephes version) - if ( (bb * xx) <= 1.0 && xx <= 0.95) - { - t = pseries(aa, bb, xx); - goto done; - } - -**/ - w = 1.0 - xx; - - /* Reverse a and b if x is greater than the mean. */ - /* aa,bb > 1 -> sharp rise at x=aa/(aa+bb) */ - if (xx > (aa / (aa + bb))) - { - flag = 1; - a = bb; - b = aa; - xc = xx; - x = w; - } - else - { - a = aa; - b = bb; - xc = w; - x = xx; - } - - if ( flag == 1 && (b * x) <= 1.0 && x <= 0.95) { - t = pseries(a, b, x); - // goto done; - } else { - /* Choose expansion for better convergence. */ - y = x * (a + b - 2.0) - (a - 1.0); - if ( y < 0.0 ) - w = incbcf( a, b, x ); - else - w = incbd( a, b, x ) / xc; - - /* Multiply w by the factor - a b _ _ _ - x (1-x) | (a+b) / (a | (a) | (b)) . */ - - y = a * Math.log(x); - t = b * Math.log(xc); - if ( (a + b) < kMAXSTIR && Math.abs(y) < kMAXLOG && Math.abs(t) < kMAXLOG ) - { - t = Math.pow(xc,b); - t *= Math.pow(x,a); - t /= a; - t *= w; - t *= gamma(a + b) / (gamma(a) * gamma(b)); - // goto done; - } else { - /* Resort to logarithms. */ - y += t + lgam(a + b) - lgam(a) - lgam(b); - y += Math.log(w / a); - if ( y < kMINLOG ) - t = 0.0; - else - t = Math.exp(y); - } - } - - // done: - - if (flag == 1) { - if ( t <= kMACHEP ) - t = 1.0 - kMACHEP; - else - t = 1.0 - t; - } - return t; -} - -/** @summary copy of ROOT::Math::Cephes::incbi - * @memberof Math */ -function incbi(aa,bb,yy0) { - let a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt, - i, rflg, dir, nflg, ihalve = true; - - // check the domain - if (aa <= 0) { - // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter a (must be > 0)'); - return 0; - } - if (bb <= 0) { - // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter b (must be > 0)'); - return 0; - } - - const process_done = () => { - if ( rflg ) { - if ( x <= kMACHEP ) - x = 1.0 - kMACHEP; - else - x = 1.0 - x; - } - return x; - }; - - i = 0; - if ( yy0 <= 0 ) - return 0.0; - if ( yy0 >= 1.0 ) - return 1.0; - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - nflg = 0; - - if ( aa <= 1.0 || bb <= 1.0 ) - { - dithresh = 1.0e-6; - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - x = a / (a + b); - y = incbet( a, b, x ); - // goto ihalve; // will start - } - else - { - dithresh = 1.0e-4; - /* approximation to inverse function */ - - yp = -ndtri(yy0); - - if ( yy0 > 0.5 ) - { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - yp = -yp; - } - else - { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } - - lgm = (yp * yp - 3.0) / 6.0; - x = 2.0 / (1.0 / (2.0 * a - 1.0) + 1.0 / (2.0 * b - 1.0)); - d = yp * Math.sqrt( x + lgm ) / x - - (1.0 / (2.0 * b - 1.0) - 1.0 / (2.0 * a - 1.0)) - * (lgm + 5.0 / 6.0 - 2.0 / (3.0 * x)); - d = 2.0 * d; - if ( d < kMINLOG ) { - // x = 1.0; - // goto under; - x = 0.0; - return process_done(); - } - x = a / (a + b * Math.exp(d)); - y = incbet( a, b, x ); - yp = (y - y0) / y0; - if ( Math.abs(yp) < 0.2 ) - ihalve = false; // instead goto newt; exclude ihalve for the first time - } - - let mainloop = 1000; - - // endless loop until coverage - while (mainloop-- > 0) { - /* Resort to interval halving if not close enough. */ - // ihalve: - if (ihalve) { - dir = 0; - di = 0.5; - for ( i = 0; i < 100; i++ ) - { - if ( i != 0 ) - { - x = x0 + di * (x1 - x0); - if ( x == 1.0 ) - x = 1.0 - kMACHEP; - if ( x == 0.0 ) - { - di = 0.5; - x = x0 + di * (x1 - x0); - if ( x == 0.0 ) - return process_done(); // goto under; - } - y = incbet( a, b, x ); - yp = (x1 - x0) / (x1 + x0); - if ( Math.abs(yp) < dithresh ) - break; // goto newt; - yp = (y - y0) / y0; - if ( Math.abs(yp) < dithresh ) - break; // goto newt; - } - if ( y < y0 ) - { - x0 = x; - yl = y; - if ( dir < 0 ) - { - dir = 0; - di = 0.5; - } - else if ( dir > 3 ) - di = 1.0 - (1.0 - di) * (1.0 - di); - else if ( dir > 1 ) - di = 0.5 * di + 0.5; - else - di = (y0 - y) / (yh - yl); - dir += 1; - if ( x0 > 0.75 ) - { - if ( rflg == 1 ) - { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } - else - { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - } - x = 1.0 - x; - y = incbet( a, b, x ); - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - continue; // goto ihalve; - } - } - else - { - x1 = x; - if ( rflg == 1 && x1 < kMACHEP ) - { - x = 0.0; - return process_done(); // goto done; - } - yh = y; - if ( dir > 0 ) - { - dir = 0; - di = 0.5; - } - else if ( dir < -3 ) - di = di * di; - else if ( dir < -1 ) - di = 0.5 * di; - else - di = (y - y0) / (yh - yl); - dir -= 1; - } - } - // math_error( 'incbi', PLOSS ); - if ( x0 >= 1.0 ) { - x = 1.0 - kMACHEP; - return process_done(); // goto done; - } - if ( x <= 0.0 ) { - // math_error( 'incbi', UNDERFLOW ); - x = 0.0; - return process_done(); // goto done; - } - break; // if here, break ihalve - } // end of ihalve - - ihalve = true; // enter loop next time - - // newt: - - if ( nflg ) - return process_done(); // goto done; - nflg = 1; - lgm = lgam(a + b) - lgam(a) - lgam(b); - - for ( i = 0; i < 8; i++ ) - { - /* Compute the function at this point. */ - if ( i != 0 ) - y = incbet(a,b,x); - if ( y < yl ) - { - x = x0; - y = yl; - } - else if ( y > yh ) - { - x = x1; - y = yh; - } - else if ( y < y0 ) - { - x0 = x; - yl = y; - } - else - { - x1 = x; - yh = y; - } - if ( x == 1.0 || x == 0.0 ) - break; - /* Compute the derivative of the function at this point. */ - d = (a - 1.0) * Math.log(x) + (b - 1.0) * Math.log(1.0 - x) + lgm; - if ( d < kMINLOG ) - return process_done(); // goto done; - if ( d > kMAXLOG ) - break; - d = Math.exp(d); - /* Compute the step to the next approximation of x. */ - d = (y - y0) / d; - xt = x - d; - if ( xt <= x0 ) - { - y = (x - x0) / (x1 - x0); - xt = x0 + 0.5 * y * (x - x0); - if ( xt <= 0.0 ) - break; - } - if ( xt >= x1 ) - { - y = (x1 - x) / (x1 - x0); - xt = x1 - 0.5 * y * (x1 - x); - if ( xt >= 1.0 ) - break; - } - x = xt; - if ( Math.abs(d / x) < 128.0 * kMACHEP ) - return process_done(); // goto done; - } - /* Did not converge. */ - dithresh = 256.0 * kMACHEP; - } // endless loop instead of // goto ihalve; - - // done: - - return process_done(); -} - -/** @summary Calculates the normalized (regularized) incomplete beta function. - * @memberof Math */ -function inc_beta(x,a,b) { - return incbet(a,b,x); -} - -const BetaIncomplete = inc_beta; - -/** @summary ROOT::Math::beta_quantile - * @memberof Math */ -function beta_quantile(z,a,b) { - return incbi(a,b,z); -} - -/** @summary Complement of the cumulative distribution function of the beta distribution. - * @memberof Math */ -function beta_cdf_c(x,a,b) { - return inc_beta(1 - x, b, a); -} - -/** @summary chisquared_cdf - * @memberof Math */ -function chisquared_cdf(x,r,x0 = 0) { - return inc_gamma(0.5 * r, 0.5 * (x - x0)); -} - -/** @summary gamma_quantile_c function - * @memberof Math */ -function gamma_quantile_c(z, alpha, theta) { - return theta * igami( alpha, z); -} - -/** @summary gamma_quantile function - * @memberof Math */ -function gamma_quantile(z, alpha, theta) { - return theta * igami( alpha, 1. - z); -} - -/** @summary breitwigner_cdf_c function - * @memberof Math */ -function breitwigner_cdf_c(x,gamma, x0 = 0) { - return 0.5 - Math.atan(2.0 * (x - x0) / gamma) / M_PI; -} - -/** @summary breitwigner_cdf function - * @memberof Math */ -function breitwigner_cdf(x, gamma, x0 = 0) { - return 0.5 + Math.atan(2.0 * (x - x0) / gamma) / M_PI; -} - -/** @summary cauchy_cdf_c function - * @memberof Math */ -function cauchy_cdf_c(x, b, x0 = 0) { - return 0.5 - Math.atan( (x - x0) / b) / M_PI; -} - -/** @summary cauchy_cdf function - * @memberof Math */ -function cauchy_cdf(x, b, x0 = 0) { - return 0.5 + Math.atan( (x - x0) / b) / M_PI; -} - -/** @summary cauchy_pdf function - * @memberof Math */ -function cauchy_pdf(x, b = 1, x0 = 0) { - return b / (M_PI * ((x - x0) * (x - x0) + b * b)); -} - -/** @summary gaussian_pdf function - * @memberof Math */ -function gaussian_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x - x0) / sigma; - return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); -} - -/** @summary gamma_pdf function - * @memberof Math */ -function gamma_pdf(x, alpha, theta, x0 = 0) { - if ((x - x0) < 0) { - return 0.0; - } else if ((x - x0) == 0) { - return (alpha == 1) ? 1.0 / theta : 0; - } else if (alpha == 1) { - return Math.exp(-(x - x0) / theta) / theta; - } - return Math.exp((alpha - 1) * Math.log((x - x0) / theta) - (x - x0) / theta - lgamma(alpha)) / theta; -} - -/** @summary tdistribution_cdf_c function - * @memberof Math */ -function tdistribution_cdf_c(x, r, x0 = 0) { - const p = x - x0, - sign = (p > 0) ? 1. : -1; - return .5 - .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; -} - -/** @summary tdistribution_cdf function - * @memberof Math */ -function tdistribution_cdf(x, r, x0 = 0) { - const p = x - x0, - sign = (p > 0) ? 1. : -1; - return .5 + .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; -} - -/** @summary tdistribution_pdf function - * @memberof Math */ -function tdistribution_pdf(x, r, x0 = 0) { - return (Math.exp(lgamma((r + 1.0) / 2.0) - lgamma(r / 2.0)) / Math.sqrt(M_PI * r)) - * Math.pow((1.0 + (x - x0) * (x - x0) / r), -(r + 1.0) / 2.0); -} - -/** @summary exponential_cdf_c function - * @memberof Math */ -function exponential_cdf_c(x, lambda, x0 = 0) { - return ((x - x0) < 0) ? 1.0 : Math.exp(-lambda * (x - x0)); -} - -/** @summary exponential_cdf function - * @memberof Math */ -function exponential_cdf(x, lambda, x0 = 0) { - return ((x - x0) < 0) ? 0.0 : -Math.expm1(-lambda * (x - x0)); -} - -/** @summary chisquared_pdf - * @memberof Math */ -function chisquared_pdf(x, r, x0 = 0) { - if ((x - x0) < 0) - return 0.0; - const a = r / 2 - 1.; - // let return inf for case x = x0 and treat special case of r = 2 otherwise will return nan - if (x == x0 && a == 0) - return 0.5; - - return Math.exp((r / 2 - 1) * Math.log((x - x0) / 2) - (x - x0) / 2 - lgamma(r / 2)) / 2; -} - -/** @summary Probability density function of the F-distribution. - * @memberof Math */ -function fdistribution_pdf(x, n, m, x0 = 0) { - if (n < 0 || m < 0) - return Number.NaN; - if ((x - x0) < 0) - return 0.0; - - return Math.exp((n / 2) * Math.log(n) + (m / 2) * Math.log(m) + lgamma((n + m) / 2) - lgamma(n / 2) - lgamma(m / 2) - + (n / 2 - 1) * Math.log(x - x0) - ((n + m) / 2) * Math.log(m + n * (x - x0))); -} - -/** @summary fdistribution_cdf_c function - * @memberof Math */ -function fdistribution_cdf_c(x, n, m, x0 = 0) { - if (n < 0 || m < 0) - return Number.NaN; - - const z = m / (m + n * (x - x0)); - // fox z->1 and large a and b IB looses precision use complement function - if (z > 0.9 && n > 1 && m > 1) - return 1. - fdistribution_cdf(x, n, m, x0); - - // for the complement use the fact that IB(x,a,b) = 1. - IB(1-x,b,a) - return inc_beta(m / (m + n * (x - x0)), .5 * m, .5 * n); -} - -/** @summary fdistribution_cdf function - * @memberof Math */ -function fdistribution_cdf(x, n, m, x0 = 0) { - if (n < 0 || m < 0) - return Number.NaN; - - const z = n * (x - x0) / (m + n * (x - x0)); - // fox z->1 and large a and b IB looses precision use complement function - if (z > 0.9 && n > 1 && m > 1) - return 1. - fdistribution_cdf_c(x, n, m, x0); - - return inc_beta(z, .5 * n, .5 * m); -} - -/** @summary Prob function - * @memberof Math */ -function Prob(chi2, ndf) { - if (ndf <= 0) - return 0; // Set CL to zero in case ndf <= 0 - - if (chi2 <= 0) { - if (chi2 < 0) - return 0; - else - return 1; - } - - return chisquared_cdf_c(chi2,ndf,0); -} - -/** @summary Gaus function - * @memberof Math */ -function Gaus(x, mean, sigma, norm) { - if (!sigma) - return 1e30; - const arg = (x - mean) / sigma; - if (arg < -39 || arg > 39) - return 0; - const res = Math.exp(-0.5 * arg * arg); - return norm ? res / (2.50662827463100024 * sigma) : res; // sqrt(2*Pi)=2.50662827463100024 -} - -/** @summary BreitWigner function - * @memberof Math */ -function BreitWigner(x, mean, gamma) { - return gamma / ((x - mean) * (x - mean) + gamma * gamma / 4) / 2 / Math.PI; -} - -/** @summary Calculates Beta-function Gamma(p)*Gamma(q)/Gamma(p+q). - * @memberof Math */ -function Beta(x,y) { - return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); -} - -/** @summary GammaDist function - * @memberof Math */ -function GammaDist(x, gamma, mu = 0, beta = 1) { - if ((x < mu) || (gamma <= 0) || (beta <= 0)) - return 0; - return gamma_pdf(x, gamma, beta, mu); -} - -/** @summary probability density function of Laplace distribution - * @memberof Math */ -function LaplaceDist(x, alpha = 0, beta = 1) { - return Math.exp(-Math.abs((x - alpha) / beta)) / (2. * beta); -} - -/** @summary distribution function of Laplace distribution - * @memberof Math */ -function LaplaceDistI(x, alpha = 0, beta = 1) { - return (x <= alpha) ? 0.5 * Math.exp(-Math.abs((x - alpha) / beta)) : 1 - 0.5 * Math.exp(-Math.abs((x - alpha) / beta)); -} - -/** @summary density function for Student's t- distribution - * @memberof Math */ -function Student(T, ndf) { - if (ndf < 1) - return 0; - - const r = ndf, - rh = 0.5 * r, - rh1 = rh + 0.5, - denom = Math.sqrt(r * Math.PI) * gamma(rh) * Math.pow(1 + T * T / r, rh1); - return gamma(rh1) / denom; -} - -/** @summary cumulative distribution function of Student's - * @memberof Math */ -function StudentI(T, ndf) { - const r = ndf; - - return (T > 0) - ? (1 - 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5)) - : 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5); -} - -/** @summary LogNormal function - * @memberof Math */ -function LogNormal(x, sigma, theta = 0, m = 1) { - if ((x < theta) || (sigma <= 0) || (m <= 0)) - return 0; - return lognormal_pdf(x, Math.log(m), sigma, theta); -} - -/** @summary Computes the probability density function of the Beta distribution - * @memberof Math */ -function BetaDist(x, p, q) { - if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) - return 0; - const beta = Beta(p, q); - return Math.pow(x, p - 1) * Math.pow(1 - x, q - 1) / beta; -} - -/** @summary Computes the distribution function of the Beta distribution. - * @memberof Math */ -function BetaDistI(x, p, q) { - if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) - return 0; - return BetaIncomplete(x, p, q); -} - -/** @summary gaus function for TFormula - * @memberof Math */ -function gaus(f, x, i) { - return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)); -} - -/** @summary gausn function for TFormula - * @memberof Math */ -function gausn(f, x, i) { - return gaus(f, x, i) / (Math.sqrt(2 * Math.PI) * f.GetParValue(i + 2)); -} - -/** @summary gausxy function for TFormula - * @memberof Math */ -function gausxy(f, x, y, i) { - return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)) - * Math.exp(-0.5 * Math.pow((y - f.GetParValue(i + 3)) / f.GetParValue(i + 4), 2)); -} - -/** @summary expo function for TFormula - * @memberof Math */ -function expo(f, x, i) { - return Math.exp(f.GetParValue(i + 0) + f.GetParValue(i + 1) * x); -} - -/** @summary landau function for TFormula - * @memberof Math */ -function landau(f, x, i) { - return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), false); -} - -/** @summary landaun function for TFormula - * @memberof Math */ -function landaun(f, x, i) { - return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), true); -} - -/** @summary Crystal ball function - * @memberof Math */ -function crystalball_function(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) - return 0.; - let z = (x - mean) / sigma; - if (alpha < 0) - z = -z; - const abs_alpha = Math.abs(alpha); - if (z > -abs_alpha) - return Math.exp(-0.5 * z * z); - const nDivAlpha = n / abs_alpha, - AA = Math.exp(-0.5 * abs_alpha * abs_alpha), - B = nDivAlpha - abs_alpha, - arg = nDivAlpha / (B - z); - return AA * Math.pow(arg,n); -} - -/** @summary pdf definition of the crystal_ball which is defined only for n > 1 otherwise integral is diverging - * @memberof Math */ -function crystalball_pdf(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) - return 0.; - if (n <= 1) - return Number.NaN; // pdf is not normalized for n <=1 - const abs_alpha = Math.abs(alpha), - C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), - D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), - N = 1. / (sigma * (C + D)); - return N * crystalball_function(x,alpha,n,sigma,mean); -} - -/** @summary compute the integral of the crystal ball function - * @memberof Math */ -function crystalball_integral(x, alpha, n, sigma, mean = 0) { - if (sigma == 0) - return 0; - if (alpha == 0) - return 0.; - const useLog = (n == 1.0), - abs_alpha = Math.abs(alpha); - - let z = (x - mean) / sigma, intgaus = 0., intpow = 0.; - if (alpha < 0 ) - z = -z; - - const sqrtpiover2 = Math.sqrt(M_PI / 2.), - sqrt2pi = Math.sqrt( 2. * M_PI), - oneoversqrt2 = 1. / Math.sqrt(2.); - if (z <= -abs_alpha) { - const A = Math.pow(n / abs_alpha,n) * Math.exp(-0.5 * alpha * alpha), - B = n / abs_alpha - abs_alpha; - - if (!useLog) { - const C = (n / abs_alpha) * (1. / (n - 1)) * Math.exp(-alpha * alpha / 2.); - intpow = C - A / (n - 1.) * Math.pow(B - z,-n + 1); - } - else { - // for n=1 the primitive of 1/x is log(x) - intpow = -A * Math.log( n / abs_alpha ) + A * Math.log(B - z); - } - intgaus = sqrtpiover2 * (1. + erf(abs_alpha * oneoversqrt2)); - } else { - intgaus = normal_cdf_c(z, 1); - intgaus *= sqrt2pi; - intpow = 0; - } - return sigma * (intgaus + intpow); -} - -/** @summary crystalball_cdf function - * @memberof Math */ -function crystalball_cdf(x, alpha, n, sigma, mean = 0) { - if (n <= 1.) - return Number.NaN; - - const abs_alpha = Math.abs(alpha), - C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), - D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), - totIntegral = sigma * (C + D), - integral = crystalball_integral(x,alpha,n,sigma,mean); - - return (alpha > 0) ? 1. - integral / totIntegral : integral / totIntegral; -} - -/** @summary crystalball_cdf_c function - * @memberof Math */ -function crystalball_cdf_c(x, alpha, n, sigma, mean = 0) { - if (n <= 1.) - return Number.NaN; - - const abs_alpha = Math.abs(alpha), - C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), - D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), - totIntegral = sigma * (C + D), - integral = crystalball_integral(x,alpha,n,sigma,mean); - - return (alpha > 0) ? integral / totIntegral : 1. - (integral / totIntegral); -} - -/** @summary ChebyshevN function - * @memberof Math */ -function ChebyshevN(n, x, c) { - let d1 = 0.0, d2 = 0.0; - const y2 = 2.0 * x; - - for (let i = n; i >= 1; i--) { - const temp = d1; - d1 = y2 * d1 - d2 + c[i]; - d2 = temp; - } - - return x * d1 - d2 + c[0]; -} - -/** @summary Chebyshev0 function - * @memberof Math */ -function Chebyshev0(_x, c0) { - return c0; -} - -/** @summary Chebyshev1 function - * @memberof Math */ -function Chebyshev1(x, c0, c1) { - return c0 + c1 * x; -} - -/** @summary Chebyshev2 function - * @memberof Math */ -function Chebyshev2(x, c0, c1, c2) { - return c0 + c1 * x + c2 * (2.0 * x * x - 1.0); -} - -/** @summary Chebyshev3 function - * @memberof Math */ -function Chebyshev3(x, ...args) { - return ChebyshevN(3, x, args); -} - -/** @summary Chebyshev4 function - * @memberof Math */ -function Chebyshev4(x, ...args) { - return ChebyshevN(4, x, args); -} - -/** @summary Chebyshev5 function - * @memberof Math */ -function Chebyshev5(x, ...args) { - return ChebyshevN(5, x, args); -} - -/** @summary Chebyshev6 function - * @memberof Math */ -function Chebyshev6(x, ...args) { - return ChebyshevN(6, x, args); -} - -/** @summary Chebyshev7 function - * @memberof Math */ -function Chebyshev7(x, ...args) { - return ChebyshevN(7, x, args); -} - -/** @summary Chebyshev8 function - * @memberof Math */ -function Chebyshev8(x, ...args) { - return ChebyshevN(8, x, args); -} - -/** @summary Chebyshev9 function - * @memberof Math */ -function Chebyshev9(x, ...args) { - return ChebyshevN(9, x, args); -} - -/** @summary Chebyshev10 function - * @memberof Math */ -function Chebyshev10(x, ...args) { - return ChebyshevN(10, x, args); -} - -// ========================================================================= - -/** @summary Calculate ClopperPearson - * @memberof Math */ -function eff_ClopperPearson(total,passed,level,bUpper) { - const alpha = (1.0 - level) / 2; - if (bUpper) - return ((passed == total) ? 1.0 : beta_quantile(1 - alpha,passed + 1,total - passed)); - - return ((passed == 0) ? 0.0 : beta_quantile(alpha,passed,total - passed + 1.0)); -} - -/** @summary Calculate normal - * @memberof Math */ -function eff_Normal(total,passed,level,bUpper) { - if (total == 0) - return bUpper ? 1 : 0; - - const alpha = (1.0 - level) / 2, - average = passed / total, - sigma = Math.sqrt(average * (1 - average) / total), - delta = normal_quantile(1 - alpha, sigma); - - if (bUpper) - return ((average + delta) > 1) ? 1.0 : (average + delta); - - return ((average - delta) < 0) ? 0.0 : (average - delta); -} - -/** @summary Calculates the boundaries for the frequentist Wilson interval - * @memberof Math */ -function eff_Wilson(total,passed,level,bUpper) { - const alpha = (1.0 - level) / 2; - if (total == 0) - return bUpper ? 1 : 0; - const average = passed / total, - kappa = normal_quantile(1 - alpha,1), - mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), - delta = kappa / (total + kappa * kappa) * Math.sqrt(total * average * (1 - average) + kappa * kappa / 4); - - if (bUpper) - return ((mode + delta) > 1) ? 1.0 : (mode + delta); - - return ((mode - delta) < 0) ? 0.0 : (mode - delta); -} - -/** @summary Calculates the boundaries for the frequentist Agresti-Coull interval - * @memberof Math */ -function eff_AgrestiCoull(total,passed,level,bUpper) { - const alpha = (1.0 - level) / 2, - kappa = normal_quantile(1 - alpha,1), - mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), - delta = kappa * Math.sqrt(mode * (1 - mode) / (total + kappa * kappa)); - - if (bUpper) - return ((mode + delta) > 1) ? 1.0 : (mode + delta); - - return ((mode - delta) < 0) ? 0.0 : (mode - delta); -} - -/** @summary Calculates the boundaries using the mid-P binomial - * @memberof Math */ -function eff_MidPInterval(total,passed,level,bUpper) { - const alpha = 1. - level, alpha_min = alpha / 2 , tol = 1e-9; // tolerance - let pmin = 0, pmax = 1, p = 0; - - // treat special case for 0 0 && passed < 1) { - const p0 = eff_MidPInterval(total, 0.0, level, bUpper), - p1 = eff_MidPInterval(total, 1.0, level, bUpper); - p = (p1 - p0) * passed + p0; - return p; - } - - while (Math.abs(pmax - pmin) > tol) { - p = (pmin + pmax) / 2; - // double v = 0.5 * ROOT::Math::binomial_pdf(int(passed), p, int(total)); - // make it work for non integer using the binomial - beta relationship - let v = 0.5 * beta_pdf(p, passed + 1., total - passed + 1) / (total + 1); - // if (passed > 0) - // v += ROOT::Math::binomial_cdf(int(passed - 1), p, int(total)); - // compute the binomial cdf at passed -1 - if ((passed - 1) >= 0) - v += beta_cdf_c(p, passed, total - passed + 1); - - const vmin = bUpper ? alpha_min : 1. - alpha_min; - if (v > vmin) - pmin = p; - else - pmax = p; - } - - return p; -} - -/** @summary for a central confidence interval for a Beta distribution - * @memberof Math */ -function eff_Bayesian(total,passed,level,bUpper,alpha,beta) { - const a = passed + alpha, - b = total - passed + beta; - if (bUpper) { - if ((a > 0) && (b > 0)) - return beta_quantile((1 + level) / 2,a,b); - else - return 1; - } else if ((a > 0) && (b > 0)) - return beta_quantile((1 - level) / 2,a,b); - - return 0; -} - -/** @summary Return function to calculate boundary of TEfficiency - * @memberof Math */ -function getTEfficiencyBoundaryFunc(option, isbayessian) { - const kFCP = 0, // Clopper-Pearson interval (recommended by PDG) - kFNormal = 1, // Normal approximation - kFWilson = 2, // Wilson interval - kFAC = 3, // Agresti-Coull interval - kFFC = 4, // Feldman-Cousins interval, too complicated for JavaScript - // kBJeffrey = 5, // Jeffrey interval (Prior ~ Beta(0.5,0.5) - // kBUniform = 6, // Prior ~ Uniform = Beta(1,1) - // kBBayesian = 7, // User specified Prior ~ Beta(fBeta_alpha,fBeta_beta) - kMidP = 8; // Mid-P Lancaster interval - - if (isbayessian) - return eff_Bayesian; - - switch (option) { - case kFCP: - return eff_ClopperPearson; - case kFNormal: - return eff_Normal; - case kFWilson: - return eff_Wilson; - case kFAC: - return eff_AgrestiCoull; - case kFFC: - console.log('Feldman-Cousins interval kFFC not supported; using kFCP'); - return eff_ClopperPearson; - case kMidP: - return eff_MidPInterval; - // case kBJeffrey: - // case kBUniform: - // case kBBayesian: return eff_ClopperPearson; - } - console.log(`Not recognized stat option ${option}, using kFCP`); - return eff_ClopperPearson; -} - -/** @summary Square function - * @memberof Math */ -function Sq(x) { - return x * x; -} - -/** @summary Pi function - * @memberof Math */ -function Pi() { - return Math.PI; -} - -/** @summary TwoPi function - * @memberof Math */ -function TwoPi() { - return 2 * Math.PI; -} - -/** @summary PiOver2 function - * @memberof Math */ -function PiOver2() -{ - return Math.PI / 2; -} - -/** @summary PiOver4 function - * @memberof Math */ -function PiOver4() -{ - return Math.PI / 4; -} - -/** @summary InvPi function - * @memberof Math */ -function InvPi() -{ - return 1 / Math.PI; -} - -var jsroot_math = /*#__PURE__*/Object.freeze({ -__proto__: null, -Beta: Beta, -BetaDist: BetaDist, -BetaDistI: BetaDistI, -BetaIncomplete: BetaIncomplete, -BreitWigner: BreitWigner, -Chebyshev0: Chebyshev0, -Chebyshev1: Chebyshev1, -Chebyshev10: Chebyshev10, -Chebyshev2: Chebyshev2, -Chebyshev3: Chebyshev3, -Chebyshev4: Chebyshev4, -Chebyshev5: Chebyshev5, -Chebyshev6: Chebyshev6, -Chebyshev7: Chebyshev7, -Chebyshev8: Chebyshev8, -Chebyshev9: Chebyshev9, -ChebyshevN: ChebyshevN, -FDist: fdistribution_pdf, -FDistI: fdistribution_cdf, -Gamma: gamma, -GammaDist: GammaDist, -Gaus: Gaus, -InvPi: InvPi, -Landau: Landau, -LaplaceDist: LaplaceDist, -LaplaceDistI: LaplaceDistI, -LogNormal: LogNormal, -Pi: Pi, -PiOver2: PiOver2, -PiOver4: PiOver4, -Polynomial1eval: Polynomial1eval, -Polynomialeval: Polynomialeval, -Prob: Prob, -Sq: Sq, -Student: Student, -StudentI: StudentI, -TwoPi: TwoPi, -beta: beta, -beta_cdf_c: beta_cdf_c, -beta_pdf: beta_pdf, -beta_quantile: beta_quantile, -breitwigner_cdf: breitwigner_cdf, -breitwigner_cdf_c: breitwigner_cdf_c, -cauchy_cdf: cauchy_cdf, -cauchy_cdf_c: cauchy_cdf_c, -cauchy_pdf: cauchy_pdf, -chisquared_cdf: chisquared_cdf, -chisquared_cdf_c: chisquared_cdf_c, -chisquared_pdf: chisquared_pdf, -crystalball_cdf: crystalball_cdf, -crystalball_cdf_c: crystalball_cdf_c, -crystalball_function: crystalball_function, -crystalball_pdf: crystalball_pdf, -erf: erf, -erfc: erfc, -expo: expo, -exponential_cdf: exponential_cdf, -exponential_cdf_c: exponential_cdf_c, -fdistribution_cdf: fdistribution_cdf, -fdistribution_cdf_c: fdistribution_cdf_c, -fdistribution_pdf: fdistribution_pdf, -gamma: gamma, -gamma_pdf: gamma_pdf, -gamma_quantile: gamma_quantile, -gamma_quantile_c: gamma_quantile_c, -gaus: gaus, -gausn: gausn, -gaussian_cdf: normal_cdf, -gaussian_cdf_c: normal_cdf_c, -gaussian_pdf: gaussian_pdf, -gausxy: gausxy, -getTEfficiencyBoundaryFunc: getTEfficiencyBoundaryFunc, -igam: igam, -igamc: igamc, -igami: igami, -inc_beta: inc_beta, -inc_gamma: inc_gamma, -inc_gamma_c: inc_gamma_c, -incbet: incbet, -incbi: incbi, -landau: landau, -landau_pdf: landau_pdf, -landaun: landaun, -lgam: lgam, -lgamma: lgamma, -lognormal_cdf: lognormal_cdf, -lognormal_cdf_c: lognormal_cdf_c, -lognormal_pdf: lognormal_pdf, -ndtri: ndtri, -normal_cdf: normal_cdf, -normal_cdf_c: normal_cdf_c, -normal_pdf: normal_pdf, -normal_quantile: normal_quantile, -normal_quantile_c: normal_quantile_c, -pseries: pseries, -stirf: stirf, -tdistribution_cdf: tdistribution_cdf, -tdistribution_cdf_c: tdistribution_cdf_c, -tdistribution_pdf: tdistribution_pdf, -tgamma: gamma -}); - -/** @summary Display progress message in the left bottom corner. - * @desc Previous message will be overwritten - * if no argument specified, any shown messages will be removed - * @param {string} msg - message to display - * @param {number} [tmout] - optional timeout in milliseconds, after message will disappear - * @param {Function} [click_handle] - optional handle to process click events - * @private */ -function showProgress(msg, tmout, click_handle) { - if (isBatchMode() || (typeof document === 'undefined')) - return; - - const id = 'jsroot_progressbox', modal = (settings.ProgressBox === 'modal') && isFunc(internals._modalProgress) ? internals._modalProgress : null; - let box = select('#' + id); - - if (!settings.ProgressBox) { - if (modal) - modal(); - return box.remove(); - } - - if (!arguments.length || !msg) { - if ((tmout !== -1) || (!box.empty() && box.property('with_timeout'))) - box.remove(); - if (modal) - modal(); - return; - } - - if (modal) { - box.remove(); - modal(msg, click_handle); - } else { - if (box.empty()) { - box = select(document.body) - .append('div').attr('id', id) - .attr('style', 'position: fixed; min-width: 100px; height: auto; overflow: visible; z-index: 101; border: 1px solid #999; background: #F8F8F8; left: 10px; bottom: 10px;'); - box.append('p'); - } - - box.property('with_timeout', false); - - const p = box.select('p'); - - if (isStr(msg)) { - p.html(msg) - .on('click', isFunc(click_handle) ? click_handle : null) - .attr('title', isFunc(click_handle) ? 'Click element to abort current operation' : ''); - } - - p.attr('style', 'font-size: 10px; margin-left: 10px; margin-right: 10px; margin-top: 3px; margin-bottom: 3px'); - } - - if (Number.isFinite(tmout) && (tmout > 0)) { - if (!box.empty()) - box.property('with_timeout', true); - setTimeout(() => showProgress('', -1), tmout); - } -} - -/** @summary Tries to close current browser tab - * @desc Many browsers do not allow simple window.close() call, - * therefore try several workarounds - * @private */ -function closeCurrentWindow() { - if (typeof window !== 'undefined') { - window.close(); - window.open('', '_self').close(); - } -} - -/** @summary Tries to open ui5 - * @private */ -function tryOpenOpenUI(sources, args) { - if (!sources?.length) { - if (isFunc(args.rejectFunc)) { - args.rejectFunc(Error('openui5 was not possible to load')); - args.rejectFunc = null; - } - return; - } - - // where to take openui5 sources - let src = sources.shift(); - - if ((src.indexOf('roothandler') === 0) && (src.indexOf('://') < 0)) - src = src.replace(/:\//g, '://'); - - if (settings.Debug) - console.log('Try openui5 from ' + src); - - const element = document.createElement('script'); - element.setAttribute('type', 'text/javascript'); - element.setAttribute('id', 'sap-ui-bootstrap'); - // use nojQuery while we are already load jquery and jquery-ui, later one can use directly sap-ui-core.js - - // this is location of openui5 scripts when working with THttpServer or when scripts are installed inside JSROOT - element.setAttribute('src', src + (args.ui5dbg ? 'resources/sap-ui-core-dbg.js' : 'resources/sap-ui-core.js')); // latest openui5 version - - element.setAttribute('data-sap-ui-libs', args.openui5libs ?? 'sap.m, sap.ui.layout, sap.ui.unified, sap.ui.commons'); - // element.setAttribute('data-sap-ui-language', args.openui5language ?? 'en'); - element.setAttribute('data-sap-ui-theme', args.openui5theme || (settings.DarkMode ? 'sap_fiori_3_dark' : 'sap_fiori_3')); - element.setAttribute('data-sap-ui-compatVersion', 'edge'); - element.setAttribute('data-sap-ui-async', 'true'); - // element.setAttribute('data-sap-ui-bindingSyntax', 'complex'); - - element.setAttribute('data-sap-ui-preload', 'async'); // '' to disable Component-preload.js - - element.setAttribute('data-sap-ui-evt-oninit', 'completeUI5Loading()'); - - element.onerror = function() { - // remove failed element - element.parentNode.removeChild(element); - // and try next - tryOpenOpenUI(sources, args); - }; - - element.onload = function() { - args.load_src = src; - }; - - document.head.appendChild(element); -} - - -/** @summary load openui5 - * @return {Promise} for loading ready - * @private */ -async function loadOpenui5(args) { - // very simple - openui5 was loaded before and will be used as is - if (typeof globalThis.sap === 'object') - return globalThis.sap; - - if (!args) - args = {}; - - let rootui5sys = exports.source_dir.replace(/jsrootsys/g, 'rootui5sys'); - - if (rootui5sys === exports.source_dir) { - // if jsrootsys location not detected, try to guess it - if (window.location.port && (window.location.pathname.indexOf('/win') >= 0) && (!args.openui5src || args.openui5src === 'nojsroot' || args.openui5src === 'jsroot')) - rootui5sys = window.location.origin + window.location.pathname + '../rootui5sys/'; - else - rootui5sys = undefined; - } - - const openui5_sources = []; - let openui5_dflt = 'https://openui5.hana.ondemand.com/1.152.0/', - openui5_root = rootui5sys ? rootui5sys + 'distribution/' : ''; - - if (isStr(args.openui5src)) { - switch (args.openui5src) { - case 'nodefault': - openui5_dflt = ''; - break; - case 'default': - openui5_sources.push(openui5_dflt); - openui5_dflt = ''; - break; - case 'nojsroot': - /* openui5_root = ''; */ - break; - case 'jsroot': - openui5_sources.push(openui5_root); - openui5_root = ''; - break; - default: - openui5_sources.push(args.openui5src); - break; - } - } else if (args.ui5dbg) - openui5_root = ''; // exclude ROOT version in debug mode - - if (openui5_root && (openui5_sources.indexOf(openui5_root) < 0)) - openui5_sources.push(openui5_root); - if (openui5_dflt && (openui5_sources.indexOf(openui5_dflt) < 0)) - openui5_sources.push(openui5_dflt); - - return new Promise((resolve, reject) => { - args.resolveFunc = resolve; - args.rejectFunc = reject; - - globalThis.completeUI5Loading = function() { - console.log(`Load openui5 version ${globalThis.sap.ui.version} from ${args.load_src}`); - - globalThis.sap.ui.loader.config({ - paths: { - jsroot: exports.source_dir, - rootui5: rootui5sys - } - }); - - if (args.resolveFunc) { - args.resolveFunc(globalThis.sap); - args.resolveFunc = null; - } - - delete globalThis.completeUI5Loading; - }; - - tryOpenOpenUI(openui5_sources, args); - }); -} - -/* eslint-disable @stylistic/js/key-spacing */ -/* eslint-disable @stylistic/js/comma-spacing */ -/* eslint-disable @stylistic/js/object-curly-spacing */ - -// some icons taken from http://uxrepo.com/ -const ToolbarIcons = { - camera: { path: 'M 152.00,304.00c0.00,57.438, 46.562,104.00, 104.00,104.00s 104.00-46.562, 104.00-104.00s-46.562-104.00-104.00-104.00S 152.00,246.562, 152.00,304.00z M 480.00,128.00L 368.00,128.00 c-8.00-32.00-16.00-64.00-48.00-64.00L 192.00,64.00 c-32.00,0.00-40.00,32.00-48.00,64.00L 32.00,128.00 c-17.60,0.00-32.00,14.40-32.00,32.00l0.00,288.00 c0.00,17.60, 14.40,32.00, 32.00,32.00l 448.00,0.00 c 17.60,0.00, 32.00-14.40, 32.00-32.00L 512.00,160.00 C 512.00,142.40, 497.60,128.00, 480.00,128.00z M 256.00,446.00c-78.425,0.00-142.00-63.574-142.00-142.00c0.00-78.425, 63.575-142.00, 142.00-142.00c 78.426,0.00, 142.00,63.575, 142.00,142.00 C 398.00,382.426, 334.427,446.00, 256.00,446.00z M 480.00,224.00l-64.00,0.00 l0.00-32.00 l 64.00,0.00 L 480.00,224.00 z' }, - disk: { path: 'M384,0H128H32C14.336,0,0,14.336,0,32v448c0,17.656,14.336,32,32,32h448c17.656,0,32-14.344,32-32V96L416,0H384z M352,160 V32h32v128c0,17.664-14.344,32-32,32H160c-17.664,0-32-14.336-32-32V32h128v128H352z M96,288c0-17.656,14.336-32,32-32h256 c17.656,0,32,14.344,32,32v192H96V288z' }, - question: { path: 'M256,512c141.375,0,256-114.625,256-256S397.375,0,256,0S0,114.625,0,256S114.625,512,256,512z M256,64 c63.719,0,128,36.484,128,118.016c0,47.453-23.531,84.516-69.891,110.016C300.672,299.422,288,314.047,288,320 c0,17.656-14.344,32-32,32c-17.664,0-32-14.344-32-32c0-40.609,37.25-71.938,59.266-84.031 C315.625,218.109,320,198.656,320,182.016C320,135.008,279.906,128,256,128c-30.812,0-64,20.227-64,64.672 c0,17.664-14.336,32-32,32s-32-14.336-32-32C128,109.086,193.953,64,256,64z M256,449.406c-18.211,0-32.961-14.75-32.961-32.969 c0-18.188,14.75-32.953,32.961-32.953c18.219,0,32.969,14.766,32.969,32.953C288.969,434.656,274.219,449.406,256,449.406z' }, - undo: { path: 'M450.159,48.042c8.791,9.032,16.983,18.898,24.59,29.604c7.594,10.706,14.146,22.207,19.668,34.489 c5.509,12.296,9.82,25.269,12.92,38.938c3.113,13.669,4.663,27.834,4.663,42.499c0,14.256-1.511,28.863-4.532,43.822 c-3.009,14.952-7.997,30.217-14.953,45.795c-6.955,15.577-16.202,31.52-27.755,47.826s-25.88,32.9-42.942,49.807 c-5.51,5.444-11.787,11.67-18.834,18.651c-7.033,6.98-14.496,14.366-22.39,22.168c-7.88,7.802-15.955,15.825-24.187,24.069 c-8.258,8.231-16.333,16.203-24.252,23.888c-18.3,18.13-37.354,37.016-57.191,56.65l-56.84-57.445 c19.596-19.472,38.54-38.279,56.84-56.41c7.75-7.685,15.772-15.604,24.108-23.757s16.438-16.163,24.33-24.057 c7.894-7.893,15.356-15.33,22.402-22.312c7.034-6.98,13.312-13.193,18.821-18.651c22.351-22.402,39.165-44.648,50.471-66.738 c11.279-22.09,16.932-43.567,16.932-64.446c0-15.785-3.217-31.005-9.638-45.671c-6.422-14.665-16.229-28.504-29.437-41.529 c-3.282-3.282-7.358-6.395-12.217-9.325c-4.871-2.938-10.381-5.503-16.516-7.697c-6.121-2.201-12.815-3.992-20.058-5.373 c-7.242-1.374-14.9-2.064-23.002-2.064c-8.218,0-16.802,0.834-25.788,2.507c-8.961,1.674-18.053,4.429-27.222,8.271 c-9.189,3.842-18.456,8.869-27.808,15.089c-9.358,6.219-18.521,13.819-27.502,22.793l-59.92,60.271l93.797,94.058H0V40.91 l93.27,91.597l60.181-60.532c13.376-15.018,27.222-27.248,41.536-36.697c14.308-9.443,28.608-16.776,42.89-21.992 c14.288-5.223,28.505-8.74,42.623-10.557C294.645,0.905,308.189,0,321.162,0c13.429,0,26.389,1.185,38.84,3.562 c12.478,2.377,24.2,5.718,35.192,10.029c11.006,4.311,21.126,9.404,30.374,15.265C434.79,34.724,442.995,41.119,450.159,48.042z' }, - arrow_right: { path: 'M30.796,226.318h377.533L294.938,339.682c-11.899,11.906-11.899,31.184,0,43.084c11.887,11.899,31.19,11.893,43.077,0 l165.393-165.386c5.725-5.712,8.924-13.453,8.924-21.539c0-8.092-3.213-15.84-8.924-21.551L338.016,8.925 C332.065,2.975,324.278,0,316.478,0c-7.802,0-15.603,2.968-21.539,8.918c-11.899,11.906-11.899,31.184,0,43.084l113.391,113.384 H30.796c-16.822,0-30.463,13.645-30.463,30.463C0.333,212.674,13.974,226.318,30.796,226.318z' }, - arrow_up: { path: 'M295.505,629.446V135.957l148.193,148.206c15.555,15.559,40.753,15.559,56.308,0c15.555-15.538,15.546-40.767,0-56.304 L283.83,11.662C276.372,4.204,266.236,0,255.68,0c-10.568,0-20.705,4.204-28.172,11.662L11.333,227.859 c-7.777,7.777-11.666,17.965-11.666,28.158c0,10.192,3.88,20.385,11.657,28.158c15.563,15.555,40.762,15.555,56.317,0 l148.201-148.219v493.489c0,21.993,17.837,39.82,39.82,39.82C277.669,669.267,295.505,651.439,295.505,629.446z' }, - arrow_diag: { path: 'M279.875,511.994c-1.292,0-2.607-0.102-3.924-0.312c-10.944-1.771-19.333-10.676-20.457-21.71L233.97,278.348 L22.345,256.823c-11.029-1.119-19.928-9.51-21.698-20.461c-1.776-10.944,4.031-21.716,14.145-26.262L477.792,2.149 c9.282-4.163,20.167-2.165,27.355,5.024c7.201,7.189,9.199,18.086,5.024,27.356L302.22,497.527 C298.224,506.426,289.397,511.994,279.875,511.994z M118.277,217.332l140.534,14.294c11.567,1.178,20.718,10.335,21.878,21.896 l14.294,140.519l144.09-320.792L118.277,217.332z' }, - auto_zoom: { path: 'M505.441,242.47l-78.303-78.291c-9.18-9.177-24.048-9.171-33.216,0c-9.169,9.172-9.169,24.045,0.006,33.217l38.193,38.188 H280.088V80.194l38.188,38.199c4.587,4.584,10.596,6.881,16.605,6.881c6.003,0,12.018-2.297,16.605-6.875 c9.174-9.172,9.174-24.039,0.011-33.217L273.219,6.881C268.803,2.471,262.834,0,256.596,0c-6.229,0-12.202,2.471-16.605,6.881 l-78.296,78.302c-9.178,9.172-9.178,24.045,0,33.217c9.177,9.171,24.051,9.171,33.21,0l38.205-38.205v155.4H80.521l38.2-38.188 c9.177-9.171,9.177-24.039,0.005-33.216c-9.171-9.172-24.039-9.178-33.216,0L7.208,242.464c-4.404,4.403-6.881,10.381-6.881,16.611 c0,6.227,2.477,12.207,6.881,16.61l78.302,78.291c4.587,4.581,10.599,6.875,16.605,6.875c6.006,0,12.023-2.294,16.61-6.881 c9.172-9.174,9.172-24.036-0.005-33.211l-38.205-38.199h152.593v152.063l-38.199-38.211c-9.171-9.18-24.039-9.18-33.216-0.022 c-9.178,9.18-9.178,24.059-0.006,33.222l78.284,78.302c4.41,4.404,10.382,6.881,16.611,6.881c6.233,0,12.208-2.477,16.611-6.881 l78.302-78.296c9.181-9.18,9.181-24.048,0-33.205c-9.174-9.174-24.054-9.174-33.21,0l-38.199,38.188v-152.04h152.051l-38.205,38.199 c-9.18,9.175-9.18,24.037-0.005,33.211c4.587,4.587,10.596,6.881,16.604,6.881c6.01,0,12.024-2.294,16.605-6.875l78.303-78.285 c4.403-4.403,6.887-10.378,6.887-16.611C512.328,252.851,509.845,246.873,505.441,242.47z' }, - statbox: { - path: 'M28.782,56.902H483.88c15.707,0,28.451-12.74,28.451-28.451C512.331,12.741,499.599,0,483.885,0H28.782 C13.074,0,0.331,12.741,0.331,28.451C0.331,44.162,13.074,56.902,28.782,56.902z' + - 'M483.885,136.845H28.782c-15.708,0-28.451,12.741-28.451,28.451c0,15.711,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.74,28.451-28.451C512.331,149.586,499.599,136.845,483.885,136.845z' + - 'M483.885,273.275H28.782c-15.708,0-28.451,12.731-28.451,28.452c0,15.707,12.744,28.451,28.451,28.451H483.88 c15.707,0,28.451-12.744,28.451-28.451C512.337,286.007,499.599,273.275,483.885,273.275z' + - 'M256.065,409.704H30.492c-15.708,0-28.451,12.731-28.451,28.451c0,15.707,12.744,28.451,28.451,28.451h225.585 c15.707,0,28.451-12.744,28.451-28.451C284.516,422.436,271.785,409.704,256.065,409.704z' - }, - circle: { path: 'M256,256 m-150,0 a150,150 0 1,0 300,0 a150,150 0 1,0 -300,0' }, - three_circles: { path: 'M256,85 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,255 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 M256,425 m-70,0 a70,70 0 1,0 140,0 a70,70 0 1,0 -140,0 ' }, - diamand: { path: 'M256,0L384,256L256,511L128,256z' }, - rect: { path: 'M90,90h352v352h-352z' }, - cross: { path: 'M80,40l176,176l176,-176l40,40l-176,176l176,176l-40,40l-176,-176l-176,176l-40,-40l176,-176l-176,-176z' }, - vrgoggles: { size: '245.82 141.73', path: 'M175.56,111.37c-22.52,0-40.77-18.84-40.77-42.07S153,27.24,175.56,27.24s40.77,18.84,40.77,42.07S198.08,111.37,175.56,111.37ZM26.84,69.31c0-23.23,18.25-42.07,40.77-42.07s40.77,18.84,40.77,42.07-18.26,42.07-40.77,42.07S26.84,92.54,26.84,69.31ZM27.27,0C11.54,0,0,12.34,0,28.58V110.9c0,16.24,11.54,30.83,27.27,30.83H99.57c2.17,0,4.19-1.83,5.4-3.7L116.47,118a8,8,0,0,1,12.52-.18l11.51,20.34c1.2,1.86,3.22,3.61,5.39,3.61h72.29c15.74,0,27.63-14.6,27.63-30.83V28.58C245.82,12.34,233.93,0,218.19,0H27.27Z' }, - th2colorz: { recs: [{ x: 128, y: 486, w: 256, h: 26, f: 'rgb(38,62,168)' }, { y: 461, f: 'rgb(22,82,205)' }, { y: 435, f: 'rgb(16,100,220)' }, { y: 410, f: 'rgb(18,114,217)' }, { y: 384, f: 'rgb(20,129,214)' }, { y: 358, f: 'rgb(14,143,209)' }, { y: 333, f: 'rgb(9,157,204)' }, { y: 307, f: 'rgb(13,167,195)' }, { y: 282, f: 'rgb(30,175,179)' }, { y: 256, f: 'rgb(46,183,164)' }, { y: 230, f: 'rgb(82,186,146)' }, { y: 205, f: 'rgb(116,189,129)' }, { y: 179, f: 'rgb(149,190,113)' }, { y: 154, f: 'rgb(179,189,101)' }, { y: 128, f: 'rgb(209,187,89)' }, { y: 102, f: 'rgb(226,192,75)' }, { y: 77, f: 'rgb(244,198,59)' }, { y: 51, f: 'rgb(253,210,43)' }, { y: 26, f: 'rgb(251,230,29)' }, { y: 0, f: 'rgb(249,249,15)' }] }, - th2color: { recs: [{x:0,y:256,w:13,h:39,f:'rgb(38,62,168)'},{x:13,y:371,w:39,h:39},{y:294,h:39},{y:256,h:39},{y:218,h:39},{x:51,y:410,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:90,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294},{y:256,h:39,f:'rgb(16,100,220)'},{y:218,h:39},{y:179,h:39,f:'rgb(22,82,205)'},{y:141,h:39},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:0,h:27},{x:128,y:448,w:39,h:39},{y:410},{y:371,h:39},{y:333,h:39,f:'rgb(22,82,205)'},{y:294,f:'rgb(20,129,214)'},{y:256,h:39,f:'rgb(9,157,204)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(20,129,214)'},{y:141,h:39,f:'rgb(16,100,220)'},{y:102,h:39,f:'rgb(22,82,205)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{y:0,h:27},{x:166,y:486,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(82,186,146)'},{y:256,h:39,f:'rgb(179,189,101)'},{y:218,h:39,f:'rgb(116,189,129)'},{y:179,h:39,f:'rgb(82,186,146)'},{y:141,h:39,f:'rgb(14,143,209)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:205,y:486,w:39,h:14},{y:448,h:39},{y:410},{y:371,h:39,f:'rgb(16,100,220)'},{y:333,h:39,f:'rgb(9,157,204)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(244,198,59)'},{y:218,h:39},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(13,167,195)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(22,82,205)'},{y:26,h:39,f:'rgb(38,62,168)'},{x:243,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(30,175,179)'},{y:294,f:'rgb(209,187,89)'},{y:256,h:39,f:'rgb(251,230,29)'},{y:218,h:39,f:'rgb(249,249,15)'},{y:179,h:39,f:'rgb(226,192,75)'},{y:141,h:39,f:'rgb(30,175,179)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:282,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(18,114,217)'},{y:333,h:39,f:'rgb(14,143,209)'},{y:294,f:'rgb(149,190,113)'},{y:256,h:39,f:'rgb(226,192,75)'},{y:218,h:39,f:'rgb(244,198,59)'},{y:179,h:39,f:'rgb(149,190,113)'},{y:141,h:39,f:'rgb(9,157,204)'},{y:102,h:39,f:'rgb(18,114,217)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:320,y:448,w:39,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39,f:'rgb(20,129,214)'},{y:294,f:'rgb(46,183,164)'},{y:256,h:39},{y:218,h:39,f:'rgb(82,186,146)'},{y:179,h:39,f:'rgb(9,157,204)'},{y:141,h:39,f:'rgb(20,129,214)'},{y:102,h:39,f:'rgb(16,100,220)'},{y:64,f:'rgb(38,62,168)'},{y:26,h:39},{x:358,y:448,h:39},{y:410},{y:371,h:39,f:'rgb(22,82,205)'},{y:333,h:39},{y:294,f:'rgb(16,100,220)'},{y:256,h:39,f:'rgb(20,129,214)'},{y:218,h:39,f:'rgb(14,143,209)'},{y:179,h:39,f:'rgb(18,114,217)'},{y:141,h:39,f:'rgb(22,82,205)'},{y:102,h:39,f:'rgb(38,62,168)'},{y:64},{y:26,h:39},{x:397,y:448,w:39,h:39},{y:371,h:39},{y:333,h:39},{y:294,f:'rgb(22,82,205)'},{y:256,h:39},{y:218,h:39},{y:179,h:39,f:'rgb(38,62,168)'},{y:141,h:39},{y:102,h:39},{y:64},{y:26,h:39},{x:435,y:410,h:39},{y:371,h:39},{y:333,h:39},{y:294},{y:256,h:39},{y:218,h:39},{y:179,h:39},{y:141,h:39},{y:102,h:39},{y:64},{x:474,y:256,h:39},{y:179,h:39}] }, - th2draw3d: { - path: 'M172.768,0H51.726C23.202,0,0.002,23.194,0.002,51.712v89.918c0,28.512,23.2,51.718,51.724,51.718h121.042 c28.518,0,51.724-23.2,51.724-51.718V51.712C224.486,23.194,201.286,0,172.768,0z M177.512,141.63c0,2.611-2.124,4.745-4.75,4.745 H51.726c-2.626,0-4.751-2.134-4.751-4.745V51.712c0-2.614,2.125-4.739,4.751-4.739h121.042c2.62,0,4.75,2.125,4.75,4.739 L177.512,141.63L177.512,141.63z ' + - 'M460.293,0H339.237c-28.521,0-51.721,23.194-51.721,51.712v89.918c0,28.512,23.2,51.718,51.721,51.718h121.045 c28.521,0,51.721-23.2,51.721-51.718V51.712C512.002,23.194,488.802,0,460.293,0z M465.03,141.63c0,2.611-2.122,4.745-4.748,4.745 H339.237c-2.614,0-4.747-2.128-4.747-4.745V51.712c0-2.614,2.133-4.739,4.747-4.739h121.045c2.626,0,4.748,2.125,4.748,4.739 V141.63z ' + - 'M172.768,256.149H51.726c-28.524,0-51.724,23.205-51.724,51.726v89.915c0,28.504,23.2,51.715,51.724,51.715h121.042 c28.518,0,51.724-23.199,51.724-51.715v-89.915C224.486,279.354,201.286,256.149,172.768,256.149z M177.512,397.784 c0,2.615-2.124,4.736-4.75,4.736H51.726c-2.626-0.006-4.751-2.121-4.751-4.736v-89.909c0-2.626,2.125-4.753,4.751-4.753h121.042 c2.62,0,4.75,2.116,4.75,4.753L177.512,397.784L177.512,397.784z ' + - 'M460.293,256.149H339.237c-28.521,0-51.721,23.199-51.721,51.726v89.915c0,28.504,23.2,51.715,51.721,51.715h121.045 c28.521,0,51.721-23.199,51.721-51.715v-89.915C512.002,279.354,488.802,256.149,460.293,256.149z M465.03,397.784 c0,2.615-2.122,4.736-4.748,4.736H339.237c-2.614,0-4.747-2.121-4.747-4.736v-89.909c0-2.626,2.121-4.753,4.747-4.753h121.045 c2.615,0,4.748,2.116,4.748,4.753V397.784z' - }, - logo: { - szx: 500, szy: 160, - paths: [{ - d: 'M5,30l25,-25h440l25,25v100l-25,25h-440l-25,-25v-100z', - s: 'fill:#E6F1FB;stroke:#378ADD;stroke-width:2.5px' - }], - txt: { x:250, y:80, 'text-anchor': 'middle', 'dominant-baseline':'central', 'font-family': 'monospace', 'font-weight': 700, 'font-size': 130, fill: '#0C447C' } - }, - - /* eslint-enable @stylistic/js/key-spacing */ - /* eslint-enable @stylistic/js/comma-spacing */ - /* eslint-enable @stylistic/js/object-curly-spacing */ - - createSVG(group, btn, size, title, arg) { - const scale = btn.szx && btn.szy ? btn.szx / btn.szy : 1, - use_dark = (arg === true) || (arg === false) ? arg : settings.DarkMode, - opacity0 = (arg === 'browser') ? (browser.touches ? 0.2 : 0) : (use_dark ? 0.8 : 0.2), - svg = group.append('svg:svg') - .attr('width', Math.round(size * scale) + 'px') - .attr('height', size + 'px') - .attr('viewBox', `0 0 ${btn.szx ?? 512} ${btn.szy ?? 512}`) - .style('overflow', 'hidden') - .style('cursor', 'pointer') - .style('fill', use_dark ? 'rgba(255, 224, 160)' : 'steelblue') - .style('opacity', opacity0) - .property('opacity0', opacity0) - .property('opacity1', use_dark ? 1 : 0.8) - .on('mouseenter', function() { - const elem = select(this); - elem.style('opacity', elem.property('opacity1')); - const func = elem.node()._mouseenter; - if (isFunc(func)) - func(); - }) - .on('mouseleave', function() { - const elem = select(this); - elem.style('opacity', elem.property('opacity0')); - const func = elem.node()._mouseleave; - if (isFunc(func)) - func(); - }); - - if (btn.recs) { - const rec = {}; - btn.recs.forEach(elem => { - Object.assign(rec, elem); - svg.append('rect').attr('x', rec.x).attr('y', rec.y) - .attr('width', rec.w).attr('height', rec.h) - .style('fill', rec.f); - }); - } else if (btn.paths) { - btn.paths.forEach(elem => { - svg.append('path').attr('d', elem.d).attr('style', elem.s); - }); - if (btn.txt) { - const el = svg.append('text').text('JSROOT'); - Object.entries(btn.txt).forEach(([k, v]) => el.attr(k, v)); - } - } else - svg.append('svg:path').attr('d', btn.path); - - // special rect to correctly get mouse events for whole button area - svg.append('svg:rect').attr('x', 0).attr('y', 0).attr('width', Math.round(scale * 512)).attr('height', 512) - .style('opacity', 0).style('fill', 'none').style('pointer-events', 'visibleFill') - .append('svg:title').text(title); - - return svg; - } - -}; // ToolbarIcons - - -/** @summary Register handle to react on window resize - * @desc function used to react on browser window resize event - * While many resize events could come in short time, - * resize will be handled with delay after last resize event - * @param {object|string} handle can be function or object with checkResize function or dom where painting was done - * @param {number} [delay] - one could specify delay after which resize event will be handled - * @protected */ -function registerForResize(handle, delay) { - if (!handle || isBatchMode() || (typeof window === 'undefined') || (typeof document === 'undefined')) - return; - - let myInterval = null; - - function ResizeTimer() { - myInterval = null; - - document.body.style.cursor = 'wait'; - if (isFunc(handle)) - handle(); - else if (isFunc(handle?.checkResize)) - handle.checkResize(); - else { - const node = new BasePainter(handle).selectDom(); - if (!node.empty()) { - const mdi = node.property('mdi'); - if (isFunc(mdi?.checkMDIResize)) - mdi.checkMDIResize(); - else - resize(node.node()); - } - } - document.body.style.cursor = 'auto'; - } - - window.addEventListener('resize', () => { - if (myInterval) - clearTimeout(myInterval); - myInterval = setTimeout(ResizeTimer, Math.max(20, delay || 300)); - }); -} - -/** @summary Detect mouse right button - * @private */ -function detectRightButton(event) { - return (event?.buttons === 2) || (event?.button === 2); -} - -/** @summary Add move handlers for drawn element - * @private */ -function addMoveHandler(painter, enabled = true, hover_handler = false) { - if (!settings.MoveResize || painter.isBatchMode() || !painter.getG()) - return; - - if (painter.getPadPainter()?.isEditable() === false) - enabled = false; - - if (!enabled) { - if (painter.getG().property('assigned_move')) { - const drag_move = drag().subject(Object); - drag_move.on('start', null).on('drag', null).on('end', null); - painter.getG() - .style('cursor', null) - .property('assigned_move', null) - .call(drag_move); - } - return; - } - - if (painter.getG().property('assigned_move')) - return; - - const drag_move = drag().subject(Object); - let not_changed = true, move_disabled = false; - - drag_move - .on('start', function(evnt) { - move_disabled = this.moveEnabled ? !this.moveEnabled() : false; - if (move_disabled || detectRightButton(evnt.sourceEvent)) - return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - const pos = pointer(evnt, this.getG().node()); - not_changed = true; - if (this.moveStart) - this.moveStart(pos[0], pos[1], evnt.sourceEvent); - }.bind(painter)).on('drag', function(evnt) { - if (move_disabled) - return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - not_changed = false; - if (this.moveDrag) - this.moveDrag(evnt.dx, evnt.dy, evnt.sourceEvent); - }.bind(painter)).on('end', function(evnt) { - if (move_disabled) - return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - if (this.moveEnd) - this.moveEnd(not_changed, evnt.sourceEvent); - - let arg = null; - if (not_changed) { - // if not changed - provide click position - const pos = pointer(evnt, this.getG().node()); - arg = { x: pos[0], y: pos[1], dbl: false }; - } - this.getPadPainter()?.selectObjectPainter(this, arg); - }.bind(painter)); - - painter.getG() - .style('cursor', hover_handler ? 'pointer' : 'move') - .property('assigned_move', true) - .call(drag_move); - - if (hover_handler) { - painter.getG().on('mouseenter', () => painter.getG().style('text-decoration', 'underline')) - .on('mouseleave', () => painter.getG().style('text-decoration', null)); - } -} - -/** @summary Inject style - * @param {string} code - css string - * @private */ -function injectStyle(code, node, tag) { - if (isBatchMode() || !code || (typeof document === 'undefined')) - return true; - - const styles = (node || document).getElementsByTagName('style'); - for (let n = 0; n < styles.length; ++n) { - if (tag && styles[n].getAttribute('tag') === tag) { - styles[n].innerText = code; - return true; - } - - if (styles[n].innerText === code) - return true; - } - - const element = document.createElement('style'); - if (tag) - element.setAttribute('tag', tag); - element.innerText = code; - (node || document.head).appendChild(element); - return true; -} - -/** @summary Select predefined style - * @private */ -function selectgStyle(name) { - gStyle.fName = name; - switch (name) { - case 'Modern': Object.assign(gStyle, { fFrameBorderMode: 0, fFrameFillColor: 0, - fCanvasBorderMode: 0, fCanvasColor: 0, fPadBorderMode: 0, fPadColor: 0, fStatColor: 0, - fTitleAlign: 23, fTitleX: 0.5, fTitleBorderSize: 0, fTitleColor: 0, fTitleStyle: 0, - fOptStat: 1111, fStatY: 0.935, - fLegendBorderSize: 1, fLegendFont: 42, fLegendTextSize: 0, fLegendFillColor: 0 }); - break; - case 'Plain': Object.assign(gStyle, { fFrameBorderMode: 0, - fCanvasBorderMode: 0, fPadBorderMode: 0, fPadColor: 0, fCanvasColor: 0, - fTitleColor: 0, fTitleBorderSize: 0, fStatColor: 0, fStatBorderSize: 1, fLegendBorderSize: 1 }); - break; - case 'Bold': Object.assign(gStyle, { fCanvasColor: 10, fCanvasBorderMode: 0, - fFrameLineWidth: 3, fFrameFillColor: 10, - fPadColor: 10, fPadTickX: 1, fPadTickY: 1, fPadBottomMargin: 0.15, fPadLeftMargin: 0.15, - fTitleColor: 10, fTitleTextColor: 600, fStatColor: 10 }); - break; - } -} - -let _storage_prefix = 'jsroot_'; - -/** @summary Set custom prefix for the local storage - * @private */ -function setStoragePrefix(prefix) { - _storage_prefix = prefix || 'jsroot_'; -} - -/** @summary Save object in local storage - * @private */ -function saveLocalStorage(obj, expires, name) { - if (isNodeJs() || (typeof localStorage === 'undefined')) - return; - if (Number.isFinite(expires) && (expires < 0)) - localStorage.removeItem(_storage_prefix + name); - else - localStorage.setItem(_storage_prefix + name, btoa_func(JSON.stringify(obj))); -} - -/** @summary Read object from storage with specified name - * @private */ -function readLocalStorage(name) { - if (isNodeJs() || (typeof localStorage === 'undefined')) - return null; - const v = localStorage.getItem(_storage_prefix + name), - s = v ? JSON.parse(atob_func(v)) : null; - return isObject(s) ? s : null; -} - -/** @summary Save JSROOT settings in local storage - * @param {number} [expires] - delete settings when negative - * @param {string} [name] - storage name, 'settings' by default - * @private */ -function saveSettings(expires = 365, name = 'settings') { - saveLocalStorage(settings, expires, name); -} - -/** @summary Read JSROOT settings from specified cookie parameter - * @param {boolean} only_check - when true just checks if settings were stored before with provided name - * @param {string} [name] - storage name, 'settings' by default - * @private */ -function readSettings(only_check = false, name = 'settings') { - const s = readLocalStorage(name); - if (!s) - return false; - if (!only_check) - Object.assign(settings, s); - return true; -} - -/** @summary Save JSROOT gStyle object in local storage - * @param {number} [expires] - delete style when negative - * @param {string} [name] - storage name, 'style' by default - * @private */ -function saveStyle(expires = 365, name = 'style') { - saveLocalStorage(gStyle, expires, name); -} - -/** @summary Read JSROOT gStyle object from local storage - * @param {boolean} [only_check] - when true just checks if settings were stored before with provided name - * @param {string} [name] - storage name, 'style' by default - * @private */ -function readStyle(only_check = false, name = 'style') { - const s = readLocalStorage(name); - if (!s) - return false; - if (!only_check) - Object.assign(gStyle, s); - return true; -} - -let _saveFileFunc = null; - -/** @summary Returns image file content as it should be stored on the disc - * @desc Replaces all kind of base64 coding - * @private */ -function getBinFileContent(content) { - if (content.indexOf(prSVG) === 0) - return decodeURIComponent(content.slice(prSVG.length)); - - if (content.indexOf(prJSON) === 0) - return decodeURIComponent(content.slice(prJSON.length)); - - if ((content.indexOf('data:image/') === 0) || (content.indexOf('data:application/pdf') === 0)) { - const p = content.indexOf('base64,'); - if (p > 0) - return atob_func(content.slice(p + 7)); - } - - return content; -} - -/** @summary Returns type of file content - * @private */ -function getContentType(content) { - if (content.indexOf('data:')) - return ''; - - const p = content.indexOf(';'); - return (p > 0) ? content.slice(5, p) : ''; -} - -/** @summary Function store content as file with filename - * @private */ -async function saveFile(filename, content) { - if (isFunc(_saveFileFunc)) - return _saveFileFunc(filename, getBinFileContent(content)); - if (isNodeJs()) { - return import('fs').then(fs => { - fs.writeFileSync(filename, getBinFileContent(content)); - return true; - }); - } else if (typeof document === 'undefined') - return false; - - const a = document.createElement('a'); - a.download = filename; - a.style.display = 'none'; - let fileURL = ''; - const contentType = getContentType(content); - - if ((content.length > 1e6) && (contentType === 'application/pdf')) { - // large PDF files do not work in the browser with plain base64 coding - const bindata = getBinFileContent(content), - len = bindata.length, - buffer = new ArrayBuffer(len), - view = new DataView(buffer, 0, len); - for (let i = 0; i < len; ++i) - view.setUint8(i, bindata.charCodeAt(i)); - const blob = new Blob([buffer], { type: contentType }); - fileURL = URL.createObjectURL(blob); - a.href = fileURL; - } else - a.href = content; - - document.body.appendChild(a); - - return new Promise(resolve => { - a.addEventListener('click', () => { - if (fileURL) { - setTimeout(() => { - a.parentNode.removeChild(a); - URL.revokeObjectURL(fileURL); - }, 3000); - } else - a.parentNode.removeChild(a); - resolve(true); - }); - a.click(); - }); -} - -/** @summary Function store content as file with filename - * @private */ -function setSaveFile(func) { - _saveFileFunc = func; -} - -/** @summary Returns color id for the color - * @private */ -function getColorId(col) { - const arr = getRootColors(); - let id = -1; - if (isStr(col)) { - if (!col || (col === 'none')) - id = 0; - else { - for (let k = 1; k < arr.length; ++k) { - if (arr[k] === col) { - id = k; - break; - } - } - } - if ((id < 0) && (col.indexOf('rgb') === 0)) - id = 9999; - } else if (Number.isInteger(col) && arr[col]) { - id = col; - col = arr[id]; - } - - return { id, col }; -} - -/** @summary Produce exec string for WebCanvas to set color value - * @desc Color can be id or string, but should belong to list of known colors - * For higher color numbers TColor::GetColor(r,g,b) will be invoked to ensure color is exists - * @private */ -function getColorExec(col, method, extra_arg) { - const d = getColorId(col); - - if (d.id < 0) - return ''; - - if (!extra_arg) - extra_arg = ''; - else - extra_arg += ','; - - // for higher color numbers ensure that such color exists - if (d.id >= 50) { - const c = color(d.col); - d.id = `TColor::GetColor(${c.r},${c.g},${c.b})`; - } - - return `exec:${method}(${extra_arg}${d.id})`; -} - -/** @summary Change object member in the painter - * @desc Used when interactively change in the menu - * Special handling for color is provided - * @private */ -function changeObjectMember(painter, member, val, is_color) { - if (is_color) { - const d = getColorId(val); - if ((d.id < 0) || (d.id === 9999)) - return; - val = d.id; - } - - const obj = painter?.getObject(); - if (obj && (obj[member] !== undefined)) - obj[member] = val; -} - -Object.assign(internals.jsroot, { addMoveHandler, registerForResize }); - -const kToFront = '__front__', kNoReorder = '__no_reorder', - sDfltName = 'root_ctx_menu', sDfltDlg = '_dialog', - sSub = 'sub:', sEndsub = 'endsub:', - sColumn = 'column:', sEndcolumn = 'endcolumn:', - sSeparator = 'separator', sHeader = 'header:'; - -/** - * @summary Abstract class for creating context menu - * - * @desc Use {@link createMenu} to create instance of the menu - * @private - */ - -class JSRootMenu { - - constructor(painter, menuname, show_event) { - this.painter = painter; - this.menuname = menuname; - if (isObject(show_event) && (show_event.clientX !== undefined) && (show_event.clientY !== undefined)) - this.show_evnt = { clientX: show_event.clientX, clientY: show_event.clientY, skip_close: show_event.skip_close }; - - this.remove_handler = () => this.remove(); - this.element = null; - this.cnt = 0; - } - - native() { return false; } - - async load() { return this; } - - /** @summary Returns object with mouse event position when context menu was activated - * @desc Return object will have members 'clientX' and 'clientY' */ - getEventPosition() { return this.show_evnt; } - - add(/* name, arg, func, title */) { - throw Error('add() method has to be implemented in the menu'); - } - - /** @summary Returns menu size */ - size() { return this.cnt; } - - /** @summary Close and remove menu */ - remove() { - if (!this.element) - return; - - if (this.show_evnt?.skip_close) { - this.show_evnt.skip_close = 0; - return; - } - - this.element.remove(); - this.element = null; - if (isFunc(this.resolveFunc)) { - const func = this.resolveFunc; - delete this.resolveFunc; - func(); - } - document.body.removeEventListener('click', this.remove_handler); - } - - show(/* event */) { - throw Error('show() method has to be implemented in the menu class'); - } - - /** @summary Add checked menu item - * @param {boolean} flag - flag - * @param {string} name - item name - * @param {number|string} arg - argument associated with the checked item - * @param {Function} func - func called when item is selected - * @param {string} [title] - optional title */ - addchk(flag, name, arg, func, title) { - let handler = func; - if (isFunc(arg)) { - title = func; - func = arg; - handler = res => func(res === '1'); - arg = flag ? '0' : '1'; - } - this.add((flag ? 'chk:' : 'unk:') + name, arg, handler, title); - } - - /** @summary Add sub-menu */ - sub(name, arg, func, title) { - this.add(sSub + name, arg, func, title); - } - - /** @summary Mark end of submenu */ - endsub() { this.add(sEndsub); } - - /** @summary Start column with items */ - column() { this.add(sColumn); } - - /** @summary End column with items */ - endcolumn() { this.add(sEndcolumn); } - - /** @summary Add separator */ - separator() { this.add(sSeparator); } - - /** @summary Add menu header - must be first entry */ - header(name, title) { - if (name.length > 25) { - if (!title) - title = name; - else if (isStr(title) && title.indexOf('https://') === 0) - title = name + title; - name = name.slice(0, 22) + '...'; - } - this.add(sHeader + name, undefined, undefined, title); - } - - /** @summary Add draw sub-menu with draw options - * @protected */ - addDrawMenu(top_name, opts, call_back, title) { - if (!opts || !opts.length) - return; - - let without_sub = false; - if (top_name.indexOf('nosub:') === 0) { - without_sub = true; - top_name = top_name.slice(6); - } - - if (opts.length === 1) { - if (opts[0] === kInspect) - top_name = top_name.replace('Draw', 'Inspect'); - this.add(top_name, opts[0], call_back); - return; - } - - const used = {}; - - if (!without_sub) - this.sub(top_name, opts[0], call_back, title); - - if ((opts.indexOf('') >= 0) && (!without_sub || opts[0])) - this.add(this._use_plain_text ? '' : '<dflt>', '', call_back); - - for (let i = 0; i < opts.length; ++i) { - let name = opts[i]; - if (!name || used[name]) - continue; - used[name] = true; - - const group = []; - if (opts.length > 5) { - // check if there are similar options, which can be grouped again - for (let i2 = i + 1; i2 < opts.length; ++i2) { - if (opts[i2] && !used[opts[i2]] && (opts[i2].indexOf(name) === 0)) - group.push(opts[i2]); - else if (name.length < 4) - break; - } - } - - if (without_sub) - name = top_name + ' ' + name; - - if (group.length) { - this.sub(name, opts[i], call_back); - group.forEach(sub => { - this.add(sub, sub, call_back); - used[sub] = true; - }); - this.endsub(); - } else if (name === kInspect) { - this.sub(name, opts[i], call_back, 'Inspect object content'); - for (let k = 0; k < 10; ++k) - this.add(k.toString(), kInspect + k, call_back, `Inspect object and expand to level ${k}`); - this.endsub(); - } else - this.add(name, opts[i], call_back); - } - if (!without_sub) { - this.add('', () => { - const opt = isFunc(this.painter?.getDrawOpt) ? this.painter.getDrawOpt() : opts[0]; - this.input('Provide draw option', opt, 'text').then(call_back); - }, 'Enter draw option in dialog'); - this.endsub(); - } - } - - /** @summary Add redraw menu for the painter - * @protected */ - addRedrawMenu(painter) { - if (!painter || !isFunc(painter.redrawWith) || !isFunc(painter.getSupportedDrawOptions)) - return false; - - const opts = painter.getSupportedDrawOptions(); - - this.addDrawMenu(`Draw ${painter.getClassName()} with`, opts, arg => { - if ((arg.indexOf(kInspect) === 0) && isFunc(painter.showInspector)) - return painter.showInspector(arg); - - painter.redrawWith(arg); - }); - return true; - } - - /** @summary Add color selection menu entries - * @protected */ - addColorMenu(name, value, set_func, fill_kind) { - if (value === undefined) - return; - const useid = !isStr(value); - this.sub(name, () => { - this.input('Enter color ' + (useid ? '(only id number)' : '(name or id)'), value, useid ? 'int' : 'text', useid ? 0 : undefined, useid ? 9999 : undefined).then(col => { - const id = parseInt(col); - if (Number.isInteger(id) && getColor(id)) - col = getColor(id); - else if (useid) - return; - - set_func(useid ? id : col); - }); - }); - - for (let ncolumn = 0; ncolumn < 5; ++ncolumn) { - this.column(); - - for (let nrow = 0; nrow < 10; nrow++) { - let n = ncolumn * 10 + nrow; - if (!useid) - --n; // use -1 as none color - - const col = (n < 0) || ((n === 0) && (fill_kind === 1)) ? 'none' : getColor(n), - lbl = (n <= 0) || ((col[0] !== '#') && (col.indexOf('rgb') < 0)) ? col : `col ${n}`, - fill = (n === 1) ? 'white' : 'black', - stroke = (n === 1) ? 'red' : 'black', - rect = (value === (useid ? n : col)) ? `` : '', - svg = `${rect}${lbl}`; - - this.add(svg, (useid ? n : col), res => set_func(useid ? parseInt(res) : res), 'Select color ' + col); - } - - this.endcolumn(); - if (!this.native()) - break; - } - - this.endsub(); - } - - /** @summary Add size selection menu entries - * @protected */ - addSizeMenu(name, min, max, step, size_value, set_func, title) { - if (size_value === undefined) - return; - - let values = [], miss_current = false; - if (isObject(step)) { - values = step; - step = 1; - } else { - for (let sz = min; sz <= max; sz += step) - values.push(sz); - } - - const match = v => Math.abs(v - size_value) < (max - min) * 1e-5, - conv = (v, more) => { - if ((v === size_value) && miss_current) - more = true; - if (step >= 1) - return v.toFixed(0); - if (step >= 0.1) - return v.toFixed(more ? 2 : 1); - return v.toFixed(more ? 4 : 2); - }; - - if (values.findIndex(match) < 0) { - miss_current = true; - values.push(size_value); - values = values.sort((a, b) => a - b); - } - - this.sub(name, () => this.input('Enter value of ' + name, conv(size_value, true), (step >= 1) ? 'int' : 'float').then(set_func), title); - values.forEach(v => this.addchk(match(v), conv(v), v, res => set_func((step >= 1) ? Number.parseInt(res) : Number.parseFloat(res)))); - this.endsub(); - } - - /** @summary Add log scale selection for pad - * @protected */ - addPadLogMenu(kind, value, func) { - this.sub('SetLog ' + kind, - () => this.input('Enter log kind: 0 - off, 1 - log10, 2 - log2, 3 - ln, ...', value, 'int', 0, 10000).then(func)); - this.addchk(value === 0, 'linear', () => func(0)); - this.addchk(value === 1, 'log10', () => func(1)); - this.addchk(value === 2, 'log2', () => func(2)); - this.addchk(value === 3, 'ln', () => func(3)); - this.addchk(value === 4, 'log4', () => func(4)); - this.addchk(value === 8, 'log8', () => func(8)); - this.endsub(); - } - - /** @summary Add palette menu entries - * @protected */ - addPaletteMenu(curr, set_func) { - const add = (id, name, title, more) => { - if (!name) - name = `pal ${id}`; - else if (!title) - title = name; - if (title) - title += `, code ${id}`; - this.addchk((id === curr) || more, name, id, set_func, title || name); - }; - - this.sub('Palette', () => this.input('Enter palette code [1..113]', curr, 'int', 1, 113).then(set_func)); - - this.column(); - add(57, 'Bird', 'Default color palette', (curr > 113)); - add(55, 'Rainbow'); - add(51, 'Deep Sea'); - add(52, 'Grayscale', 'New gray scale'); - add(1, '', 'Old gray scale', (curr > 0) && (curr < 10)); - add(50, 'ROOT 5', 'Default color palette in ROOT 5', (curr >= 10) && (curr < 51)); - add(53, '', 'Dark body radiator'); - add(54, '', 'Two-color hue'); - add(56, '', 'Inverted dark body radiator'); - add(58, 'Cubehelix'); - add(59, '', 'Green Red Violet'); - add(60, '', 'Blue Red Yellow'); - add(61, 'Ocean'); - this.endcolumn(); - - if (!this.native()) - return this.endsub(); - - this.column(); - add(62, '', 'Color Printable On Grey'); - add(63, 'Alpine'); - add(64, 'Aquamarine'); - add(65, 'Army'); - add(66, 'Atlantic'); - add(67, 'Aurora'); - add(68, 'Avocado'); - add(69, 'Beach'); - add(70, 'Black Body'); - add(71, '', 'Blue Green Yellow'); - add(72, 'Brown Cyan'); - add(73, 'CMYK'); - add(74, 'Candy'); - this.endcolumn(); - - this.column(); - add(75, 'Cherry'); - add(76, 'Coffee'); - add(77, '', 'Dark Rain Bow'); - add(78, '', 'Dark Terrain'); - add(79, 'Fall'); - add(80, 'Fruit Punch'); - add(81, 'Fuchsia'); - add(82, 'Grey Yellow'); - add(83, '', 'Green Brown Terrain'); - add(84, 'Green Pink'); - add(85, 'Island'); - add(86, 'Lake'); - add(87, '', 'Light Temperature'); - this.endcolumn(); - - this.column(); - add(88, '', 'Light Terrain'); - add(89, 'Mint'); - add(90, 'Neon'); - add(91, 'Pastel'); - add(92, 'Pearl'); - add(93, 'Pigeon'); - add(94, 'Plum'); - add(95, 'Red Blue'); - add(96, 'Rose'); - add(97, 'Rust'); - add(98, '', 'Sandy Terrain'); - add(99, 'Sienna'); - add(100, 'Solar'); - this.endcolumn(); - - this.column(); - add(101, '', 'South West'); - add(102, '', 'Starry Night'); - add(103, '', 'Sunset'); - add(104, '', 'Temperature Map'); - add(105, '', 'Thermometer'); - add(106, 'Valentine'); - add(107, '', 'Visible Spectrum'); - add(108, '', 'Water Melon'); - add(109, 'Cool'); - add(110, 'Copper'); - add(111, '', 'Gist Earth'); - add(112, 'Viridis'); - add(113, 'Cividis'); - this.endcolumn(); - - this.endsub(); - } - - /** @summary Add rebin menu entries - * @protected */ - addRebinMenu(rebin_func) { - this.sub('Rebin', () => this.input('Enter rebin value', 2, 'int', 2).then(rebin_func)); - for (let sz = 2; sz <= 7; sz++) - this.add(sz.toString(), sz, res => rebin_func(parseInt(res))); - this.endsub(); - } - - /** @summary Add selection menu entries - * @param {string} name - name of submenu - * @param {Array} values - array of string entries used as list for selection - * @param {string|number} value - currently selected value, either name or index - * @param {Function} set_func - function called when item selected, either name or index depending from value parameter - * @param {string} [title] - optional title for menu items - * @protected */ - addSelectMenu(name, values, value, set_func, title) { - const use_number = (typeof value === 'number'); - this.sub(name, undefined, undefined, title); - for (let n = 0; n < values.length; ++n) - this.addchk(use_number ? (n === value) : (values[n] === value), values[n], use_number ? n : values[n], res => set_func(use_number ? Number.parseInt(res) : res)); - this.endsub(); - } - - /** @summary Add RColor selection menu entries - * @protected */ - addRColorMenu(name, value, set_func) { - const colors = ['default', 'black', 'white', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan']; - - this.sub(name, () => { - this.input('Enter color name - empty string will reset color', value).then(set_func); - }); - let fillcol = 'black'; - for (let n = 0; n < colors.length; ++n) { - const coltxt = colors[n]; - let match = false, bkgr = ''; - if (n > 0) { - bkgr = 'background-color:' + coltxt; - fillcol = (coltxt === 'white') ? 'black' : 'white'; - - if (isStr(value) && value && (value !== 'auto') && (value[0] !== '[')) - match = (rgb(value).toString() === rgb(coltxt).toString()); - } else - match = !value; - - const svg = `${coltxt}`; - this.addchk(match, svg, coltxt, res => set_func(res === 'default' ? null : res)); - } - this.endsub(); - } - - /** @summary Add items to change RAttrText - * @protected */ - addRAttrTextItems(fontHandler, opts, set_func) { - if (!opts) - opts = {}; - this.addRColorMenu('color', fontHandler.color, value => set_func({ name: 'color', value })); - if (fontHandler.scaled) - this.addSizeMenu('size', 0.01, 0.10, 0.01, fontHandler.size / fontHandler.scale, value => set_func({ name: 'size', value })); - else - this.addSizeMenu('size', 6, 20, 2, fontHandler.size, value => set_func({ name: 'size', value })); - - this.addSelectMenu('family', [kArial, 'Times New Roman', 'Courier New', 'Symbol'], fontHandler.name, value => set_func({ name: 'font_family', value })); - - this.addSelectMenu('style', ['normal', 'italic', 'oblique'], fontHandler.style || 'normal', res => set_func({ name: 'font_style', value: res === 'normal' ? null : res })); - - this.addSelectMenu('weight', ['normal', 'lighter', 'bold', 'bolder'], fontHandler.weight || 'normal', res => set_func({ name: 'font_weight', value: res === 'normal' ? null : res })); - - if (!opts.noalign) - this.add('align'); - if (!opts.noangle) - this.add('angle'); - } - - /** @summary Add line style menu - * @private */ - addLineStyleMenu(name, value, set_func) { - this.sub(name, () => this.input('Enter line style id (1-solid)', value, 'int', 1, 11).then(val => { - if (getSvgLineStyle(val)) - set_func(val); - })); - for (let n = 1; n < 11; ++n) { - const dash = getSvgLineStyle(n), - svg = `${n}`; - - this.addchk((value === n), svg, n, arg => set_func(parseInt(arg))); - } - this.endsub(); - } - - /** @summary Add fill style menu - * @private */ - addFillStyleMenu(name, value, color_index, set_func) { - this.sub(name, () => { - this.input('Enter fill style id (1001-solid, 3100..4000)', value, 'int', 0, 4000).then(id => { - if ((id >= 0) && (id <= 4000)) - set_func(id); - }); - }); - - const supported = [1, 1001]; - for (let k = 3001; k < 3025; ++k) - supported.push(k); - supported.push(3144, 3244, 3344, 3305, 3315, 3325, 3490, 3481, 3472); - - for (let n = 0; n < supported.length; ++n) { - if (n % 7 === 0) - this.column(); - - const selected = (value === supported[n]); - - if (typeof document !== 'undefined') { - const svgelement = select(document.createElement('svg')), - handler = new TAttFillHandler({ color: color_index || 1, pattern: supported[n], svg: svgelement }); - svgelement.attr('width', 60).attr('height', 24); - if (selected) - svgelement.append('rect').attr('x', 0).attr('y', 0).attr('width', 60).attr('height', 24).style('stroke', 'red').style('fill', 'none').style('stroke-width', '3px'); - svgelement.append('rect').attr('x', 3).attr('y', 3).attr('width', 54).attr('height', 18).style('stroke', 'none').call(handler.func); - this.add(svgelement.node().outerHTML, supported[n], arg => set_func(parseInt(arg)), `Pattern : ${supported[n]}` + (selected ? ' Active' : '')); - } else - this.addchk(selected, supported[n].toString(), supported[n], arg => set_func(parseInt(arg))); - if (n % 7 === 6) - this.endcolumn(); - } - this.endsub(); - } - - /** @summary Add font selection menu - * @private */ - addFontMenu(name, value, set_func) { - const prec = value && Number.isInteger(value) ? value % 10 : 2; - - this.sub(name, () => { - this.input('Enter font id from [0..20]', Math.floor(value / 10), 'int', 0, 20).then(id => { - if ((id >= 0) && (id <= 20)) - set_func(id * 10 + prec); - }); - }); - - this.column(); - - const doc = getDocument(); - - for (let n = 1; n < 20; ++n) { - const id = n * 10 + prec, - handler = new FontHandler(id, 14), - txt = select(doc.createElementNS(nsSVG, 'text')); - let fullname = handler.getFontName(), qual = ''; - if (handler.weight) { - qual += 'b'; - fullname += ' ' + handler.weight; - } - if (handler.style) { - qual += handler.style[0]; - fullname += ' ' + handler.style; - } - if (qual) - qual = ' ' + qual; - txt.attr('x', 1).attr('y', 15).text(fullname.split(' ')[0] + qual); - handler.setFont(txt); - - const rect = (value !== id) ? '' : '', - svg = `${txt.node().outerHTML}${rect}`; - this.add(svg, id, arg => set_func(parseInt(arg)), `${id}: ${fullname}`); - - if (n === 10) { - this.endcolumn(); - this.column(); - } - } - - this.endcolumn(); - this.endsub(); - } - - /** @summary Add align selection menu - * @private */ - addAlignMenu(name, value, set_func) { - this.sub(name, () => { - this.input('Enter align like 12 or 31', value).then(arg => { - const id = parseInt(arg); - if ((id < 11) || (id > 33)) - return; - const h = Math.floor(id / 10), v = id % 10; - if ((h > 0) && (h < 4) && (v > 0) && (v < 4)) - set_func(id); - }); - }); - - const hnames = ['left', 'middle', 'right'], vnames = ['bottom', 'centered', 'top']; - for (let h = 1; h < 4; ++h) { - for (let v = 1; v < 4; ++v) - this.addchk(h * 10 + v === value, `${h * 10 + v}: ${hnames[h - 1]} ${vnames[v - 1]}`, h * 10 + v, arg => set_func(parseInt(arg))); - } - - this.endsub(); - } - - /** @summary Fill context menu for graphical attributes in painter - * @desc this method used to fill entries for different attributes of the object - * like TAttFill, TAttLine, TAttText - * There is special handling for the frame where attributes handled by the pad - * @private */ - addAttributesMenu(painter, prefix) { - const is_frame = painter === painter.getFramePainter(), - pp = is_frame ? painter.getPadPainter() : null, - redraw_arg = !prefix && !is_frame ? 'attribute' : true; - if (!prefix) - prefix = ''; - - if (painter.lineatt?.used) { - this.sub(`${prefix}Line att`); - this.addSizeMenu('width', 1, 10, 1, painter.lineatt.width, arg => { - painter.lineatt.change(undefined, arg); - changeObjectMember(painter, 'fLineWidth', arg); - changeObjectMember(pp, 'fFrameLineWidth', arg); - painter.interactiveRedraw(redraw_arg, `exec:SetLineWidth(${arg})`); - }); - if (!painter.lineatt.nocolor) { - this.addColorMenu('color', painter.lineatt.color, arg => { - painter.lineatt.change(arg); - changeObjectMember(painter, 'fLineColor', arg, true); - changeObjectMember(pp, 'fFrameLineColor', arg, true); - painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetLineColor')); - }); - } - this.addLineStyleMenu('style', painter.lineatt.style, id => { - painter.lineatt.change(undefined, undefined, id); - changeObjectMember(painter, 'fLineStyle', id); - changeObjectMember(pp, 'fFrameLineStyle', id); - painter.interactiveRedraw(redraw_arg, `exec:SetLineStyle(${id})`); - }); - this.endsub(); - - if (!is_frame && painter.lineatt?.excl_side) { - this.sub('Exclusion'); - this.sub('side'); - for (let side = -1; side <= 1; ++side) { - this.addchk((painter.lineatt.excl_side === side), side, side, - arg => { painter.lineatt.changeExcl(parseInt(arg)); painter.interactiveRedraw(); }); - } - this.endsub(); - - this.addSizeMenu('width', 10, 100, 10, painter.lineatt.excl_width, - arg => { painter.lineatt.changeExcl(undefined, arg); painter.interactiveRedraw(); }); - - this.endsub(); - } - } - - if (painter.fillatt?.used) { - this.sub(`${prefix}Fill att`); - this.addColorMenu('color', painter.fillatt.colorindx, arg => { - painter.fillatt.change(arg, undefined, painter.getCanvSvg()); - changeObjectMember(painter, 'fFillColor', arg, true); - changeObjectMember(pp, 'fFrameFillColor', arg, true); - painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetFillColor')); - }, painter.fillatt.kind); - this.addFillStyleMenu('style', painter.fillatt.pattern, painter.fillatt.colorindx, id => { - painter.fillatt.change(undefined, id, painter.getCanvSvg()); - changeObjectMember(painter, 'fFillStyle', id); - changeObjectMember(pp, 'fFrameFillStyle', id); - painter.interactiveRedraw(redraw_arg, `exec:SetFillStyle(${id})`); - }); - this.endsub(); - } - - if (painter.markeratt?.used) { - this.sub(`${prefix}Marker att`); - this.addColorMenu('color', painter.markeratt.color, arg => { - changeObjectMember(painter, 'fMarkerColor', arg, true); - painter.markeratt.change(arg); - painter.interactiveRedraw(redraw_arg, getColorExec(arg, 'SetMarkerColor')); - }); - this.addSizeMenu('size', 0.5, 6, 0.5, painter.markeratt.size, arg => { - changeObjectMember(painter, 'fMarkerSize', arg); - painter.markeratt.change(undefined, undefined, arg); - painter.interactiveRedraw(redraw_arg, `exec:SetMarkerSize(${arg})`); - }); - - this.sub('style'); - const supported = [1, 2, 3, 4, 5, 6, 7, 8, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34]; - - for (let n = 0; n < supported.length; ++n) { - const clone = new TAttMarkerHandler({ style: supported[n], color: painter.markeratt.color, size: 1.7 }), - svg = `${supported[n].toString()}`; - - this.addchk(painter.markeratt.style === supported[n], svg, supported[n], - arg => { painter.markeratt.change(undefined, parseInt(arg)); painter.interactiveRedraw(redraw_arg, `exec:SetMarkerStyle(${arg})`); }); - } - this.endsub(); - this.endsub(); - } - - if (painter.textatt?.used) { - this.sub(`${prefix}Text att`); - - this.addFontMenu('font', painter.textatt.font, arg => { - changeObjectMember(painter, 'fTextFont', arg); - painter.textatt.change(arg); - painter.interactiveRedraw(true, `exec:SetTextFont(${arg})`); - }); - - const rel = painter.textatt.size < 1.0; - - this.addSizeMenu('size', rel ? 0.03 : 6, rel ? 0.20 : 26, rel ? 0.01 : 2, painter.textatt.size, arg => { - changeObjectMember(painter, 'fTextSize', arg); - painter.textatt.change(undefined, arg); - painter.interactiveRedraw(true, `exec:SetTextSize(${arg})`); - }); - - this.addColorMenu('color', painter.textatt.color, arg => { - changeObjectMember(painter, 'fTextColor', arg, true); - painter.textatt.change(undefined, undefined, arg); - painter.interactiveRedraw(true, getColorExec(arg, 'SetTextColor')); - }); - - this.addAlignMenu('align', painter.textatt.align, arg => { - changeObjectMember(painter, 'fTextAlign', arg); - painter.textatt.change(undefined, undefined, undefined, arg); - painter.interactiveRedraw(true, `exec:SetTextAlign(${arg})`); - }); - - if (painter.textatt.can_rotate) { - this.addSizeMenu('angle', -180, 180, 45, painter.textatt.angle, arg => { - changeObjectMember(painter, 'fTextAngle', arg); - painter.textatt.change(undefined, undefined, undefined, undefined, arg); - painter.interactiveRedraw(true, `exec:SetTextAngle(${arg})`); - }); - } - - this.endsub(); - } - } - - /** @summary Fill context menu for axis - * @private */ - addTAxisMenu(EAxisBits, painter, faxis, kind, axis_painter, frame_painter) { - const is_gaxis = faxis._typename === clTGaxis; - - this.add('Divisions', () => this.input('Set Ndivisions', is_gaxis ? faxis.fNdiv : faxis.fNdivisions, 'int').then(val => { - if (is_gaxis) - faxis.fNdiv = val; - else - faxis.fNdivisions = val; - painter.interactiveRedraw('pad', `exec:SetNdivisions(${val})`, kind); - })); - - if (kind !== 'v') { - this.sub('Labels'); - this.addchk(faxis.TestBit(EAxisBits.kCenterLabels), 'Center', - arg => { faxis.SetBit(EAxisBits.kCenterLabels, arg); painter.interactiveRedraw('pad', `exec:CenterLabels(${arg})`, kind); }); - this.addchk(faxis.TestBit(EAxisBits.kLabelsVert), 'Rotate', - arg => { faxis.SetBit(EAxisBits.kLabelsVert, arg); painter.interactiveRedraw('pad', `exec:SetBit(TAxis::kLabelsVert,${arg})`, kind); }); - this.addColorMenu('Color', faxis.fLabelColor, - arg => { faxis.fLabelColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLabelColor'), kind); }); - this.addSizeMenu('Offset', -0.02, 0.1, 0.01, faxis.fLabelOffset, - arg => { faxis.fLabelOffset = arg; painter.interactiveRedraw('pad', `exec:SetLabelOffset(${arg})`, kind); }); - const a = faxis.fLabelSize >= 1; - this.addSizeMenu('Size', a ? 2 : 0.02, a ? 30 : 0.11, a ? 2 : 0.01, faxis.fLabelSize, - arg => { faxis.fLabelSize = arg; painter.interactiveRedraw('pad', `exec:SetLabelSize(${arg})`, kind); }); - - if (frame_painter && (axis_painter?.kind === kAxisLabels) && (faxis.fNbins > 20)) { - this.add('Find label', () => this.input('Label id').then(id => { - if (!id) - return; - for (let bin = 0; bin < faxis.fNbins; ++bin) { - const lbl = axis_painter.formatLabels(bin); - if (lbl === id) - return frame_painter.zoomSingle(kind, Math.max(0, bin - 4), Math.min(faxis.fNbins, bin + 5)); - } - }), 'Zoom into region around specific label'); - } - if (frame_painter && faxis.fLabels) { - const ignore = `${kind}_ignore_labels`; - this.addchk(!frame_painter[ignore], 'Custom', flag => { - frame_painter[ignore] = !flag; - painter.interactiveRedraw('pad'); - }, `Use of custom labels in axis ${kind}`); - } - this.endsub(); - } - - this.sub('Title'); - this.add('SetTitle', () => { - this.input('Enter axis title', faxis.fTitle).then(t => { - faxis.fTitle = t; - painter.interactiveRedraw('pad', `exec:SetTitle("${t}")`, kind); - }); - }); - this.addchk(faxis.TestBit(EAxisBits.kCenterTitle), 'Center', - arg => { faxis.SetBit(EAxisBits.kCenterTitle, arg); painter.interactiveRedraw('pad', `exec:CenterTitle(${arg})`, kind); }); - if (!painter?.hasSnapId()) { - this.addchk(faxis.TestBit(EAxisBits.kOppositeTitle), 'Opposite', - arg => { faxis.SetBit(EAxisBits.kOppositeTitle, arg); painter.redrawPad(); }); - } - this.addchk(faxis.TestBit(EAxisBits.kRotateTitle), 'Rotate', - arg => { faxis.SetBit(EAxisBits.kRotateTitle, arg); painter.interactiveRedraw('pad', is_gaxis ? `exec:SetBit(TAxis::kRotateTitle, ${arg})` : `exec:RotateTitle(${arg})`, kind); }); - this.addColorMenu('Color', is_gaxis ? faxis.fTextColor : faxis.fTitleColor, arg => { - if (is_gaxis) - faxis.fTextColor = arg; - else - faxis.fTitleColor = arg; - - painter.interactiveRedraw('pad', getColorExec(arg, 'SetTitleColor'), kind); - }); - this.addSizeMenu('Offset', 0, 3, 0.2, faxis.fTitleOffset, - arg => { faxis.fTitleOffset = arg; painter.interactiveRedraw('pad', `exec:SetTitleOffset(${arg})`, kind); }); - const p = faxis.fTitleSize >= 1; - this.addSizeMenu('Size', p ? 2 : 0.02, p ? 30 : 0.11, p ? 2 : 0.01, faxis.fTitleSize, - arg => { faxis.fTitleSize = arg; painter.interactiveRedraw('pad', `exec:SetTitleSize(${arg})`, kind); }); - this.endsub(); - - this.sub('Ticks'); - if (is_gaxis) { - this.addColorMenu('Color', faxis.fLineColor, - arg => { faxis.fLineColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetLineColor'), kind); }); - this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickSize, - arg => { faxis.fTickSize = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); - } else { - this.addColorMenu('Color', faxis.fAxisColor, - arg => { faxis.fAxisColor = arg; painter.interactiveRedraw('pad', getColorExec(arg, 'SetAxisColor'), kind); }); - this.addSizeMenu('Size', -0.05, 0.055, 0.01, faxis.fTickLength, - arg => { faxis.fTickLength = arg; painter.interactiveRedraw('pad', `exec:SetTickLength(${arg})`, kind); }); - } - this.endsub(); - - if (is_gaxis) { - this.add('Options', () => this.input('Enter TGaxis options like +L or -G', faxis.fChopt, 'string').then(arg => { - faxis.fChopt = arg; - painter.interactiveRedraw('pad', `exec:SetOption("${arg}")`, kind); - })); - } - } - - /** @summary Fill menu to edit settings properties - * @private */ - addSettingsMenu(with_hierarchy, alone, handle_func) { - if (!isFunc(handle_func)) - handle_func = () => {}; - if (!alone) - this.sub('Settings'); - else if (isStr(alone)) - this.header(alone, 'https://root.cern/js/'); - else - this.header('Settings'); - - this.sub('Files'); - - if (with_hierarchy) { - this.addchk(settings.OnlyLastCycle, 'Last cycle', flag => { - settings.OnlyLastCycle = flag; - handle_func('refresh'); - }); - - this.addchk(!settings.SkipStreamerInfos, 'Streamer infos', flag => { - settings.SkipStreamerInfos = !flag; - handle_func('refresh'); - }); - } - - this.addchk(settings.UseStamp, 'Use stamp arg', flag => { settings.UseStamp = flag; }); - this.addSizeMenu('Max ranges', 1, 1000, [1, 10, 20, 50, 200, 1000], settings.MaxRanges, value => { settings.MaxRanges = value; }, 'Maximal number of ranges in single http request'); - - this.addchk(settings.HandleWrongHttpResponse, 'Handle wrong http response', flag => { settings.HandleWrongHttpResponse = flag; }, 'Let detect and solve problem when server returns wrong Content-Length header, see https://github.com/root-project/jsroot/issues/189'); - this.addchk(settings.WithCredentials, 'With credentials', flag => { settings.WithCredentials = flag; }, 'Submit http request with user credentials'); - - this.endsub(); - - this.sub('Toolbar'); - this.addchk(settings.ToolBar === false, 'Off', flag => { settings.ToolBar = !flag; }); - this.addchk(settings.ToolBar === true, 'On', flag => { settings.ToolBar = flag; }); - this.addchk(settings.ToolBar === 'popup', 'Popup', flag => { settings.ToolBar = flag ? 'popup' : false; }); - this.separator(); - this.addchk(settings.ToolBarSide === 'left', 'Left side', flag => { settings.ToolBarSide = flag ? 'left' : 'right'; }); - this.addchk(settings.ToolBarVert, 'Vertical', flag => { settings.ToolBarVert = flag; }); - this.endsub(); - - this.sub('Interactive'); - this.addchk(settings.Tooltip, 'Tooltip', flag => { settings.Tooltip = flag; }); - this.addchk(settings.ContextMenu, 'Context menus', flag => { settings.ContextMenu = flag; }); - this.sub('Zooming'); - this.addchk(settings.Zooming, 'Global', flag => { settings.Zooming = flag; }); - this.addchk(settings.ZoomMouse, 'Mouse', flag => { settings.ZoomMouse = flag; }); - this.addchk(settings.ZoomWheel, 'Wheel', flag => { settings.ZoomWheel = flag; }); - this.addchk(settings.ZoomTouch, 'Touch', flag => { settings.ZoomTouch = flag; }); - this.endsub(); - this.addchk(settings.HandleKeys, 'Keypress handling', flag => { settings.HandleKeys = flag; }); - this.addchk(!settings.UserSelect, 'User select', flag => { settings.UserSelect = flag ? '' : 'none'; }, 'Set "user-select: none" for drawings to avoid text selection '); - this.addchk(settings.MoveResize, 'Move and resize', flag => { settings.MoveResize = flag; }); - this.addchk(settings.DragAndDrop, 'Drag and drop', flag => { settings.DragAndDrop = flag; }); - this.addchk(settings.DragGraphs, 'Drag graph points', flag => { settings.DragGraphs = flag; }); - this.addSelectMenu('Progress box', ['off', 'on', 'modal'], isStr(settings.ProgressBox) ? settings.ProgressBox : (settings.ProgressBox ? 'on' : 'off'), value => { - settings.ProgressBox = (value === 'off') ? false : (value === 'on' ? true : value); - }); - this.endsub(); - - this.sub('Drawing'); - this.addSelectMenu('Optimize', ['None', 'Smart', 'Always'], settings.OptimizeDraw, value => { settings.OptimizeDraw = value; }, 'Histogram drawing optimization'); - this.sub('SmallPad', undefined, undefined, 'Minimal pad size drawn normally'); - this.add(`width ${settings.SmallPad?.width ?? 0}px`, () => this.input('Small pad width', settings.SmallPad?.width, 'int', 1, 1000).then(val => { settings.SmallPad.width = val; })); - this.add(`height ${settings.SmallPad?.height ?? 0}px`, () => this.input('Small pad height', settings.SmallPad?.height, 'int', 1, 800).then(val => { settings.SmallPad.height = val; })); - this.add('disable', () => { settings.SmallPad = { width: 0, height: 0 }; }, 'disable small pad drawing optimization'); - this.add('default', () => { settings.SmallPad = { width: 150, height: 100 }; }, 'Set to default 150x100 dimension'); - this.endsub(); - this.addPaletteMenu(settings.Palette, pal => { settings.Palette = pal; }); - this.addchk(settings.AutoStat, 'Auto stat box', flag => { settings.AutoStat = flag; }); - this.addchk(settings.LoadSymbolTtf, 'Load symbol.ttf', flag => { settings.LoadSymbolTtf = flag; }, 'Use symbol.ttf font file to render greek symbols, also used in PDF'); - - this.sub('Axis'); - this.addchk(settings.StripAxisLabels, 'Strip labels', flag => { settings.StripAxisLabels = flag; }, 'Provide shorter labels like 10^0 -> 1'); - this.addchk(settings.CutAxisLabels, 'Cut labels', flag => { settings.CutAxisLabels = flag; }, 'Remove labels which may exceed graphical range'); - this.add(`Tilt angle ${settings.AxisTiltAngle}`, () => this.input('Axis tilt angle', settings.AxisTiltAngle, 'int', 0, 180).then(val => { settings.AxisTiltAngle = val; })); - this.add(`X format ${settings.XValuesFormat ?? gStyle.fStatFormat}`, () => this.input('X axis format', settings.XValuesFormat).then(val => { settings.XValuesFormat = val; })); - this.add(`Y format ${settings.YValuesFormat ?? gStyle.fStatFormat}`, () => this.input('Y axis format', settings.YValuesFormat).then(val => { settings.YValuesFormat = val; })); - this.add(`Z format ${settings.ZValuesFormat ?? gStyle.fStatFormat}`, () => this.input('Z axis format', settings.ZValuesFormat).then(val => { settings.ZValuesFormat = val; })); - this.endsub(); - this.addSelectMenu('Latex', ['Off', 'Symbols', 'Normal', 'MathJax', 'Force MathJax'], settings.Latex, value => { settings.Latex = value; }); - this.addSelectMenu('3D rendering', ['Default', 'WebGL', 'Image'], settings.Render3D, value => { settings.Render3D = value; }); - this.addSelectMenu('WebGL embeding', ['Default', 'Overlay', 'Embed'], settings.Embed3D, value => { settings.Embed3D = value; }); - if (internals.setDefaultDrawOpt) { - this.add('Default options', () => this.input('List of options like TH2:lego2;TH3:glbox2', settings._dflt_drawopt || '').then(v => { - settings._dflt_drawopt = v; - internals.setDefaultDrawOpt(v); - }), 'Configure custom default draw options for some classes'); - } - this.endsub(); - - this.sub('Geometry'); - this.add('Grad per segment: ' + settings.GeoGradPerSegm, () => this.input('Grad per segment in geometry', settings.GeoGradPerSegm, 'int', 1, 60).then(val => { settings.GeoGradPerSegm = val; })); - this.addchk(settings.GeoCompressComp, 'Compress composites', flag => { settings.GeoCompressComp = flag; }); - this.endsub(); - - if (with_hierarchy) { - this.sub('Browser'); - this.add('Hierarchy limit: ' + settings.HierarchyLimit, () => this.input('Max number of items in hierarchy', settings.HierarchyLimit, 'int', 10, 100000).then(val => { - settings.HierarchyLimit = val; - handle_func('refresh'); - })); - this.add('Browser width: ' + settings.BrowserWidth, () => this.input('Browser width in px', settings.BrowserWidth, 'int', 50, 2000).then(val => { - settings.BrowserWidth = val; - handle_func('width'); - })); - this.endsub(); - } - - this.add('Dark mode: ' + (settings.DarkMode ? 'On' : 'Off'), () => { - settings.DarkMode = !settings.DarkMode; - handle_func('dark'); - }); - - const setStyleField = arg => { gStyle[arg.slice(1)] = parseInt(arg[0]); }, - addStyleIntField = (name, field, arr) => { - this.sub(name); - const curr = gStyle[field] >= arr.length ? 1 : gStyle[field]; - for (let v = 0; v < arr.length; ++v) - this.addchk(curr === v, arr[v], `${v}${field}`, setStyleField); - this.endsub(); - }; - - this.sub('gStyle'); - - this.sub('Canvas'); - this.addColorMenu('Color', gStyle.fCanvasColor, col => { gStyle.fCanvasColor = col; }); - addStyleIntField('Draw date', 'fOptDate', ['Off', 'Current time', 'File create time', 'File modify time']); - this.add(`Time zone: ${settings.TimeZone}`, () => this.input('Input time zone like UTC. empty string - local timezone', settings.TimeZone, 'string').then(val => { settings.TimeZone = val; })); - addStyleIntField('Draw file', 'fOptFile', ['Off', 'File name', 'Full file URL', 'Item name']); - this.addSizeMenu('Date X', 0.01, 0.1, 0.01, gStyle.fDateX, x => { gStyle.fDateX = x; }, 'configure gStyle.fDateX for date/item name drawings'); - this.addSizeMenu('Date Y', 0.01, 0.1, 0.01, gStyle.fDateY, y => { gStyle.fDateY = y; }, 'configure gStyle.fDateY for date/item name drawings'); - this.endsub(); - - this.sub('Pad'); - this.addColorMenu('Color', gStyle.fPadColor, col => { gStyle.fPadColor = col; }); - this.sub('Grid'); - this.addchk(gStyle.fPadGridX, 'X', flag => { gStyle.fPadGridX = flag; }); - this.addchk(gStyle.fPadGridY, 'Y', flag => { gStyle.fPadGridY = flag; }); - this.addColorMenu('Color', gStyle.fGridColor, col => { gStyle.fGridColor = col; }); - this.addSizeMenu('Width', 1, 10, 1, gStyle.fGridWidth, w => { gStyle.fGridWidth = w; }); - this.addLineStyleMenu('Style', gStyle.fGridStyle, st => { gStyle.fGridStyle = st; }); - this.endsub(); - addStyleIntField('Ticks X', 'fPadTickX', ['normal', 'ticks on both sides', 'labels on both sides']); - addStyleIntField('Ticks Y', 'fPadTickY', ['normal', 'ticks on both sides', 'labels on both sides']); - addStyleIntField('Log X', 'fOptLogx', ['off', 'on', 'log 2']); - addStyleIntField('Log Y', 'fOptLogy', ['off', 'on', 'log 2']); - addStyleIntField('Log Z', 'fOptLogz', ['off', 'on', 'log 2']); - this.endsub(); - - this.sub('Frame'); - this.addColorMenu('Fill color', gStyle.fFrameFillColor, col => { gStyle.fFrameFillColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fFrameFillStyle, gStyle.fFrameFillColor, id => { gStyle.fFrameFillStyle = id; }); - this.addColorMenu('Line color', gStyle.fFrameLineColor, col => { gStyle.fFrameLineColor = col; }); - this.addSizeMenu('Line width', 1, 10, 1, gStyle.fFrameLineWidth, w => { gStyle.fFrameLineWidth = w; }); - this.addLineStyleMenu('Line style', gStyle.fFrameLineStyle, st => { gStyle.fFrameLineStyle = st; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fFrameBorderSize, sz => { gStyle.fFrameBorderSize = sz; }); - this.addSelectMenu('Border mode', ['Down', 'Off', 'Up'], gStyle.fFrameBorderMode + 1, v => { gStyle.fFrameBorderMode = v - 1; }); - - // fFrameBorderMode: 0, - this.sub('Margins'); - this.addSizeMenu('Bottom', 0, 0.5, 0.05, gStyle.fPadBottomMargin, v => { gStyle.fPadBottomMargin = v; }); - this.addSizeMenu('Top', 0, 0.5, 0.05, gStyle.fPadTopMargin, v => { gStyle.fPadTopMargin = v; }); - this.addSizeMenu('Left', 0, 0.5, 0.05, gStyle.fPadLeftMargin, v => { gStyle.fPadLeftMargin = v; }); - this.addSizeMenu('Right', 0, 0.5, 0.05, gStyle.fPadRightMargin, v => { gStyle.fPadRightMargin = v; }); - this.endsub(); - this.endsub(); - - this.sub('Title'); - this.addColorMenu('Fill color', gStyle.fTitleColor, col => { gStyle.fTitleColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fTitleStyle, gStyle.fTitleColor, id => { gStyle.fTitleStyle = id; }); - this.addColorMenu('Text color', gStyle.fTitleTextColor, col => { gStyle.fTitleTextColor = col; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fTitleBorderSize, sz => { gStyle.fTitleBorderSize = sz; }); - this.addSizeMenu('Font size', 0.01, 0.1, 0.01, gStyle.fTitleFontSize, sz => { gStyle.fTitleFontSize = sz; }); - this.addFontMenu('Font', gStyle.fTitleFont, fnt => { gStyle.fTitleFont = fnt; }); - this.addSizeMenu('X: ' + gStyle.fTitleX.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleX, v => { gStyle.fTitleX = v; }); - this.addSizeMenu('Y: ' + gStyle.fTitleY.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleY, v => { gStyle.fTitleY = v; }); - this.addSizeMenu('W: ' + gStyle.fTitleW.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleW, v => { gStyle.fTitleW = v; }); - this.addSizeMenu('H: ' + gStyle.fTitleH.toFixed(2), 0.0, 1.0, 0.1, gStyle.fTitleH, v => { gStyle.fTitleH = v; }); - this.endsub(); - - this.sub('Stat box'); - this.addColorMenu('Fill color', gStyle.fStatColor, col => { gStyle.fStatColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fStatStyle, gStyle.fStatColor, id => { gStyle.fStatStyle = id; }); - this.addColorMenu('Text color', gStyle.fStatTextColor, col => { gStyle.fStatTextColor = col; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fStatBorderSize, sz => { gStyle.fStatBorderSize = sz; }); - this.addSizeMenu('Font size', 0, 30, 5, gStyle.fStatFontSize, sz => { gStyle.fStatFontSize = sz; }); - this.addFontMenu('Font', gStyle.fStatFont, fnt => { gStyle.fStatFont = fnt; }); - this.add('Stat format', () => this.input('Stat format', gStyle.fStatFormat).then(fmt => { gStyle.fStatFormat = fmt; })); - this.addSizeMenu('X: ' + gStyle.fStatX.toFixed(2), 0.2, 1.0, 0.1, gStyle.fStatX, v => { gStyle.fStatX = v; }); - this.addSizeMenu('Y: ' + gStyle.fStatY.toFixed(2), 0.2, 1.0, 0.1, gStyle.fStatY, v => { gStyle.fStatY = v; }); - this.addSizeMenu('Width: ' + gStyle.fStatW.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatW, v => { gStyle.fStatW = v; }); - this.addSizeMenu('Height: ' + gStyle.fStatH.toFixed(2), 0.1, 1.0, 0.1, gStyle.fStatH, v => { gStyle.fStatH = v; }); - this.endsub(); - - this.sub('Legend'); - this.addColorMenu('Fill color', gStyle.fLegendFillColor, col => { gStyle.fLegendFillColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fLegendFillStyle, gStyle.fLegendFillColor, id => { gStyle.fLegendFillStyle = id; }); - this.addSizeMenu('Border size', 0, 10, 1, gStyle.fLegendBorderSize, sz => { gStyle.fLegendBorderSize = sz; }); - this.addFontMenu('Font', gStyle.fLegendFont, fnt => { gStyle.fLegendFont = fnt; }); - this.addSizeMenu('Text size', 0, 0.1, 0.01, gStyle.fLegendTextSize, v => { gStyle.fLegendTextSize = v; }, 'legend text size, when 0 - auto adjustment is used'); - this.endsub(); - - this.sub('Histogram'); - this.addchk(gStyle.fOptTitle === 1, 'Hist title', flag => { gStyle.fOptTitle = flag ? 1 : 0; }); - this.addchk(gStyle.fOrthoCamera, 'Orthographic camera', flag => { gStyle.fOrthoCamera = flag; }); - this.addchk(gStyle.fHistMinimumZero, 'Base0', flag => { gStyle.fHistMinimumZero = flag; }, 'when true, BAR and LEGO drawing using base = 0'); - this.add('Text format', () => this.input('Paint text format', gStyle.fPaintTextFormat).then(fmt => { gStyle.fPaintTextFormat = fmt; })); - this.add('Time offset', () => this.input('Time offset in seconds, default is 788918400 for 1/1/1995', gStyle.fTimeOffset, 'int').then(ofset => { gStyle.fTimeOffset = ofset; })); - this.addSizeMenu('ErrorX: ' + gStyle.fErrorX.toFixed(2), 0.0, 1.0, 0.1, gStyle.fErrorX, v => { gStyle.fErrorX = v; }); - this.addSizeMenu('End error', 0, 12, 1, gStyle.fEndErrorSize, v => { gStyle.fEndErrorSize = v; }, 'size in pixels of end error for E1 draw options, gStyle.fEndErrorSize'); - this.addSizeMenu('Top margin', 0.0, 0.5, 0.05, gStyle.fHistTopMargin, v => { gStyle.fHistTopMargin = v; }, 'Margin between histogram top and frame top'); - this.addColorMenu('Fill color', gStyle.fHistFillColor, col => { gStyle.fHistFillColor = col; }); - this.addFillStyleMenu('Fill style', gStyle.fHistFillStyle, gStyle.fHistFillColor, id => { gStyle.fHistFillStyle = id; }); - this.addColorMenu('Line color', gStyle.fHistLineColor, col => { gStyle.fHistLineColor = col; }); - this.addSizeMenu('Line width', 1, 10, 1, gStyle.fHistLineWidth, w => { gStyle.fHistLineWidth = w; }); - this.addLineStyleMenu('Line style', gStyle.fHistLineStyle, st => { gStyle.fHistLineStyle = st; }); - this.endsub(); - - this.separator(); - this.sub('Predefined'); - ['Modern', 'Plain', 'Bold'].forEach(name => this.addchk((gStyle.fName === name), name, name, selectgStyle)); - this.endsub(); - - this.endsub(); // gStyle - - this.separator(); - - this.add('Save settings', () => { - const promise = readSettings(true) ? Promise.resolve(true) : this.confirm('Save settings', 'Pressing OK one agrees that JSROOT will store settings in browser local storage'); - promise.then(res => { - if (res) { - saveSettings(); - saveStyle(); - } - }); - }, 'Store settings and gStyle in browser local storage'); - this.add('Delete settings', () => { - saveSettings(-1); - saveStyle(-1); - }, 'Delete settings and gStyle from browser local storage'); - - if (!alone) - this.endsub(); - } - - /** @summary Run modal dialog - * @return {Promise} with html element inside dialog - * @private */ - async runModal() { - throw Error('runModal() must be reimplemented'); - } - - /** @summary Show modal info dialog - * @param {string} title - title - * @param {string} message - message - * @protected */ - info(title, message) { - return this.runModal(title, `

${message}

`, { height: 120, width: 400, resizable: true }); - } - - /** @summary Show confirm dialog - * @param {string} title - title - * @param {string} message - message - * @return {Promise} with true when 'Ok' pressed or false when 'Cancel' pressed - * @protected */ - async confirm(title, message) { - return this.runModal(title, message, { btns: true, height: 120, width: 400 }).then(elem => Boolean(elem)); - } - - /** @summary Input value - * @return {Promise} with input value - * @param {string} title - input dialog title - * @param {any} value - initial value - * @param {string} [kind] - use 'text' (default), 'number', 'float' or 'int' - * @protected */ - async input(title, value, kind, min, max) { - let onchange = null; - if (isFunc(kind)) { - onchange = kind; - kind = ''; - } - if (!kind) - kind = 'text'; - const inp_type = (kind === 'int') ? 'number' : 'text', value0 = value; - let ranges = ''; - if ((value === undefined) || (value === null)) - value = ''; - if (kind === 'int') { - if (min !== undefined) - ranges += ` min="${min}"`; - if (max !== undefined) - ranges += ` max="${max}"`; - } - - const main_content = - '
' + - `` + - '
', oninit = !onchange ? null : elem => { - const inp = elem.querySelector('.jsroot_dlginp'); - if (inp) - inp.oninput = () => onchange(inp.value); - }; - - return new Promise(resolveFunc => { - this.runModal(title, main_content, { btns: true, height: 150, width: 400, oninit }).then(element => { - if (!element) { - if (onchange) - onchange(value0); - return; - } - let val = element.querySelector('.jsroot_dlginp').value, isok = true; - if (kind === 'float') { - val = Number.parseFloat(val); - isok = Number.isFinite(val); - } else if (kind === 'int') { - val = parseInt(val); - isok = Number.isInteger(val); - } - if (isok) { - if (onchange) - onchange(val); - resolveFunc(val); - } - }); - }); - } - - /** @summary Let input arguments from the method - * @return {Promise} with method argument */ - async showMethodArgsDialog(method) { - const dlg_id = this.menuname + sDfltDlg; - let main_content = '
'; - - for (let n = 0; n < method.fArgs.length; ++n) { - const arg = method.fArgs[n]; - arg.fValue = arg.fDefault; - if (arg.fValue === '""') - arg.fValue = ''; - main_content += ` - `; - } - - main_content += '
'; - - return new Promise(resolveFunc => { - this.runModal(method.fClassName + '::' + method.fName, main_content, { btns: true, height: 100 + method.fArgs.length * 60, width: 400, resizable: true }).then(element => { - if (!element) - return; - let args = ''; - - for (let k = 0; k < method.fArgs.length; ++k) { - const arg = method.fArgs[k]; - let value = element.querySelector(`#${dlg_id}_inp${k}`).value; - if (value === '') - value = arg.fDefault; - if ((arg.fTitle === 'Option_t*') || (arg.fTitle === 'const char*')) { - // check quotes, - // TODO: need to make more precise checking of escape characters - if (!value) - value = '""'; - if (value[0] !== '"') - value = '"' + value; - if (value.at(-1) !== '"') - value += '"'; - } - - args += (k > 0 ? ',' : '') + value; - } - - resolveFunc(args); - }); - }); - } - - /** @summary Let input arguments from the Command - * @return {Promise} with command argument */ - async showCommandArgsDialog(cmdname, args) { - const dlg_id = this.menuname + sDfltDlg; - let main_content = '
'; - - for (let n = 0; n < args.length; ++n) { - main_content += `` + - ``; - } - - main_content += '
'; - - return new Promise(resolveFunc => { - this.runModal('Arguments for command ' + cmdname, main_content, { btns: true, height: 110 + args.length * 60, width: 400, resizable: true }).then(element => { - if (!element) - return resolveFunc(null); - - const resargs = []; - for (let k = 0; k < args.length; ++k) - resargs.push(element.querySelector(`#${dlg_id}_inp${k}`).value); - resolveFunc(resargs); - }); - }); - } - -} // class JSRootMenu - -/** - * @summary Context menu class using plain HTML/JavaScript - * - * @desc Use {@link createMenu} to create instance of the menu - * based on {@link https://github.com/L1quidH2O/ContextMenu.js} - * @private - */ - -class StandaloneMenu extends JSRootMenu { - - constructor(painter, menuname, show_event) { - super(painter, menuname, show_event); - - this.code = []; - this._use_plain_text = true; - this.stack = [this.code]; - } - - native() { return true; } - - /** @summary Load required modules, noop for that menu class */ - async load() { return this; } - - /** @summary Add menu item - * @param {string} name - item name - * @param {number|string} arg - argument associated with the item - * @param {Function} func - func called when item is selected */ - add(name, arg, func, title) { - let curr = this.stack.at(-1); - - if (name === sSeparator) - return curr.push({ divider: true }); - - if (name.indexOf(sHeader) === 0) - return curr.push({ text: name.slice(sHeader.length), header: true, title }); - - if (name === sEndsub) { - this.stack.pop(); - curr = this.stack.at(-1); - if (!curr.at(-1).sub.length) - curr.at(-1).sub = undefined; - return; - } - - if (name === sEndcolumn) - return this.stack.pop(); - - if (isFunc(arg)) { - title = func; - func = arg; - arg = name; - } - - const elem = {}; - curr.push(elem); - - if (name === sColumn) { - elem.column = true; - elem.sub = []; - this.stack.push(elem.sub); - return; - } - - if (name.indexOf(sSub) === 0) { - name = name.slice(4); - elem.sub = []; - this.stack.push(elem.sub); - } - - if (name.indexOf('chk:') === 0) { - elem.checked = true; - name = name.slice(4); - } else if (name.indexOf('unk:') === 0) { - elem.checked = false; - name = name.slice(4); - } - - elem.text = name; - elem.title = title; - elem.arg = arg; - elem.func = func; - } - - /** @summary Returns size of main menu */ - size() { return this.code.length; } - - /** @summary Build HTML elements of the menu - * @private */ - _buildContextmenu(menu, left, top, loc) { - const doc = getDocument(), - outer = doc.createElement('div'), - clname = 'jsroot_ctxt_container', - clfocus = 'jsroot_ctxt_focus', - clcolumn = 'jsroot_ctxt_column', - container_style = - 'position: absolute; top: 0; user-select: none; z-index: 100000; background-color: rgb(250, 250, 250); margin: 0; padding: 0px; width: auto;' + - 'min-width: 100px; box-shadow: 0px 0px 10px rgb(0, 0, 0, 0.2); border: 3px solid rgb(215, 215, 215); font-family: Arial, helvetica, sans-serif, serif;' + - 'font-size: 13px; color: rgb(0, 0, 0, 0.8); line-height: 15px;'; - - // if loc !== doc.body then its a submenu, so it needs to have position: relative; - if (loc === doc.body) { - // delete all elements with menu className - const deleteElems = doc.getElementsByClassName(clname); - for (let k = deleteElems.length - 1; k >= 0; --k) - deleteElems[k].parentNode.removeChild(deleteElems[k]); - - outer.className = clname; - outer.style = container_style; - outer.style.position = 'fixed'; - outer.style.left = left + 'px'; - outer.style.top = top + 'px'; - } else if ((left < 0) && (top === left)) { - // column - outer.className = clcolumn; - outer.style.float = 'left'; - outer.style.width = (100 / -left).toFixed(1) + '%'; - } else { - outer.className = clname; - outer.style = container_style; - outer.style.left = -loc.offsetLeft + loc.offsetWidth + 'px'; - } - - let need_check_area = false, ncols = 0; - menu.forEach(d => { - if (d.checked) - need_check_area = true; - if (d.column) - ncols++; - }); - - menu.forEach(d => { - if (ncols > 0) { - outer.style.display = 'flex'; - if (d.column) - this._buildContextmenu(d.sub, -ncols, -ncols, outer); - return; - } - - if (d.divider) { - const hr = doc.createElement('hr'); - hr.style = 'width: 85%; margin: 3px auto; border: 1px solid rgb(0, 0, 0, 0.15)'; - outer.appendChild(hr); - return; - } - - const item = doc.createElement('div'); - item.style.position = 'relative'; - outer.appendChild(item); - - if (d.header) { - item.style = 'background-color: lightblue; padding: 3px 7px; font-weight: bold; border-bottom: 1px;'; - - let url = '', title = ''; - if (d.title) { - const p = d.title.indexOf('https://'); - if (p >= 0) { - url = d.title.slice(p); - title = d.title.slice(0, p); - } else - title = d.title; - } - if (!url) - item.innerText = d.text; - else { - item.style.display = 'flex'; - item.style['justify-content'] = 'space-between'; - - const txt = doc.createElement('span'); - txt.innerText = d.text; - txt.style = 'display: inline-block; margin: 0;'; - item.appendChild(txt); - - const anchor = doc.createElement('span'); - anchor.style = 'margin: 0; color: blue; opacity: 0.1; margin-left: 7px; right: 3px; display: inline-block; cursor: pointer;'; - anchor.textContent = '?'; - anchor.title = url; - anchor.addEventListener('click', () => { - const cp = this.painter?.getCanvPainter(); - if (cp?.canSendWebsocket()) - cp.sendWebsocket(`SHOWURL:${url}`); - else - window.open(url); - }); - anchor.addEventListener('mouseenter', () => { anchor.style.opacity = 1; }); - anchor.addEventListener('mouseleave', () => { anchor.style.opacity = 0.1; }); - item.appendChild(anchor); - } - if (title) - item.setAttribute('title', title); - - return; - } - - const hovArea = doc.createElement('div'); - hovArea.style = 'width: 100%; height: 100%; display: flex; justify-content: space-between; cursor: pointer;'; - if (d.title) - hovArea.setAttribute('title', d.title); - - item.appendChild(hovArea); - if (!d.text) - d.text = 'item'; - - const text = doc.createElement('div'); - text.style = 'margin: 0; padding: 3px 7px; pointer-events: none; white-space: nowrap'; - - if (d.text.indexOf('= 0) { - if (need_check_area) { - text.style.display = 'flex'; - - const chk = doc.createElement('span'); - chk.innerText = d.checked ? '\u2713' : ''; - chk.style.display = 'inline-block'; - chk.style.width = '1em'; - text.appendChild(chk); - - const sub = doc.createElement('div'); - sub.innerHTML = d.text; - text.appendChild(sub); - } else - text.innerHTML = d.text; - } else { - if (need_check_area) { - const chk = doc.createElement('span'); - chk.innerText = d.checked ? '\u2713' : ''; - chk.style.display = 'inline-block'; - chk.style.width = '1em'; - text.appendChild(chk); - } - - const sub = doc.createElement('span'); - sub.textContent = d.text; - text.appendChild(sub); - } - - hovArea.appendChild(text); - - function changeFocus(fitem, on) { - if (on) { - fitem.classList.add(clfocus); - fitem.style['background-color'] = 'rgb(220, 220, 220)'; - } else if (fitem.classList.contains(clfocus)) { - fitem.style['background-color'] = null; - fitem.classList.remove(clfocus); - fitem.querySelector(`.${clname}`)?.remove(); - } - } - - if (d.extraText || d.sub) { - const extraText = doc.createElement('span'); - extraText.className = 'jsroot_ctxt_extraText'; - extraText.style = 'margin: 0; padding: 3px 7px; color: rgba(0, 0, 0, 0.6);'; - extraText.textContent = d.sub ? '\u25B6' : d.extraText; - hovArea.appendChild(extraText); - - if (d.sub && browser.touches) { - extraText.addEventListener('click', evnt => { - evnt.preventDefault(); - evnt.stopPropagation(); - const was_active = item.parentNode.querySelector(`.${clfocus}`); - - if (was_active) - changeFocus(was_active, false); - - if (item !== was_active) { - changeFocus(item, true); - this._buildContextmenu(d.sub, 0, 0, item); - } - }); - } - } - - if (!browser.touches) { - hovArea.addEventListener('mouseenter', () => { - if (this.prevHovArea) - this.prevHovArea.style['background-color'] = null; - hovArea.style['background-color'] = 'rgb(235, 235, 235)'; - this.prevHovArea = hovArea; - - outer.childNodes.forEach(chld => changeFocus(chld, false)); - - if (d.sub) { - changeFocus(item, true); - this._buildContextmenu(d.sub, 0, 0, item); - } - }); - } - - if (d.func) { - item.addEventListener('click', evnt => { - const func = this.painter ? d.func.bind(this.painter) : d.func; - func(d.arg); - evnt.stopPropagation(); - this.remove(); - }); - } - }); - - loc.appendChild(outer); - - const docWidth = doc.documentElement.clientWidth, docHeight = doc.documentElement.clientHeight; - - // Now determine where the contextmenu will be - if (loc === doc.body) { - if (left + outer.offsetWidth > docWidth) { - // Does sub-contextmenu overflow window width? - outer.style.left = (docWidth - outer.offsetWidth) + 'px'; - } - if (outer.offsetHeight > docHeight) { - // is the contextmenu height larger than the window height? - outer.style.top = 0; - outer.style.overflowY = 'scroll'; - outer.style.overflowX = 'hidden'; - outer.style.height = docHeight + 'px'; - } else if (top + outer.offsetHeight > docHeight) { - // Does contextmenu overflow window height? - outer.style.top = (docHeight - outer.offsetHeight) + 'px'; - } - } else if (outer.className !== clcolumn) { - // if its sub-contextmenu - const dimensionsLoc = loc.getBoundingClientRect(), dimensionsOuter = outer.getBoundingClientRect(); - - // Does sub-contextmenu overflow window width? - if (dimensionsOuter.left + dimensionsOuter.width > docWidth) - outer.style.left = (-loc.offsetLeft - dimensionsOuter.width) + 'px'; - - - if (dimensionsOuter.height > docHeight) { - // is the sub-contextmenu height larger than the window height? - outer.style.top = -dimensionsOuter.top + 'px'; - outer.style.overflowY = 'scroll'; - outer.style.overflowX = 'hidden'; - outer.style.height = docHeight + 'px'; - } else if (dimensionsOuter.height < docHeight && dimensionsOuter.height > docHeight / 2) { - // is the sub-contextmenu height smaller than the window height AND larger than half of window height? - if (dimensionsOuter.top - docHeight / 2 >= 0) { // If sub-contextmenu is closer to bottom of the screen - outer.style.top = (-dimensionsOuter.top - dimensionsOuter.height + docHeight) + 'px'; - } else { // If sub-contextmenu is closer to top of the screen - outer.style.top = (-dimensionsOuter.top) + 'px'; - } - } else if (dimensionsOuter.top + dimensionsOuter.height > docHeight) { - // Does sub-contextmenu overflow window height? - outer.style.top = (-dimensionsOuter.height + dimensionsLoc.height) + 'px'; - } - } - return outer; - } - - /** @summary Show standalone menu */ - async show(event) { - this.remove(); - - if (!event && this.show_evnt) - event = this.show_evnt; - - const doc = getDocument(), - woffset = typeof window === 'undefined' ? { x: 0, y: 0 } : { x: window.scrollX, y: window.scrollY }; - - doc.body.addEventListener('click', this.remove_handler); - - doc.getElementById(this.menuname)?.remove(); - - this.element = this._buildContextmenu(this.code, (event?.clientX || 0) + woffset.x, (event?.clientY || 0) + woffset.y, doc.body); - - this.element.setAttribute('id', this.menuname); - - return this; - } - - /** @summary Run modal elements with standalone code */ - createModal(title, main_content, args) { - if (!args) - args = {}; - - if (!args.Ok) - args.Ok = 'Ok'; - - const modal = { args }, dlg_id = (this?.menuname ?? sDfltName) + sDfltDlg; - select(`#${dlg_id}`).remove(); - select(`#${dlg_id}_block`).remove(); - - const w = Math.min(args.width || 450, Math.round(0.9 * browser.screenWidth)), - b = select('body'); - modal.block = b.append('div') - .attr('id', `${dlg_id}_block`) - .attr('class', 'jsroot_dialog_block') - .attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white'); - modal.element = b.append('div') - .attr('id', dlg_id) - .attr('class', 'jsroot_dialog') - .style('position', 'absolute') - .style('width', `${w}px`) - .style('left', '50%') - .style('top', '50%') - .style('z-index', 100001) - .attr('tabindex', '0'); - - modal.element.html( - '
' + - `
${title}
` + - `
${main_content}
` + - '
' + - `` + - (args.btns ? '' : '') + - '
' - ); - - const drag_move = drag().on('start', () => { modal.y0 = 0; }).on('drag', evnt => { - if (!modal.y0) - modal.y0 = pointer(evnt, modal.element.node())[1]; - let p0 = Math.max(0, pointer(evnt, b.node())[1] - modal.y0); - if (b.node().clientHeight) - p0 = Math.min(p0, 0.8 * b.node().clientHeight); - modal.element.style('top', `${p0}px`); - }); - modal.element.select('.jsroot_dialog_title').call(drag_move); - - modal.done = function(res) { - if (this._done) - return; - this._done = true; - if (isFunc(this.call_back)) - this.call_back(res); - this.element.remove(); - this.block.remove(); - }; - - modal.setContent = function(content, btn_text) { - if (!this._done) { - this.element.select('.jsroot_dialog_content').html(content); - if (btn_text) { - this.args.Ok = btn_text; - this.element.select('.jsroot_dialog_button').text(btn_text); - } - } - }; - - modal.element.on('keyup', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); - modal.done(evnt.code === 'Enter' ? modal.element.node() : null); - } - }); - modal.element.on('keydown', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); - } - }); - modal.element.selectAll('.jsroot_dialog_button').on('click', evnt => { - modal.done(args.btns && (select(evnt.target).text() === args.Ok) ? modal.element.node() : null); - }); - - let f = modal.element.select('.jsroot_dialog_content').select('input'); - if (f.empty()) - f = modal.element.select('.jsroot_dialog_footer').select('button'); - if (!f.empty()) - f.node().focus(); - if (isFunc(args.oninit)) - args.oninit(modal.element.node()); - return modal; - } - - /** @summary Run modal elements with standalone code */ - async runModal(title, main_content, args) { - const modal = this.createModal(title, main_content, args); - return new Promise(resolveFunc => { - modal.call_back = resolveFunc; - }); - } - -} // class StandaloneMenu - - -/** @summary Create JSROOT menu - * @desc See {@link JSRootMenu} class for detailed list of methods - * @param {object} [evnt] - event object like mouse context menu event - * @param {object} [handler] - object with handling function, in this case one not need to bind function - * @param {string} [menuname] - optional menu name - * @example - * import { createMenu } from 'https://root.cern/js/latest/modules/gui/menu.mjs'; - * let menu = await createMenu(); - * menu.add('First', () => console.log('Click first')); - * let flag = true; - * menu.addchk(flag, 'Checked', arg => console.log(`Now flag is ${arg}`)); - * menu.show(); */ -function createMenu(evnt, handler, menuname) { - const menu = new StandaloneMenu(handler, menuname || sDfltName, evnt); - return menu.load(); -} - -/** @summary Close previously created and shown JSROOT menu - * @param {string} [menuname] - optional menu name */ -function closeMenu(menuname) { - const element = getDocument().getElementById(menuname || sDfltName); - element?.remove(); - return Boolean(element); -} - -/** @summary Returns true if menu or modal dialog present - * @private */ -function hasMenu(menuname) { - const doc = getDocument(); - if (doc.getElementById(menuname || sDfltName)) - return true; - if (doc.getElementById((menuname || sDfltName) + sDfltDlg)) - return true; - return false; -} - -/** @summary Fill and show context menu for painter object - * @private */ -function showPainterMenu(evnt, painter, kind) { - if (isFunc(evnt.stopPropagation)) { - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - } - - createMenu(evnt, painter).then(menu => { - painter.fillContextMenu(menu); - if (kind === kNoReorder) - kind = undefined; - else if (isFunc(painter.bringToFront)) - menu.add('Bring to front', () => painter.bringToFront(true)); - if (kind === kToFront) - kind = undefined; - return painter.fillObjectExecMenu(menu, kind); - }).then(menu => menu.show()); -} - -/** @summary Internal method to implement modal progress - * @private */ -internals._modalProgress = function(msg, click_handle) { - if (!msg || !isStr(msg)) { - internals.modal?.done(); - delete internals.modal; - return; - } - - if (!internals.modal) - internals.modal = StandaloneMenu.prototype.createModal('Progress', msg); - - internals.modal.setContent(msg, click_handle ? 'Abort' : 'Ok'); - - internals.modal.call_back = click_handle; -}; - -/** @summary Assign handler for context menu for painter draw element - * @private */ -function assignContextMenu(painter, kind) { - if (!painter?.isBatchMode()) - painter?.getG()?.on('contextmenu', settings.ContextMenu ? evnt => showPainterMenu(evnt, painter, kind) : null); -} - -Object.assign(internals.jsroot, { createMenu, closeMenu, assignContextMenu, kToFront, kNoReorder }); - -/** @summary Return time offset value for given TAxis object - * @private */ -function getTimeOffset(axis) { - const dflt_time_offset = 788918400000; - - if (!axis) - return dflt_time_offset; - const idF = axis.fTimeFormat.indexOf('%F'); - if (idF < 0) - return gStyle.fTimeOffset * 1000; - let sof = axis.fTimeFormat.slice(idF + 2); - // default string in axis offset - if (sof.indexOf('1995-01-01 00:00:00s0') === 0) - return dflt_time_offset; - // another default string with unix time - if (sof.indexOf('1970-01-01 00:00:00s0') === 0) - return 0; - // special case, used from DABC painters - if ((sof === '0') || (sof === '')) - return 0; - - // decode time from ROOT string - const next = (separ, min, max) => { - const pos = sof.indexOf(separ); - if (pos < 0) - return min; - const val = parseInt(sof.slice(0, pos)); - sof = sof.slice(pos + 1); - if (!Number.isInteger(val) || (val < min) || (val > max)) - return min; - return val; - }; - // eslint-disable-next-line one-var - const year = next('-', 1900, 2900), - month = next('-', 1, 12) - 1, - day = next(' ', 1, 31), - hour = next(':', 0, 23), - min = next(':', 0, 59), - sec = next('s', 0, 59), - msec = next(' ', 0, 999); - - let offset = Date.UTC(year, month, day, hour, min, sec, msec); - - // now also handle suffix like GMT or GMT -0600 - sof = sof.toUpperCase(); - - if (sof.indexOf('GMT') === 0) { - sof = sof.slice(4).trim(); - if (sof.length > 3) { - let p = 0, sign = 1000; - if (sof[0] === '-') { - p = 1; - sign = -1e3; - } - offset -= sign * (parseInt(sof.slice(p, p + 2)) * 3600 + parseInt(sof.slice(p + 2, p + 4)) * 60); - } - } - - return offset; -} - -/** @summary Return true when GMT option configured in time format - * @private */ -function getTimeGMT(axis) { - const fmt = axis?.fTimeFormat ?? ''; - return (fmt.indexOf('gmt') > 0) || (fmt.indexOf('GMT') > 0); -} - -/** @summary Tries to choose time format for provided time interval - * @private */ -function chooseTimeFormat(awidth, ticks) { - if (awidth < 0.5) - return ticks ? '%S.%L' : '%H:%M:%S.%L'; - if (awidth < 30) - return ticks ? '%Mm%S' : '%H:%M:%S'; - awidth /= 60; - if (awidth < 30) - return ticks ? '%Hh%M' : '%d/%m %H:%M'; - awidth /= 60; - if (awidth < 12) - return ticks ? '%d-%Hh' : '%d/%m/%y %Hh'; - awidth /= 24; - if (awidth < 15.218425) - return ticks ? '%d/%m' : '%d/%m/%y'; - awidth /= 30.43685; - if (awidth < 6) - return '%d/%m/%y'; - awidth /= 12; - if (awidth < 2) - return ticks ? '%m/%y' : '%d/%m/%y'; - return '%Y'; -} - -/** - * @summary Base axis painter methods - * - * @private - */ - -const AxisPainterMethods = { - - initAxisPainter() { - this.name = 'yaxis'; - this.kind = kAxisNormal; - this.func = null; - this.order = 0; // scaling order for axis labels - - this.full_min = 0; - this.full_max = 1; - this.scale_min = 0; - this.scale_max = 1; - this.ticks = []; // list of major ticks - }, - - /** @summary Cleanup axis painter */ - cleanupAxisPainter() { - this.ticks = []; - delete this.format; - delete this.func; - delete this.tfunc1; - delete this.tfunc2; - delete this.gr; - }, - - /** @summary Assign often used members of frame painter */ - assignFrameMembers(fp, axis) { - fp[`gr${axis}`] = this.gr; // fp.grx - fp[`log${axis}`] = this.log; // fp.logx - fp[`scale_${axis}min`] = this.scale_min; // fp.scale_xmin - fp[`scale_${axis}max`] = this.scale_max; // fp.scale_xmax - }, - - /** @summary Convert axis value into the Date object */ - convertDate(v) { - const dt = new Date(this.timeoffset + v * 1000); - let res = dt; - if (!this.timegmt && settings.TimeZone) { - try { - const ms = dt.getMilliseconds(); - res = new Date(dt.toLocaleString('en-US', { timeZone: settings.TimeZone })); - res.setMilliseconds(ms); - } catch { - res = dt; - } - } - return res; - }, - - /** @summary Convert graphical point back into axis value */ - revertPoint(pnt) { - const value = this.func.invert(pnt); - return this.kind === kAxisTime ? (value - this.timeoffset) / 1000 : value; - }, - - /** @summary Provide label for time axis */ - formatTime(dt, asticks) { - return asticks ? this.tfunc1(dt) : this.tfunc2(dt); - }, - - /** @summary Provide label for log axis */ - formatLog(d, asticks, fmt) { - const val = parseFloat(d), rnd = Math.round(val); - if (!asticks) - return ((rnd === val) && (Math.abs(rnd) < 1e9)) ? rnd.toString() : floatToString(val, fmt || gStyle.fStatFormat); - if (val <= 0) - return null; - let vlog = Math.log10(val); - const base = this.logbase; - if (base !== 10) - vlog /= Math.log10(base); - if (this.moreloglabels || (Math.abs(vlog - Math.round(vlog)) < 0.001)) { - if (!this.noexp && (asticks !== 2)) - return this.formatExp(base, Math.floor(vlog + 0.01), val); - if (Math.abs(base - Math.E) < 0.001) - return floatToString(val, fmt || gStyle.fStatFormat); - return (vlog < 0) ? val.toFixed(Math.round(-vlog + 0.5)) : val.toFixed(0); - } - return null; - }, - - /** @summary Detect if tick is extra-ticks */ - isExtraLogTick(val) { - if (!this.log) - return false; - let vlog = Math.log10(val); - if (this.logbase !== 10) - vlog /= Math.log10(this.logbase); - - return Math.abs(vlog - Math.round(vlog)) >= 0.001; - }, - - /** @summary Provide label for normal axis */ - formatNormal(d, asticks, fmt) { - let val = parseFloat(d); - if (asticks && this.order) - val /= Math.pow(10, this.order); - - if (gStyle.fStripDecimals && (val === Math.round(val))) - return Math.abs(val) < 1e9 ? val.toFixed(0) : val.toExponential(4); - - if (asticks) { - if (this.ndig > 10) - return val.toExponential(this.ndig - 11); - let res = val.toFixed(this.ndig); - const p = res.indexOf('.'); - if ((p > 0) && settings.StripAxisLabels) { - while ((res.length >= p) && ((res.at(-1) === '0') || (res.at(-1) === '.'))) - res = res.slice(0, res.length - 1); - } - return res; - } - - return floatToString(val, fmt || '8.6g'); - }, - - /** @summary Provide label for exponential form */ - formatExp(base, order, value) { - let res = ''; - const sbase = Math.abs(base - Math.E) < 0.001 ? 'e' : base.toString(); - if (value) { - value = Math.round(value / Math.pow(base, order)); - if (settings.StripAxisLabels) { - if (order === 0) - return value.toString(); - else if ((order === 1) && (value === 1)) - return sbase; - } - if (value !== 1) - res = value.toString() + (settings.Latex ? '#times' : 'x'); - } - res += sbase; - if (settings.Latex > constants$1.Latex.Symbols) - return res + `^{${order}}`; - const superscript_symbols = { - 0: '\u2070', 1: '\xB9', 2: '\xB2', 3: '\xB3', 4: '\u2074', 5: '\u2075', - 6: '\u2076', 7: '\u2077', 8: '\u2078', 9: '\u2079', '-': '\u207B' - }, str = order.toString(); - for (let n = 0; n < str.length; ++n) - res += superscript_symbols[str[n]]; - return res; - }, - - /** @summary Convert 'raw' axis value into text */ - axisAsText(value, fmt) { - if (this.kind === kAxisTime) - value = this.convertDate(value); - if (this.format) - return this.format(value, false, fmt); - return value.toPrecision(4); - }, - - /** @summary Produce ticks for d3.scaleLog - * @desc Fixing following problem, described [here]{@link https://stackoverflow.com/questions/64649793} */ - poduceLogTicks(func, number) { - const linearArray = arr => { - if (arr.length < 2) - return false; - let sum1 = 0, sum2 = 0; - for (let k = 1; k < arr.length; ++k) { - const diff = (arr[k] - arr[k - 1]); - sum1 += diff; - sum2 += diff ** 2; - } - const mean = sum1 / (arr.length - 1), - dev = sum2 / (arr.length - 1) - mean ** 2; - - if (dev <= 0) - return true; - if (Math.abs(mean) < 1e-100) - return false; - return Math.sqrt(dev) / mean < 1e-6; - }; - - let arr = func.ticks(number); - - while ((number > 4) && linearArray(arr)) { - number = Math.round(number * 0.8); - arr = func.ticks(number); - } - - // if still linear array, try to sort out 'bad' ticks - if ((number < 5) && linearArray(arr) && this.logbase && (this.logbase !== 10)) { - const arr2 = []; - arr.forEach(val => { - const pow = Math.log10(val) / Math.log10(this.logbase); - if (Math.abs(Math.round(pow) - pow) < 0.01) - arr2.push(val); - }); - if (arr2.length) - arr = arr2; - } - - return arr; - }, - - /** @summary Produce axis ticks */ - produceTicks(ndiv, ndiv2) { - if (!this.noticksopt && !this.exact_ticks) { - const total = ndiv * (ndiv2 || 1); - - if (this.log) - return this.poduceLogTicks(this.func, total); - - const dom = this.func.domain(), - check = ticks => { - if (ticks.length <= total) - return true; - if (ticks.length > total + 1) - return false; - return (ticks[0] === dom[0]) || (ticks[total] === dom[1]); // special case of N+1 ticks, but match any range - }, res1 = this.func.ticks(total); - if (ndiv2 || check(res1)) - return res1; - - const res2 = this.func.ticks(Math.round(total * 0.7)); - return (res2.length > 2) && check(res2) ? res2 : res1; - } - - const dom = this.func.domain(), ticks = []; - if (ndiv2) - ndiv = (ndiv - 1) * ndiv2; - for (let n = 0; n <= ndiv; ++n) - ticks.push((dom[0] * (ndiv - n) + dom[1] * n) / ndiv); - return ticks; - }, - - /** @summary Method analyze mouse wheel event and returns item with suggested zooming range */ - analyzeWheelEvent(evnt, dmin, item, test_ignore) { - if (!item) - item = {}; - - let delta = 0, delta_left = 1, delta_right = 1; - - if ('dleft' in item) { - delta_left = item.dleft; - delta = 1; - } - if ('dright' in item) { - delta_right = item.dright; - delta = 1; - } - - if (item.delta) - delta = item.delta; - else if (evnt) - delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); - - if (!delta || (test_ignore && item.ignore)) - return; - - delta = (delta < 0) ? -0.2 : 0.2; - delta_left *= delta; - delta_right *= delta; - - const lmin = item.min = this.scale_min, - lmax = item.max = this.scale_max, - gmin = this.full_min, - gmax = this.full_max; - - if ((item.min === item.max) && (delta < 0)) { - item.min = gmin; - item.max = gmax; - } - - if (item.min >= item.max) - return; - - if (item.reverse) - dmin = 1 - dmin; - - if ((dmin > 0) && (dmin < 1)) { - if (this.log) { - let factor = (item.min > 0) ? Math.log10(item.max / item.min) : 2; - if (factor > 10) - factor = 10; - else if (factor < 0.01) - factor = 0.01; - item.min /= Math.pow(10, factor * delta_left * dmin); - item.max *= Math.pow(10, factor * delta_right * (1 - dmin)); - // special handling for Z scale - limit zooming of color scale - if (this.minposbin && this.name === 'zaxis') - item.min = Math.max(item.min, 0.3 * this.minposbin); - } else if ((delta_left === -delta_right) && !item.reverse) { - // shift left/right, try to keep range constant - let delta_shift = (item.max - item.min) * delta_right * dmin; - - if ((Math.round(item.max) === item.max) && (Math.round(item.min) === item.min) && (Math.abs(delta_shift) > 1)) - delta_shift = Math.round(delta_shift); - - if (item.min + delta_shift < gmin) - delta_shift = gmin - item.min; - else if (item.max + delta_shift > gmax) - delta_shift = gmax - item.max; - - if (delta_shift) { - item.min += delta_shift; - item.max += delta_shift; - } else { - delete item.min; - delete item.max; - } - } else { - let rx_left = (item.max - item.min), rx_right = rx_left; - if (delta_left > 0) - rx_left = 1.001 * rx_left / (1 - delta_left); - item.min += -delta_left * dmin * rx_left; - if (delta_right > 0) - rx_right = 1.001 * rx_right / (1 - delta_right); - item.max -= -delta_right * (1 - dmin) * rx_right; - } - if (item.min >= item.max) - item.min = item.max = undefined; - else if (delta_left !== delta_right) { - // extra check case when moving left or right - if (((item.min < gmin) && (lmin === gmin)) || - ((item.max > gmax) && (lmax === gmax))) - item.min = item.max = undefined; - } else { - item.min = Math.max(item.min, gmin); - item.max = Math.min(item.max, gmax); - } - } else - item.min = item.max = undefined; - - item.changed = (item.min !== undefined) && (item.max !== undefined); - - return item; - } - -}; // AxisPainterMethods - - -/** - * @summary Painter for TAxis object - * - * @private - */ - -class TAxisPainter extends ObjectPainter { - - /** @summary constructor - * @param {object|string} dom - identifier or dom element - * @param {object} axis - object to draw - * @param {boolean} embedded - if true, painter used in other objects painters */ - constructor(dom, axis, embedded) { - super(dom, axis); - - this.is_gaxis = axis?._typename === clTGaxis; - - Object.assign(this, AxisPainterMethods); - this.initAxisPainter(); - - this.embedded = embedded; // indicate that painter embedded into the histogram painter - this.invert_side = false; - this.lbls_both_sides = false; // draw labels on both sides - } - - /** @summary cleanup painter */ - cleanup() { - this.cleanupAxisPainter(); - delete this.hist_painter; - delete this.hist_axis; - delete this.is_gaxis; - super.cleanup(); - } - - /** @summary Use in GED to identify kind of axis */ - getAxisType() { return clTAxis; } - - /** @summary Configure axis painter - * @desc Axis can be drawn inside frame group with offset to 0 point for the frame - * Therefore one should distinguish when calculated coordinates used for axis drawing itself or for calculation of frame coordinates - * @private */ - configureAxis(name, min, max, smin, smax, vertical, range, opts) { - const axis = this.getObject(); - - this.name = name; - this.full_min = min; - this.full_max = max; - this.kind = kAxisNormal; - this.vertical = vertical; - this.log = opts.log || 0; - this.minposbin = opts.minposbin; - this.ignore_labels = opts.ignore_labels; - this.noexp_changed = opts.noexp_changed; - this.symlog = opts.symlog || false; - this.reverse = opts.reverse || false; - // special flag to change align of labels on vertical axis - // it is workaround shown in TGaxis docu - this.reverseAlign = this.vertical && this.reverse && this.is_gaxis && (axis.fX1 !== axis.fX2); - this.swap_side = opts.swap_side || false; - this.fixed_ticks = opts.fixed_ticks || null; - this.maxTickSize = opts.maxTickSize || 0; - this.value_axis = opts.value_axis ?? false; // use fMinimum/fMaximum from source object - - if (opts.time_scale || axis.fTimeDisplay) { - this.kind = kAxisTime; - this.timeoffset = getTimeOffset(axis); - this.timegmt = getTimeGMT(axis); - } else if (opts.axis_func) - this.kind = kAxisFunc; - else - this.kind = !axis.fLabels || this.ignore_labels ? kAxisNormal : kAxisLabels; - - if (this.kind === kAxisTime) - this.func = time().domain([this.convertDate(smin), this.convertDate(smax)]); - else if (this.log) { - if ((this.log === 1) || (this.log === 10)) - this.logbase = 10; - else if (this.log === 3) - this.logbase = Math.E; - else - this.logbase = Math.round(this.log); - - if (smax <= 0) - smax = 1; - - if (opts.log_min_nz) - this.log_min_nz = opts.log_min_nz; - else if (axis && opts.logcheckmin) { - let v = 0; - for (let i = 0; i < axis.fNbins; ++i) { - v = axis.GetBinLowEdge(i + 1); - if (v > 0) - break; - v = axis.GetBinCenter(i + 1); - if (v > 0) - break; - } - if (v > 0) - this.log_min_nz = v; - } - - if ((smin <= 0) && this.log_min_nz) - smin = this.log_min_nz; - - if ((smin <= 0) || (smin >= smax)) - smin = smax * (opts.logminfactor || 1e-4); - - if (this.kind === kAxisFunc) - this.func = this.createFuncHandle(opts.axis_func, this.logbase, smin, smax); - else - this.func = log$1().base(this.logbase).domain([smin, smax]); - } else if (this.symlog) { - let v = Math.max(Math.abs(smin), Math.abs(smax)); - if (Number.isInteger(this.symlog) && (this.symlog > 0)) - v *= Math.pow(10, -1 * this.symlog); - else - v *= 0.01; - this.func = symlog().constant(v).domain([smin, smax]); - } else if (this.kind === kAxisFunc) - this.func = this.createFuncHandle(opts.axis_func, 0, smin, smax); - else - this.func = linear().domain([smin, smax]); - - if (this.vertical ^ this.reverse) - [range[0], range[1]] = [range[1], range[0]]; - - this.func.range(range); - - this.scale_min = smin; - this.scale_max = smax; - - if (this.kind === kAxisTime) - this.gr = val => this.func(this.convertDate(val)); - else if (this.log) - this.gr = val => { return (val < this.scale_min) ? (this.vertical ? this.func.range()[0] + 5 : -5) : this.func(val); }; - else - this.gr = this.func; - - delete this.format; // remove formatting func - - let ndiv = 508; - if (this.is_gaxis) - ndiv = axis.fNdiv; - else if (axis) - ndiv = axis.fNdivisions; - - this.exact_ticks = ndiv < 0; - if (this.exact_ticks) - ndiv = Math.abs(ndiv); - ndiv = Math.max(ndiv, 4); - this.nticks = ndiv % 100; - this.nticks2 = (ndiv % 10000 - this.nticks) / 100; - this.nticks3 = Math.floor(ndiv / 10000); - - if (axis && !this.is_gaxis) - this.nticks = Math.min(this.nticks, 20); - - let gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - if (gr_range <= 0) - gr_range = 100; - - if (this.kind === kAxisTime) { - this.nticks = Math.min(this.nticks, 8); - - const scale_range = this.scale_max - this.scale_min, - idF = axis.fTimeFormat.indexOf('%F'), - tf2 = chooseTimeFormat(scale_range / gr_range, false); - let tf1 = (idF >= 0) ? axis.fTimeFormat.slice(0, idF) : axis.fTimeFormat; - - if (!tf1 || (scale_range < 0.1 * (this.full_max - this.full_min))) - tf1 = chooseTimeFormat(scale_range / this.nticks, true); - - this.tfunc1 = this.tfunc2 = this.timegmt ? utcFormat(tf1) : timeFormat(tf1); - if (tf2 !== tf1) - this.tfunc2 = this.timegmt ? utcFormat(tf2) : timeFormat(tf2); - - this.format = this.formatTime; - } else if (this.log) { - if (this.nticks2 > 1) { - this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally - this.nticks2 = 1; - } - this.noexp = axis?.TestBit(EAxisBits.kNoExponent); - if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed && (this.log === 1)) - this.noexp = true; - this.moreloglabels = axis?.TestBit(EAxisBits.kMoreLogLabels); - this.format = this.formatLog; - } else if (this.kind === kAxisLabels) { - this.nticks = 50; // for text output allow max 50 names - const scale_range = this.scale_max - this.scale_min; - if (this.nticks > scale_range) - this.nticks = Math.round(scale_range); - - this.regular_labels = true; - - if (axis?.fNbins && axis?.fLabels) { - if ((axis.fNbins !== Math.round(axis.fXmax - axis.fXmin)) || - axis.fXmin || (axis.fXmax !== axis.fNbins)) - this.regular_labels = false; - } - - this.nticks2 = 1; - - this.format = this.formatLabels; - } else { - this.order = 0; - this.ndig = 0; - this.format = this.formatNormal; - } - } - - /** @summary Check zooming value for log scale - * @private */ - checkZoomMin(value) { - return this.log && this.log_min_nz ? Math.max(value, this.log_min_nz) : value; - } - - /** @summary Return scale min */ - getScaleMin() { - return this.func?.domain()[0] ?? 0; - } - - /** @summary Return scale max */ - getScaleMax() { - return this.func?.domain()[1] ?? 0; - } - - /** @summary Return true if labels may be removed while they are not fit to graphical range */ - cutLabels() { - if (!settings.CutAxisLabels) - return false; - if (isStr(settings.CutAxisLabels)) - return settings.CutAxisLabels.indexOf(this.name) >= 0; - return this.vertical; // cut vertical axis by default - } - - /** @summary Provide label for axis value */ - formatLabels(d) { - const a = this.getObject(); - let indx = parseFloat(d); - if (!this.regular_labels) - indx = Math.round((indx - a.fXmin) / (a.fXmax - a.fXmin) * a.fNbins); - else - indx = Math.floor(indx); - if ((indx < 0) || (indx >= a.fNbins)) - return null; - const arr = a.fLabels.arr; - for (let i = 0; i < arr.length; ++i) { - if (arr[i].fUniqueID === indx + 1) - return arr[i].fString; - } - return null; - } - - /** @summary Creates array with minor/middle/major ticks */ - createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { - if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) - this.noticksopt = true; - - const handle = { painter: this, nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: [], middle: [], major: [] }; - let ticks = []; - - if (this.fixed_ticks) { - this.fixed_ticks.forEach(v => { - if ((v >= this.scale_min) && (v <= this.scale_max)) - ticks.push(v); - }); - } else if (this.kind === kAxisLabels) { - handle.lbl_pos = []; - const axis = this.getObject(); - for (let n = 0; n <= axis.fNbins; ++n) { - const x = this.regular_labels ? n : axis.fXmin + n / axis.fNbins * (axis.fXmax - axis.fXmin); - if ((x >= this.scale_min) && (x <= this.scale_max)) { - handle.lbl_pos.push(x); - ticks.push(x); - } - } - } else - ticks = this.produceTicks(this.nticks); - - handle.minor = handle.middle = handle.major = ticks; - - if (only_major_as_array) { - const res = handle.major, delta = (this.scale_max - this.scale_min) * 1e-5; - if (res.at(0) > this.scale_min + delta) - res.unshift(this.scale_min); - if (res.at(-1) < this.scale_max - delta) - res.push(this.scale_max); - return res; - } - - if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10)) && !this.fixed_ticks) { - handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); - const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - // avoid black filling by middle-size - if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range)) - handle.minor = handle.middle = handle.major; - else if ((this.nticks3 > 1) && !this.log) { - handle.minor = this.produceTicks(handle.middle.length, this.nticks3); - if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range)) - handle.minor = handle.middle; - } - } - - handle.reset = function() { - this.nminor = this.nmiddle = this.nmajor = 0; - }; - - handle.next = function(doround) { - if (this.nminor >= this.minor.length) - return false; - - this.tick = this.minor[this.nminor++]; - this.grpos = this.func(this.tick); - if (doround) - this.grpos = Math.round(this.grpos); - this.kind = 3; - - if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { - this.nmiddle++; - this.kind = 2; - } - - if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1)) { - this.nmajor++; - this.kind = 1; - } - return true; - }; - - handle.last_major = function() { - return (this.kind !== 1) ? false : this.nmajor === this.major.length; - }; - - handle.next_major_grpos = function() { - return this.nmajor >= this.major.length ? null : this.func(this.major[this.nmajor]); - }; - - handle.get_modifier = function() { - return this.painter.findLabelModifier(this.painter.getObject(), this.nmajor - 1, this.major); - }; - - this.order = 0; - this.ndig = 0; - - // at the moment when drawing labels, we can try to find most optimal text representation for them - if (((this.kind === kAxisNormal) || (this.kind === kAxisFunc)) && !this.log && handle.major.length) { - let maxorder = 0, minorder = 0, exclorder3 = false; - - if (!optionNoexp && !this.cutLabels()) { - const maxtick = Math.max(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), - mintick = Math.min(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), - ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick) / 3) * 3 : 0, - ord2 = (mintick > 0) ? Math.round(Math.log10(mintick) / 3) * 3 : 0; - - exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - - if (maxtick || mintick) { - maxorder = Math.max(ord1, ord2) + 3; - minorder = Math.min(ord1, ord2) - 3; - } - } - - // now try to find best combination of order and ndig for labels - let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; - - for (let order = minorder; order <= maxorder; order += 3) { - if (exclorder3 && (order === 3)) - continue; - this.order = order; - this.ndig = 0; - let lbls = [], indx = 0, totallen = 0; - while (indx < handle.major.length) { - const v0 = handle.major[indx], - lbl = this.format(v0, true); - - let bad_value = lbls.indexOf(lbl) >= 0; - if (!bad_value) { - try { - const v1 = parseFloat(lbl) * Math.pow(10, order); - bad_value = (Math.abs(v0) > 1e-30) && (Math.abs(v1 - v0) / Math.abs(v0) > 1e-8); - } catch { - console.warn('Failure by parsing of', lbl); - bad_value = true; - } - } - if (bad_value) { - if (++this.ndig > 15) { - totallen += 1e10; - break; // not too many digits, anyway it will be exponential - } - lbls = []; - indx = totallen = 0; - } else { - lbls.push(lbl); - const p = lbl.indexOf('.'); - if (!order && !optionNoexp && ((p > gStyle.fAxisMaxDigits) || ((p < 0) && (lbl.length > gStyle.fAxisMaxDigits)))) { - totallen += 1e10; // do not use order = 0 when too many digits are there - exclorder3 = false; - } - totallen += lbl.length; - indx++; - } - } - - // for order === 0 we should virtually remove '0.' and extra label on top - if (!order && (this.ndig < 4)) - totallen -= handle.major.length * 2 + 3; - - if (totallen < bestlen) { - bestlen = totallen; - bestorder = this.order; - bestndig = this.ndig; - } - } - - this.order = bestorder; - this.ndig = bestndig; - - if (optionInt) { - if (this.order) - console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); - if (this.ndig) - console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); - this.ndig = 0; - this.order = 0; - } - } - - return handle; - } - - /** @summary Is labels should be centered */ - isCenteredLabels() { - if (this.kind === kAxisLabels) - return true; - if (this.log) - return false; - return this.getObject()?.TestBit(EAxisBits.kCenterLabels); - } - - /** @summary Is labels should be rotated */ - isRotateLabels() { - return this.getObject()?.TestBit(EAxisBits.kLabelsVert); - } - - /** @summary Is title should be rotated */ - isRotateTitle() { - return this.getObject()?.TestBit(EAxisBits.kRotateTitle); - } - - /** @summary Add interactive elements to draw axes title */ - addTitleDrag(title_g, vertical, offset_k, reverse, axis_length) { - if (!settings.MoveResize || this.isBatchMode()) - return; - - let drag_rect = null, x_0, y_0, i_0, - acc_x, acc_y, new_x, new_y, sign_0, alt_pos, curr_indx, can_indx0 = true; - const drag_move = drag().subject(Object); - - drag_move.on('start', evnt => { - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const box = title_g.node().getBBox(), // check that elements visible, request precise value - title_length = vertical ? box.height : box.width; - - x_0 = new_x = acc_x = title_g.property('shift_x'); - y_0 = new_y = acc_y = title_g.property('shift_y'); - - sign_0 = vertical ? (acc_x > 0) : (acc_y > 0); // sign should remain - can_indx0 = !this.hist_painter?.getSnapId(); // online canvas does not allow alternate position - - alt_pos = vertical ? [axis_length, axis_length / 2, 0] : [0, axis_length / 2, axis_length]; // possible positions - const off = vertical ? -title_length / 2 : title_length / 2; - if (this.title_align === 'middle') { - alt_pos[0] += off; - alt_pos[2] -= off; - } else if (this.title_align === 'begin') { - alt_pos[1] -= off; - alt_pos[2] -= 2 * off; - } else { // end - alt_pos[0] += 2 * off; - alt_pos[1] += off; - } - - if (this.titleCenter) - curr_indx = 1; - else if ((reverse ^ this.titleOpposite) && can_indx0) - curr_indx = 0; - else - curr_indx = 2; - - alt_pos[curr_indx] = vertical ? acc_y : acc_x; - i_0 = curr_indx; - - drag_rect = title_g.append('rect') - .attr('x', box.x) - .attr('y', box.y) - .attr('width', box.width) - .attr('height', box.height) - .style('cursor', 'move') - .call(addHighlightStyle, true); - // .style('pointer-events','none'); // let forward double click to underlying elements - }).on('drag', evnt => { - if (!drag_rect) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - acc_x += evnt.dx; - acc_y += evnt.dy; - - let set_x, set_y, besti = can_indx0 ? 0 : 1; - const p = vertical ? acc_y : acc_x; - - for (let i = 1; i < 3; ++i) { - if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) - besti = i; - } - - if (vertical) { - set_x = acc_x; - set_y = alt_pos[besti]; - } else { - set_y = acc_y; - set_x = alt_pos[besti]; - } - - if (sign_0 === (vertical ? (set_x > 0) : (set_y > 0))) { - new_x = set_x; - new_y = set_y; - curr_indx = besti; - makeTranslate(title_g, new_x, new_y); - } - }).on('end', evnt => { - if (!drag_rect) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - title_g.property('shift_x', new_x) - .property('shift_y', new_y); - - const axis = this.getObject(), axis2 = this.source_axis, - setBit = (bit, on) => { - axis?.SetBit(bit, on); - axis2?.SetBit(bit, on); - }; - - this.titleOffset = (vertical ? new_x : new_y) / offset_k; - const offset = this.titleOffset / this.offsetScaling / this.titleSize; - if (axis) - axis.fTitleOffset = offset; - if (axis2) - axis2.fTitleOffset = offset; - - if (curr_indx === 1) { - setBit(EAxisBits.kCenterTitle, true); - this.titleCenter = true; - setBit(EAxisBits.kOppositeTitle, false); - this.titleOpposite = false; - } else if (curr_indx === 0) { - setBit(EAxisBits.kCenterTitle, false); - this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, true); - this.titleOpposite = true; - } else { - setBit(EAxisBits.kCenterTitle, false); - this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, false); - this.titleOpposite = false; - } - - drag_rect.remove(); - drag_rect = null; - - if ((x_0 !== new_x) || (y_0 !== new_y) || (i_0 !== curr_indx)) - this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter ? 1 : 0})`); - - if (this.hist_painter && this.hist_axis) - this.hist_painter.getCanvPainter()?.producePadEvent('select', this.hist_painter.getPadPainter(), this); - }); - - title_g.style('cursor', 'move').call(drag_move); - } - - /** @summary Configure hist painter which creates axis - to be able submit execs - * @private */ - setHistPainter(hist_painter, axis_name) { - this.hist_painter = hist_painter; - this.hist_axis = axis_name; - } - - /** @summary Submit exec for the axis - if possible - * @private */ - submitAxisExec(exec, only_gaxis) { - const snapid = this.hist_painter?.getSnapId(); - if (snapid && this.hist_axis && !only_gaxis) - this.submitCanvExec(exec, `${snapid}#${this.hist_axis}`); - else if (this.is_gaxis) - this.submitCanvExec(exec); - } - - /** @summary Produce svg path for axis ticks */ - produceTicksPath(handle, side, tickSize, ticksPlusMinus, secondShift, real_draw) { - let path1 = '', path2 = ''; - this.ticks = []; - - while (handle.next(true)) { - let h1 = Math.round(tickSize / 4), h2 = 0; - - if (handle.kind < 3) - h1 = Math.round(tickSize / 2); - - if (handle.kind === 1) { - // if not showing labels, not show large tick - // FIXME: for labels last tick is smaller - if (!this.isExtraLogTick(handle.tick) && (this.format(handle.tick, true) !== null)) - h1 = tickSize; - this.ticks.push(handle.grpos); // keep graphical positions of major ticks - } - - if (ticksPlusMinus > 0) - h2 = -h1; - else if (side < 0) { - h2 = -h1; - h1 = 0; - } - - path1 += this.vertical ? `M${h1},${handle.grpos}H${h2}` : `M${handle.grpos},${-h1}V${-h2}`; - - if (secondShift) - path2 += this.vertical ? `M${secondShift - h1},${handle.grpos}H${secondShift - h2}` : `M${handle.grpos},${secondShift + h1}V${secondShift + h2}`; - } - - return real_draw ? path1 + path2 : ''; - } - - /** @summary Returns modifier for axis label */ - findLabelModifier(axis, nlabel, positions) { - if (!axis.fModLabs) - return null; - for (let n = 0; n < axis.fModLabs.arr.length; ++n) { - const mod = axis.fModLabs.arr[n]; - - if ((mod.fLabValue !== undefined) && (mod.fLabNum === 0)) { - const eps = this.log ? positions[nlabel] * 1e-6 : (this.scale_max - this.scale_min) * 1e-6; - if (Math.abs(mod.fLabValue - positions[nlabel]) < eps) - return mod; - } - - if ((mod.fLabNum === nlabel + 1) || - ((mod.fLabNum < 0) && (nlabel === positions.length + mod.fLabNum))) - return mod; - } - return null; - } - - /** @summary Draw axis labels - * @return {Promise} with array label size and max width */ - async drawLabels(axis_g, axis, w, h, handle, labelsside, labelsFont, labeloffset, tickSize, ticksPlusMinus, max_text_width, frame_ygap) { - const center_lbls = this.isCenteredLabels(), - label_g = [axis_g.append('svg:g').attr('class', 'axis_labels')], - lbl_pos = handle.lbl_pos || handle.major, - tilt_angle = gStyle.AxisTiltAngle ?? 25; - let rotate_lbls = this.isRotateLabels(), - textscale = 1, flipscale = 1, maxtextlen = 0, applied_scale = 0, - lbl_tilt = false, any_modified = false, max_textwidth = 0, max_tiltsize = 0; - - if (this.lbls_both_sides) - label_g.push(axis_g.append('svg:g').attr('class', 'axis_labels').attr('transform', this.vertical ? `translate(${w})` : `translate(0,${-h})`)); - - if (frame_ygap > 0) - max_tiltsize = frame_ygap / Math.sin(tilt_angle / 180 * Math.PI) - Math.tan(tilt_angle / 180 * Math.PI); - - // function called when text is drawn to analyze width, required to correctly scale all labels - // must be function to correctly handle 'this' argument - function process_drawtext_ready(painter) { - const textwidth = this.result_width; - max_textwidth = Math.max(max_textwidth, textwidth); - - const maxwidth = !this.gap_before ? 0.9 * this.gap_after : (!this.gap_after ? 0.9 * this.gap_before : this.gap_before * 0.45 + this.gap_after * 0.45); - - if (!painter.vertical && !rotate_lbls && this.result_height && maxwidth) - flipscale = Math.min(flipscale, maxwidth / this.result_height); - - if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) - textscale = Math.min(textscale, maxwidth / textwidth); - else if (painter.vertical && max_text_width && this.normal_side && (max_text_width - labeloffset > 20) && (textwidth > max_text_width - labeloffset)) - textscale = Math.min(textscale, (max_text_width - labeloffset) / textwidth); - - if ((textscale > 0.0001) && (textscale < 0.7) && !any_modified && - !painter.vertical && !rotate_lbls && (label_g.length === 1) && (lbl_tilt === false)) { - if (maxtextlen > 5) - lbl_tilt = true; - } - - let scale = textscale; - - if (lbl_tilt) { - if (max_tiltsize && max_textwidth) { - scale = Math.min(1, 0.8 * max_tiltsize / max_textwidth); - if (scale < textscale) { - // if due to tilt scale is even smaller - ignore tilting - lbl_tilt = 0; - scale = textscale; - } - } else - scale *= 3; - } - - if (((scale > 0.0001) && (scale < 1)) || (lbl_tilt !== false)) { - applied_scale = 1 / scale; - painter.scaleTextDrawing(applied_scale, label_g[0]); - } - } - - // check if short labels can be rotated - if (!this.vertical && this.regular_labels && !rotate_lbls) { - let tlen = 0; - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - const text = this.format(lbl_pos[nmajor], true); - if (text) - tlen = Math.max(tlen, text.length); - } - - if ((tlen > 2) && (tlen <= 5) && (lbl_pos.length * labelsFont.size > w / 2)) { - rotate_lbls = true; - lbl_tilt = 0; - } - } - - const draw_labels = (lcnt, side) => { - return this.startTextDrawingAsync(labelsFont, 'font', label_g[lcnt]).then(() => { - let lastpos = 0; - const fix_coord = this.vertical ? -labeloffset * side : labeloffset * side + ticksPlusMinus * tickSize; - - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - let text = this.format(lbl_pos[nmajor], true); - if (text === null) - continue; - - const mod = this.findLabelModifier(axis, nmajor, lbl_pos); - if (mod?.fTextSize === 0) - continue; - - if (mod) - any_modified = true; - if (mod?.fLabText) - text = mod.fLabText; - - const arg = { text, color: labelsFont.color, latex: 1, draw_g: label_g[lcnt], normal_side: (lcnt === 0) }; - let pos = Math.round(this.func(lbl_pos[nmajor])); - - // exclude labels for extra log ticks - if (lastpos && this.vertical && (Math.abs(pos - lastpos) < labelsFont.size * 1.1) && this.isExtraLogTick(lbl_pos[nmajor])) { - lastpos = pos; - continue; - } - - if (mod?.fTextColor > 0) - arg.color = this.getColor(mod.fTextColor); - - arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; - - arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; - - if (center_lbls) { - const gap = arg.gap_after || arg.gap_before; - pos = Math.round(pos - ((this.vertical !== this.reverse) ? 0.5 * gap : -0.5 * gap)); - if ((pos < -5) || (pos > (this.vertical ? h : w) + 5)) - continue; - } - - maxtextlen = Math.max(maxtextlen, text.length); - - if (this.vertical) { - arg.x = fix_coord; - arg.y = pos; - const flag = this.optionLeft || this.reverseAlign || (side < 0); - arg.align = rotate_lbls ? (flag ? 23 : 21) : (flag ? 12 : 32); - if (this.cutLabels()) { - const gap = labelsFont.size * (rotate_lbls ? 1.5 : 0.6); - if ((pos < gap) || (pos > h - gap)) - continue; - } - } else { - arg.x = pos; - arg.y = fix_coord; - arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 21 : 23); - if (this.log && !this.noexp && !this.vertical && arg.align === 23) { - arg.align = 21; - arg.y += labelsFont.size; - } else if (arg.align % 10 === 3) - arg.y -= labelsFont.size * 0.1; // font takes 10% more by top align - - if (this.cutLabels()) { - const gap = labelsFont.size * (rotate_lbls ? 0.4 : 1.5); - if ((pos < gap) || (pos > w - gap)) - continue; - } - } - - if (rotate_lbls) - arg.rotate = 270; - else if (mod && mod.fTextAngle !== -1) - arg.rotate = -mod.fTextAngle; - - // only for major text drawing scale factor need to be checked - // for modified labels ignore scaling - if ((lcnt === 0) && !mod?.fLabText) - arg.post_process = process_drawtext_ready; - - this.drawText(arg); - - // workaround for symlog where labels can be compressed to close - if (this.symlog && lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { - const axis_step = Math.abs(pos - lastpos); - textscale = Math.min(textscale, 1.1 * axis_step / labelsFont.size); - } - - lastpos = pos; - } - - if (this.order) { - let xoff = 0, yoff = 0; - if (this.name === 'xaxis') { - xoff = gStyle.fXAxisExpXOffset || 0; - yoff = gStyle.fXAxisExpYOffset || 0; - } else if (this.name === 'yaxis') { - xoff = gStyle.fYAxisExpXOffset || 0; - yoff = gStyle.fYAxisExpYOffset || 0; - } - - if (xoff) - xoff = Math.round(xoff * (this.getPadPainter()?.getPadWidth() ?? 0)); - if (yoff) - yoff = Math.round(yoff * (this.getPadPainter()?.getPadHeight() ?? 0)); - - this.drawText({ - color: labelsFont.color, - x: xoff + (this.vertical ? side * 5 : w + 5), - y: yoff + (this.has_obstacle ? fix_coord : (this.vertical ? -3 : -3 * side)), - align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), - latex: 1, - text: '#times' + this.formatExp(10, this.order), - draw_g: label_g[lcnt] - }); - } - - if ((lcnt > 0) && applied_scale) - this.scaleTextDrawing(applied_scale, label_g[lcnt]); - - return this.finishTextDrawing(label_g[lcnt], true); - }); - }; - - return draw_labels(0, labelsside).then(() => { - return label_g.length < 2 ? true : draw_labels(1, -labelsside); - }).then(() => { - this._maxlbllen = maxtextlen; // for internal use in palette painter - - if (lbl_tilt) { - label_g[0].selectAll('text').each(function() { - const txt = select(this), tr = txt.attr('transform'); - if (lbl_tilt) - txt.attr('transform', `${tr} rotate(${tilt_angle})`).style('text-anchor', 'start'); - }); - } - - return max_textwidth; - }); - } - - /** @summary Extract major draw attributes, which are also used in interactive operations - * @private */ - extractDrawAttributes(scalingSize, w, h) { - const axis = this.getObject(); - let pp = this.getPadPainter(); - if (axis.$use_top_pad) - pp = pp?.getPadPainter(); // workaround for ratio plot - const pad_w = pp?.getPadWidth() || scalingSize || w / 0.8, // use factor 0.8 as ratio between frame and pad size - pad_h = pp?.getPadHeight() || scalingSize || h / 0.8, - // if no external scaling size use scaling as in TGaxis.cxx:1448 - NDC axis length is in the scaling factor - tickScalingSize = scalingSize || (this.vertical ? h / pad_h * pad_w : w / pad_w * pad_h), - bit_plus = axis.TestBit(EAxisBits.kTickPlus), bit_minus = axis.TestBit(EAxisBits.kTickMinus); - - let tickSize, titleColor, titleFontId, offset; - - this.scalingSize = scalingSize || Math.max(Math.min(pad_w, pad_h), 10); - - if (this.is_gaxis) { - const optionSize = axis.fChopt.indexOf('S') >= 0; - this.optionUnlab = axis.fChopt.indexOf('U') >= 0; - this.optionMinus = (axis.fChopt.indexOf('-') >= 0) || bit_minus; - this.optionPlus = (axis.fChopt.indexOf('+') >= 0) || bit_plus; - this.optionNoopt = (axis.fChopt.indexOf('N') >= 0); // no ticks position optimization - this.optionInt = (axis.fChopt.indexOf('I') >= 0); // integer labels - this.optionText = (axis.fChopt.indexOf('T') >= 0); // text scaling? - this.optionLeft = (axis.fChopt.indexOf('L') >= 0); // left text align - this.optionRight = (axis.fChopt.indexOf('R') >= 0); // right text align - this.optionCenter = (axis.fChopt.indexOf('C') >= 0); // center text align - this.createAttLine({ attr: axis }); - tickSize = optionSize ? axis.fTickSize : 0.03; - titleColor = this.getColor(axis.fTextColor); - titleFontId = axis.fTextFont; - offset = axis.fLabelOffset; - // workaround for old reverse axes where offset is not properly working - if (this.reverse && (!this.vertical || (!this.optionMinus && (axis.fX1 !== axis.fX2)))) - offset = -offset; - } else { - this.optionUnlab = false; - if (!bit_plus && !bit_minus) { - this.optionMinus = this.vertical ^ this.invert_side; - this.optionPlus = !this.optionMinus; - } else { - this.optionPlus = bit_plus; - this.optionMinus = bit_minus; - } - this.optionNoopt = false; // no ticks position optimization - this.optionInt = false; // integer labels - this.optionText = false; - this.createAttLine({ color: axis.fAxisColor, width: 1, style: 1 }); - tickSize = axis.fTickLength; - titleColor = this.getColor(axis.fTitleColor); - titleFontId = axis.fTitleFont; - offset = axis.fLabelOffset; - } - - offset += (this.vertical ? 0.002 : 0.005); - - if (this.kind === kAxisLabels) - this.optionText = true; - - this.optionNoexp = axis.TestBit(EAxisBits.kNoExponent); - - this.ticksSize = Math.round(tickSize * tickScalingSize); - if (scalingSize && (this.ticksSize < 0)) - this.ticksSize = -this.ticksSize; - - if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) - this.ticksSize = this.maxTickSize; - - // now used only in 3D drawing - this.ticksColor = this.lineatt.color; - this.ticksWidth = this.lineatt.width; - - const k = this.optionText ? 0.66666 : 1; // set TGaxis.cxx, line 1504 - this.labelSize = Math.round((axis.fLabelSize < 1) ? k * axis.fLabelSize * this.scalingSize : k * axis.fLabelSize); - this.labelsOffset = Math.round(offset * this.scalingSize); - this.labelsFont = new FontHandler(axis.fLabelFont, this.labelSize, scalingSize); - if ((this.labelSize <= 0) || (Math.abs(axis.fLabelOffset) > 1.1)) - this.optionUnlab = true; // disable labels when size not specified - this.labelsFont.setColor(this.getColor(axis.fLabelColor)); - - this.fTitle = axis.fTitle; - if (this.fTitle) { - this.titleSize = (axis.fTitleSize >= 1) ? axis.fTitleSize : Math.round(axis.fTitleSize * this.scalingSize); - this.titleFont = new FontHandler(titleFontId, this.titleSize, scalingSize); - this.titleFont.setColor(titleColor); - this.offsetScaling = (axis.fTitleSize >= 1) ? 1 : (this.vertical ? pad_w : pad_h) / this.scalingSize; - this.titleOffset = axis.fTitleOffset; - if (!this.titleOffset && this.name[0] === 'x') - this.titleOffset = gStyle.fXaxis.fTitleOffset; - this.titleOffset *= this.titleSize * this.offsetScaling; - this.titleCenter = axis.TestBit(EAxisBits.kCenterTitle); - this.titleOpposite = axis.TestBit(EAxisBits.kOppositeTitle); - } else { - delete this.titleSize; - delete this.titleFont; - delete this.offsetScaling; - delete this.titleOffset; - delete this.titleCenter; - delete this.titleOpposite; - } - } - - /** @summary function draws TAxis or TGaxis object - * @return {Promise} for drawing ready */ - async drawAxis(layer, w, h, transform, secondShift, disable_axis_drawing, max_text_width, calculate_position, frame_ygap) { - const axis = this.getObject(), - swap_side = this.swap_side || false; - let axis_g = layer, draw_lines = true; - - // shift for second ticks set (if any) - if (!secondShift) - secondShift = 0; - else if (this.invert_side) - secondShift = -secondShift; - - this.extractDrawAttributes(undefined, w, h); - - if (this.is_gaxis) - draw_lines = Boolean(axis.fLineColor); - - if (!this.is_gaxis || (this.name === 'zaxis')) { - axis_g = layer.selectChild(`.${this.name}_container`); - if (axis_g.empty()) - axis_g = layer.append('svg:g').attr('class', `${this.name}_container`); - else - axis_g.selectAll('*').remove(); - } - - let axis_lines = ''; - if (draw_lines) { - axis_lines = 'M0,0' + (this.vertical ? `v${h}` : `h${w}`); - if (secondShift) - axis_lines += this.vertical ? `M${secondShift},0v${h}` : `M0,${secondShift}h${w}`; - } - - axis_g.attr('transform', transform); - - let side = 1, ticksPlusMinus = 0; - - if (this.optionPlus && this.optionMinus) - side = ticksPlusMinus = 1; - else if (this.optionMinus) - side = (swap_side ^ this.vertical) ? 1 : -1; - else if (this.optionPlus) - side = (swap_side ^ this.vertical) ? -1 : 1; - - // first draw ticks - - const handle = this.createTicks(false, this.optionNoexp, this.optionNoopt, this.optionInt); - - axis_lines += this.produceTicksPath(handle, side, this.ticksSize, ticksPlusMinus, secondShift, draw_lines && !disable_axis_drawing && !this.disable_ticks); - - if (!disable_axis_drawing && axis_lines && !this.lineatt.empty()) { - axis_g.append('svg:path') - .attr('d', axis_lines) - .call(this.lineatt.func); - } - - let title_shift_x = 0, title_shift_y = 0, title_g, labelsMaxWidth = 0; - // draw labels (sometime on both sides) - const labelSize = Math.max(this.labelsFont.size, 5), - pr = (disable_axis_drawing || this.optionUnlab) - ? Promise.resolve(0) - : this.drawLabels(axis_g, axis, w, h, handle, side, this.labelsFont, this.labelsOffset, this.ticksSize, ticksPlusMinus, max_text_width, frame_ygap); - - return pr.then(maxw => { - labelsMaxWidth = maxw; - - if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { - const r = axis_g.append('svg:rect') - .attr('class', 'axis_zoom') - .style('opacity', '0') - .style('cursor', 'crosshair'); - - if (this.vertical) { - const rw = Math.max(labelsMaxWidth, 2 * labelSize) + 3; - r.attr('x', (side > 0) ? -rw : 0).attr('y', 0) - .attr('width', rw).attr('height', h); - } else { - r.attr('x', 0).attr('y', (side > 0) ? 0 : -labelSize - 3) - .attr('width', w).attr('height', labelSize + 3); - } - } - - this.position = 0; - - if (calculate_position) { - const node1 = axis_g.node(), - node2 = this.getPadPainter()?.getPadSvg().node(); - if (isFunc(node1?.getBoundingClientRect) && isFunc(node2?.getBoundingClientRect)) { - const rect1 = node1.getBoundingClientRect(), - rect2 = node2.getBoundingClientRect(); - this.position = rect1.left - rect2.left; // use to control left position of Y scale - } - if (node1 && !node2) - console.warn('Why PAD element missing when search for position'); - } - - if (!this.fTitle || disable_axis_drawing) - return; - - title_g = axis_g.append('svg:g').attr('class', 'axis_title'); - - return this.startTextDrawingAsync(this.titleFont, 'font', title_g); - }).then(() => { - if (!title_g) - return; - - const rotate = this.isRotateTitle() ? -1 : 1, - xor_reverse = swap_side ^ this.titleOpposite, myxor = (rotate < 0) ^ xor_reverse; - - let title_offest_k = side; - - this.title_align = this.titleCenter ? 'middle' : (myxor ? 'begin' : 'end'); - - if (this.vertical) { - title_offest_k *= -1.6; - - title_shift_x = Math.round(title_offest_k * this.titleOffset); - - title_shift_y = Math.round(this.titleCenter ? h / 2 : (xor_reverse ? h : 0)); - - this.drawText({ - align: this.title_align + ';middle', - rotate: (rotate < 0) ? 90 : 270, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g - }); - } else { - title_offest_k *= 1.6; - - title_shift_x = Math.round(this.titleCenter ? w / 2 : (xor_reverse ? 0 : w)); - title_shift_y = Math.round(title_offest_k * this.titleOffset); - this.drawText({ - align: this.title_align + ';middle', - rotate: (rotate < 0) ? 180 : 0, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g - }); - } - - this.addTitleDrag(title_g, this.vertical, title_offest_k, swap_side, this.vertical ? h : w); - - return this.finishTextDrawing(title_g); - }).then(() => { - if (title_g) { - if (!this.titleOffset && this.vertical) - title_shift_x = Math.round(-side * ((labelsMaxWidth || labelSize) + 0.7 * this.offsetScaling * this.titleSize)); - makeTranslate(title_g, title_shift_x, title_shift_y); - title_g.property('shift_x', title_shift_x) - .property('shift_y', title_shift_y); - } - - return this; - }); - } - -} // class TAxisPainter - -const logminfactorX = 0.0001, logminfactorY = 3e-4; - -/** @summary Configure tooltip enable flag for painter - * @private */ -function setPainterTooltipEnabled(painter, on) { - if (!painter) - return; - - const fp = painter.getFramePainter(); - if (isFunc(fp?.setTooltipEnabled)) { - fp.setTooltipEnabled(on); - fp.processFrameTooltipEvent(null); - } - // this is 3D control object - if (isFunc(painter.control?.setTooltipEnabled)) - painter.control.setTooltipEnabled(on); -} - -/** @summary Return pointers on touch event - * @private */ -function get_touch_pointers(event, node) { - return event.$touch_arr ?? pointers(event, node); -} - -/** @summary Returns coordinates transformation func - * @private */ -function getEarthProjectionFunc(id) { - switch (id) { - // Aitoff2xy - case 1: return (l, b) => { - const DegToRad = Math.PI / 180, - alpha2 = (l / 2) * DegToRad, - delta = b * DegToRad, - r2 = Math.sqrt(2), - f = 2 * r2 / Math.PI, - cdec = Math.cos(delta), - denom = Math.sqrt(1.0 + cdec * Math.cos(alpha2)); - return { - x: cdec * Math.sin(alpha2) * 2.0 * r2 / denom / f / DegToRad, - y: Math.sin(delta) * r2 / denom / f / DegToRad - }; - }; - // mercator - case 2: return (l, b) => { return { x: l, y: Math.log(Math.tan((Math.PI / 2 + b / 180 * Math.PI) / 2)) }; }; - // sinusoidal - case 3: return (l, b) => { return { x: l * Math.cos(b / 180 * Math.PI), y: b }; }; - // parabolic - case 4: return (l, b) => { return { x: l * (2.0 * Math.cos(2 * b / 180 * Math.PI / 3) - 1), y: 180 * Math.sin(b / 180 * Math.PI / 3) }; }; - // Mollweide projection - case 5: return (l, b) => { - const theta0 = b * Math.PI / 180; - let theta = theta0, num, den; - for (let i = 0; i < 100; i++) { - num = 2 * theta + Math.sin(2 * theta) - Math.PI * Math.sin(theta0); - den = 4 * (Math.cos(theta) ** 2); - if (den < 1e-20) { - theta = theta0; - break; - } - theta -= num / den; - if (Math.abs(num / den) < 1e-4) - break; - } - return { - x: l * Math.cos(theta), - y: 90 * Math.sin(theta) - }; - }; - } -} - -/** @summary Unzoom preselected range for main histogram painter - * @desc Used with TGraph where Y zooming selected with fMinimum/fMaximum but histogram - * axis range can be wider. Or for normal histogram drawing when preselected range smaller than histogram range - * @private */ -function unzoomHistogramYRange(main) { - if (!isFunc(main?.getDimension) || main.getDimension() !== 1) - return; - - const ymin = main.draw_content ? main.hmin : main.ymin, - ymax = main.draw_content ? main.hmax : main.ymax; - - if ((main.zoom_ymin !== main.zoom_ymax) && (ymin !== ymax) && - (ymin <= main.zoom_ymin) && (main.zoom_ymax <= ymax)) - main.zoom_ymin = main.zoom_ymax = 0; -} - -// global, allow single drag at once -let drag_rect = null, drag_kind = '', drag_painter = null; - -/** @summary Check if dragging performed currently - * @private */ -function is_dragging(painter, kind) { - return drag_rect && (drag_painter === painter) && (drag_kind === kind); -} - -/** @summary Add drag for interactive rectangular elements for painter - * @private */ -function addDragHandler(_painter, arg) { - if (!settings.MoveResize) - return; - - const painter = _painter, pp = painter.getPadPainter(); - if (pp?.isFastDrawing() || pp?.isBatchMode()) - return; - // cleanup all drag elements when canvas is not editable - if (pp?.isEditable() === false) - arg.cleanup = true; - - if (!isFunc(arg.getDrawG)) - arg.getDrawG = () => painter?.getG(); - - function makeResizeElements(group, handler) { - function addElement(cursor, d) { - const clname = 'js_' + cursor.replace(/[-]/g, '_'); - let elem = group.selectChild('.' + clname); - if (arg.cleanup) - return elem.remove(); - if (elem.empty()) - elem = group.append('path').classed(clname, true); - elem.style('opacity', 0).style('cursor', cursor).attr('d', d); - if (handler) - elem.call(handler); - } - - addElement('nw-resize', 'M2,2h15v-5h-20v20h5Z'); - addElement('ne-resize', `M${arg.width - 2},2h-15v-5h20v20h-5 Z`); - addElement('sw-resize', `M2,${arg.height - 2}h15v5h-20v-20h5Z`); - addElement('se-resize', `M${arg.width - 2},${arg.height - 2}h-15v5h20v-20h-5Z`); - - if (!arg.no_change_x) { - addElement('w-resize', `M-3,18h5v${Math.max(0, arg.height - 2 * 18)}h-5Z`); - addElement('e-resize', `M${arg.width + 3},18h-5v${Math.max(0, arg.height - 2 * 18)}h5Z`); - } - if (!arg.no_change_y) { - addElement('n-resize', `M18,-3v5h${Math.max(0, arg.width - 2 * 18)}v-5Z`); - addElement('s-resize', `M18,${arg.height + 3}v-5h${Math.max(0, arg.width - 2 * 18)}v5Z`); - } - } - - const complete_drag = (newx, newy, newwidth, newheight) => { - drag_painter = null; - drag_kind = ''; - if (drag_rect) { - drag_rect.remove(); - drag_rect = null; - } - - const draw_g = arg.getDrawG(); - - if (!draw_g) - return false; - - const oldx = arg.x, oldy = arg.y; - - if (arg.minwidth && newwidth < arg.minwidth) - newwidth = arg.minwidth; - if (arg.minheight && newheight < arg.minheight) - newheight = arg.minheight; - - const change_size = (newwidth !== arg.width) || (newheight !== arg.height), - change_pos = (newx !== oldx) || (newy !== oldy); - - arg.x = newx; - arg.y = newy; - arg.width = newwidth; - arg.height = newheight; - - if (!arg.no_transform) - makeTranslate(draw_g, newx, newy); - - setPainterTooltipEnabled(painter, true); - - makeResizeElements(draw_g); - - if (change_size || change_pos) { - if (change_size && isFunc(arg.resize)) - arg.resize(newwidth, newheight); - - if (change_pos && isFunc(arg.move)) - arg.move(newx, newy, newx - oldx, newy - oldy); - - if (change_size || change_pos) { - if (arg.obj) { - const rect = arg.pad_rect ?? pp.getPadRect(); - arg.obj.fX1NDC = newx / rect.width; - arg.obj.fX2NDC = (newx + newwidth) / rect.width; - arg.obj.fY1NDC = 1 - (newy + newheight) / rect.height; - arg.obj.fY2NDC = 1 - newy / rect.height; - arg.obj.$modifiedNDC = true; // indicate that NDC was interactively changed, block in updated - } else if (isFunc(arg.move_resize)) - arg.move_resize(newx, newy, newwidth, newheight); - - if (isFunc(arg.redraw)) - arg.redraw(arg); - } - } - - return change_size || change_pos; - }, drag_move = drag().subject(Object), drag_move_off = drag().subject(Object); - - drag_move_off.on('start', null).on('drag', null).on('end', null); - - drag_move - .on('start', evnt => { - if (detectRightButton(evnt.sourceEvent) || drag_kind) - return; - if (isFunc(arg.is_disabled) && arg.is_disabled('move')) - return; - - closeMenu(); // close menu - setPainterTooltipEnabled(painter, false); // disable tooltip - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { - x: arg.x, y: arg.y, width: arg.width, height: arg.height, - acc_x1: arg.x, acc_y1: arg.y, - pad_w: pad_rect.width - arg.width, - pad_h: pad_rect.height - arg.height, - drag_tm: new Date(), - path: `v${arg.height}h${arg.width}v${-arg.height}z`, - evnt_x: evnt.x, evnt_y: evnt.y - }; - - drag_painter = painter; - drag_kind = 'move'; - drag_rect = select(arg.getDrawG().node().parentNode).append('path') - .attr('d', `M${handle.acc_x1},${handle.acc_y1}${handle.path}`) - .style('cursor', 'move') - .style('pointer-events', 'none') // let forward double click to underlying elements - .property('drag_handle', handle) - .call(addHighlightStyle, true); - }).on('drag', evnt => { - if (!is_dragging(painter, 'move')) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const handle = drag_rect.property('drag_handle'); - - if (!arg.no_change_x) - handle.acc_x1 += evnt.dx; - if (!arg.no_change_y) - handle.acc_y1 += evnt.dy; - - handle.x = Math.min(Math.max(handle.acc_x1, 0), handle.pad_w); - handle.y = Math.min(Math.max(handle.acc_y1, 0), handle.pad_h); - - drag_rect.attr('d', `M${handle.x},${handle.y}${handle.path}`); - }).on('end', evnt => { - if (!is_dragging(painter, 'move')) - return; - - evnt.sourceEvent.stopPropagation(); - evnt.sourceEvent.preventDefault(); - - const handle = drag_rect.property('drag_handle'); - - if (complete_drag(handle.x, handle.y, arg.width, arg.height) === false) { - const spent = (new Date()).getTime() - handle.drag_tm.getTime(); - - if (arg.ctxmenu && (spent > 600)) - showPainterMenu({ clientX: handle.evnt_x, clientY: handle.evnt_y, skip_close: 1 }, painter); - else if (arg.canselect && (spent <= 600)) - painter.getPadPainter()?.selectObjectPainter(painter); - } - }); - - const drag_resize = drag().subject(Object); - - drag_resize - .on('start', function(evnt) { - if (detectRightButton(evnt.sourceEvent) || drag_kind) - return; - if (isFunc(arg.is_disabled) && arg.is_disabled('resize')) - return; - - closeMenu(); // close menu - setPainterTooltipEnabled(painter, false); // disable tooltip - - evnt.sourceEvent.stopPropagation(); - evnt.sourceEvent.preventDefault(); - - const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { - x: arg.x, y: arg.y, width: arg.width, height: arg.height, - acc_x1: arg.x, acc_y1: arg.y, - acc_x2: arg.x + arg.width, acc_y2: arg.y + arg.height, - pad_w: pad_rect.width, pad_h: pad_rect.height - }; - - drag_painter = painter; - drag_kind = 'resize'; - drag_rect = select(arg.getDrawG().node().parentNode) - .append('rect') - .style('cursor', select(this).style('cursor')) - .attr('x', handle.acc_x1) - .attr('y', handle.acc_y1) - .attr('width', handle.acc_x2 - handle.acc_x1) - .attr('height', handle.acc_y2 - handle.acc_y1) - .property('drag_handle', handle) - .call(addHighlightStyle, true); - }).on('drag', function(evnt) { - if (!is_dragging(painter, 'resize')) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const handle = drag_rect.property('drag_handle'), - elem = select(this), - dx = arg.no_change_x ? 0 : evnt.dx, - dy = arg.no_change_y ? 0 : evnt.dy; - - if (elem.classed('js_nw_resize')) { - handle.acc_x1 += dx; - handle.acc_y1 += dy; - } else if (elem.classed('js_ne_resize')) { - handle.acc_x2 += dx; - handle.acc_y1 += dy; - } else if (elem.classed('js_sw_resize')) { - handle.acc_x1 += dx; - handle.acc_y2 += dy; - } else if (elem.classed('js_se_resize')) { - handle.acc_x2 += dx; - handle.acc_y2 += dy; - } else if (elem.classed('js_w_resize')) - handle.acc_x1 += dx; - else if (elem.classed('js_n_resize')) - handle.acc_y1 += dy; - else if (elem.classed('js_e_resize')) - handle.acc_x2 += dx; - else if (elem.classed('js_s_resize')) - handle.acc_y2 += dy; - - const x1 = Math.max(0, handle.acc_x1), x2 = Math.min(handle.acc_x2, handle.pad_w), - y1 = Math.max(0, handle.acc_y1), y2 = Math.min(handle.acc_y2, handle.pad_h); - - handle.x = Math.min(x1, x2); - handle.y = Math.min(y1, y2); - handle.width = Math.abs(x2 - x1); - handle.height = Math.abs(y2 - y1); - - drag_rect.attr('x', handle.x).attr('y', handle.y).attr('width', handle.width).attr('height', handle.height); - }).on('end', evnt => { - if (!is_dragging(painter, 'resize')) - return; - - evnt.sourceEvent.preventDefault(); - - const handle = drag_rect.property('drag_handle'); - - complete_drag(handle.x, handle.y, handle.width, handle.height); - }); - - if (!arg.only_resize) - arg.getDrawG().style('cursor', arg.cleanup ? null : 'move').call(arg.cleanup ? drag_move_off : drag_move); - - if (!arg.only_move) - makeResizeElements(arg.getDrawG(), drag_resize); -} - -/** @summary Tooltip handler class - * @private */ -class TooltipHandler extends ObjectPainter { - - // cannot use private members because of RFramePainter - - /** @desc only canvas info_layer can be used while other pads can overlay - * @return layer where frame tooltips are shown */ - hints_layer() { - return this.getCanvPainter()?.getLayerSvg('info_layer') ?? select(null); - } - - /** @return true if tooltip is shown, use to prevent some other action */ - isTooltipShown() { - if (!this.tooltip_enabled || !this.isTooltipAllowed()) - return false; - const hintsg = this.hints_layer().selectChild('.objects_hints'); - return hintsg.empty() ? false : hintsg.property('hints_pad') === this.getPadPainter()?.getPadName(); - } - - /** @summary set tooltips enabled on/off */ - setTooltipEnabled(enabled) { - if (enabled !== undefined) - this.tooltip_enabled = enabled; - } - - /** @summary central function which let show selected hints for the object */ - processFrameTooltipEvent(pnt, evnt) { - if (pnt?.handler) { - // special use of interactive handler in the frame painter - const rect = this.getG()?.selectChild('.main_layer'); - if (!rect || rect.empty()) - pnt = null; // disable - else if (pnt.touch && evnt) { - const pos = get_touch_pointers(evnt, rect.node()); - pnt = (pos && pos.length === 1) ? { touch: true, x: pos[0][0], y: pos[0][1] } : null; - } else if (evnt) { - const pos = pointer(evnt, rect.node()); - pnt = { touch: false, x: pos[0], y: pos[1] }; - } - } - - let nhints = 0, nexact = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false; - const hmargin = 3, wmargin = 3, hstep = 1.2, - frame_rect = this.getFrameRect(), - pp = this.getPadPainter(), - pad_width = pp?.getPadWidth(), - scale = pp?.getPadScale() ?? 1, - textheight = (pnt?.touch ? 15 : 11) * scale, - font = new FontHandler(160, textheight), - disable_tooltips = !this.isTooltipAllowed() || !this.tooltip_enabled; - - if (pnt) { - pnt.disabled = disable_tooltips; // indicate that highlighting is not required - pnt.painters = true; // get also painter - } - - // collect tooltips from pad painter - it has list of all drawn objects - const hints = pp?.processPadTooltipEvent(pnt) ?? []; - - if (pnt && frame_rect) - pp.deliverWebCanvasEvent('move', frame_rect.x + pnt.x, frame_rect.y + pnt.y, hints ? hints[0]?.painter?.getSnapId() : ''); - - for (let n = 0; n < hints.length; ++n) { - const hint = hints[n]; - if (!hint) - continue; - - if (hint.user_info !== undefined) - hint.painter?.provideUserTooltip(hint.user_info); - - if (!hint.lines?.length) { - hints[n] = null; - continue; - } - - // check if fully duplicated hint already exists - for (let k = 0; k < n; ++k) { - const hprev = hints[k]; - let diff = hprev?.lines.length !== hint.lines.length; - for (let l = 0; l < hint.lines.length && !diff; ++l) { - if (hprev.lines[l] !== hint.lines[l]) - diff = true; - } - if (!diff) { - hints[n] = null; - break; - } - } - if (!hints[n]) - continue; - - nhints++; - - if (hint.exact) - nexact++; - - hint.lines.forEach(line => { maxlen = Math.max(maxlen, line.length); }); - - hint.height = Math.round(hint.lines.length * textheight * hstep + 2 * hmargin - textheight * (hstep - 1)); - - if ((hint.color1 !== undefined) && (hint.color1 !== 'none')) { - if (lastcolor1 && (lastcolor1 !== hint.color1)) - usecolor1 = true; - lastcolor1 = hint.color1; - } - } - - let path_name = null, same_path = hints.length > 1; - for (let n = 0; n < hints.length; ++n) { - const hint = hints[n], p = hint?.lines ? hint.lines[0]?.lastIndexOf('/') : -1; - if (p > 0) { - const path = hint.lines[0].slice(0, p + 1); - if (path_name === null) - path_name = path; - else if (path_name !== path) - same_path = false; - } else - same_path = false; - } - - const layer = this.hints_layer(), - show_only_best = nhints > 15, - coordinates = pnt ? Math.round(pnt.x) + ',' + Math.round(pnt.y) : ''; - let hintsg = layer.selectChild('.objects_hints'), // group with all tooltips - title = '', name = '', info = '', - hint0 = null, best_dist2 = 1e10, best_hint = null; - - // try to select hint with exact match of the position when several hints available - for (let k = 0; k < hints.length; ++k) { - if (!hints[k]) - continue; - if (!hint0) - hint0 = hints[k]; - - // select exact hint if this is the only one - if (hints[k].exact && (nexact < 2) && (!hint0 || !hint0.exact)) { - hint0 = hints[k]; - break; - } - - if (!pnt || (hints[k].x === undefined) || (hints[k].y === undefined)) - continue; - - const dist2 = (pnt.x - hints[k].x) ** 2 + (pnt.y - hints[k].y) ** 2; - if (dist2 < best_dist2) { - best_dist2 = dist2; - best_hint = hints[k]; - } - } - - if ((!hint0 || !hint0.exact) && (best_dist2 < 400)) - hint0 = best_hint; - - if (hint0) { - name = (hint0.lines && hint0.lines.length > 1) ? hint0.lines[0] : hint0.name; - title = hint0.title || ''; - info = hint0.line; - if (!info && hint0.lines) - info = hint0.lines.slice(1).join(' '); - } - - this.showObjectStatus(name, title, info, coordinates); - - // end of closing tooltips - if (!pnt || disable_tooltips || !hints.length || (maxlen === 0) || (show_only_best && !best_hint)) { - hintsg.remove(); - return; - } - - // we need to set pointer-events=none for all elements while hints - // placed in front of so-called interactive rect in frame, used to catch mouse events - - if (hintsg.empty()) { - hintsg = layer.append('svg:g') - .attr('class', 'objects_hints') - .style('pointer-events', 'none'); - } - - let frame_shift = { x: 0, y: 0 }, trans = frame_rect.transform || ''; - if (!pp?.isCanvas()) { - frame_shift = getAbsPosInCanvas(pp.getPadSvg(), frame_shift); - trans = `translate(${frame_shift.x},${frame_shift.y}) ${trans}`; - } - - // copy transform attributes from frame itself - hintsg.attr('transform', trans) - .property('last_point', pnt) - .property('hints_pad', pp.getPadName()); - - let viewmode = hintsg.property('viewmode') || '', - actualw = 0, posx = pnt.x + frame_rect.hint_delta_x; - - if (show_only_best || (nhints === 1)) { - viewmode = 'single'; - posx += 15; - } else { - // if there are many hints, place them left or right - - let bleft = 0.5, bright = 0.5; - - if (viewmode === 'left') - bright = 0.7; - else if (viewmode === 'right') - bleft = 0.3; - - if (posx <= bleft * frame_rect.width) { - viewmode = 'left'; - posx = 20; - } else if (posx >= bright * frame_rect.width) { - viewmode = 'right'; - posx = frame_rect.width - 60; - } else - posx = hintsg.property('startx') ?? 20; - } - - if (viewmode !== hintsg.property('viewmode')) { - hintsg.property('viewmode', viewmode); - hintsg.selectAll('*').remove(); - } - - let curry = 10, // normal y coordinate - gapy = 10, // y coordinate, taking into account all gaps - gapminx = -1111, gapmaxx = -1111; - const minhinty = -frame_shift.y, - cp = this.getCanvPainter(), - maxhinty = cp.getPadHeight() - frame_rect.y - frame_shift.y; - - for (let n = 0; n < hints.length; ++n) { - let hint = hints[n], - group = hintsg.selectChild(`.painter_hint_${n}`); - - if (show_only_best && (hint !== best_hint)) - hint = null; - - if (hint === null) { - group.remove(); - continue; - } - - const was_empty = group.empty(); - - if (was_empty) { - group = hintsg.append('svg:svg') - .attr('class', `painter_hint_${n}`) - .attr('opacity', 0) // use attribute, not style to make animation with d3.transition() - .style('overflow', 'hidden') - .style('pointer-events', 'none'); - } - - if (viewmode === 'single') - curry = pnt.touch ? (pnt.y - hint.height - 5) : Math.min(pnt.y + 15, maxhinty - hint.height - 3) + frame_rect.hint_delta_y; - else { - for (let n2 = 0; (n2 < hints.length) && (gapy < maxhinty); ++n2) { - const hint2 = hints[n2]; - if (!hint2) - continue; - if ((hint2.y >= gapy - 5) && (hint2.y <= gapy + hint2.height + 5)) { - gapy = hint2.y + 10; - n2 = -1; - } - } - if ((gapminx === -1111) && (gapmaxx === -1111)) - gapminx = gapmaxx = hint.x; - gapminx = Math.min(gapminx, hint.x); - gapmaxx = Math.max(gapmaxx, hint.x); - } - - group.attr('x', posx) - .attr('y', curry) - .property('curry', curry) - .property('gapy', gapy); - - curry += hint.height + 5; - gapy += hint.height + 5; - - if (!was_empty) - group.selectAll('*').remove(); - - group.attr('width', 60) - .attr('height', hint.height); - - const r = group.append('rect') - .attr('x', 0) - .attr('y', 0) - .attr('width', 60) - .attr('height', hint.height) - .style('fill', 'lightgrey') - .style('pointer-events', 'none'); - - if (nhints > 1) { - const col = usecolor1 ? hint.color1 : hint.color2; - if (col && (col !== 'none')) - r.style('stroke', col); - } - r.attr('stroke-width', hint.exact ? 3 : 1); - - for (let l = 0; l < (hint.lines?.length ?? 0); l++) { - let line = hint.lines[l]; - if (l === 0 && path_name && same_path) - line = line.slice(path_name.length); - if (line) { - const txt = group.append('svg:text') - .attr('text-anchor', 'start') - .attr('x', wmargin) - .attr('y', hmargin + l * textheight * hstep) - .attr('dy', '.8em') - .style('fill', 'black') - .style('pointer-events', 'none') - .call(font.func) - .text(line), - box = getElementRect(txt, 'bbox'); - - actualw = Math.max(actualw, box.width); - } - } - - function translateFn() { - // We only use 'd', but list d,i,a as params just to show can have them as params. - // Code only really uses d and t. - return function(/* d, i, a */) { - return function(t) { - return t < 0.8 ? '0' : (t - 0.8) * 5; - }; - }; - } - - if (was_empty) { - if (settings.TooltipAnimation > 0) - group.transition().duration(settings.TooltipAnimation).attrTween('opacity', translateFn()); - else - group.attr('opacity', 1); - } - } - - actualw += 2 * wmargin; - - const svgs = hintsg.selectAll('svg'); - - if ((viewmode === 'right') && (posx + actualw > frame_rect.width - 20)) { - posx = frame_rect.width - actualw - 20; - svgs.attr('x', posx); - } - - if ((viewmode === 'single') && (posx + actualw > pad_width - frame_rect.x) && (posx > actualw + 20)) { - posx -= (actualw + 20); - svgs.attr('x', posx); - } - - // if gap not very big, apply gapy coordinate to open view on the histogram - if ((viewmode !== 'single') && (gapy < maxhinty) && (gapy !== curry)) { - if ((gapminx <= posx + actualw + 5) && (gapmaxx >= posx - 5)) - svgs.attr('y', function() { return select(this).property('gapy'); }); - } else if ((viewmode !== 'single') && (curry > maxhinty)) { - const shift = Math.max((maxhinty - curry - 10), minhinty); - if (shift < 0) - svgs.attr('y', function() { return select(this).property('curry') + shift; }); - } - - if (actualw > 10) - svgs.attr('width', actualw).select('rect').attr('width', actualw); - - hintsg.property('startx', posx); - - if (cp._highlight_connect && isFunc(cp.processHighlightConnect)) - cp.processHighlightConnect(hints); - } - -} // class TooltipHandler - - -/** @summary Frame interactivity class - * @private */ -class FrameInteractive extends TooltipHandler { - - // cannot use private members because of RFramePainter - - /** @summary Adding basic interactivity */ - addBasicInteractivity() { - this.setTooltipEnabled(true); - - if (this.$can_drag) { - addDragHandler(this, { - obj: this, x: this.getFrameX(), y: this.getFrameY(), width: this.getFrameWidth(), height: this.getFrameHeight(), - is_disabled: kind => { return (kind === 'move') && this.mode3d; }, - only_resize: true, minwidth: 20, minheight: 20, redraw: () => this.sizeChanged() - }); - } - - const top_rect = this.getG().selectChild('path'), - main_svg = this.getG().selectChild('.main_layer'); - - top_rect.style('pointer-events', 'visibleFill') // let process mouse events inside frame - .style('cursor', 'default'); // show normal cursor - - main_svg.style('pointer-events', 'visibleFill') - .style('cursor', 'default') - .property('handlers_set', 0); - - const handlers_set = this.getPadPainter()?.isFastDrawing() ? 0 : 1; - - if (main_svg.property('handlers_set') !== handlers_set) { - const close_handler = handlers_set ? evnt => this.processFrameTooltipEvent(null, evnt) : null, - mouse_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: false }, evnt) : null; - - main_svg.property('handlers_set', handlers_set) - .on('mouseenter', mouse_handler) - .on('mousemove', mouse_handler) - .on('mouseleave', close_handler); - - if (browser.touches) { - const touch_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: true }, evnt) : null; - - main_svg.on('touchstart', touch_handler) - .on('touchmove', touch_handler) - .on('touchend', close_handler) - .on('touchcancel', close_handler); - } - } - - main_svg.attr('x', 0) - .attr('y', 0) - .attr('width', this.getFrameWidth()) - .attr('height', this.getFrameHeight()); - - const hintsg = this.hints_layer().selectChild('.objects_hints'); - // if tooltips were visible before, try to reconstruct them after short timeout - if (!hintsg.empty() && this.isTooltipAllowed() && (hintsg.property('hints_pad') === this.getPadPainter()?.getPadName())) - setTimeout(() => this.processFrameTooltipEvent(hintsg.property('last_point'), null), 10); - } - - getFrameSvg() { return this.getPadPainter().getFrameSvg(); } - - /** @summary Add interactive handlers */ - async addFrameInteractivity(for_second_axes) { - const pp = this.getPadPainter(), - svg = this.getFrameSvg(); - if (pp?.isFastDrawing() || svg.empty()) - return this; - - if (for_second_axes) { - // add extra handlers for second axes - const svg_x2 = svg.selectAll('.x2axis_container'), - svg_y2 = svg.selectAll('.y2axis_container'); - if (settings.ContextMenu) { - svg_x2.on('contextmenu', evnt => this.showContextMenu('x2', evnt)); - svg_y2.on('contextmenu', evnt => this.showContextMenu('y2', evnt)); - } - svg_x2.on('mousemove', evnt => this.showAxisStatus('x2', evnt)); - svg_y2.on('mousemove', evnt => this.showAxisStatus('y2', evnt)); - return this; - } - - const svg_x = svg.selectAll('.xaxis_container'), - svg_y = svg.selectAll('.yaxis_container'); - - this.can_zoom_x = this.can_zoom_y = settings.Zooming; - - if (pp?.options) { - if (pp.options.NoZoomX) - this.can_zoom_x = false; - if (pp.options.NoZoomY) - this.can_zoom_y = false; - } - - if (!svg.property('interactive_set')) { - this.addKeysHandler(); - - this.zoom_kind = 0; // 0 - none, 1 - XY, 2 - only X, 3 - only Y, (+100 for touches) - this.zoom_rect = null; - this.zoom_origin = null; // original point where zooming started - this.zoom_curr = null; // current point for zooming - } - - if (settings.Zooming) { - if (settings.ZoomMouse) { - svg.on('mousedown', evnt => this.startRectSel(evnt)); - svg.on('dblclick', evnt => this.mouseDoubleClick(evnt)); - } - if (settings.ZoomWheel) - svg.on('wheel', evnt => this.mouseWheel(evnt)); - } - - if (browser.touches && ((settings.Zooming && settings.ZoomTouch) || settings.ContextMenu)) - svg.on('touchstart', evnt => this.startTouchZoom(evnt)); - - if (settings.ContextMenu) { - if (browser.touches) { - svg_x.on('touchstart', evnt => this.startSingleTouchHandling('x', evnt)); - svg_y.on('touchstart', evnt => this.startSingleTouchHandling('y', evnt)); - } - svg.on('contextmenu', evnt => this.showContextMenu('', evnt)); - svg_x.on('contextmenu', evnt => this.showContextMenu('x', evnt)); - svg_y.on('contextmenu', evnt => this.showContextMenu('y', evnt)); - } - - svg_x.on('mousemove', evnt => this.showAxisStatus('x', evnt)); - svg_y.on('mousemove', evnt => this.showAxisStatus('y', evnt)); - - svg.property('interactive_set', true); - - return this; - } - - /** @summary Handle key press */ - processKeyPress(evnt) { - // no custom keys handling when menu is present - if (hasMenu()) - return true; - - const allowed = ['PageUp', 'PageDown', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'PrintScreen', 'Escape', '*'], - main = this.selectDom(), - pp = this.getPadPainter(); - let key = evnt.key; - - if (!settings.HandleKeys || main.empty() || (this.isEnabledKeys() === false) || - (getActivePad() !== pp) || (allowed.indexOf(key) < 0)) - return false; - - if (evnt.shiftKey) - key = `Shift ${key}`; - if (evnt.altKey) - key = `Alt ${key}`; - if (evnt.ctrlKey) - key = `Ctrl ${key}`; - - const zoom = { name: 'x', dleft: 0, dright: 0 }; - - switch (key) { - case 'ArrowLeft': - zoom.dleft = -1; - zoom.dright = 1; - break; - case 'ArrowRight': - zoom.dleft = 1; - zoom.dright = -1; - break; - case 'Ctrl ArrowLeft': - zoom.dleft = zoom.dright = -1; - break; - case 'Ctrl ArrowRight': - zoom.dleft = zoom.dright = 1; - break; - case 'ArrowUp': - zoom.name = 'y'; - zoom.dleft = 1; - zoom.dright = -1; - break; - case 'ArrowDown': - zoom.name = 'y'; - zoom.dleft = -1; - zoom.dright = 1; - break; - case 'Ctrl ArrowUp': - zoom.name = 'y'; - zoom.dleft = zoom.dright = 1; - break; - case 'Ctrl ArrowDown': - zoom.name = 'y'; - zoom.dleft = zoom.dright = -1; - break; - case 'Escape': - pp?.enlargePad(null, false, true); - return true; - } - - if (zoom.dleft || zoom.dright) { - if (!settings.Zooming) - return false; - // in 3d mode with orbit control ignore simple arrows, - // intentionally no compare for indexOf while only Ctrl is allowed - if (this.mode3d && key.indexOf('Ctrl')) - return false; - this.analyzeMouseWheelEvent(null, zoom, 0.5); - if (zoom.changed) - this.zoomSingle(zoom.name, zoom.min, zoom.max, true); - evnt.stopPropagation(); - evnt.preventDefault(); - } else { - const func = pp?.findPadButton(key); - if (func) { - pp.clickPadButton(func); - evnt.stopPropagation(); - evnt.preventDefault(); - } - } - - return true; // just process any key press - } - - /** @summary Function called when frame is clicked and object selection can be performed - * @desc such event can be used to select */ - processFrameClick(pnt, dblckick) { - const pp = this.getPadPainter(); - if (!pp) - return; - - pnt.painters = true; // provide painters reference in the hints - pnt.disabled = true; // do not invoke graphics - - // collect tooltips from pad painter - it has list of all drawn objects - const hints = pp.processPadTooltipEvent(pnt); - let exact = null, res; - for (let k = 0; (k < hints.length) && !exact; ++k) { - if (hints[k] && hints[k].exact) - exact = hints[k]; - } - - if (exact) { - const handler = dblckick ? this.getDblclickHandler() : this.getClickHandler(); - if (isFunc(handler)) - res = handler(exact.user_info, pnt); - } - - if (!dblckick) { - pp.selectObjectPainter(exact ? exact.painter : this, - { x: pnt.x + this.getFrameX(), y: pnt.y + this.getFrameY() }); - } - - return res; - } - - /** @summary Check mouse moving */ - shiftMoveHanlder(evnt, pos0) { - if (evnt.buttons === this._shifting_buttons) { - const frame = this.getFrameSvg(), - pos = pointer(evnt, frame.node()), - main_svg = this.getG().selectChild('.main_layer'), - dx = pos0[0] - pos[0], - dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], - w = this.getFrameWidth(), h = this.getFrameHeight(); - - this._shifting_dx = dx; - this._shifting_dy = dy; - - main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); - - evnt.preventDefault(); - evnt.stopPropagation(); - } - } - - /** @summary mouse up handler for shifting */ - shiftUpHanlder(evnt) { - evnt.preventDefault(); - - select(window).on('mousemove.shiftHandler', null) - .on('mouseup.shiftHandler', null); - - if ((this._shifting_dx !== undefined) && (this._shifting_dy !== undefined)) - this.performScalesShift(); - } - - /** @summary Shift scales on defined positions */ - performScalesShift() { - const w = this.getFrameWidth(), h = this.getFrameHeight(), - main_svg = this.getG().selectChild('.main_layer'), - gr = this.getGrFuncs(), - xmin = gr.revertAxis('x', this._shifting_dx), - xmax = gr.revertAxis('x', this._shifting_dx + w), - ymin = gr.revertAxis('y', this._shifting_dy + h), - ymax = gr.revertAxis('y', this._shifting_dy); - - main_svg.attr('viewBox', `0 0 ${w} ${h}`); - - this._shifting_dx = this._shifting_dy = undefined; - - setPainterTooltipEnabled(this, true); - - if (this.scales_ndim === 1) - this.zoomSingle('x', xmin, xmax); - else - this.zoom(xmin, xmax, ymin, ymax); - } - - /** @summary Start mouse rect zooming */ - startRectSel(evnt) { - // ignore when touch selection is activated - if (this.zoom_kind > 100) - return; - - const frame = this.getFrameSvg(), - pos = pointer(evnt, frame.node()); - - if ((evnt.buttons === 3) || (evnt.button === 1)) { - this.clearInteractiveElements(); - this._shifting_buttons = evnt.buttons; - - if (!evnt.$emul) { - select(window).on('mousemove.shiftHandler', evnt2 => this.shiftMoveHanlder(evnt2, pos)) - .on('mouseup.shiftHandler', evnt2 => this.shiftUpHanlder(evnt2), true); - } - - setPainterTooltipEnabled(this, false); - evnt.preventDefault(); - evnt.stopPropagation(); - return; - } - - // ignore all events from non-left button - if (evnt.button) - return; - - evnt.preventDefault(); - - this.clearInteractiveElements(); - - const w = this.getFrameWidth(), h = this.getFrameHeight(); - - this.zoom_lastpos = pos; - this.zoom_curr = [Math.max(0, Math.min(w, pos[0])), Math.max(0, Math.min(h, pos[1]))]; - - this.zoom_origin = [0, 0]; - this.zoom_second = false; - - if ((pos[0] < 0) || (pos[0] > w)) { - this.zoom_second = (pos[0] > w) && this.y2_handle; - this.zoom_kind = 3; // only y - this.zoom_origin[1] = this.zoom_curr[1]; - this.zoom_curr[0] = w; - this.zoom_curr[1] += 1; - } else if ((pos[1] < 0) || (pos[1] > h)) { - this.zoom_second = (pos[1] < 0) && this.x2_handle; - this.zoom_kind = 2; // only x - this.zoom_origin[0] = this.zoom_curr[0]; - this.zoom_curr[0] += 1; - this.zoom_curr[1] = h; - } else { - this.zoom_kind = 1; // x and y - this.zoom_origin[0] = this.zoom_curr[0]; - this.zoom_origin[1] = this.zoom_curr[1]; - } - - if (!evnt.$emul) { - select(window).on('mousemove.zoomRect', evnt2 => this.moveRectSel(evnt2)) - .on('mouseup.zoomRect', evnt2 => this.endRectSel(evnt2), true); - } - - this.zoom_rect = null; - - // disable tooltips in frame painter - setPainterTooltipEnabled(this, false); - - evnt.stopPropagation(); - - if (this.zoom_kind !== 1) - return postponePromise(() => this.startLabelsMove(), 500); - } - - /** @summary Starts labels move */ - startLabelsMove() { - if (this.zoom_rect) - return; - - const handle = (this.zoom_kind === 2) ? this.x_handle : this.y_handle; - - if (!isFunc(handle?.processLabelsMove) || !this.zoom_lastpos) - return; - - if (handle.processLabelsMove('start', this.zoom_lastpos)) - this.zoom_labels = handle; - } - - /** @summary Process mouse rect zooming */ - moveRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) - return; - - evnt.preventDefault(); - const m = pointer(evnt, this.getFrameSvg().node()); - - if (this.zoom_labels) - return this.zoom_labels.processLabelsMove('move', m); - - this.zoom_lastpos[0] = m[0]; - this.zoom_lastpos[1] = m[1]; - - m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); - m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); - - switch (this.zoom_kind) { - case 1: - this.zoom_curr[0] = m[0]; - this.zoom_curr[1] = m[1]; - break; - case 2: - this.zoom_curr[0] = m[0]; - break; - case 3: - this.zoom_curr[1] = m[1]; - break; - } - - const x = Math.min(this.zoom_origin[0], this.zoom_curr[0]), - y = Math.min(this.zoom_origin[1], this.zoom_curr[1]), - w = Math.abs(this.zoom_curr[0] - this.zoom_origin[0]), - h = Math.abs(this.zoom_curr[1] - this.zoom_origin[1]); - - if (!this.zoom_rect) { - // ignore small changes, can be switching to labels move - if ((this.zoom_kind !== 1) && ((w < 2) || (h < 2))) - return; - - this.zoom_rect = this.getFrameSvg() - .append('rect') - .style('pointer-events', 'none') - .call(addHighlightStyle, true); - } - - this.zoom_rect.attr('x', x).attr('y', y).attr('width', w).attr('height', h); - } - - /** @summary Finish mouse rect zooming */ - endRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) - return; - - evnt.preventDefault(); - - if (!evnt.$emul) { - select(window).on('mousemove.zoomRect', null) - .on('mouseup.zoomRect', null); - } - - const m = pointer(evnt, this.getFrameSvg().node()); - let kind = this.zoom_kind, pr; - - if (this.zoom_labels) - this.zoom_labels.processLabelsMove('stop', m); - else { - const changed = [this.can_zoom_x, this.can_zoom_y]; - m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); - m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); - - switch (this.zoom_kind) { - case 1: - this.zoom_curr[0] = m[0]; - this.zoom_curr[1] = m[1]; - break; - case 2: // only X - this.zoom_curr[0] = m[0]; - changed[1] = false; - break; - case 3: // only Y - this.zoom_curr[1] = m[1]; - changed[0] = false; - break; - } - - let xmin, xmax, ymin, ymax, isany = false, - namex = 'x', namey = 'y'; - - if (changed[0] && (Math.abs(this.zoom_curr[0] - this.zoom_origin[0]) > 5)) { - if (this.zoom_second && (this.zoom_kind === 2)) - namex = 'x2'; - const v1 = this.revertAxis(namex, this.zoom_origin[0]), - v2 = this.revertAxis(namex, this.zoom_curr[0]); - xmin = Math.min(v1, v2); - xmax = Math.max(v1, v2); - isany = true; - } - - if (changed[1] && (Math.abs(this.zoom_curr[1] - this.zoom_origin[1]) > 5)) { - if (this.zoom_second && (this.zoom_kind === 3)) - namey = 'y2'; - - const v1 = this.revertAxis(namey, this.zoom_origin[1]), - v2 = this.revertAxis(namey, this.zoom_curr[1]); - ymin = Math.min(v1, v2); - ymax = Math.max(v1, v2); - isany = true; - } - - if (this.swap_xy() && !this.zoom_second) - [xmin, xmax, ymin, ymax] = [ymin, ymax, xmin, xmax]; - - if (namex === 'x2') { - pr = this.zoomSingle(namex, xmin, xmax, true); - kind = 0; - } else if (namey === 'y2') { - pr = this.zoomSingle(namey, ymin, ymax, true); - kind = 0; - } else if (isany) { - pr = this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); - kind = 0; - } - } - - const pnt = (kind === 1) ? { x: this.zoom_origin[0], y: this.zoom_origin[1] } : null; - - this.clearInteractiveElements(); - - // if no zooming was done, select active object instead - switch (kind) { - case 1: - this.processFrameClick(pnt); - break; - case 2: - this.getPadPainter()?.selectObjectPainter(this.x_handle); - break; - case 3: - this.getPadPainter()?.selectObjectPainter(this.y_handle); - break; - } - - // return promise - if any - return pr; - } - - /** @summary Handle mouse double click on frame */ - mouseDoubleClick(evnt) { - evnt.preventDefault(); - const m = pointer(evnt, this.getFrameSvg().node()), - fw = this.getFrameWidth(), fh = this.getFrameHeight(); - this.clearInteractiveElements(); - - const valid_x = (m[0] >= 0) && (m[0] <= fw), - valid_y = (m[1] >= 0) && (m[1] <= fh); - - if (valid_x && valid_y && this.getDblclickHandler()) { - if (this.processFrameClick({ x: m[0], y: m[1] }, true)) - return; - } - - let kind = (this.can_zoom_x ? 'x' : '') + (this.can_zoom_y ? 'y' : '') + 'z'; - if (!valid_x) { - if (!this.can_zoom_y) - return; - kind = this.swap_xy() ? 'x' : 'y'; - if ((m[0] > fw) && this[kind + '2_handle']) - kind += '2'; // let unzoom second axis - } else if (!valid_y) { - if (!this.can_zoom_x) - return; - kind = this.swap_xy() ? 'y' : 'x'; - if ((m[1] < 0) && this[kind + '2_handle']) - kind += '2'; // let unzoom second axis - } - return this.unzoom(kind).then(changed => { - if (changed) - return; - const pp = this.getPadPainter(), rect = this.getFrameRect(); - return pp?.selectObjectPainter(pp, { x: m[0] + rect.x, y: m[1] + rect.y, dbl: true }); - }); - } - - /** @summary Start touch zoom */ - startTouchZoom(evnt) { - evnt.preventDefault(); - evnt.stopPropagation(); - - // in case when zooming was started, block any other kind of events - // also prevent zooming together with active dragging - if (this.zoom_kind || drag_kind) - return; - - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - - // normally double-touch will be handled - // touch with single click used for context menu - if (arr.length === 1) { - // this is touch with single element - - const now = new Date().getTime(); - let tmdiff = 1e10, dx = 100, dy = 100; - - if (this.last_touch_time && this.last_touch_pos) { - tmdiff = now - this.last_touch_time; - dx = Math.abs(arr[0][0] - this.last_touch_pos[0]); - dy = Math.abs(arr[0][1] - this.last_touch_pos[1]); - } - - this.last_touch_time = now; - this.last_touch_pos = arr[0]; - - if ((tmdiff < 500) && (dx < 20) && (dy < 20)) { - this.clearInteractiveElements(); - this.unzoom('xyz'); - - delete this.last_touch_time; - } else if (settings.ContextMenu) - this.startSingleTouchHandling('', evnt); - } - - if ((arr.length !== 2) || !settings.Zooming || !settings.ZoomTouch) - return; - - this.clearInteractiveElements(); - - // clear single touch handler - this.endSingleTouchHandling(null); - - const pnt1 = arr[0], pnt2 = arr[1], w = this.getFrameWidth(), h = this.getFrameHeight(); - - this.zoom_curr = [Math.min(pnt1[0], pnt2[0]), Math.min(pnt1[1], pnt2[1])]; - this.zoom_origin = [Math.max(pnt1[0], pnt2[0]), Math.max(pnt1[1], pnt2[1])]; - this.zoom_second = false; - - if ((this.zoom_curr[0] < 0) || (this.zoom_curr[0] > w)) { - this.zoom_second = (this.zoom_curr[0] > w) && this.y2_handle; - this.zoom_kind = 103; // only y - this.zoom_curr[0] = 0; - this.zoom_origin[0] = w; - } else if ((this.zoom_origin[1] > h) || (this.zoom_origin[1] < 0)) { - this.zoom_second = (this.zoom_origin[1] < 0) && this.x2_handle; - this.zoom_kind = 102; // only x - this.zoom_curr[1] = 0; - this.zoom_origin[1] = h; - } else - this.zoom_kind = 101; // x and y - - drag_kind = 'zoom'; // block other possible dragging - - setPainterTooltipEnabled(this, false); - - this.zoom_rect = this.getFrameSvg().append('rect') - .attr('id', 'zoomRect') - .attr('x', this.zoom_curr[0]) - .attr('y', this.zoom_curr[1]) - .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) - .attr('height', this.zoom_origin[1] - this.zoom_curr[1]) - .call(addHighlightStyle, true); - - if (!evnt.$emul) { - select(window).on('touchmove.zoomRect', evnt2 => this.moveTouchZoom(evnt2)) - .on('touchcancel.zoomRect', evnt2 => this.endTouchZoom(evnt2)) - .on('touchend.zoomRect', evnt2 => this.endTouchZoom(evnt2)); - } - } - - /** @summary Move touch zooming */ - moveTouchZoom(evnt) { - if (this.zoom_kind < 100) - return; - - evnt.preventDefault(); - - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - - if (arr.length !== 2) - return this.clearInteractiveElements(); - - const pnt1 = arr[0], pnt2 = arr[1]; - - if (this.zoom_kind !== 103) { - this.zoom_curr[0] = Math.min(pnt1[0], pnt2[0]); - this.zoom_origin[0] = Math.max(pnt1[0], pnt2[0]); - } - if (this.zoom_kind !== 102) { - this.zoom_curr[1] = Math.min(pnt1[1], pnt2[1]); - this.zoom_origin[1] = Math.max(pnt1[1], pnt2[1]); - } - - this.zoom_rect.attr('x', this.zoom_curr[0]) - .attr('y', this.zoom_curr[1]) - .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) - .attr('height', this.zoom_origin[1] - this.zoom_curr[1]); - - if ((this.zoom_origin[0] - this.zoom_curr[0] > 10) || (this.zoom_origin[1] - this.zoom_curr[1] > 10)) - setPainterTooltipEnabled(this, false); - - evnt.stopPropagation(); - } - - /** @summary End touch zooming handler */ - endTouchZoom(evnt) { - if (this.zoom_kind < 100) - return; - - drag_kind = ''; // reset global flag - - evnt.preventDefault(); - if (!evnt.$emul) { - select(window).on('touchmove.zoomRect', null) - .on('touchend.zoomRect', null) - .on('touchcancel.zoomRect', null); - } - - let xmin, xmax, ymin, ymax, isany = false, namex = 'x', namey = 'y'; - const xid = this.swap_xy() ? 1 : 0, yid = 1 - xid, changed = [true, true]; - - if (this.zoom_kind === 102) - changed[1] = false; - if (this.zoom_kind === 103) - changed[0] = false; - - if (changed[xid] && (Math.abs(this.zoom_curr[xid] - this.zoom_origin[xid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 102)) - namex = 'x2'; - xmin = Math.min(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); - xmax = Math.max(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); - isany = true; - } - - if (changed[yid] && (Math.abs(this.zoom_curr[yid] - this.zoom_origin[yid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 103)) - namey = 'y2'; - ymin = Math.min(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); - ymax = Math.max(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); - isany = true; - } - - this.clearInteractiveElements(); - delete this.last_touch_time; - - if (namex === 'x2') - this.zoomSingle(namex, xmin, xmax, true); - else if (namey === 'y2') - this.zoomSingle(namey, ymin, ymax, true); - else if (isany) - this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); - - evnt.stopPropagation(); - } - - /** @summary Analyze zooming with mouse wheel */ - analyzeMouseWheelEvent(event, item, dmin, test_ignore, second_side) { - // if there is second handle, use it - const handle2 = second_side ? this[item.name + '2_handle'] : null; - if (handle2) { - item.second = Object.assign({}, item); - return handle2.analyzeWheelEvent(event, dmin, item.second, test_ignore); - } - const handle = this[item.name + '_handle']; - return handle?.analyzeWheelEvent(event, dmin, item, test_ignore); - } - - /** @summary return true if default Y zooming should be enabled - * @desc it is typically for 2-Dim histograms or - * when histogram not draw, defined by other painters */ - isAllowedDefaultYZooming() { - if (this.self_drawaxes) - return true; - - let res; - this.forEachPainter(objp => { - res = res ?? objp._wheel_zoomy; - }, 'objects'); - return res; - } - - /** @summary Handles mouse wheel event */ - mouseWheel(evnt) { - evnt.stopPropagation(); - evnt.preventDefault(); - this.clearInteractiveElements(); - - const itemx = { name: 'x', reverse: this.reverse_x() }, - itemy = { name: 'y', reverse: this.reverse_y(), ignore: !this.isAllowedDefaultYZooming() }, - cur = pointer(evnt, this.getFrameSvg().node()), - w = this.getFrameWidth(), h = this.getFrameHeight(); - - if (this.can_zoom_x) - this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemy : itemx, cur[0] / w, (cur[1] >= 0) && (cur[1] <= h), cur[1] < 0); - - if (this.can_zoom_y) - this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemx : itemy, 1 - cur[1] / h, (cur[0] >= 0) && (cur[0] <= w), cur[0] > w); - - let pr = this.zoom(itemx.min, itemx.max, itemy.min, itemy.max, undefined, undefined, itemx.changed || itemy.changed); - - if (itemx.second) - pr = pr.then(() => this.zoomSingle('x2', itemx.second.min, itemx.second.max, itemx.second.changed)); - - if (itemy.second) - pr = pr.then(() => this.zoomSingle('y2', itemy.second.min, itemy.second.max, itemy.second.changed)); - - return pr; - } - - /** @summary Show frame context menu */ - showContextMenu(kind, evnt, obj) { - // disable context menu left/right buttons clicked - if (evnt?.buttons === 3) - return evnt.preventDefault(); - - // ignore context menu when touches zooming is ongoing or - if (this.zoom_kind && (this.zoom_kind > 100)) - return; - - let pnt, menu_painter = this, exec_painter = null, - frame_corner = false, fp = null; // object used to show context menu - const svg_node = this.getFrameSvg().node(); - - if (isFunc(evnt?.stopPropagation)) { - evnt.preventDefault(); - evnt.stopPropagation(); // disable main context menu - const ms = pointer(evnt, svg_node), - tch = get_touch_pointers(evnt, svg_node); - if (tch.length === 1) - pnt = { x: tch[0][0], y: tch[0][1], touch: true }; - else if (ms.length === 2) - pnt = { x: ms[0], y: ms[1], touch: false }; - } else if ((evnt?.x !== undefined) && (evnt?.y !== undefined) && (evnt?.clientX === undefined)) { - pnt = evnt; - const rect = svg_node.getBoundingClientRect(); - evnt = { clientX: rect.left + pnt.x, clientY: rect.top + pnt.y }; - } - - if ((kind === 'painter') && obj) { - menu_painter = obj; - kind = ''; - } else if (kind === 'main') { - menu_painter = this.getMainPainter(true); - kind = ''; - } else if (!kind) { - const pp = this.getPadPainter(); - let sel = null; - - fp = this; - if (pnt && pp) { - pnt.painters = true; // assign painter for every tooltip - const hints = pp.processPadTooltipEvent(pnt); - let bestdist = 1000; - for (let n = 0; n < hints.length; ++n) { - if (hints[n]?.menu) { - const dist = hints[n].menu_dist ?? 7; - if (dist < bestdist) { - sel = hints[n].painter; - bestdist = dist; - } - } - } - } - - if (sel) - menu_painter = sel; - else - kind = 'frame'; - - if (pnt) - frame_corner = (pnt.x > 0) && (pnt.x < 20) && (pnt.y > 0) && (pnt.y < 20); - - fp.setLastEventPos(pnt); - } else if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'pal')) { - exec_painter = this.getMainPainter(true); // histogram painter delivers items for axis menu - - if (this.v7_frame && isFunc(exec_painter?.v7EvalAttr)) - exec_painter = null; - } - - if (!exec_painter) - exec_painter = menu_painter; - - if (!isFunc(menu_painter?.fillContextMenu)) - return; - - this.clearInteractiveElements(); - - return createMenu(evnt, menu_painter).then(menu => { - let domenu = menu.painter.fillContextMenu(menu, kind, obj); - - // fill frame menu by default - or append frame elements when activated in the frame corner - if (fp && (!domenu || (frame_corner && (kind !== 'frame')))) - domenu = fp.fillContextMenu(menu); - - if (domenu) { - return exec_painter.fillObjectExecMenu(menu, kind).then(menu2 => { - // suppress any running zooming - setPainterTooltipEnabled(menu2.painter, false); - return menu2.show().then(() => setPainterTooltipEnabled(menu2.painter, true)); - }); - } - }); - } - - /** @summary Activate touch handling on frame - * @private */ - startSingleTouchHandling(kind, evnt) { - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - if (arr.length !== 1) - return; - - evnt.preventDefault(); - evnt.stopPropagation(); - closeMenu(); - - const tm = new Date().getTime(); - - this._shifting_dx = 0; - this._shifting_dy = 0; - - setPainterTooltipEnabled(this, false); - - select(window).on('touchmove.singleTouch', kind ? null : evnt2 => this.moveTouchHandling(evnt2, kind, arr[0])) - .on('touchcancel.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)) - .on('touchend.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)); - } - - /** @summary Moving of touch pointer - * @private */ - moveTouchHandling(evnt, kind, pos0) { - const frame = this.getFrameSvg(), - main_svg = this.getG().selectChild('.main_layer'); - let pos; - - try { - pos = get_touch_pointers(evnt, frame.node())[0]; - } catch { - pos = [0, 0]; - if (evnt?.changedTouches) - pos = [evnt.changedTouches[0].clientX, evnt.changedTouches[0].clientY]; - } - - const dx = pos0[0] - pos[0], - dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], - w = this.getFrameWidth(), h = this.getFrameHeight(); - - this._shifting_dx = dx; - this._shifting_dy = dy; - - main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); - } - - /** @summary Process end-touch event, which can cause content menu to appear - * @private */ - endSingleTouchHandling(evnt, kind, pos, tm) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - setPainterTooltipEnabled(this, true); - - select(window).on('touchmove.singleTouch', null) - .on('touchcancel.singleTouch', null) - .on('touchend.singleTouch', null); - - if (evnt === null) - return; - - if (Math.abs(this._shifting_dx) > 2 || Math.abs(this._shifting_dy) > 2) - this.performScalesShift(); - else if (new Date().getTime() - tm > 700) - this.showContextMenu(kind, { x: pos[0], y: pos[1] }); - } - - /** @summary Clear frame interactive elements */ - clearInteractiveElements() { - closeMenu(); - this.zoom_kind = 0; - this.zoom_rect?.remove(); - delete this.zoom_rect; - delete this.zoom_curr; - delete this.zoom_origin; - delete this.zoom_lastpos; - delete this.zoom_labels; - - // enable tooltip in frame painter - setPainterTooltipEnabled(this, true); - } - - /** @summary Assign interactive methods to frame painter */ - static assign(fpainter) { - const src = FrameInteractive.prototype, - src2 = TooltipHandler.prototype; - Object.getOwnPropertyNames(src).forEach(name => { - if (name !== 'constructor') - fpainter[name] = src[name]; - }); - Object.getOwnPropertyNames(src2).forEach(name => { - if (name !== 'constructor') - fpainter[name] = src2[name]; - }); - } - -} // class FrameInteractive - -/** - * @summary Painter class for TFrame, main handler for interactivity - * @private - */ - -class TFramePainter extends FrameInteractive { - - #frame_x; // frame X coordinate - #frame_y; // frame Y coordinate - #frame_width; // frame width - #frame_height; // frame height - #frame_trans; // transform of frame element - #swap_xy; // swap X/Y axis on the frame - #reverse_x; // reverse X axis - #reverse_y; // reverse Y axis - #reverse_x2; // reverse X2 axis - #reverse_y2; // reverse Y2 axis - #border_mode; // frame border mode - #border_size; // frame border size - #axes_drawn; // when axes are drawn - #axes2_drawn; // when axes are drawn - #shrink_frame_left; // shrink frame on left side - #projection; // id of projection function - #click_handler; // handle for click events - #dblclick_handler; // handle for double click events - #keys_handler; // assigned handler for keyboard events - #enabled_keys; // when keyboard press handling enabled - #last_event_pos; // position of last event - - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} frame - TFrame object */ - constructor(dom, frame) { - super(dom, frame?.$dummy ? null : frame); - this.zoom_kind = 0; - this.mode3d = false; - this.#shrink_frame_left = 0.0; - this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing - this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing - this.ranges_set = false; - this.#axes_drawn = false; - this.#axes2_drawn = false; - this.#border_mode = gStyle.fFrameBorderMode; - this.#border_size = gStyle.fFrameBorderSize; - this.#projection = 0; // different projections - } - - /** @summary Returns frame painter - object itself */ - getFramePainter() { return this; } - - /** @summary Returns true if it is ROOT6 frame - * @private */ - is_root6() { return true; } - - /** @summary Returns frame or sub-objects, used in GED editor */ - getObject(place) { - if (place === 'xaxis') - return this.xaxis; - if (place === 'yaxis') - return this.yaxis; - return super.getObject(); - } - - /** @summary Set active flag for frame - can block some events - * @private */ - setFrameActive(on) { - this.#enabled_keys = on && settings.HandleKeys; - // used only in 3D mode where control is used - if (this.control) - this.control.enableKeys = this.#enabled_keys; - } - - /** @summary Returns true if keys handling enabled - * @private */ - isEnabledKeys() { return this.#enabled_keys; } - - /** @summary Returns true if X/Y axis swapped */ - swap_xy() { return this.#swap_xy; } - - /** @summary Is reverse x */ - reverse_x() { return this.#reverse_x; } - - /** @summary Is reverse x */ - reverse_y() { return this.#reverse_y; } - - /** @summary Shrink frame size - * @private */ - shrinkFrame(shrink_left, shrink_right) { - this.fX1NDC += shrink_left; - this.fX2NDC -= shrink_right; - } - - /** @summary Set position of last context menu event - * @private */ - setLastEventPos(pnt) { this.#last_event_pos = pnt; } - - /** @summary Return position of last event - * @private */ - getLastEventPos() { return this.#last_event_pos; } - - /** @summary Returns coordinates transformation func */ - getProjectionFunc() { return getEarthProjectionFunc(this.#projection); } - - /** @summary Recalculate frame ranges using specified projection functions */ - recalculateRange(Proj, change_x, change_y) { - this.#projection = Proj || 0; - - if ((this.#projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { - console.warn(`Mercator Projection: Latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); - this.#projection = 0; - } - - const func = this.getProjectionFunc(); - if (!func) - return; - - const pnts = [func(this.scale_xmin, this.scale_ymin), - func(this.scale_xmin, this.scale_ymax), - func(this.scale_xmax, this.scale_ymax), - func(this.scale_xmax, this.scale_ymin)]; - if (this.scale_xmin < 0 && this.scale_xmax > 0) { - pnts.push(func(0, this.scale_ymin)); - pnts.push(func(0, this.scale_ymax)); - } - if (this.scale_ymin < 0 && this.scale_ymax > 0) { - pnts.push(func(this.scale_xmin, 0)); - pnts.push(func(this.scale_xmax, 0)); - } - - this.original_xmin = this.scale_xmin; - this.original_xmax = this.scale_xmax; - this.original_ymin = this.scale_ymin; - this.original_ymax = this.scale_ymax; - - if (change_x) - this.scale_xmin = this.scale_xmax = pnts[0].x; - if (change_y) - this.scale_ymin = this.scale_ymax = pnts[0].y; - - for (let n = 1; n < pnts.length; ++n) { - if (change_x) { - this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x); - this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x); - } - if (change_y) { - this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y); - this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y); - } - } - } - - /** @summary Configure frame axes ranges */ - setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax, hpainter) { - this.ranges_set = true; - - this.xaxis = xaxis; - this.xmin = xmin; - this.xmax = xmax; - - this.yaxis = yaxis; - this.ymin = ymin; - this.ymax = ymax; - - this.zaxis = zaxis; - this.zmin = zmin; - this.zmax = zmax; - - if (hpainter?.check_pad_range) { - delete hpainter.check_pad_range; - const ndim = hpainter.getDimension(); - this.applyAxisZoom('x'); - if (ndim > 1) - this.applyAxisZoom('y'); - if (ndim > 2) - this.applyAxisZoom('z'); - } - - if (hpainter && !hpainter._checked_zooming) { - hpainter._checked_zooming = true; - - if (hpainter.options.minimum !== kNoZoom) { - this.zoom_zmin = hpainter.options.minimum; - this.zoom_zmax = this.zmax; - } - if (hpainter.options.maximum !== kNoZoom) { - this.zoom_zmax = hpainter.options.maximum; - this.zoom_zmin ??= this.zmin; - } - } - } - - /** @summary Configure secondary frame axes ranges */ - setAxes2Ranges(second_x, xaxis, xmin, xmax, second_y, yaxis, ymin, ymax) { - if (second_x) { - this.x2axis = xaxis; - this.x2min = xmin; - this.x2max = xmax; - } - if (second_y) { - this.y2axis = yaxis; - this.y2min = ymin; - this.y2max = ymax; - } - } - - /** @summary Returns associated axis object */ - getAxis(name) { - switch (name) { - case 'x': return this.xaxis; - case 'y': return this.yaxis; - case 'z': return this.zaxis; - case 'x2': return this.x2axis; - case 'y2': return this.y2axis; - } - return null; - } - - /** @summary Apply axis zooming from pad user range - * @private */ - applyPadUserRange(pad, name) { - if (!pad) - return; - - // seems to be, not always user range calculated - let umin = pad[`fU${name}min`], - umax = pad[`fU${name}max`], - eps = 1e-7; - - if (name === 'x') { - if ((Math.abs(pad.fX1) > eps) || (Math.abs(pad.fX2 - 1) > eps)) { - const dx = pad.fX2 - pad.fX1; - umin = pad.fX1 + dx * pad.fLeftMargin; - umax = pad.fX2 - dx * pad.fRightMargin; - } - } else if ((Math.abs(pad.fY1) > eps) || (Math.abs(pad.fY2 - 1) > eps)) { - const dy = pad.fY2 - pad.fY1; - umin = pad.fY1 + dy * pad.fBottomMargin; - umax = pad.fY2 - dy * pad.fTopMargin; - } - - if ((umin >= umax) || (Math.abs(umin) < eps && Math.abs(umax - 1) < eps)) - return; - - if (pad[`fLog${name}`] > 0) { - umin = Math.exp(umin * Math.log(10)); - umax = Math.exp(umax * Math.log(10)); - } - - const aname = !this.#swap_xy ? name : (name === 'x' ? 'y' : 'x'), - smin = this[`scale_${aname}min`], - smax = this[`scale_${aname}max`]; - - eps = (smax - smin) * 1e-7; - - if ((Math.abs(umin - smin) > eps) || (Math.abs(umax - smax) > eps)) { - this[`zoom_${aname}min`] = umin; - this[`zoom_${aname}max`] = umax; - } - } - - /** @summary Apply zooming from TAxis attributes */ - applyAxisZoom(name) { - if (this.zoomChangedInteractive(name)) - return; - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - - const axis = this.getAxis(name); - - if (axis?.TestBit(EAxisBits.kAxisRange)) { - if ((axis.fFirst !== axis.fLast) && ((axis.fFirst > 1) || (axis.fLast < axis.fNbins))) { - this[`zoom_${name}min`] = axis.fFirst > 1 ? axis.GetBinLowEdge(axis.fFirst) : axis.fXmin; - this[`zoom_${name}max`] = axis.fLast < axis.fNbins ? axis.GetBinLowEdge(axis.fLast + 1) : axis.fXmax; - // reset user range for main painter - axis.SetBit(EAxisBits.kAxisRange, false); - axis.fFirst = 1; - axis.fLast = axis.fNbins; - } - } - } - - /** @summary Create x,y objects which maps user coordinates into pixels - * @desc While only first painter really need such object, all others just reuse it - * following functions are introduced - * this.GetBin[X/Y] return bin coordinate - * this.[x,y] these are d3.scale objects - * this.gr[x,y] converts root scale into graphical value - * @private */ - createXY(opts) { - this.cleanXY(); // remove all previous configurations - - if (!opts) - opts = { ndim: 1 }; - - this.#swap_xy = opts.swap_xy || false; - this.#reverse_x = opts.reverse_x || false; - this.#reverse_y = opts.reverse_y || false; - - this.logx = this.logy = 0; - - const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), pad = pp.getRootPad(), - pad_logx = pad.fLogx, - pad_logy = (opts.ndim === 1 ? pad.fLogv : undefined) ?? pad.fLogy; - - this.scales_ndim = opts.ndim; - - this.scale_xmin = this.xmin; - this.scale_xmax = this.xmax; - - this.scale_ymin = this.ymin; - this.scale_ymax = this.ymax; - - if (opts.extra_y_space) { - const log_scale = this.#swap_xy ? pad_logx : pad_logy; - if (log_scale && (this.scale_ymax > 0)) - this.scale_ymax = Math.exp(Math.log(this.scale_ymax) * 1.1); - else - this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1; - } - - if (opts.check_pad_range) { - // take zooming out of pad or axis attributes - this.applyAxisZoom('x'); - if (opts.ndim > 1) - this.applyAxisZoom('y'); - if (opts.ndim > 2) - this.applyAxisZoom('z'); - - // Use configured pad range - only when main histogram drawn with SAME draw option - if (opts.check_pad_range === 'pad_range') { - this.applyPadUserRange(pad, 'x'); - this.applyPadUserRange(pad, 'y'); - } - } - - if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { - this.zoom_xmin = opts.zoom_xmin; - this.zoom_xmax = opts.zoom_xmax; - } - - if ((opts.zoom_ymin !== opts.zoom_ymax) && ((this.zoom_ymin === this.zoom_ymax) || !this.zoomChangedInteractive('y'))) { - this.zoom_ymin = opts.zoom_ymin; - this.zoom_ymax = opts.zoom_ymax; - } - - let orig_x = true, orig_y = true; - - if (this.zoom_xmin !== this.zoom_xmax) { - this.scale_xmin = this.zoom_xmin; - this.scale_xmax = this.zoom_xmax; - orig_x = false; - } - - if (this.zoom_ymin !== this.zoom_ymax) { - this.scale_ymin = this.zoom_ymin; - this.scale_ymax = this.zoom_ymax; - orig_y = false; - } - - // projection should be assigned - this.recalculateRange(opts.Proj, orig_x, orig_y); - - this.x_handle = new TAxisPainter(pp, this.xaxis, true); - this.x_handle.setHistPainter(opts.hist_painter, 'x'); - - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { - reverse: this.#reverse_x, - log: this.#swap_xy ? pad_logy : pad_logx, - ignore_labels: this.x_ignore_labels, - noexp_changed: this.x_noexp_changed, - fixed_ticks: opts.xticks, - symlog: this.#swap_xy ? opts.symlog_y : opts.symlog_x, - log_min_nz: opts.xmin_nz && (opts.xmin_nz <= this.xmax) ? 0.9 * opts.xmin_nz : 0, - logcheckmin: (opts.ndim > 1) || !this.#swap_xy, - logminfactor: logminfactorX - }); - - this.x_handle.assignFrameMembers(this, 'x'); - - this.y_handle = new TAxisPainter(pp, this.yaxis, true); - this.y_handle.setHistPainter(opts.hist_painter, 'y'); - - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { - value_axis: opts.ndim === 1, - reverse: this.#reverse_y, - log: this.#swap_xy ? pad_logx : pad_logy, - ignore_labels: this.y_ignore_labels, - noexp_changed: this.y_noexp_changed, - fixed_ticks: opts.yticks, - symlog: this.#swap_xy ? opts.symlog_x : opts.symlog_y, - log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5 * opts.ymin_nz : 0, - logcheckmin: (opts.ndim > 1) || this.#swap_xy, - logminfactor: logminfactorY - }); - - this.y_handle.assignFrameMembers(this, 'y'); - - this.setRootPadRange(pad); - } - - /** @summary Create x,y objects for drawing of second axes - * @private */ - createXY2(opts) { - if (!opts) - opts = { ndim: this.scales_ndim ?? 1 }; - - this.#reverse_x2 = opts.reverse_x || false; - this.#reverse_y2 = opts.reverse_y || false; - - this.logx2 = this.logy2 = 0; - - const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), pad = pp.getRootPad(); - - if (opts.second_x) { - this.scale_x2min = this.x2min; - this.scale_x2max = this.x2max; - } - - if (opts.second_y) { - this.scale_y2min = this.y2min; - this.scale_y2max = this.y2max; - } - - if (opts.extra_y_space && opts.second_y) { - const log_scale = this.#swap_xy ? pad.fLogx : pad.fLogy; - if (log_scale && (this.scale_y2max > 0)) - this.scale_y2max = Math.exp(Math.log(this.scale_y2max) * 1.1); - else - this.scale_y2max += (this.scale_y2max - this.scale_y2min) * 0.1; - } - - if ((this.zoom_x2min !== this.zoom_x2max) && opts.second_x) { - this.scale_x2min = this.zoom_x2min; - this.scale_x2max = this.zoom_x2max; - } - - if ((this.zoom_y2min !== this.zoom_y2max) && opts.second_y) { - this.scale_y2min = this.zoom_y2min; - this.scale_y2max = this.zoom_y2max; - } - - if (opts.second_x) { - this.x2_handle = new TAxisPainter(pp, this.x2axis, true); - this.x2_handle.setHistPainter(opts.hist_painter, 'x'); - - this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { - reverse: this.#reverse_x2, - log: this.#swap_xy ? pad.fLogy : pad.fLogx, - ignore_labels: this.x2_ignore_labels, - noexp_changed: this.x2_noexp_changed, - logcheckmin: (opts.ndim > 1) || !this.#swap_xy, - logminfactor: logminfactorX - }); - - this.x2_handle.assignFrameMembers(this, 'x2'); - } - - if (opts.second_y) { - this.y2_handle = new TAxisPainter(pp, this.y2axis, true); - this.y2_handle.setHistPainter(opts.hist_painter, 'y'); - - this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { - reverse: this.#reverse_y2, - log: this.#swap_xy ? pad.fLogx : pad.fLogy, - ignore_labels: this.y2_ignore_labels, - noexp_changed: this.y2_noexp_changed, - logcheckmin: (opts.ndim > 1) || this.#swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.y2max) ? 0.5 * opts.ymin_nz : 0, - logminfactor: logminfactorY - }); - - this.y2_handle.assignFrameMembers(this, 'y2'); - } - } - - /** @summary Return functions to create x/y points based on coordinates - * @desc In default case returns frame painter itself - * @private */ - getGrFuncs(second_x, second_y) { - const use_x2 = second_x && this.grx2, - use_y2 = second_y && this.gry2; - if (!use_x2 && !use_y2) - return this; - - return { - use_x2, - grx: use_x2 ? this.grx2 : this.grx, - logx: this.logx, - x_handle: use_x2 ? this.x2_handle : this.x_handle, - scale_xmin: use_x2 ? this.scale_x2min : this.scale_xmin, - scale_xmax: use_x2 ? this.scale_x2max : this.scale_xmax, - use_y2, - gry: use_y2 ? this.gry2 : this.gry, - logy: this.logy, - y_handle: use_y2 ? this.y2_handle : this.y_handle, - scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, - scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, - fp: this, - _remap(name) { - if ((name === 'x') && this.use_x2) - return 'x2'; - if ((name === 'y') && this.use_y2) - return 'y2'; - return name; - }, - swap_xy() { return this.fp.swap_xy(); }, - isAxisZoomed(name) { return this.fp.isAxisZoomed(this._remap(name)); }, - revertAxis(name, v) { return this.fp.revertAxis(this._remap(name), v); }, - axisAsText(name, v) { return this.fp.axisAsText(this._remap(name), v); }, - getFrameWidth() { return this.fp.getFrameWidth(); }, - getFrameHeight() { return this.fp.getFrameHeight(); } - }; - } - - /** @summary Set selected range back to TPad object - * @private */ - setRootPadRange(pad, is3d) { - if (!pad || !this.ranges_set) - return; - - if (is3d) { - // this is fake values, algorithm should be copied from TView3D class of ROOT - pad.fUxmin = pad.fUymin = -0.9; - pad.fUxmax = pad.fUymax = 0.9; - } else { - pad.fLogx = this.#swap_xy ? this.logy : this.logx; - pad.fUxmin = pad.fLogx ? Math.log10(this.scale_xmin) : this.scale_xmin; - pad.fUxmax = pad.fLogx ? Math.log10(this.scale_xmax) : this.scale_xmax; - pad.fLogy = this.#swap_xy ? this.logx : this.logy; - pad.fUymin = pad.fLogy ? Math.log10(this.scale_ymin) : this.scale_ymin; - pad.fUymax = pad.fLogy ? Math.log10(this.scale_ymax) : this.scale_ymax; - } - - const rx = pad.fUxmax - pad.fUxmin, - ry = pad.fUymax - pad.fUymin, - mx = Math.max(0.001, 1 - pad.fLeftMargin - pad.fRightMargin), - my = Math.max(0.001, 1 - pad.fBottomMargin - pad.fTopMargin); - - pad.fX1 = pad.fUxmin - rx / mx * pad.fLeftMargin; - pad.fX2 = pad.fUxmax + rx / mx * pad.fRightMargin; - pad.fY1 = pad.fUymin - ry / my * pad.fBottomMargin; - pad.fY2 = pad.fUymax + ry / my * pad.fTopMargin; - } - - /** @summary Draw axes grids - * @desc Called immediately after axes drawing */ - drawGrids(draw_grids) { - const layer = this.getFrameSvg().selectChild('.axis_layer'); - - layer.selectAll('.xgrid').remove(); - layer.selectAll('.ygrid').remove(); - - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - h = this.getFrameHeight(), - w = this.getFrameWidth(), - grid_style = gStyle.fGridStyle; - - // add a grid on x axis, if the option is set - if (pad?.fGridx && draw_grids && this.x_handle?.ticks) { - const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('x')?.fAxisColor ?? 1); - let gridx = ''; - this.x_handle.ticks.forEach(pos => { - gridx += this.#swap_xy ? `M0,${pos}h${w}` : `M${pos},0v${h}`; - }); - - layer.append('svg:path') - .attr('class', 'xgrid') - .attr('d', gridx) - .style('stroke', this.getColor(colid) || 'black') - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', getSvgLineStyle(grid_style)); - } - - // add a grid on y axis, if the option is set - if (pad?.fGridy && draw_grids && this.y_handle?.ticks) { - const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('y')?.fAxisColor ?? 1); - let gridy = ''; - this.y_handle.ticks.forEach(pos => { - gridy += this.#swap_xy ? `M${pos},0v${h}` : `M0,${pos}h${w}`; - }); - - layer.append('svg:path') - .attr('class', 'ygrid') - .attr('d', gridy) - .style('stroke', this.getColor(colid) || 'black') - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', getSvgLineStyle(grid_style)); - } - } - - /** @summary Converts 'raw' axis value into text */ - axisAsText(axis, value) { - const handle = this[`${axis}_handle`]; - - if (handle) - return handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']); - - return value.toPrecision(4); - } - - /** @summary Identify if requested axes are drawn - * @desc Checks if x/y axes are drawn. Also if second side is already there */ - hasDrawnAxes(second_x, second_y) { return !second_x && !second_y ? this.#axes_drawn : this.#axes2_drawn; } - - /** @summary draw axes, - * @return {Promise} which ready when drawing is completed */ - async drawAxes(shrink_forbidden, disable_x_draw, disable_y_draw, AxisPos, has_x_obstacle, has_y_obstacle, enable_grids) { - this.cleanAxesDrawings(); - - if ((this.xmin === this.xmax) || (this.ymin === this.ymax)) - return false; - - if (AxisPos === undefined) - AxisPos = 0; - - const layer = this.getFrameSvg().selectChild('.axis_layer'), - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(true), - draw_grids = enable_grids && (pad?.fGridx || pad?.fGridy); - - this.x_handle.invert_side = (AxisPos >= 10); - this.x_handle.lbls_both_sides = !this.x_handle.invert_side && (pad?.fTickx > 1); // labels on both sides - this.x_handle.has_obstacle = has_x_obstacle; - - this.y_handle.invert_side = ((AxisPos % 10) === 1); - this.y_handle.lbls_both_sides = !this.y_handle.invert_side && (pad?.fTicky > 1); // labels on both sides - this.y_handle.has_obstacle = has_y_obstacle; - - const draw_horiz = this.#swap_xy ? this.y_handle : this.x_handle, - draw_vertical = this.#swap_xy ? this.x_handle : this.y_handle; - - if ((!disable_x_draw || !disable_y_draw) && pp.isFastDrawing()) - disable_x_draw = disable_y_draw = true; - - let pr = Promise.resolve(true); - - if (!disable_x_draw || !disable_y_draw || draw_grids) { - draw_vertical.optionLeft = draw_vertical.invert_side; // text align - - const can_adjust_frame = !shrink_forbidden && settings.CanAdjustFrame, - - pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - pad?.fTickx ? -h : 0, disable_x_draw, - undefined, false, pp.getPadHeight() - h - this.getFrameY()), - - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - pad?.fTicky ? w : 0, disable_y_draw, - draw_vertical.invert_side ? 0 : this.#frame_x, can_adjust_frame); - - pr = Promise.all([pr1, pr2]).then(() => { - this.drawGrids(draw_grids); - - if (!can_adjust_frame) - return; - - let shrink = 0.0; - const ypos = draw_vertical.position; - - if ((-0.2 * w < ypos) && (ypos < 0)) { - shrink = -ypos / w + 0.001; - this.#shrink_frame_left += shrink; - } else if ((ypos > 0) && (ypos < 0.3 * w) && (this.#shrink_frame_left > 0) && (ypos / w > this.#shrink_frame_left)) { - shrink = -this.#shrink_frame_left; - this.#shrink_frame_left = 0.0; - } - - if (shrink) { - this.shrinkFrame(shrink, 0); - return this.redraw().then(() => this.drawAxes(true)); - } - }); - } - - return pr.then(() => { - if (!shrink_forbidden) - this.#axes_drawn = true; - return true; - }); - } - - /** @summary draw second axes (if any) */ - async drawAxes2(second_x, second_y) { - const layer = this.getFrameSvg().selectChild('.axis_layer'), - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(true); - - if (second_x) { - this.x2_handle.invert_side = true; - this.x2_handle.lbls_both_sides = false; - this.x2_handle.has_obstacle = false; - } - - if (second_y) { - this.y2_handle.invert_side = true; - this.y2_handle.lbls_both_sides = false; - } - - let draw_horiz = this.#swap_xy ? this.y2_handle : this.x2_handle, - draw_vertical = this.#swap_xy ? this.x2_handle : this.y2_handle; - - if ((draw_horiz || draw_vertical) && pp.isFastDrawing()) - draw_horiz = draw_vertical = null; - - let pr1, pr2; - - if (draw_horiz) { - pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - pad?.fTickx ? -h : 0, false, - undefined, false); - } - - if (draw_vertical) { - draw_vertical.optionLeft = draw_vertical.invert_side; - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - pad?.fTicky ? w : 0, false, - draw_vertical.invert_side ? 0 : this.#frame_x, false); - } - - return Promise.all([pr1, pr2]).then(() => { - this.#axes2_drawn = true; - return true; - }); - } - - - /** @summary Update frame attributes - * @private */ - updateAttributes(force) { - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - tframe = this.getObject(); - - if ((this.fX1NDC === undefined) || (force && !this.$modifiedNDC)) { - if (!pad) { - this.fX1NDC = gStyle.fPadLeftMargin; - this.fX2NDC = 1 - gStyle.fPadRightMargin; - this.fY1NDC = gStyle.fPadBottomMargin; - this.fY2NDC = 1 - gStyle.fPadTopMargin; - } else { - this.fX1NDC = pad.fLeftMargin; - this.fX2NDC = 1 - pad.fRightMargin; - this.fY1NDC = pad.fBottomMargin; - this.fY2NDC = 1 - pad.fTopMargin; - } - } - - if (tframe) { - this.createAttFill({ attr: tframe }); - this.#border_mode = tframe.fBorderMode; - this.#border_size = tframe.fBorderSize; - } else if (!this.fillatt) { - if (pad?.fFrameFillColor) - this.createAttFill({ pattern: pad.fFrameFillStyle, color: pad.fFrameFillColor }); - else if (pad) - this.createAttFill({ attr: pad }); - else - this.createAttFill({ pattern: gStyle.fFrameFillStyle, color: gStyle.fFrameFillColor }); - - // force white color for the canvas frame - if (!tframe && this.fillatt.empty() && pp?.isCanvas()) - this.fillatt.setSolidColor('white'); - else if ((pad?.fFillStyle === 4000) && !this.fillatt.empty()) // special case of transpad.C macro, which set transparent pad - this.fillatt.setOpacity(0); - - if (pad && (pad.fFrameBorderMode || (pad.fFrameBorderSize !== 1))) { - this.#border_mode = pad.fFrameBorderMode; - this.#border_size = pad.fFrameBorderSize; - } - } - - if (!tframe && (pad?.fFrameLineColor !== undefined)) - this.createAttLine({ color: pad.fFrameLineColor, width: pad.fFrameLineWidth, style: pad.fFrameLineStyle }); - else if (tframe) - this.createAttLine({ attr: tframe, color: 'black' }); - else - this.createAttLine({ color: gStyle.fFrameLineColor, width: gStyle.fFrameLineWidth, style: gStyle.fFrameLineStyle }); - } - - /** @summary Function called at the end of resize of frame - * @desc One should apply changes to the pad - * @private */ - sizeChanged() { - const pad = this.getPadPainter()?.getRootPad(true); - - if (pad) { - pad.fLeftMargin = this.fX1NDC; - pad.fRightMargin = 1 - this.fX2NDC; - pad.fBottomMargin = this.fY1NDC; - pad.fTopMargin = 1 - this.fY2NDC; - this.setRootPadRange(pad); - } - - this.interactiveRedraw('pad', 'frame'); - } - - /** @summary Remove all kinds of X/Y function for axes transformation */ - cleanXY() { - delete this.grx; - delete this.gry; - delete this.grz; - delete this.grx2; - delete this.gry2; - - this.x_handle?.cleanup(); - this.y_handle?.cleanup(); - this.z_handle?.cleanup(); - this.x2_handle?.cleanup(); - this.y2_handle?.cleanup(); - - delete this.x_handle; - delete this.y_handle; - delete this.z_handle; - delete this.x2_handle; - delete this.y2_handle; - } - - /** @summary remove all axes drawings */ - cleanAxesDrawings() { - this.x_handle?.removeG(); - this.y_handle?.removeG(); - this.z_handle?.removeG(); - this.x2_handle?.removeG(); - this.y2_handle?.removeG(); - - this.getG()?.selectChild('.axis_layer').selectAll('*').remove(); - this.#axes_drawn = this.#axes2_drawn = false; - } - - /** @summary Returns frame rectangle plus extra info for hint display */ - cleanFrameDrawings() { - // cleanup all 3D drawings if any - if (isFunc(this.create3DScene)) - this.create3DScene(-1); - - this.cleanAxesDrawings(); - this.cleanXY(); - - this.ranges_set = false; - - this.xmin = this.xmax = 0; - this.ymin = this.ymax = 0; - this.zmin = this.zmax = 0; - - this.zoom_xmin = this.zoom_xmax = 0; - this.zoom_ymin = this.zoom_ymax = 0; - this.zoom_zmin = this.zoom_zmax = 0; - - this.scale_xmin = this.scale_xmax = 0; - this.scale_ymin = this.scale_ymax = 0; - this.scale_zmin = this.scale_zmax = 0; - - this.getG()?.selectChild('.main_layer').selectAll('*').remove(); - this.getG()?.selectChild('.upper_layer').selectAll('*').remove(); - - this.xaxis = null; - this.yaxis = null; - this.zaxis = null; - - this.getG()?.selectAll('*').remove(); - this.getG()?.on('mousedown', null) - .on('dblclick', null) - .on('wheel', null) - .on('contextmenu', null) - .property('interactive_set', null) - .remove(); - - this.setG(undefined); // frame element managed by the pad - - if (this.#keys_handler) { - window.removeEventListener('keydown', this.#keys_handler, false); - this.#keys_handler = undefined; - } - } - - /** @summary Cleanup frame */ - cleanup() { - this.cleanFrameDrawings(); - this.#click_handler = undefined; - this.#dblclick_handler = undefined; - this.#enabled_keys = undefined; - - this.getPadPainter()?.setFramePainter(this, false); - - super.cleanup(); - } - - /** @summary Redraw TFrame */ - redraw(/* reason */) { - const pp = this.getPadPainter(); - pp?.setFramePainter(this, true); - - // first update all attributes from objects - this.updateAttributes(); - - const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, - lm = Math.round(rect.width * this.fX1NDC), - tm = Math.round(rect.height * (1 - this.fY2NDC)), - rotate = pp?.options?.RotateFrame, - w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), - h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)); - - this.#frame_x = lm; - this.#frame_y = tm; - this.#frame_width = rotate ? h : w; - this.#frame_height = rotate ? w : h; - this.#frame_trans = rotate ? `rotate(-90,${lm},${tm}) translate(${lm - h},${tm})` : makeTranslate(lm, tm); - this.$can_drag = !rotate && !pp?.options?.FixFrame; - - return this.mode3d ? this : this.createFrameG(); - } - - /** @summary Create frame element and update all attributes - * @private */ - createFrameG() { - // this is svg:g object - container for every other items belonging to frame - let g = this.setG(this.getFrameSvg()), - top_rect, main_svg; - - if (g.empty()) { - g = this.setG(this.getPadPainter().getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame')); - - // empty title on the frame required to suppress title of the canvas - if (!this.isBatchMode()) - g.append('svg:title').text(''); - - top_rect = g.append('svg:path'); - - main_svg = g.append('svg:svg') - .attr('class', 'main_layer') - .attr('x', 0) - .attr('y', 0) - .attr('overflow', 'hidden'); - - g.append('svg:g').attr('class', 'axis_layer'); - g.append('svg:g').attr('class', 'upper_layer'); - } else { - top_rect = g.selectChild('path'); - main_svg = g.selectChild('.main_layer'); - } - - this.#axes_drawn = this.#axes2_drawn = false; - - g.attr('transform', this.#frame_trans); - - top_rect.attr('d', `M0,0H${this.#frame_width}V${this.#frame_height}H0Z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - - main_svg.attr('width', this.#frame_width) - .attr('height', this.#frame_height) - .attr('viewBox', `0 0 ${this.#frame_width} ${this.#frame_height}`); - - g.selectAll('.frame_deco').remove(); - if (this.#border_mode && this.fillatt.hasColor()) { - const paths = getBoxDecorations(0, 0, this.#frame_width, this.#frame_height, this.#border_mode, this.#border_size || 2, this.#border_size || 2); - g.insert('svg:path', '.main_layer') - .attr('class', 'frame_deco') - .attr('d', paths[0]) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatRgb()); - g.insert('svg:path', '.main_layer') - .attr('class', 'frame_deco') - .attr('d', paths[1]) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).darker(0.5).formatRgb()); - } - - return this; - } - - /** @summary Change log state of specified axis - * @param {string} axis - name of axis like 'x' or 'y' - * @param {number} value - 0 (linear), 1 (log) or 2 (log2) */ - changeAxisLog(axis, value) { - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true); - if (!pad) - return; - - pp.options._interactively_changed = true; - - const name = `fLog${axis}`; - - // do not allow log scale for labels - if (!pad[name]) { - if (this.#swap_xy && axis === 'x') - axis = 'y'; - else if (this.#swap_xy && axis === 'y') - axis = 'x'; - const handle = this[`${axis}_handle`]; - if (handle?.kind === kAxisLabels) - return; - } - - if ((value === 'toggle') || (value === undefined)) - value = pad[name] ? 0 : 1; - - // directly change attribute in the pad - pad[name] = value; - - return this.interactiveRedraw('pad', `log${axis}`); - } - - /** @summary Toggle log state on the specified axis */ - toggleAxisLog(axis) { - return this.changeAxisLog(axis, 'toggle'); - } - - /** @summary Fill context menu for the frame - * @desc It could be appended to the histogram menus */ - fillContextMenu(menu, kind, obj) { - const main = this.getMainPainter(true), - wrk = main?.$stack_hist ? main.getPrimary() : main, - pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - is_pal = kind === 'pal'; - - if (is_pal) - kind = 'z'; - - if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'x2') || (kind === 'y2')) { - const faxis = obj || this[kind + 'axis'], - handle = this[`${kind}_handle`]; - if (!isFunc(faxis?.TestBit)) - return false; - const hist_painter = handle?.hist_painter || main; - - menu.header(`${kind.toUpperCase()} axis`, `${urlClassPrefix}${clTAxis}.html`); - - menu.sub('Range'); - menu.add('Zoom', () => { - let min = this[`zoom_${kind}min`] ?? this[`${kind}min`], - max = this[`zoom_${kind}max`] ?? this[`${kind}max`]; - if (min === max) { - min = this[`${kind}min`]; - max = this[`${kind}max`]; - } - menu.input('Enter zoom range like: [min, max]', `[${min}, ${max}]`).then(v => { - const arr = JSON.parse(v); - if (arr && Array.isArray(arr) && (arr.length === 2)) { - let flag = false; - if (arr[0] < faxis.fXmin) { - faxis.fFirst = 0; - flag = true; - } else - faxis.fFirst = 1; - if (arr[1] > faxis.fXmax) { - faxis.fLast = faxis.fNbins + 1; - flag = true; - } else - faxis.fLast = faxis.fNbins; - faxis.SetBit(EAxisBits.kAxisRange, flag); - hist_painter?.scanContent(); - this.zoomSingle(kind, arr[0], arr[1], true).then(res => { - if (!res && flag) - this.interactiveRedraw('pad'); - }); - } - }); - }); - menu.add('Unzoom', () => { - this.unzoomSingle(kind).then(res => { - if (!res && (faxis.fFirst !== faxis.fLast)) { - faxis.fFirst = faxis.fLast = 0; - hist_painter?.scanContent(); - this.interactiveRedraw('pad'); - } - }); - }); - if (handle?.value_axis && isFunc(wrk?.accessMM)) { - menu.add('Minimum', () => { - menu.input(`Enter minimum value or ${kNoZoom} as default`, wrk.accessMM(true), 'float').then(v => { - this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; - wrk.accessMM(true, v); - }); - }); - menu.add('Maximum', () => { - menu.input(`Enter maximum value or ${kNoZoom} as default`, wrk.accessMM(false), 'float').then(v => { - this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; - wrk.accessMM(false, v); - }); - }); - } - menu.endsub(); - - if (pad) - menu.addPadLogMenu(kind[0], pad[`fLog${kind[0]}`], v => this.changeAxisLog(kind[0], v)); - - menu.addchk(faxis.TestBit(EAxisBits.kMoreLogLabels), 'More log', flag => { - faxis.SetBit(EAxisBits.kMoreLogLabels, flag); - if (hist_painter?.getSnapId() && (kind.length === 1)) - hist_painter.interactiveRedraw('pad', `exec:SetMoreLogLabels(${flag})`, kind); - else - this.interactiveRedraw('pad'); - }); - menu.addchk(handle?.noexp ?? faxis.TestBit(EAxisBits.kNoExponent), 'No exponent', flag => { - faxis.SetBit(EAxisBits.kNoExponent, flag); - if (handle) - handle.noexp_changed = true; - this[`${kind}_noexp_changed`] = true; - if (hist_painter?.getSnapId() && (kind.length === 1)) - hist_painter.interactiveRedraw('pad', `exec:SetNoExponent(${flag})`, kind); - else - this.interactiveRedraw('pad'); - }); - - if ((kind === 'z') && isFunc(hist_painter?.fillPaletteMenu)) - hist_painter.fillPaletteMenu(menu, !is_pal); - - menu.addTAxisMenu(EAxisBits, hist_painter || this, faxis, kind, handle, this); - return true; - } - - const alone = menu.size() === 0; - - if (alone) - menu.header('Frame', `${urlClassPrefix}${clTFrame}.html`); - else - menu.separator(); - - if (this.zoom_xmin !== this.zoom_xmax) - menu.add('Unzoom X', () => this.unzoom('x')); - if (this.zoom_ymin !== this.zoom_ymax) - menu.add('Unzoom Y', () => this.unzoom('y')); - if (this.zoom_zmin !== this.zoom_zmax) - menu.add('Unzoom Z', () => this.unzoom('z')); - if (this.zoom_x2min !== this.zoom_x2max) - menu.add('Unzoom X2', () => this.unzoom('x2')); - if (this.zoom_y2min !== this.zoom_y2max) - menu.add('Unzoom Y2', () => this.unzoom('y2')); - menu.add('Unzoom all', () => this.unzoom('all')); - - if (pad) { - menu.addchk(pad.fLogx, 'SetLogx', () => this.toggleAxisLog('x')); - menu.addchk(pad.fLogy, 'SetLogy', () => this.toggleAxisLog('y')); - - if (isFunc(main?.getDimension) && (main.getDimension() > 1)) - menu.addchk(pad.fLogz, 'SetLogz', () => this.toggleAxisLog('z')); - menu.separator(); - } - - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - menu.addAttributesMenu(this, alone ? '' : 'Frame '); - - menu.sub('Border'); - menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], this.#border_mode + 1, v => { - this.#border_mode = v - 1; - this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); - }, 'Frame border mode'); - menu.addSizeMenu('Size', 0, 20, 2, this.#border_size, v => { - this.#border_size = v; - this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); - }, 'Frame border size'); - menu.endsub(); - - menu.add('Save to gStyle', () => { - gStyle.fPadBottomMargin = this.fY1NDC; - gStyle.fPadTopMargin = 1 - this.fY2NDC; - gStyle.fPadLeftMargin = this.fX1NDC; - gStyle.fPadRightMargin = 1 - this.fX2NDC; - this.fillatt?.saveToStyle('fFrameFillColor', 'fFrameFillStyle'); - this.lineatt?.saveToStyle('fFrameLineColor', 'fFrameLineWidth', 'fFrameLineStyle'); - gStyle.fFrameBorderMode = this.#border_mode; - gStyle.fFrameBorderSize = this.#border_size; - }, 'Store frame position and graphical attributes to gStyle'); - - menu.separator(); - - menu.sub('Save as'); - const fmts = ['svg', 'png', 'jpeg', 'webp']; - if (internals.makePDF) - fmts.push('pdf'); - fmts.forEach(fmt => menu.add(`frame.${fmt}`, () => pp.saveAs(fmt, 'frame', `frame.${fmt}`))); - menu.endsub(); - - return true; - } - - /** @summary Fill option object used in TWebCanvas - * @private */ - fillWebObjectOptions(res) { - res.fcust = 'frame'; - res.fopt = [this.scale_xmin || 0, this.scale_ymin || 0, this.scale_xmax || 0, this.scale_ymax || 0]; - } - - /** @summary Returns frame X position */ - getFrameX() { return this.#frame_x || 0; } - - /** @summary Returns frame Y position */ - getFrameY() { return this.#frame_y || 0; } - - /** @summary Returns frame width */ - getFrameWidth() { return this.#frame_width || 0; } - - /** @summary Returns frame height */ - getFrameHeight() { return this.#frame_height || 0; } - - /** @summary Returns frame rectangle plus extra info for hint display */ - getFrameRect() { - return { - x: this.#frame_x || 0, - y: this.#frame_y || 0, - width: this.getFrameWidth(), - height: this.getFrameHeight(), - transform: this.getG()?.attr('transform') || '', - hint_delta_x: 0, - hint_delta_y: 0 - }; - } - - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was performed - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of click will be disabled */ - configureUserClickHandler(handler) { - this.#click_handler = isFunc(handler) ? handler : null; - } - - /** @summary Returns actual click handler */ - getClickHandler() { return this.#click_handler; } - - /** @summary Configure user-defined dblclick handler - * @desc Function will be called every time when double click was called - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ - configureUserDblclickHandler(handler) { - this.#dblclick_handler = isFunc(handler) ? handler : null; - } - - /** @summary Returns actual double-click handler */ - getDblclickHandler() { return this.#dblclick_handler; } - - /** @summary Function can be used for zooming into specified range - * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be un-zoomed - * @param {number} xmin - X minimum - * @param {number} xmax - X maximum - * @param {number} [ymin] - Y minimum - * @param {number} [ymax] - Y maximum - * @param {number} [zmin] - Z minimum - * @param {number} [zmax] - Z maximum - * @param {boolean} [interactive] - if changes was performed interactively - * @return {Promise} with boolean flag if zoom operation was performed */ - async zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { - if (xmin === 'x') { - xmin = xmax; - xmax = ymin; - interactive = ymax; - ymin = ymax = undefined; - } else if (xmin === 'y') { - interactive = ymax; - ymax = ymin; - ymin = xmax; - xmin = xmax = undefined; - } else if (xmin === 'z') { - interactive = ymax; - zmin = xmax; - zmax = ymin; - xmin = xmax = ymin = ymax = undefined; - } - - let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), - unzoom_x = false, unzoom_y = false, unzoom_z = false; - - if (zoom_x) { - let cnt = 0; - xmin = this.x_handle?.checkZoomMin(xmin) ?? xmin; - if (xmin <= this.xmin) { - xmin = this.xmin; - cnt++; - } - if (xmax >= this.xmax) { - xmax = this.xmax; - cnt++; - } - if (cnt === 2) { - zoom_x = false; - unzoom_x = true; - } - } else - unzoom_x = (xmin === xmax) && (xmin === 0); - - if (zoom_y) { - let cnt = 0; - ymin = this.y_handle?.checkZoomMin(ymin) ?? ymin; - if (ymin <= this.ymin) { - ymin = this.ymin; - cnt++; - } - if (ymax >= this.ymax) { - ymax = this.ymax; - cnt++; - } - if ((cnt === 2) && (this.scales_ndim !== 1)) { - zoom_y = false; - unzoom_y = true; - } - } else - unzoom_y = (ymin === ymax) && (ymin === 0); - - if (zoom_z) { - let cnt = 0; - zmin = this.z_handle?.checkZoomMin(zmin) ?? zmin; - if (zmin <= this.zmin) { - zmin = this.zmin; - cnt++; - } - if (zmax >= this.zmax) { - zmax = this.zmax; - cnt++; - } - if ((cnt === 2) && (this.scales_ndim > 2)) { - zoom_z = false; - unzoom_z = true; - } - } else - unzoom_z = (zmin === zmax) && (zmin === 0); - - let changed = false; - - // first process zooming (if any) - if (zoom_x || zoom_y || zoom_z) { - this.forEachPainter(obj => { - if (!isFunc(obj.canZoomInside)) - return; - if (zoom_x && obj.canZoomInside('x', xmin, xmax)) { - this.zoom_xmin = xmin; - this.zoom_xmax = xmax; - changed = true; - zoom_x = false; - if (interactive) - this.zoomChangedInteractive('x', interactive); - } - if (zoom_y && obj.canZoomInside('y', ymin, ymax)) { - this.zoom_ymin = ymin; - this.zoom_ymax = ymax; - changed = true; - zoom_y = false; - if (interactive) - this.zoomChangedInteractive('y', interactive); - } - if (zoom_z && obj.canZoomInside('z', zmin, zmax)) { - this.zoom_zmin = zmin; - this.zoom_zmax = zmax; - changed = true; - zoom_z = false; - if (interactive) - this.zoomChangedInteractive('z', interactive); - } - }); - } - - // and process unzoom, if any - if (unzoom_x || unzoom_y || unzoom_z) { - if (unzoom_x) { - if (this.zoom_xmin !== this.zoom_xmax) - changed = true; - this.zoom_xmin = this.zoom_xmax = 0; - if (interactive) - this.zoomChangedInteractive('x', interactive); - } - if (unzoom_y) { - if (this.zoom_ymin !== this.zoom_ymax) { - changed = true; - unzoomHistogramYRange(this.getMainPainter()); - } - this.zoom_ymin = this.zoom_ymax = 0; - if (interactive) - this.zoomChangedInteractive('y', interactive); - } - if (unzoom_z) { - if (this.zoom_zmin !== this.zoom_zmax) - changed = true; - this.zoom_zmin = this.zoom_zmax = 0; - if (interactive) - this.zoomChangedInteractive('z', interactive); - } - - // than try to unzoom all overlapped objects - if (!changed) { - this.forEachPainter(painter => { - if (isFunc(painter?.unzoomUserRange)) { - if (painter.unzoomUserRange(unzoom_x, unzoom_y, unzoom_z)) - changed = true; - } - }, 'objects'); - } - } - if (!changed) - return false; - - return this.interactiveRedraw('pad', 'zoom').then(() => true); - } - - /** @summary Zooming of single axis - * @param {string} name - axis name like x/y/z but also second axis x2 or y2 - * @param {number} vmin - axis minimal value, 0 for unzoom - * @param {number} vmax - axis maximal value, 0 for unzoom - * @param {boolean} [interactive] - if change was performed interactively - * @protected */ - async zoomSingle(name, vmin, vmax, interactive) { - const handle = this[`${name}_handle`], name_min = `zoom_${name}min`, name_max = `zoom_${name}max`; - if (!handle && (name !== 'z')) - return false; - - let zoom_v = (vmin !== vmax), unzoom_v = false; - - if (zoom_v) { - let cnt = 0; - vmin = handle?.checkZoomMin(vmin) ?? vmin; - if (vmin <= this[name + 'min']) { - vmin = this[name + 'min']; - cnt++; - } - if (vmax >= this[name + 'max']) { - vmax = this[name + 'max']; - cnt++; - } - if (cnt === 2) { - zoom_v = false; - unzoom_v = true; - } - } else - unzoom_v = (vmin === vmax) && (vmin === 0); - - let changed = false; - - // first process zooming - if (zoom_v) { - this.forEachPainter(obj => { - if (zoom_v && isFunc(obj.canZoomInside) && obj.canZoomInside(name[0], vmin, vmax)) { - this[name_min] = vmin; - this[name_max] = vmax; - changed = true; - zoom_v = false; - } - }); - } - - // and process unzoom, if any - if (unzoom_v) { - if (this[name_min] !== this[name_max]) { - changed = true; - if (name === 'y') - unzoomHistogramYRange(this.getMainPainter()); - } - this[name_min] = this[name_max] = 0; - } - - if (!changed) - return false; - - if (interactive) - this.zoomChangedInteractive(name, interactive); - - return this.interactiveRedraw('pad', 'zoom').then(() => true); - } - - /** @summary Unzoom single axis */ - async unzoomSingle(name, interactive) { - return this.zoomSingle(name, 0, 0, typeof interactive === 'undefined' ? 'unzoom' : interactive); - } - - /** @summary Checks if specified axis zoomed */ - isAxisZoomed(axis) { - return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; - } - - /** @summary Unzoom specified axes - * @return {Promise} with boolean flag if zooming changed */ - async unzoom(dox, doy, doz) { - if (dox === 'all') - return this.unzoomSingle('x2').then(() => this.unzoomSingle('y2')).then(() => this.unzoom('xyz')); - - if ((dox === 'x2') || (dox === 'y2')) - return this.unzoomSingle(dox); - - if (typeof dox === 'undefined') - dox = doy = doz = true; - else if (isStr(dox)) { - doz = dox.indexOf('z') >= 0; - doy = dox.indexOf('y') >= 0; - dox = dox.indexOf('x') >= 0; - } - - return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, - doy ? 0 : undefined, doy ? 0 : undefined, - doz ? 0 : undefined, doz ? 0 : undefined, - 'unzoom'); - } - - /** @summary Reset all zoom attributes - * @private */ - resetZoom() { - ['x', 'y', 'z', 'x2', 'y2'].forEach(n => { - this[`zoom_${n}min`] = undefined; - this[`zoom_${n}max`] = undefined; - this[`zoom_changed_${n}`] = undefined; - }); - } - - /** @summary Mark/check if zoom for specific axis was changed interactively - * @private */ - zoomChangedInteractive(axis, value) { - if (axis === 'reset') { - this.zoom_changed_x = this.zoom_changed_y = this.zoom_changed_z = undefined; - return; - } - if (!axis || axis === 'any') - return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - - if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) - return; - - const fld = 'zoom_changed_' + axis; - if (value === undefined) - return this[fld]; - - // special handling of unzoom, only if was never changed before flag set to true - if (value === 'unzoom') - this[fld] = (this[fld] === undefined); - else if (value) - this[fld] = true; - } - - /** @summary Convert graphical coordinate into axis value */ - revertAxis(axis, pnt) { - if (this.#swap_xy) - axis = (axis[0] === 'x') ? 'y' : 'x'; - return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; - } - - /** @summary Show axis status message - * @desc method called normally when mouse enter main object element - * @private */ - showAxisStatus(axis_name, evnt) { - const taxis = this.getAxis(axis_name), - m = pointer(evnt, this.getFrameSvg().node()); - let hint_name = axis_name, - hint_title = clTAxis, - id = (axis_name === 'x') ? 0 : 1; - - if (taxis) { - hint_name = taxis.fName; - hint_title = taxis.fTitle || `TAxis object for ${axis_name}`; - } - if (this.#swap_xy) - id = 1 - id; - - const axis_value = this.revertAxis(axis_name, m[id]); - - this.showObjectStatus(hint_name, hint_title, `${axis_name} : ${this.axisAsText(axis_name, axis_value)}`, `${m[0]},${m[1]}`); - } - - /** @summary Add interactive keys handlers - * @private */ - addKeysHandler() { - if (this.isBatchMode() || this.#keys_handler || (typeof window === 'undefined')) - return; - - this.#keys_handler = evnt => this.processKeyPress(evnt); - - window.addEventListener('keydown', this.#keys_handler, false); - } - - /** @summary Add interactive functionality to the frame - * @private */ - addInteractivity(for_second_axes) { - if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) - return false; - - if (!for_second_axes) - this.addBasicInteractivity(); - - return this.addFrameInteractivity(for_second_axes); - } - -} // class TFramePainter - -const kBrFloat = 'float', kBrFix = 'fix'; - -/** @summary Current hierarchy painter - * @desc Instance of {@link HierarchyPainter} object - * @private */ -let _first_hpainter = null; - -/** @summary Returns current hierarchy painter object - * @private */ -function getHPainter() { return _first_hpainter; } - -/** @summary Set hierarchy painter object - * @private */ -function setHPainter(hp) { _first_hpainter = hp; } - -/** - * @summary Base class to manage multiple document interface for drawings - * - * @private - */ - -class MDIDisplay extends BasePainter { - - /** @summary constructor */ - constructor(frameid) { - super(); - this.frameid = frameid; - if (frameid !== '$batch$') { - this.setDom(frameid); - this.selectDom().property('mdi', this); - } - this.cleanupFrame = cleanup; // use standard cleanup function by default - this.active_frame_title = ''; // keep title of active frame - } - - /** @summary Assign func which called for each newly created frame */ - setInitFrame(func) { - this.initFrame = func; - this.forEachFrame(frame => func(frame)); - } - - /** @summary method called before new frame is created */ - beforeCreateFrame(title) { this.active_frame_title = title; } - - /** @summary method called after new frame is created - * @private */ - afterCreateFrame(frame) { - if (isFunc(this.initFrame)) - this.initFrame(frame); - return frame; - } - - /** @summary method dedicated to iterate over existing panels - * @param {Function} userfunc is called with arguments (frame) - * @param {boolean} only_visible let select only visible frames */ - forEachFrame(userfunc, only_visible) { - console.warn(`forEachFrame not implemented in MDIDisplay ${typeof userfunc} ${only_visible}`); - } - - /** @summary method dedicated to iterate over existing panels - * @param {Function} userfunc is called with arguments (painter, frame) - * @param {boolean} only_visible let select only visible frames */ - forEachPainter(userfunc, only_visible) { - this.forEachFrame(frame => { - new ObjectPainter(frame).forEachPainter(painter => userfunc(painter, frame)); - }, only_visible); - } - - /** @summary Returns total number of drawings */ - numDraw() { - let cnt = 0; - this.forEachFrame(() => ++cnt); - return cnt; - } - - /** @summary Search for the frame using item name */ - findFrame(searchtitle, force) { - let found_frame = null; - - this.forEachFrame(frame => { - if (select(frame).attr('frame_title') === searchtitle) - found_frame = frame; - }); - - if (!found_frame && force) - found_frame = this.createFrame(searchtitle); - - return found_frame; - } - - /** @summary Activate frame */ - activateFrame(frame) { this.active_frame_title = frame ? select(frame).attr('frame_title') : ''; } - - /** @summary Return active frame */ - getActiveFrame() { return this.findFrame(this.active_frame_title); } - - /** @summary perform resize for each frame - * @protected */ - checkMDIResize(only_frame_id, size) { - let resized_frame = null; - - this.forEachPainter((painter, frame) => { - if (only_frame_id && (select(frame).attr('id') !== only_frame_id)) - return; - - if ((painter.getItemName() !== null) && isFunc(painter.checkResize)) { - // do not call resize for many painters on the same frame - if (resized_frame === frame) - return; - painter.checkResize(size); - resized_frame = frame; - } - }); - } - - /** @summary Cleanup all drawings */ - cleanup() { - this.active_frame_title = ''; - - this.forEachFrame(this.cleanupFrame); - - this.selectDom().html('').property('mdi', null); - } - -} // class MDIDisplay - - -/** - * @summary Custom MDI display - * - * @desc All HTML frames should be created before and add via {@link CustomDisplay#addFrame} calls - * @private - */ - -class CustomDisplay extends MDIDisplay { - - constructor() { - super('dummy'); - this.frames = {}; // array of configured frames - } - - addFrame(divid, itemname) { - const prev = this.frames[divid] || ''; - this.frames[divid] = prev + (itemname + ';'); - } - - forEachFrame(userfunc) { - const ks = Object.keys(this.frames); - for (let k = 0; k < ks.length; ++k) { - const node = select('#' + ks[k]); - if (!node.empty()) - userfunc(node.node()); - } - } - - createFrame(title) { - this.beforeCreateFrame(title); - - const ks = Object.keys(this.frames); - for (let k = 0; k < ks.length; ++k) { - const items = this.frames[ks[k]]; - if (items.indexOf(title + ';') >= 0) - return select('#' + ks[k]).node(); - } - return null; - } - - cleanup() { - super.cleanup(); - this.forEachFrame(frame => select(frame).html('')); - } - -} // class CustomDisplay - -/** - * @summary Generic grid MDI display - * - * @private - */ - -class GridDisplay extends MDIDisplay { - - /** @summary Create GridDisplay instance - * @param {string} frameid - where grid display is created - * @param {string} kind - kind of grid - * @desc following kinds are supported - * - vertical or horizontal - only first letter matters, defines basic orientation - * - 'x' in the name disable interactive separators - * - v4 or h4 - 4 equal elements in specified direction - * - v231 - created 3 vertical elements, first divided on 2, second on 3 and third on 1 part - * - v23_52 - create two vertical elements with 2 and 3 subitems, size ratio 5:2 - * - gridNxM - normal grid layout without interactive separators - * - gridiNxM - grid layout with interactive separators - * - simple - no layout, full frame used for object drawings */ - constructor(frameid, kind, kind2) { - super(frameid); - - this.framecnt = 0; - this.getcnt = 0; - this.groups = []; - this.vertical = kind && (kind[0] === 'v'); - this.use_separarators = !kind || (kind.indexOf('x') < 0); - this.simple_layout = false; - - const dom = this.selectDom(); - dom.style('overflow', 'hidden'); - - if (kind === 'simple') { - this.simple_layout = true; - this.use_separarators = false; - this.framecnt = 1; - return; - } - - let num = 2, arr, sizes, chld_sizes; - - if (kind === 'projxy') { - this.vertical = false; - this.use_separarators = true; - arr = [2, 2]; - sizes = [1, 3]; - chld_sizes = [[3, 1], [3, 1]]; - kind = ''; - this.match_sizes = true; - } else if ((kind.indexOf('grid') === 0) || kind2) { - if (kind2) - kind = kind + 'x' + kind2; - else - kind = kind.slice(4).trim(); - this.use_separarators = false; - if (kind[0] === 'i') { - this.use_separarators = true; - kind = kind.slice(1); - } - - const separ = kind.indexOf('x'); - let sizex, sizey; - - if (separ > 0) { - sizey = parseInt(kind.slice(separ + 1)); - sizex = parseInt(kind.slice(0, separ)); - } else - sizex = sizey = parseInt(kind); - - if (!Number.isInteger(sizex)) - sizex = 3; - if (!Number.isInteger(sizey)) - sizey = 3; - - if (sizey > 1) { - this.vertical = true; - num = sizey; - if (sizex > 1) - arr = new Array(num).fill(sizex); - } else if (sizex > 1) { - this.vertical = false; - num = sizex; - } else { - this.simple_layout = true; - this.use_separarators = false; - this.framecnt = 1; - return; - } - kind = ''; - } - - if (kind && kind.indexOf('_') > 0) { - let arg = parseInt(kind.slice(kind.indexOf('_') + 1), 10); - if (Number.isInteger(arg) && (arg > 10)) { - kind = kind.slice(0, kind.indexOf('_')); - sizes = []; - while (arg > 0) { - sizes.unshift(Math.max(arg % 10, 1)); - arg = Math.round((arg - sizes[0]) / 10); - if (sizes[0] === 0) - sizes[0] = 1; - } - } - } - - kind = kind ? parseInt(kind.replace(/^\D+/g, ''), 10) : 0; - if (Number.isInteger(kind) && (kind > 1)) { - if (kind < 10) - num = kind; - else { - arr = []; - while (kind > 0) { - arr.unshift(kind % 10); - kind = Math.round((kind - arr[0]) / 10); - if (arr[0] === 0) - arr[0] = 1; - } - num = arr.length; - } - } - - if (sizes?.length !== num) - sizes = undefined; - if (chld_sizes?.length !== num) - chld_sizes = undefined; - - if (!this.simple_layout) - this.createGroup(this, dom, num, arr, sizes, chld_sizes); - } - - /** @summary Create frames group - * @private */ - createGroup(handle, main, num, childs, sizes, childs_sizes) { - if (!sizes) - sizes = new Array(num); - let sum1 = 0, sum2 = 0; - for (let n = 0; n < num; ++n) - sum1 += (sizes[n] || 1); - for (let n = 0; n < num; ++n) { - sizes[n] = Math.round(100 * (sizes[n] || 1) / sum1); - sum2 += sizes[n]; - if (n === num - 1) - sizes[n] += (100 - sum2); // make 100% - } - - for (let cnt = 0; cnt < num; ++cnt) { - const group = { id: cnt, drawid: -1, position: 0, size: sizes[cnt], parent: handle }; - if (cnt > 0) - group.position = handle.groups[cnt - 1].position + handle.groups[cnt - 1].size; - group.position0 = group.position; - - if (!childs || !childs[cnt] || childs[cnt] < 2) - group.drawid = this.framecnt++; - - handle.groups.push(group); - - const elem = main.append('div').attr('groupid', group.id); - - // remember HTML node only when need to match sizes of different groups - if (handle.match_sizes) - group.node = elem.node(); - - if (handle.vertical) - elem.style('height', group.size.toFixed(2) + '%').style('width', '100%'); - else - elem.style('float', 'left').style('width', group.size.toFixed(2) + '%').style('height', '100%'); - - if (group.drawid >= 0) { - elem.classed('jsroot_newgrid', true); - if (isStr(this.frameid)) - elem.attr('id', `${this.frameid}_${group.drawid}`); - } else - elem.style('display', 'flex').style('flex-direction', handle.vertical ? 'row' : 'column'); - - if (childs && (childs[cnt] > 1)) { - group.vertical = !handle.vertical; - group.groups = []; - elem.style('overflow', 'hidden'); - this.createGroup(group, elem, childs[cnt], null, childs_sizes ? childs_sizes[cnt] : null); - } - } - - if (this.use_separarators && isFunc(this.createSeparator)) { - for (let cnt = 1; cnt < num; ++cnt) - this.createSeparator(handle, main, handle.groups[cnt]); - } - } - - /** @summary Handle interactive separator movement - * @private */ - handleSeparator(elem, action) { - const findGroup = (node, grid) => { - let chld = node?.firstChild; - while (chld) { - if (chld.getAttribute('groupid') === grid) - return select(chld); - chld = chld.nextSibling; - } - // should never happen, but keep it here like - return select(node).select(`[groupid='${grid}']`); - }, setGroupSize = (h, node, grid) => { - const name = h.vertical ? 'height' : 'width', - size = h.groups[grid].size.toFixed(2) + '%'; - findGroup(node, grid).style(name, size) - .selectAll('.jsroot_separator').style(name, size); - }, resizeGroup = (node, grid) => { - let sel = findGroup(node, grid); - if (!sel.classed('jsroot_newgrid')) - sel = sel.select('.jsroot_newgrid'); - sel.each(function() { resize(this); }); - }, posSepar = (h, group, separ) => { - separ.style(h.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`); - }; - // eslint-disable-next-line one-var - const separ = select(elem), - parent = elem.parentNode, - handle = separ.property('handle'), - id = separ.property('separator_id'), - group = handle.groups[id]; - - if (action === 'start') { - group.startpos = group.position; - group.acc_drag = 0; - return; - } - - let needResize, needSetSize = false; - - if (action === 'end') { - if (Math.abs(group.startpos - group.position) < 0.5) - return; - needResize = true; - } else { - let pos; - if (action === 'restore') - pos = group.position0; - else if (handle.vertical) { - group.acc_drag += action.dy; - pos = group.startpos + ((group.acc_drag + 2) / parent.clientHeight) * 100; - } else { - group.acc_drag += action.dx; - pos = group.startpos + ((group.acc_drag + 2) / parent.clientWidth) * 100; - } - - const diff = group.position - pos; - if ((Math.abs(diff) < 0.3) || (Math.min(handle.groups[id - 1].size - diff, group.size + diff) < 3)) - return; // if no significant change, do nothing - - handle.groups[id - 1].size -= diff; - group.size += diff; - group.position = pos; - - posSepar(handle, group, separ); - - needSetSize = true; - needResize = (action === 'restore'); - } - - if (needSetSize) { - setGroupSize(handle, parent, id - 1); - setGroupSize(handle, parent, id); - } - - if (needResize) { - resizeGroup(parent, id - 1); - resizeGroup(parent, id); - } - - // now handling match of the sizes - if (!handle.parent?.match_sizes) - return; - - for (let k = 0; k < handle.parent.groups.length; ++k) { - const hh = handle.parent.groups[k]; - if ((hh === handle) || !hh.node) - continue; - hh.groups[id].size = handle.groups[id].size; - hh.groups[id].position = handle.groups[id].position; - hh.groups[id - 1].size = handle.groups[id - 1].size; - hh.groups[id - 1].position = handle.groups[id - 1].position; - if (needSetSize) { - select(hh.node).selectAll('.jsroot_separator').each(function() { - const s = select(this); - if (s.property('separator_id') === id) - posSepar(hh, hh.groups[id], s); - }); - setGroupSize(hh, hh.node, id - 1); - setGroupSize(hh, hh.node, id); - } - if (needResize) { - resizeGroup(hh.node, id - 1); - resizeGroup(hh.node, id); - } - } - } - - /** @summary Create group separator - * @private */ - createSeparator(handle, main, group) { - const separ = main.append('div'); - - separ.classed('jsroot_separator', true) - .property('handle', handle) - .property('separator_id', group.id) - .attr('style', 'pointer-events: all; border: 0; margin: 0; padding: 0; position: absolute;') - .style(handle.vertical ? 'top' : 'left', `calc(${group.position.toFixed(2)}% - 2px)`) - .style(handle.vertical ? 'width' : 'height', (handle.size?.toFixed(2) || 100) + '%') - .style(handle.vertical ? 'height' : 'width', '5px') - .style('cursor', handle.vertical ? 'ns-resize' : 'ew-resize') - .append('div').attr('style', 'position: absolute;' + (handle.vertical - ? 'left: 0; right: 0; top: 50%; height: 3px; border-top: 1px dotted #ff0000' - : 'top: 0; bottom: 0; left: 50%; width: 3px; border-left: 1px dotted #ff0000')); - - const pthis = this, drag_move = - drag().on('start', function() { pthis.handleSeparator(this, 'start'); }) - .on('drag', function(evnt) { pthis.handleSeparator(this, evnt); }) - .on('end', function() { pthis.handleSeparator(this, 'end'); }); - - separ.call(drag_move).on('dblclick', function() { pthis.handleSeparator(this, 'restore'); }); - - // need to get touches events handling in drag - if (browser.touches && !main.on('touchmove')) - main.on('touchmove', () => {}); - } - - /** @summary Call function for each frame */ - forEachFrame(userfunc) { - if (this.simple_layout) - userfunc(this.getGridFrame()); - else - this.selectDom().selectAll('.jsroot_newgrid').each(function() { userfunc(this); }); - } - - /** @summary Returns active frame */ - getActiveFrame() { - if (this.simple_layout) - return this.getGridFrame(); - - let found = super.getActiveFrame(); - this.forEachFrame(frame => { - if (!found) - found = frame; - }); - - return found; - } - - /** @summary Returns number of frames in grid layout */ - numGridFrames() { return this.framecnt; } - - /** @summary Return grid frame by its id */ - getGridFrame(id) { - if (this.simple_layout) - return this.selectDom('origin').node(); - let res = null; - this.selectDom().selectAll('.jsroot_newgrid').each(function() { - if (id-- === 0) - res = this; - }); - return res; - } - - /** @summary Create new frame */ - createFrame(title) { - this.beforeCreateFrame(title); - - let frame = null, maxloop = this.framecnt || 2; - - while (!frame && maxloop--) { - frame = this.getGridFrame(this.getcnt); - if (!this.simple_layout && this.framecnt) - this.getcnt = (this.getcnt + 1) % this.framecnt; - - if (select(frame).classed('jsroot_fixed_frame')) - frame = null; - } - - if (frame) { - this.cleanupFrame(frame); - select(frame).attr('frame_title', title); - } - - return this.afterCreateFrame(frame); - } - -} // class GridDisplay - - -// ================================================ - -/** - * @summary Tabs-based display - * - * @private - */ - -class TabsDisplay extends MDIDisplay { - - constructor(frameid) { - super(frameid); - this.cnt = 0; // use to count newly created frames - this.selectDom().style('overflow', 'hidden'); - } - - /** @summary Cleanup all drawings */ - cleanup() { - this.selectDom().style('overflow', null); - this.cnt = 0; - super.cleanup(); - } - - /** @summary call function for each frame */ - forEachFrame(userfunc, only_visible) { - if (!isFunc(userfunc)) - return; - - if (only_visible) { - const active = this.getActiveFrame(); - if (active) - userfunc(active); - return; - } - - const main = this.selectDom().select('.jsroot_tabs_main'); - - main.selectAll('.jsroot_tabs_draw').each(function() { - userfunc(this); - }); - } - - /** @summary modify tab state by id */ - modifyTabsFrame(frame_id, action) { - const top = this.selectDom().select('.jsroot_tabs'), - labels = top.select('.jsroot_tabs_labels'), - main = top.select('.jsroot_tabs_main'); - - labels.selectAll('.jsroot_tabs_label').each(function() { - const id = select(this).property('frame_id'), - is_same = (id === frame_id), - active_color = settings.DarkMode ? '#333' : 'white'; - - if (action === 'activate') { - select(this).style('background', is_same ? active_color : (settings.DarkMode ? 'black' : '#ddd')) - .style('color', settings.DarkMode ? '#ddd' : 'inherit') - .style('border-color', active_color); - } else if ((action === 'close') && is_same) - this.parentNode.remove(); - }); - - let selected_frame, other_frame; - - main.selectAll('.jsroot_tabs_draw').each(function() { - const match = select(this).property('frame_id') === frame_id; - if (match) - selected_frame = this; - else - other_frame = this; - if (action === 'activate') - select(this).style('background', settings.DarkMode ? 'black' : 'white'); - }); - - if (!selected_frame) - return; - - if (action === 'activate') - selected_frame.parentNode.appendChild(selected_frame); - // super.activateFrame(selected_frame); - else if (action === 'close') { - const was_active = (selected_frame === this.getActiveFrame()); - cleanup(selected_frame); - selected_frame.remove(); - - if (was_active) - this.activateFrame(other_frame); - } - } - - /** @summary activate frame */ - activateFrame(frame) { - if (frame) - this.modifyTabsFrame(select(frame).property('frame_id'), 'activate'); - super.activateFrame(frame); - } - - /** @summary create new frame */ - createFrame(title) { - this.beforeCreateFrame(title); - - const dom = this.selectDom(); - let top = dom.select('.jsroot_tabs'), labels, main; - - if (top.empty()) { - top = dom.append('div').attr('class', 'jsroot_tabs') - .attr('style', 'display: flex; flex-direction: column; position: absolute; overflow: hidden; left: 0px; top: 0px; bottom: 0px; right: 0px;'); - labels = top.append('div').attr('class', 'jsroot_tabs_labels') - .attr('style', 'white-space: nowrap; position: relative; overflow-x: auto'); - main = top.append('div').attr('class', 'jsroot_tabs_main') - .attr('style', 'margin: 0; flex: 1 1 0%; position: relative'); - } else { - labels = top.select('.jsroot_tabs_labels'); - main = top.select('.jsroot_tabs_main'); - } - - const frame_id = this.cnt++, mdi = this; - let lbl = title; - - if (!lbl || !isStr(lbl)) - lbl = `frame_${frame_id}`; - - if (lbl.length > 15) { - let p = lbl.lastIndexOf('/'); - if (p === lbl.length - 1) - p = lbl.lastIndexOf('/', p - 1); - if ((p > 0) && (lbl.length - p < 20) && (lbl.length - p > 1)) - lbl = lbl.slice(p + 1); - else - lbl = '...' + lbl.slice(lbl.length - 17); - } - - labels.append('span') - .attr('tabindex', 0) - .append('label') - .attr('class', 'jsroot_tabs_label') - .attr('style', 'border: 1px solid; display: inline-block; font-size: 1rem; left: 1px;' + - 'margin-left: 3px; padding: 0px 5px 1px 5px; position: relative; vertical-align: bottom;') - .property('frame_id', frame_id) - .text(lbl) - .attr('title', title) - .on('click', function(evnt) { - evnt.preventDefault(); // prevent handling in close button - mdi.modifyTabsFrame(select(this).property('frame_id'), 'activate'); - }).append('button') - .attr('title', 'close') - .attr('style', 'margin-left: .5em; padding: 0; font-size: 0.5em; width: 1.8em; height: 1.8em; vertical-align: center;') - .text('\u2715') - .on('click', function() { - mdi.modifyTabsFrame(select(this.parentNode).property('frame_id'), 'close'); - }); - - const draw_frame = main.append('div') - .attr('frame_title', title) - .attr('class', 'jsroot_tabs_draw') - .attr('style', 'overflow: hidden; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px;') - .property('frame_id', frame_id); - - this.modifyTabsFrame(frame_id, 'activate'); - - return this.afterCreateFrame(draw_frame.node()); - } - - /** @summary Handle changes in dark mode */ - changeDarkMode() { - const frame = this.getActiveFrame(); - this.modifyTabsFrame(select(frame).property('frame_id'), 'activate'); - } - -} // class TabsDisplay - - -/** - * @summary Generic flexible MDI display - * - * @private - */ - -class FlexibleDisplay extends MDIDisplay { - - constructor(frameid) { - super(frameid); - this.cnt = 0; // use to count newly created frames - this.selectDom().on('contextmenu', evnt => this.showContextMenu(evnt)) - .style('overflow', 'auto'); - } - - /** @summary Cleanup all drawings */ - cleanup() { - this.selectDom().style('overflow', null) - .on('contextmenu', null); - this.cnt = 0; - super.cleanup(); - } - - /** @summary call function for each frame */ - forEachFrame(userfunc, only_visible) { - if (!isFunc(userfunc)) - return; - - const mdi = this, top = this.selectDom().select('.jsroot_flex_top'); - - top.selectAll('.jsroot_flex_draw').each(function() { - // check if only visible specified - if (only_visible && (mdi.getFrameState(this) === 'min')) - return; - - userfunc(this); - }); - } - - /** @summary return active frame */ - getActiveFrame() { - let found = super.getActiveFrame(); - if (found && select(found.parentNode).property('state') !== 'min') - return found; - - found = null; - this.forEachFrame(frame => { found = frame; }, true); - return found; - } - - /** @summary activate frame */ - activateFrame(frame) { - if ((frame === 'first') || (frame === 'last')) { - let res = null; - this.forEachFrame(f => { - if (frame === 'last' || !res) - res = f; - }, true); - frame = res; - } - if ((frame?.getAttribute('class') !== 'jsroot_flex_draw') || (this.getActiveFrame() === frame)) - return; - - super.activateFrame(frame); - - const main = frame.parentNode; - main.parentNode.append(main); - - if (this.getFrameState(frame) !== 'min') { - selectActivePad({ pp: getElementCanvPainter(frame), active: true }); - resize(frame); - } - } - - /** @summary get frame state */ - getFrameState(frame) { - const main = select(frame.parentNode); - return main.property('state'); - } - - /** @summary returns frame rect */ - getFrameRect(frame) { - if (this.getFrameState(frame) === 'max') { - const top = this.selectDom().select('.jsroot_flex_top'); - return { x: 0, y: 0, w: top.node().clientWidth, h: top.node().clientHeight }; - } - - const main = select(frame.parentNode), left = main.style('left'), top = main.style('top'); - return { - x: parseInt(left.slice(0, left.length - 2)), y: parseInt(top.slice(0, top.length - 2)), - w: main.node().clientWidth, h: main.node().clientHeight - }; - } - - /** @summary change frame state */ - changeFrameState(frame, newstate, no_redraw) { - const main = select(frame.parentNode), - state = main.property('state'), - top = this.selectDom().select('.jsroot_flex_top'); - - if (state === newstate) - return false; - - if (state === 'normal') - main.property('original_style', main.attr('style')); - - // clear any previous settings - top.style('overflow', null); - - switch (newstate) { - case 'min': - main.style('height', 'auto').style('width', 'auto'); - main.select('.jsroot_flex_draw').style('display', 'none'); - break; - case 'max': - main.style('height', '100%').style('width', '100%').style('left', '').style('top', ''); - main.select('.jsroot_flex_draw').style('display', null); - top.style('overflow', 'hidden'); - break; - default: - main.select('.jsroot_flex_draw').style('display', null); - main.attr('style', main.property('original_style')); - } - - main.select('.jsroot_flex_header').selectAll('button').each(function(d) { - const btn = select(this); - if (((d.t === 'minimize') && (newstate === 'min')) || - ((d.t === 'maximize') && (newstate === 'max'))) - btn.text('\u259E').attr('title', 'restore'); - else - btn.text(d.n).attr('title', d.t); - }); - - main.property('state', newstate); - main.select('.jsroot_flex_resize').style('display', (newstate === 'normal') ? null : 'none'); - - // adjust position of new minified rect - if (newstate === 'min') { - const rect = this.getFrameRect(frame), - ww = top.node().clientWidth, - hh = top.node().clientHeight, - arr = [], step = 4, - crossX = (r1, r2) => ((r1.x <= r2.x) && (r1.x + r1.w >= r2.x)) || ((r2.x <= r1.x) && (r2.x + r2.w >= r1.x)), - crossY = (r1, r2) => ((r1.y <= r2.y) && (r1.y + r1.h >= r2.y)) || ((r2.y <= r1.y) && (r2.y + r2.h >= r1.y)); - - this.forEachFrame(f => { - if ((f !== frame) && (this.getFrameState(f) === 'min')) - arr.push(this.getFrameRect(f)); - }); - - rect.y = hh; - do { - rect.x = step; - rect.y -= rect.h + step; - let maxx = step, iscrossed = false; - arr.forEach(r => { - if (crossY(r, rect)) { - maxx = Math.max(maxx, r.x + r.w + step); - if (crossX(r, rect)) - iscrossed = true; - } - }); - if (iscrossed) - rect.x = maxx; - } while ((rect.x + rect.w > ww - step) && (rect.y > 0)); - if (rect.y < 0) { - rect.x = step; - rect.y = hh - rect.h - step; - } - - main.style('left', rect.x + 'px').style('top', rect.y + 'px'); - } else if (!no_redraw) - resize(frame); - - return true; - } - - /** @summary handle button click - * @private */ - _clickButton(btn) { - const kind = select(btn).datum(), - main = select(btn.parentNode.parentNode), - frame = main.select('.jsroot_flex_draw').node(); - - if (kind.t === 'close') { - this.cleanupFrame(frame); - main.remove(); - this.activateFrame('last'); // set active as last non-minified window - return; - } - - const state = main.property('state'); - let newstate; - if (kind.t === 'maximize') - newstate = (state === 'max') ? 'normal' : 'max'; - else - newstate = (state === 'min') ? 'normal' : 'min'; - - if (this.changeFrameState(frame, newstate)) - this.activateFrame(newstate !== 'min' ? frame : 'last'); - } - - /** @summary create new frame */ - createFrame(title) { - this.beforeCreateFrame(title); - - const mdi = this, - dom = this.selectDom(); - let top = dom.select('.jsroot_flex_top'); - - if (top.empty()) { - top = dom.append('div') - .attr('class', 'jsroot_flex_top') - .attr('style', 'overflow: auto; position: relative; height: 100%; width: 100%'); - } - - const w = top.node().clientWidth, - h = top.node().clientHeight, - main = top.append('div'); - - main.html('
' + - '

' + - `
` + - '
◿
'); - - main.select('.jsroot_flex_header p').text(title); - - main.attr('class', 'jsroot_flex_frame') - .style('position', 'absolute') - .style('left', Math.round(w * (this.cnt % 5) / 10) + 'px') - .style('top', Math.round(h * (this.cnt % 5) / 10) + 'px') - .style('width', Math.round(w * 0.58) + 'px') - .style('height', Math.round(h * 0.58) + 'px') - .style('border', '1px solid black') - .style('box-shadow', '1px 1px 2px 2px #aaa') - .property('state', 'normal') - .select('.jsroot_flex_header') - .on('contextmenu', evnt => mdi.showContextMenu(evnt, true)) - .on('click', function() { mdi.activateFrame(select(this.parentNode).select('.jsroot_flex_draw').node()); }) - .selectAll('button') - .data([{ n: '\u2715', t: 'close' }, { n: '\u2594', t: 'maximize' }, { n: '\u2581', t: 'minimize' }]) - .enter() - .append('button') - .attr('type', 'button') - .attr('style', 'float: right; padding: 0; width: 1.4em; text-align: center; font-size: 10px; margin-top: 2px; margin-right: 4px') - .attr('title', d => d.t) - .text(d => d.n) - .on('click', function() { mdi._clickButton(this); }); - - let moving_frame = null, moving_div = null, doing_move = false, current = []; - const drag_object = drag().subject(Object); - drag_object.on('start', function(evnt) { - if (evnt.sourceEvent.target.type === 'button') - return mdi._clickButton(evnt.sourceEvent.target); - - if (detectRightButton(evnt.sourceEvent)) - return; - - const mframe = select(this.parentNode); - if (!mframe.classed('jsroot_flex_frame') || (mframe.property('state') === 'max')) - return; - - doing_move = !select(this).classed('jsroot_flex_resize'); - if (!doing_move && (mframe.property('state') === 'min')) - return; - - mdi.activateFrame(mframe.select('.jsroot_flex_draw').node()); - - moving_div = top.append('div').attr('style', mframe.attr('style')).style('border', '2px dotted #00F'); - - if (mframe.property('state') === 'min') { - moving_div.style('width', mframe.node().clientWidth + 'px') - .style('height', mframe.node().clientHeight + 'px'); - } - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - moving_frame = mframe; - current = []; - }).on('drag', evnt => { - if (!moving_div) - return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - const changeProp = (i, name, dd) => { - if (i >= current.length) { - const v = moving_div.style(name); - current[i] = parseInt(v.slice(0, v.length - 2)); - } - current[i] += dd; - moving_div.style(name, Math.max(0, current[i]) + 'px'); - }; - if (doing_move) { - changeProp(0, 'left', evnt.dx); - changeProp(1, 'top', evnt.dy); - } else { - changeProp(0, 'width', evnt.dx); - changeProp(1, 'height', evnt.dy); - } - }).on('end', evnt => { - if (!moving_div) - return; - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - if (doing_move) { - moving_frame.style('left', moving_div.style('left')); - moving_frame.style('top', moving_div.style('top')); - } else { - moving_frame.style('width', moving_div.style('width')); - moving_frame.style('height', moving_div.style('height')); - } - moving_div.remove(); - moving_div = null; - if (!doing_move) - resize(moving_frame.select('.jsroot_flex_draw').node()); - }); - - main.select('.jsroot_flex_header').call(drag_object); - main.select('.jsroot_flex_resize').call(drag_object); - - const draw_frame = main.select('.jsroot_flex_draw') - .attr('frame_title', title) - .property('frame_cnt', this.cnt++) - .node(); - - return this.afterCreateFrame(draw_frame); - } - - /** @summary minimize all frames */ - minimizeAll() { - this.forEachFrame(frame => this.changeFrameState(frame, 'min')); - } - - /** @summary show all frames which are minimized */ - showAll() { - this.forEachFrame(frame => { - if (this.getFrameState(frame) === 'min') - this.changeFrameState(frame, 'normal'); - }); - } - - /** @summary close all frames */ - closeAllFrames() { - const arr = []; - this.forEachFrame(frame => arr.push(frame)); - arr.forEach(frame => { - this.cleanupFrame(frame); - select(frame.parentNode).remove(); - }); - } - - /** @summary cascade frames */ - sortFrames(kind) { - const arr = []; - this.forEachFrame(frame => { - const state = this.getFrameState(frame); - if (state === 'min') - return; - if (state === 'max') - this.changeFrameState(frame, 'normal', true); - arr.push(frame); - }); - - if (!arr.length) - return; - - const top = this.selectDom(), - w = top.node().clientWidth, - h = top.node().clientHeight, - dx = Math.min(40, Math.round(w * 0.4 / arr.length)), - dy = Math.min(40, Math.round(h * 0.4 / arr.length)); - let nx = Math.ceil(Math.sqrt(arr.length)), ny = nx; - - // calculate number of divisions for 'tile' sorting - if ((nx > 1) && (nx * (nx - 1) >= arr.length)) { - if (w > h) - ny--; - else - nx--; - } - - arr.forEach((frame, i) => { - const main = select(frame.parentNode); - if (kind === 'cascade') { - main.style('left', (i * dx) + 'px') - .style('top', (i * dy) + 'px') - .style('width', Math.round(w * 0.58) + 'px') - .style('height', Math.round(h * 0.58) + 'px'); - } else { - main.style('left', Math.round(w / nx * (i % nx)) + 'px') - .style('top', Math.round(h / ny * ((i - i % nx) / nx)) + 'px') - .style('width', Math.round(w / nx - 4) + 'px') - .style('height', Math.round(h / ny - 4) + 'px'); - } - resize(frame); - }); - } - - /** @summary context menu */ - showContextMenu(evnt, is_header) { - // no context menu for no windows - if (this.numDraw() === 0) - return; - // handle context menu only for MDI area or for window header - if (!is_header && evnt.target.getAttribute('class') !== 'jsroot_flex_top') - return; - - evnt.preventDefault(); - - const arr = []; - let nummin = 0; - this.forEachFrame(f => { - arr.push(f); - if (this.getFrameState(f) === 'min') - nummin++; - }); - const active = this.getActiveFrame(); - - arr.sort((f1, f2) => (select(f1).property('frame_cnt') < select(f2).property('frame_cnt') ? -1 : 1)); - - createMenu(evnt, this).then(menu => { - menu.header('Flex'); - menu.add('Cascade', () => this.sortFrames('cascade'), 'Cascade frames'); - menu.add('Tile', () => this.sortFrames('tile'), 'Tile all frames'); - if (nummin < arr.length) - menu.add('Minimize all', () => this.minimizeAll(), 'Minimize all frames'); - if (nummin > 0) - menu.add('Show all', () => this.showAll(), 'Restore minimized frames'); - menu.add('Close all', () => this.closeAllFrames()); - menu.separator(); - - arr.forEach((f, i) => { - menu.addchk((f === active), ((this.getFrameState(f) === 'min') ? '[min] ' : '') + select(f).attr('frame_title'), i, arg => { - const frame = arr[arg]; - if (this.getFrameState(frame) === 'min') - this.changeFrameState(frame, 'normal'); - this.activateFrame(frame); - }); - }); - - menu.show(); - }); - } - -} // class FlexibleDisplay - - -/** - * @summary Batch MDI display - * - * @desc Can be used together with hierarchy painter in node.js - * @private - */ - -class BatchDisplay extends MDIDisplay { - - constructor(width, height, jsdom_body) { - super('$batch$'); - this.frames = []; // array of configured frames - this.width = width || settings.CanvasWidth; - this.height = height || settings.CanvasHeight; - this.jsdom_body = jsdom_body || select('body'); // d3 body handle - } - - /** @summary Call function for each frame */ - forEachFrame(userfunc) { - this.frames.forEach(userfunc); - } - - /** @summary Create batch frame */ - createFrame(title) { - this.beforeCreateFrame(title); - - const frame = - this.jsdom_body.append('div') - .style('visibility', 'hidden') - .attr('width', this.width).attr('height', this.height) - .style('width', this.width + 'px').style('height', this.height + 'px') - .attr('id', 'jsroot_batch_' + this.frames.length) - .attr('frame_title', title); - - this.frames.push(frame.node()); - - return this.afterCreateFrame(frame.node()); - } - - /** @summary Create final frame */ - createFinalBatchFrame() { - const cnt = this.numFrames(), prs = []; - - for (let n = 0; n < cnt; ++n) { - const json = this.makeJSON(n, 1, true); - if (json) - select(this.frames[n]).text('json:' + btoa_func(json)); - else - prs.push(this.makeSVG(n, true)); - } - - return Promise.all(prs).then(() => { - this.jsdom_body.append('div') - .attr('id', 'jsroot_batch_final') - .text(`${cnt}`); - }); - } - - /** @summary Returns number of created frames */ - numFrames() { return this.frames.length; } - - /** @summary returns JSON representation if any - * @desc Now works only for inspector, can be called once */ - makeJSON(id, spacing, keep_frame) { - const frame = this.frames[id]; - if (!frame) - return; - const obj = select(frame).property('_json_object_'); - if (obj) { - select(frame).property('_json_object_', null); - cleanup(frame); - if (!keep_frame) - select(frame).remove(); - return toJSON(obj, spacing); - } - } - - /** @summary Create SVG for specified frame id - used in testing */ - makeSVG(id, keep_frame) { - const frame = this.frames[id]; - if (!frame) - return; - const main = select(frame), - mainsvg = main.select('svg'); - if (mainsvg.empty()) - return; - - const style_filter = mainsvg.style('filter'); - - mainsvg.attr('xmlns', nsSVG) - .attr('title', null).attr('style', null).attr('class', null).attr('x', null).attr('y', null); - - if (!mainsvg.attr('width') && !mainsvg.attr('height')) - mainsvg.attr('width', this.width).attr('height', this.height); - - if (style_filter && (style_filter !== 'none')) - mainsvg.style('filter', style_filter); - - function clear_element() { - const elem = select(this); - if (elem.style('display') === 'none') - elem.remove(); - } - - main.selectAll('g.root_frame').each(clear_element); - main.selectAll('svg').each(clear_element); - - if (internals.batch_png) { - return svgToImage(compressSVG(main.html()), 'png').then(href => { - select(this.frames[id]).text('png:' + href); - }); - } - - if (keep_frame) - return true; - - const svg = compressSVG(main.html()); - - cleanup(frame); - main.remove(); - return svg; - } - -} // class BatchDisplay - - -/** - * @summary Special browser layout - * - * @desc Contains three different areas for browser (left), status line (bottom) and central drawing - * Main application is normal browser, but also used in other applications like ROOT6 canvas - * @private - */ - -class BrowserLayout { - - #float_left; - #float_top; - #max_left; - #max_top; - #float_width; - #float_height; - #max_width; - #max_height; - #hsepar_position; - #vsepar_position; - #hsepar_move; - #vsepar_move; - - /** @summary Constructor */ - constructor(id, hpainter, objpainter) { - this.gui_div = id; - this.hpainter = hpainter; // painter for browser area (if any) - this.objpainter = objpainter; // painter for object area (if any) - this.browser_kind = null; // should be 'float' or 'fix' - } - - /** @summary Selects main element */ - main() { return select('#' + this.gui_div); } - - /** @summary Selects browser div */ - browser() { return this.main().select('.jsroot_browser'); } - - /** @summary Selects drawing div */ - drawing() { return select(`#${this.gui_div}_drawing`); } - - /** @summary Selects drawing div */ - status() { return select(`#${this.gui_div}_status`); } - - /** @summary Returns drawing divid */ - drawing_divid() { return this.gui_div + '_drawing'; } - - /** @summary Check resize action */ - checkResize() { - if (isFunc(this.hpainter?.checkResize)) - this.hpainter.checkResize(); - else if (isFunc(this.objpainter?.checkResize)) - this.objpainter.checkResize(true); - } - - /** @summary Create or update CSS style */ - createStyle() { - const bkgr_color = settings.DarkMode ? 'black' : '#E6E6FA', - title_color = settings.DarkMode ? '#ccc' : 'inherit', - text_color = settings.DarkMode ? '#ddd' : 'inherit', - input_style = settings.DarkMode ? `background-color: #222; color: ${text_color}` : ''; - - injectStyle(` -.jsroot_browser { pointer-events: none; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; margin: 0px; border: 0px; overflow: hidden; } -.jsroot_draw_area { background-color: ${bkgr_color}; overflow: hidden; margin: 0px; border: 0px; } -.jsroot_browser_area { color: ${text_color}; background-color: ${bkgr_color}; font-size: 12px; font-family: Verdana; pointer-events: all; box-sizing: initial; } -.jsroot_browser_area input { ${input_style} } -.jsroot_browser_area select { ${input_style} } -.jsroot_browser_title { font-family: Verdana; font-size: 20px; color: ${title_color}; } -.jsroot_browser_btns { pointer-events: all; display: flex; flex-direction: column; } -.jsroot_browser_area p { margin-top: 5px; margin-bottom: 5px; white-space: nowrap; } -.jsroot_browser_hierarchy { flex: 1; margin-top: 2px; } -.jsroot_status_area { background-color: ${bkgr_color}; overflow: hidden; font-size: 12px; font-family: Verdana; pointer-events: all; } -.jsroot_browser_resize { position: absolute; right: 3px; bottom: 3px; margin-bottom: 0px; margin-right: 0px; opacity: 0.5; cursor: se-resize; z-index: 1; } -`, this.main().node(), 'browser_layout_style'); - } - - /** @summary method used to create basic elements - * @desc should be called only once */ - create(with_browser) { - const main = this.main(); - - main.append('div').attr('id', this.drawing_divid()) - .classed('jsroot_draw_area', true) - .style('position', 'absolute') - .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - - if (with_browser) - main.append('div').classed('jsroot_browser', true); - - this.createStyle(); - } - - /** @summary Create buttons in the layout */ - createBrowserBtns() { - const br = this.browser(); - if (br.empty()) - return; - let btns = br.select('.jsroot_browser_btns'); - if (btns.empty()) { - btns = br.append('div') - .attr('class', 'jsroot jsroot_browser_btns') - .attr('style', 'position: absolute; left: 7px; top: 7px'); - } else - btns.html(''); - return btns; - } - - /** @summary Remove browser buttons */ - removeBrowserBtns() { - this.browser().select('.jsroot_browser_btns').remove(); - } - - /** @summary Set browser content */ - setBrowserContent(guiCode) { - const main = this.browser(); - if (main.empty()) - return; - - main.insert('div', '.jsroot_browser_btns').classed('jsroot_browser_area', true) - .style('position', 'absolute').style('left', '0px').style('top', '0px').style('bottom', '0px').style('width', '250px') - .style('overflow', 'hidden') - .style('padding-left', '5px') - .style('display', 'flex').style('flex-direction', 'column') /* use the flex model */ - .html(`

title

${guiCode}`); - } - - /** @summary Check if there is browser content */ - hasContent() { - const main = this.browser(); - return main.empty() ? false : !main.select('.jsroot_browser_area').empty(); - } - - /** @summary Delete content */ - deleteContent(keep_status) { - const main = this.browser(); - if (main.empty()) - return; - - if (!keep_status) - this.createStatusLine(0, 'delete'); - - this.toggleBrowserVisibility(true); - - if (keep_status) { - // try to delete only content, not status - main.select('.jsroot_browser_area').remove(); - main.select('.jsroot_browser_btns').remove(); - main.select('.jsroot_v_separator').remove(); - } else - main.selectAll('*').remove(); - - delete this.browser_visible; - delete this.browser_kind; - - this.checkResize(); - } - - /** @summary Returns true when status line exists */ - hasStatus() { - const main = this.browser(); - return main.empty() ? false : !this.status().empty(); - } - - /** @summary Set browser title text - * @desc Title also used for dragging of the float browser */ - setBrowserTitle(title) { - const main = this.browser(), - elem = !main.empty() ? main.select('.jsroot_browser_title') : null; - if (elem) - elem.text(title).style('cursor', this.browser_kind === kBrFloat ? 'move' : null); - return elem; - } - - /** @summary Toggle browser kind - * @desc used together with browser buttons */ - toggleKind(browser_kind) { - if (this.browser_visible !== 'changing') { - if (browser_kind === this.browser_kind) - this.toggleBrowserVisibility(); - else - this.toggleBrowserKind(browser_kind); - } - } - - /** @summary Creates status line */ - async createStatusLine(height, mode) { - const main = this.browser(); - if (main.empty()) - return ''; - - const id = this.gui_div + '_status', - line = select('#' + id), - is_visible = !line.empty(); - - if (mode === 'toggle') - mode = !is_visible; - else if (mode === 'delete') { - mode = false; - height = 0; - delete this.status_layout; - } else if (mode === undefined) { - mode = true; - this.status_layout = 'app'; - } - - if (is_visible) { - if (mode === true) - return id; - - const hsepar = main.select('.jsroot_h_separator'); - - hsepar.remove(); - line.remove(); - - if (this.status_layout !== 'app') - delete this.status_layout; - - if (this.status_handler && (internals.showStatus === this.status_handler)) { - delete internals.showStatus; - delete this.status_handler; - } - - this.adjustSeparators(null, 0, true); - return ''; - } - - if (mode === false) - return ''; - - const left_pos = this.drawing().style('left'); - - main.insert('div', '.jsroot_browser_area') - .attr('id', id) - .classed('jsroot_status_area', true) - .style('position', 'absolute').style('left', left_pos).style('height', '20px').style('bottom', '0px').style('right', '0px') - .style('margin', 0).style('border', 0); - - const separ_color = settings.DarkMode ? 'grey' : 'azure', - hsepar = main.insert('div', '.jsroot_browser_area') - .classed('jsroot_h_separator', true) - .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; left: ${left_pos}; right: 0; bottom: 20px; height: 5px; cursor: ns-resize;`), - - drag_move = drag().on('start', () => { - this.#hsepar_move = this.#hsepar_position; - hsepar.style('background-color', 'grey'); - }).on('drag', evnt => { - this.#hsepar_move -= evnt.dy; // hsepar is position from bottom - this.adjustSeparators(null, Math.max(5, Math.round(this.#hsepar_move))); - }).on('end', () => { - this.#hsepar_move = undefined; - hsepar.style('background-color', null); - this.checkResize(); - }); - - hsepar.call(drag_move); - - // need to get touches events handling in drag - if (browser.touches && !main.on('touchmove')) - main.on('touchmove', () => {}); - - if (!height || isStr(height)) - height = this.last_hsepar_height || 20; - - this.adjustSeparators(null, height, true); - - if (this.status_layout === 'app') - return id; - - this.status_layout = new GridDisplay(id, 'horizx4_1231'); - - const frame_titles = ['object name', 'object title', 'mouse coordinates', 'object info']; - for (let k = 0; k < 4; ++k) { - select(this.status_layout.getGridFrame(k)) - .attr('title', frame_titles[k]).style('overflow', 'hidden').style('display', 'flex').style('align-items', 'center') - .append('label').attr('style', 'margin: 5px 5px 5px 3px; font-size: 14px; white-space: nowrap;'); - } - - internals.showStatus = this.status_handler = this.showStatus.bind(this); - - return id; - } - - /** @summary Adjust separator positions */ - adjustSeparators(vsepar, hsepar, redraw, first_time) { - if (!this.gui_div) - return; - - const main = this.browser(), w = 5; - - if ((hsepar === null) && first_time && !main.select('.jsroot_h_separator').empty()) { - // if separator set for the first time, check if status line present - hsepar = main.select('.jsroot_h_separator').style('bottom'); - if (isStr(hsepar) && (hsepar.length > 2) && (hsepar.indexOf('px') === hsepar.length - 2)) - hsepar = hsepar.slice(0, hsepar.length - 2); - else - hsepar = null; - } - - if (hsepar !== null) { - hsepar = parseInt(hsepar); - const elem = main.select('.jsroot_h_separator'); - let hlimit = 0; - - if (!elem.empty()) { - if (hsepar < 5) - hsepar = 5; - - const maxh = main.node().clientHeight - w; - if (maxh > 0) { - if (hsepar < 0) - hsepar += maxh; - if (hsepar > maxh) - hsepar = maxh; - } - - this.last_hsepar_height = hsepar; - elem.style('bottom', hsepar + 'px').style('height', w + 'px'); - this.status().style('height', hsepar + 'px'); - hlimit = hsepar + w; - } - - this.#hsepar_position = hsepar; - - this.drawing().style('bottom', `${hlimit}px`); - } - - if (vsepar !== null) { - vsepar = Math.max(50, Number.parseInt(vsepar)); - this.#vsepar_position = vsepar; - main.select('.jsroot_browser_area').style('width', (vsepar - 5) + 'px'); - this.drawing().style('left', (vsepar + w) + 'px'); - main.select('.jsroot_h_separator').style('left', (vsepar + w) + 'px'); - this.status().style('left', (vsepar + w) + 'px'); - main.select('.jsroot_v_separator').style('left', vsepar + 'px').style('width', w + 'px'); - } - - if (redraw) - this.checkResize(); - } - - /** @summary Show status information inside special fields of browser layout */ - showStatus(...msgs) { - if (!isObject(this.status_layout) || !isFunc(this.status_layout.getGridFrame)) - return; - - let maxh = 0; - for (let n = 0; n < 4; ++n) { - const lbl = this.status_layout.getGridFrame(n).querySelector('label'); - maxh = Math.max(maxh, lbl.clientHeight); - lbl.innerText = msgs[n] || ''; - } - - if (!this.status_layout.first_check) { - this.status_layout.first_check = true; - if ((maxh > 5) && ((maxh > this.last_hsepar_height) || (maxh < this.last_hsepar_height + 5))) - this.adjustSeparators(null, maxh, true); - } - } - - /** @summary Toggle browser visibility */ - toggleBrowserVisibility(fast_close) { - if (!this.gui_div || isStr(this.browser_visible)) - return; - - const main = this.browser(), - area = main.select('.jsroot_browser_area'); - if (area.empty()) - return; - - const vsepar = main.select('.jsroot_v_separator'), - drawing = select(`#${this.gui_div}_drawing`); - let tgt = area.property('last_left'), - tgt_separ = area.property('last_vsepar'), - tgt_drawing = area.property('last_drawing'); - - if (!this.browser_visible) { - if (fast_close) - return; - area.property('last_left', null).property('last_vsepar', null).property('last_drawing', null); - } else { - area.property('last_left', area.style('left')); - if (!vsepar.empty()) { - area.property('last_vsepar', vsepar.style('left')); - area.property('last_drawing', drawing.style('left')); - } - - tgt = (-area.node().clientWidth - 10) + 'px'; - const mainw = main.node().clientWidth; - - if (vsepar.empty() && (area.node().offsetLeft > mainw / 2)) - tgt = (mainw + 10) + 'px'; - - tgt_separ = '-10px'; - tgt_drawing = '0px'; - } - - const visible_at_the_end = !this.browser_visible, _duration = fast_close ? 0 : 700; - - this.browser_visible = 'changing'; - - area.transition().style('left', tgt).duration(_duration).on('end', () => { - if (fast_close) - return; - this.browser_visible = visible_at_the_end; - if (visible_at_the_end) - this.setButtonsPosition(); - }); - - if (!visible_at_the_end) - main.select('.jsroot_browser_btns').transition().style('left', '7px').style('top', '7px').duration(_duration); - - if (!vsepar.empty()) { - vsepar.transition().style('left', tgt_separ).duration(_duration); - drawing.transition().style('left', tgt_drawing).duration(_duration).on('end', this.checkResize.bind(this)); - } - - if (this.status_layout && (this.browser_kind === kBrFix)) { - main.select('.jsroot_h_separator').transition().style('left', tgt_drawing).duration(_duration); - main.select('.jsroot_status_area').transition().style('left', tgt_drawing).duration(_duration); - } - } - - /** @summary Adjust browser size */ - adjustBrowserSize(onlycheckmax) { - if (!this.gui_div || (this.browser_kind !== kBrFloat)) - return; - - const main = this.browser(); - if (main.empty()) - return; - - const area = main.select('.jsroot_browser_area'), - cont = main.select('.jsroot_browser_hierarchy'), - chld = select(cont.node().firstChild); - - if (onlycheckmax) { - if (area.node().parentNode.clientHeight - 10 < area.node().clientHeight) - area.style('bottom', '0px').style('top', '0px'); - return; - } - - if (chld.empty()) - return; - - const h1 = cont.node().clientHeight, - h2 = chld.node().clientHeight; - if ((h2 !== undefined) && (h2 < h1 * 0.7)) - area.style('bottom', ''); - } - - /** @summary Set buttons position */ - setButtonsPosition() { - if (!this.gui_div) - return; - - const main = this.browser(), - btns = main.select('.jsroot_browser_btns'); - if (btns.empty()) - return; - - let top = 7, left = 7; - if (this.browser_visible) { - const area = main.select('.jsroot_browser_area'); - top = area.node().offsetTop + 7; - left = area.node().offsetLeft - main.node().offsetLeft + area.node().clientWidth - 27; - } - - btns.style('left', `${left}px`).style('top', `${top}px`); - } - - /** @summary Toggle browser kind */ - async toggleBrowserKind(kind) { - if (!this.gui_div) - return null; - - if (!kind) { - if (!this.browser_kind) - return null; - kind = (this.browser_kind === kBrFloat) ? kBrFix : kBrFloat; - } - - const main = this.browser(), - area = main.select('.jsroot_browser_area'); - - if (this.browser_kind === kBrFloat) { - area.style('bottom', '0px') - .style('top', '0px') - .style('width', '') - .style('height', '') - .classed('jsroot_float_browser', false) - .style('border', null); - } else if (this.browser_kind === kBrFix) { - main.select('.jsroot_v_separator').remove(); - area.style('left', '0px'); - this.drawing().style('left', '0px'); // reset size - main.select('.jsroot_h_separator').style('left', '0px'); - this.status().style('left', '0px'); // reset left - this.checkResize(); - } - - this.browser_kind = kind; - this.browser_visible = true; - - main.select('.jsroot_browser_resize').style('display', kind === kBrFloat ? null : 'none'); - main.select('.jsroot_browser_title').style('cursor', kind === kBrFloat ? 'move' : null); - - if (kind === kBrFloat) { - area.style('bottom', '40px') - .classed('jsroot_float_browser', true) - .style('border', 'solid 3px white'); - - const drag_move = drag().on('start', () => { - const sl = area.style('left'), st = area.style('top'); - this.#float_left = parseInt(sl.slice(0, sl.length - 2)); - this.#float_top = parseInt(st.slice(0, st.length - 2)); - this.#max_left = Math.max(0, main.node().clientWidth - area.node().offsetWidth - 1); - this.#max_top = Math.max(0, main.node().clientHeight - area.node().offsetHeight - 1); - }).filter(evnt => { - return main.select('.jsroot_browser_title').node() === evnt.target; - }).on('drag', evnt => { - this.#float_left += evnt.dx; - this.#float_top += evnt.dy; - area.style('left', Math.min(Math.max(0, this.#float_left), this.#max_left) + 'px') - .style('top', Math.min(Math.max(0, this.#float_top), this.#max_top) + 'px'); - this.setButtonsPosition(); - }); - - // eslint-disable-next-line one-var - const drag_resize = drag().on('start', () => { - const sw = area.style('width'); - this.#float_width = parseInt(sw.slice(0, sw.length - 2)); - this.#float_height = area.node().clientHeight; - this.#max_width = main.node().clientWidth - area.node().offsetLeft - 1; - this.#max_height = main.node().clientHeight - area.node().offsetTop - 1; - }).on('drag', evnt => { - this.#float_width += evnt.dx; - this.#float_height += evnt.dy; - - area.style('width', Math.min(Math.max(100, this.#float_width), this.#max_width) + 'px') - .style('height', Math.min(Math.max(100, this.#float_height), this.#max_height) + 'px'); - - this.setButtonsPosition(); - }); - - main.call(drag_move); - main.select('.jsroot_browser_resize').call(drag_resize); - - this.adjustBrowserSize(); - } else { - area.style('left', '0px').style('top', '0px').style('bottom', '0px').style('height', null); - - const separ_color = settings.DarkMode ? 'grey' : 'azure', - vsepar = main.append('div').classed('jsroot_v_separator', true) - .attr('style', `pointer-events: all; border: 0; margin: 0; padding: 0; background-color: ${separ_color}; position: absolute; top: 0; bottom: 0; cursor: ew-resize;`); - - // eslint-disable-next-line one-var - const drag_move = drag().on('start', () => { - this.#vsepar_move = this.#vsepar_position; - vsepar.style('background-color', 'grey'); - }).on('drag', evnt => { - this.#vsepar_move += evnt.dx; - this.setButtonsPosition(); - settings.BrowserWidth = Math.max(50, Math.round(this.#vsepar_move)); - this.adjustSeparators(settings.BrowserWidth, null); - }).on('end', () => { - this.#vsepar_move = undefined; - vsepar.style('background-color', null); - this.checkResize(); - }); - - vsepar.call(drag_move); - - // need to get touches events handling in drag - if (browser.touches && !main.on('touchmove')) - main.on('touchmove', () => {}); - - this.adjustSeparators(settings.BrowserWidth, null, true, true); - } - - this.setButtonsPosition(); - - return this; - } - -} // class BrowserLayout - -const clTButton = 'TButton', kIsGrayscale = BIT(22), - // identifier used in TWebCanvas painter - webSnapIds = { kObject: 1, kSVG: 2, kSubPad: 3, kColors: 4, kStyle: 5, kFont: 6 }; - -// eslint-disable-next-line one-var -const PadButtonsHandler = { - - getButtonSize(fact) { - const cp = this.getCanvPainter(); - return Math.round((fact || 1) * (cp?.getPadScale() || 1) * (cp === this ? 16 : 12)); - }, - - toggleButtonsVisibility(action, evnt) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - const group = this.getLayerSvg('btns_layer'), - btn = group.select('[name=\'Toggle\']'); - if (btn.empty()) - return; - - let state = btn.property('buttons_state'); - - if (btn.property('timout_handler')) { - if ((action !== 'timeout') && (action !== 'timeout2')) - clearTimeout(btn.property('timout_handler')); - btn.property('timout_handler', null); - } - - let is_visible = false; - switch (action) { - case 'enable': - is_visible = true; - this.btns_active_flag = true; - break; - case 'enterbtn': - this.btns_active_flag = true; - return; // do nothing, just cleanup timeout - case 'hidemain': - case 'timeout': - if (!browser.touches) - btn.property('timout_handler', setTimeout(() => this.toggleButtonsVisibility('timeout2'), 5000)); - break; - case 'timeout2': - btn.style('opacity', 0); // hide JSROOT button, but keep handling - return; - case 'toggle': - state = !state; - btn.property('buttons_state', state); - is_visible = state; - break; - case 'disable': - case 'leavebtn': - this.btns_active_flag = false; - if (!state) - btn.property('timout_handler', setTimeout(() => this.toggleButtonsVisibility('timeout'), 1200)); - return; - } - - group.selectAll('svg').each(function() { - if (this !== btn.node()) - select(this).style('display', is_visible ? '' : 'none'); - else if (is_visible) - btn.style('opacity', null); // default opacity - }); - }, - - - alignButtons(btns, width, height) { - const isfast = this.isFastDrawing(), - isvert = btns.property('vertical'), - sz0x = isfast || isvert ? this.getButtonSize(1.25) : this.$first_button_width, - nextx = (btns.property('nextx') || 0) + sz0x; - let btns_x, btns_y; - - if (isvert) { - btns_x = btns.property('leftside') ? 2 : (width - sz0x); - btns_y = height - nextx; - } else { - btns_x = btns.property('leftside') ? 2 : (width - nextx); - btns_y = height - this.getButtonSize(1.25); - } - - makeTranslate(btns, btns_x, btns_y); - }, - - findPadButton(keyname) { - const group = this.getLayerSvg('btns_layer'); - let found_func = ''; - if (!group.empty()) { - group.selectAll('svg').each(function() { - if (select(this).attr('key') === keyname) - found_func = select(this).attr('name'); - }); - } - return found_func; - }, - - removePadButtons() { - const group = this.getLayerSvg('btns_layer'); - if (!group.empty()) { - group.selectAll('*').remove(); - group.property('nextx', null); - } - }, - - showPadButtons() { - const group = this.getLayerSvg('btns_layer'); - if (group.empty()) - return; - - // clean all previous buttons - group.selectAll('*').remove(); - if (!this._buttons) - return; - - const istop = this.isTopPad(), - isfast = this.isFastDrawing(), - y = 0; - this.$first_button_width = this.getButtonSize(isfast || !istop ? 1.25 : 3.5); - let ctrl, x = group.property('leftside') ? this.$first_button_width : 0; - - if (isfast) { - ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.circle, this.getButtonSize(), 'enlargePad', false) - .attr('name', 'Enlarge').attr('x', 0).attr('y', 0) - .on('click', evnt => this.clickPadButton('enlargePad', evnt)); - } else { - ctrl = ToolbarIcons.createSVG(group, istop ? ToolbarIcons.logo : ToolbarIcons.rect, this.getButtonSize(), istop ? `JSROOT version: ${version}` : `Toggle buttons on ${this.getPadName()}`, false) - .attr('name', 'Toggle').attr('x', 0).attr('y', 0) - .property('buttons_state', (settings.ToolBar !== 'popup') || browser.touches) - .property('pointer-events', 'visibleFill') - .on('click', evnt => this.toggleButtonsVisibility('toggle', evnt)); - - ctrl.node()._mouseenter = () => this.toggleButtonsVisibility('enable'); - ctrl.node()._mouseleave = () => this.toggleButtonsVisibility('disable'); - - if (istop && settings.ContextMenu) { - ctrl.on('contextmenu', evnt => { - evnt.preventDefault(); - evnt.stopPropagation(); - createMenu(evnt).then(menu => { - menu.addSettingsMenu(false, 'JSROOT', arg => { - if (arg === 'dark') - this.changeDarkMode(); - }); - menu.show(); - }); - }); - } - - for (let k = 0; k < this._buttons.length; ++k) { - const item = this._buttons[k]; - let btn = item.btn; - - if (isStr(btn)) - btn = ToolbarIcons[btn]; - if (!btn) - btn = ToolbarIcons.circle; - - const svg = ToolbarIcons.createSVG(group, btn, this.getButtonSize(), - item.tooltip + (istop ? '' : (` on pad ${this.getPadName()}`)) + (item.keyname ? ` (keyshortcut ${item.keyname})` : ''), false); - - if (group.property('vertical')) - svg.attr('x', y).attr('y', x); - else - svg.attr('x', x).attr('y', y); - - svg.attr('name', item.funcname) - .style('display', ctrl.property('buttons_state') ? '' : 'none') - .attr('key', item.keyname || null) - .on('click', evnt => this.clickPadButton(item.funcname, evnt)); - - svg.node()._mouseenter = () => this.toggleButtonsVisibility('enterbtn'); - svg.node()._mouseleave = () => this.toggleButtonsVisibility('leavebtn'); - - x += this.getButtonSize(1.25); - } - } - - group.property('nextx', x); - - this.alignButtons(group, this.getPadWidth(), this.getPadHeight()); - - if (group.property('vertical')) - ctrl.attr('y', x); - else if (!group.property('leftside')) - ctrl.attr('x', x); - - if (!browser.touches) - this.toggleButtonsVisibility('hidemain'); - }, - - assign(painter) { - Object.assign(painter, this); - } - -}; // PadButtonsHandler - - -/** @summary Fill TWebObjectOptions for painter - * @private */ -function createWebObjectOptions(painter) { - const snapid = painter?.getSnapId(); - if (!snapid) - return null; - - const obj = { _typename: 'TWebObjectOptions', snapid, opt: painter.getDrawOpt(true), fcust: '', fopt: [] }; - if (isFunc(painter.fillWebObjectOptions)) - painter.fillWebObjectOptions(obj); - return obj; -} - - -/** - * @summary Painter for TPad object - * @private - */ - -class TPadPainter extends ObjectPainter { - - #iscan; // is canvas flag - #pad_name; // name of the pad - #pad; // TPad object - #painters; // painters in the pad - #pad_scale; // scale factor of the pad - #pad_x; // pad x coordinate - #pad_y; // pad y coordinate - #pad_width; // pad width - #pad_height; // pad height - #doing_draw; // drawing handles - #pad_draw_disabled; // disable drawing of the pad - #last_grayscale; // grayscale change flag - #custom_palette; // custom palette - #custom_colors; // custom colors - #custom_palette_indexes; // custom palette indexes - #custom_palette_colors; // custom palette colors - #frame_painter_ref; // frame painter - #main_painter_ref; // main painter on the pad - #snap_primitives; // stored snap primitives from web canvas - #has_execs; // indicate is pad has TExec objects assigned - #deliver_move_events; // deliver move events to server - #readonly; // if changes on pad is not allowed - #num_primitives; // number of primitives - #num_specials; // number of special objects - if counted - #auto_color_cnt; // counter used in assigning auto colors - #auto_palette; // palette for creating of automatic colors - #fixed_size; // fixed size flag - #has_canvas; // indicate if top canvas painter exists - #fast_drawing; // fast drawing flag - #resize_tmout; // timeout handle for resize - #start_draw_tm; // time when start drawing primitives - - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} pad - TPad object to draw - * @param {string} [opt] - draw option - * @param {boolean} [iscan] - if TCanvas object - * @param {boolean} [add_to_primitives] - add pad painter to canvas - * */ - constructor(dom, pad, opt, iscan, add_to_primitives) { - super(dom, pad); - this.#pad = pad; - this.#iscan = iscan; // indicate if working with canvas - this.#pad_name = ''; - if (!iscan && pad?.fName) { - this.#pad_name = pad.fName.replace(' ', '_'); // avoid empty symbol in pad name - const regexp = /^[A-Za-z][A-Za-z0-9_]*$/; - if (!regexp.test(this.#pad_name) || ((this.#pad_name === 'button') && (pad._typename === clTButton))) - this.#pad_name = 'jsroot_pad_' + internals.id_counter++; - } - this.#painters = []; // complete list of all painters in the pad - this.#has_canvas = true; - this.forEachPainter = this.forEachPainterInPad; - const d = this.selectDom(); - if (!d.empty() && d.property('_batch_mode')) - this.batch_mode = true; - - if (opt !== undefined) - this.decodeOptions(opt); - - if (add_to_primitives) { - if ((add_to_primitives !== 'webpad') && this.getCanvSvg().empty()) { - // one can draw pad without canvas - this.#has_canvas = false; - this.#pad_name = ''; - this.setTopPainter(); - } else { - // pad painter will be registered in the parent pad - this.addToPadPrimitives(); - } - } - - if (pad?.$disable_drawing) - this.#pad_draw_disabled = true; - } - - /** @summary returns pad painter - * @protected */ - getPadPainter() { return this.isTopPad() ? null : super.getPadPainter(); } - - /** @summary returns canvas painter - * @protected */ - getCanvPainter(try_select) { return this.isTopPad() ? this : super.getCanvPainter(try_select); } - - /** @summary Returns pad name - * @protected */ - getPadName() { return this.#pad_name; } - - /** @summary Indicates that drawing runs in batch mode - * @private */ - isBatchMode() { - if (this.batch_mode !== undefined) - return this.batch_mode; - - if (isBatchMode()) - return true; - - return this.isTopPad() ? false : this.getCanvPainter()?.isBatchMode(); - } - - /** @summary Indicates that is is Root6 pad painter - * @private */ - isRoot6() { return true; } - - /** @summary Returns true if pad is editable */ - isEditable() { return this.#pad?.fEditable ?? true; } - - /** @summary Returns true if button */ - isButton() { return this.matchObjectType(clTButton); } - - /** @summary Returns true if read-only mode is enabled */ - isReadonly() { return this.#readonly; } - - /** @summary Returns true if it is canvas - * @param {boolean} [is_online = false] - if specified, checked if it is canvas with configured connection to server */ - isCanvas(is_online = false) { - if (!this.#iscan) - return false; - if (is_online === true) - return isFunc(this.getWebsocket) && this.getWebsocket(); - return isStr(is_online) ? this.#iscan === is_online : true; - } - - /** @summary Returns true if it is canvas or top pad without canvas */ - isTopPad() { return this.isCanvas() || !this.#has_canvas; } - - /** @summary Canvas main svg element - * @return {object} d3 selection with canvas svg - * @protected */ - getCanvSvg() { return this.selectDom().select('.root_canvas'); } - - /** @summary Pad svg element - * @return {object} d3 selection with pad svg - * @protected */ - getPadSvg() { - const c = this.getCanvSvg(); - if (!this.#pad_name || c.empty()) - return c; - - return c.select('.primitives_layer .__root_pad_' + this.#pad_name); - } - - /** @summary Method selects immediate layer under canvas/pad main element - * @param {string} name - layer name lik 'primitives_layer', 'btns_layer', 'info_layer' - * @protected */ - getLayerSvg(name) { return this.getPadSvg().selectChild('.' + name); } - - /** @summary Returns svg element for the frame in current pad - * @protected */ - getFrameSvg() { - const layer = this.getLayerSvg('primitives_layer'); - if (layer.empty()) - return layer; - let node = layer.node().firstChild; - while (node) { - const elem = select(node); - if (elem.classed('root_frame')) - return elem; - node = node.nextSibling; - } - return select(null); - } - - /** @summary Returns main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - getMainPainter() { return this.#main_painter_ref || null; } - - /** @summary Assign main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - setMainPainter(painter, force) { - if (!this.#main_painter_ref || force) - this.#main_painter_ref = painter; - } - - /** @summary cleanup pad and all primitives inside */ - cleanup() { - if (this.#doing_draw) - console.error('pad drawing is not completed when cleanup is called'); - - this.#painters.forEach(p => p.cleanup()); - - const svg_p = this.getPadSvg(); - if (!svg_p.empty()) { - svg_p.property('pad_painter', null); - if (!this.isCanvas()) - svg_p.remove(); - } - - this.#main_painter_ref = undefined; - this.#frame_painter_ref = undefined; - this.#pad_x = this.#pad_y = this.#pad_width = this.#pad_height = undefined; - this.#doing_draw = undefined; - this.#snap_primitives = undefined; - this.#last_grayscale = undefined; - this.#custom_palette = this.#custom_colors = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; - - this.#painters = []; - this.#pad = undefined; - this.#pad_name = undefined; - this.#has_canvas = false; - - selectActivePad({ pp: this, active: false }); - - super.cleanup(); - } - - /** @summary Returns frame painter inside the pad - * @private */ - getFramePainter() { return this.#frame_painter_ref; } - - /** @summary Assign actual frame painter - * @private */ - setFramePainter(fp, on) { - if (on) - this.#frame_painter_ref = fp; - else if (this.#frame_painter_ref === fp) - this.#frame_painter_ref = undefined; - } - - /** @summary get pad width */ - getPadWidth() { return this.#pad_width || 0; } - - /** @summary get pad height */ - getPadHeight() { return this.#pad_height || 0; } - - /** @summary get pad height */ - getPadScale() { return this.#pad_scale || 1; } - - /** @summary get pad rect */ - getPadRect() { - return { - x: this.#pad_x || 0, - y: this.#pad_y || 0, - width: this.getPadWidth(), - height: this.getPadHeight() - }; - } - - /** @summary return pad log state x or y are allowed */ - getPadLog(name) { - const pad = this.getRootPad(); - if (name === 'x') - return pad?.fLogx; - if (name === 'y') - return pad?.fLogv ?? pad?.fLogy; - return false; - } - - /** @summary Returns frame coordinates - also when frame is not drawn */ - getFrameRect() { - const fp = this.getFramePainter(); - if (fp) - return fp.getFrameRect(); - - const w = this.getPadWidth(), - h = this.getPadHeight(), - rect = {}; - - if (this.#pad) { - rect.szx = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fLeftMargin, this.#pad.fRightMargin)) * w); - rect.szy = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fBottomMargin, this.#pad.fTopMargin)) * h); - } else { - rect.szx = Math.round(0.5 * w); - rect.szy = Math.round(0.5 * h); - } - - rect.width = 2 * rect.szx; - rect.height = 2 * rect.szy; - rect.x = Math.round(w / 2 - rect.szx); - rect.y = Math.round(h / 2 - rect.szy); - rect.hint_delta_x = rect.szx; - rect.hint_delta_y = rect.szy; - rect.transform = makeTranslate(rect.x, rect.y) || ''; - - return rect; - } - - /** @summary return RPad object */ - getRootPad(is_root6) { - return (is_root6 === undefined) || is_root6 ? this.#pad : null; - } - - /** @summary Cleanup primitives from pad - selector lets define which painters to remove - * @return true if any painter was removed */ - cleanPrimitives(selector) { - // remove all primitives - if (selector === true) - selector = () => true; - - if (!isFunc(selector)) - return false; - - let is_any = false; - - for (let k = this.#painters.length - 1; k >= 0; --k) { - const subp = this.#painters[k]; - if (!subp || selector(subp)) { - subp?.cleanup(); - this.#painters.splice(k, 1); - is_any = true; - } - } - - return is_any; - } - - /** @summary Removes and cleanup specified primitive - * @desc also secondary primitives will be removed - * @return new index to continue loop or -111 if main painter removed - * @private */ - removePrimitive(arg, clean_only_secondary) { - let indx, prim; - if (Number.isInteger(arg)) { - indx = arg; - prim = this.#painters[indx]; - } else { - indx = this.#painters.indexOf(arg); - prim = arg; - } - if (indx < 0) - return indx; - - const arr = [], get_main = clean_only_secondary ? this.getMainPainter() : null; - let resindx = indx - 1; // object removed itself - arr.push(prim); - this.#painters.splice(indx, 1); - - // loop to extract all dependent painters - let len0 = 0; - while (len0 < arr.length) { - for (let k = this.#painters.length - 1; k >= 0; --k) { - if (this.#painters[k].isSecondary(arr[len0])) { - arr.push(this.#painters[k]); - this.#painters.splice(k, 1); - if (k < indx) - resindx--; - } - } - len0++; - } - - arr.forEach(painter => { - if ((painter !== prim) || !clean_only_secondary) - painter.cleanup(); - if (this.getMainPainter() === painter) { - this.setMainPainter(undefined, true); - resindx = -111; - } - }); - - // when main painter disappears because of special cleanup - also reset zooming - if (clean_only_secondary && get_main && !this.getMainPainter()) - this.getFramePainter()?.resetZoom(); - - return resindx; - } - - /** @summary returns custom palette associated with pad or top canvas - * @private */ - getCustomPalette(no_recursion) { - return this.#custom_palette || (no_recursion ? null : this.getCanvPainter()?.getCustomPalette(true)); - } - - _getCustomPaletteIndexes() { return this.#custom_palette_indexes; } - - /** @summary Provides automatic color - * @desc Uses ROOT colors palette if possible - * @private */ - getAutoColor(numprimitives, indx) { - if (!numprimitives || indx === undefined) { - numprimitives = Math.max(numprimitives || (this.#num_primitives || 5) - (this.#num_specials || 0), 2); - indx = this.#auto_color_cnt ?? 0; - this.#auto_color_cnt = (indx + 1) % numprimitives; - if (indx >= numprimitives) - indx = numprimitives - 1; - } - - let indexes = this._getCustomPaletteIndexes(); - if (!indexes) { - const cp = this.getCanvPainter(); - if ((cp !== this) && isFunc(cp?._getCustomPaletteIndexes)) - indexes = cp._getCustomPaletteIndexes(); - } - - if (indexes?.length) { - const p = Math.round(indx * (indexes.length - 3) / (numprimitives - 1)); - return indexes[p]; - } - - if (!this.#auto_palette) - this.#auto_palette = getColorPalette(settings.Palette, this.isGrayscale()); - const palindx = Math.round(indx * (this.#auto_palette.getLength() - 3) / (numprimitives - 1)), - colvalue = this.#auto_palette.getColor(palindx); - - return this.addColor(colvalue); - } - - /** @summary Returns number of painters - * @protected */ - getNumPainters() { return this.#painters.length; } - - /** @summary Add painter to pad list of painters - * @protected */ - addToPrimitives(painter) { - if (this.#painters.indexOf(painter) < 0) - this.#painters.push(painter); - return this; - } - - /** @summary Call function for each painter in pad - * @param {Function} userfunc - function to call - * @param {string} kind - 'all' for all objects (default), 'pads' only pads and sub-pads, 'objects' only for object in current pad - * @private */ - forEachPainterInPad(userfunc, kind) { - if (!kind) - kind = 'all'; - if (kind !== 'objects') - userfunc(this); - for (let k = 0; k < this.#painters.length; ++k) { - const sub = this.#painters[k]; - if (isFunc(sub.forEachPainterInPad)) { - if (kind !== 'objects') - sub.forEachPainterInPad(userfunc, kind); - } else if (kind !== 'pads') - userfunc(sub); - } - } - - /** @summary register for pad events receiver - * @desc in pad painter, while pad may be drawn without canvas */ - registerForPadEvents(receiver) { - this.pad_events_receiver = receiver; - } - - /** @summary Generate pad events, normally handled by GED - * @desc in pad painter, while pad may be drawn without canvas - * @private */ - producePadEvent(what, padpainter, painter, position) { - if ((what === 'select') && isFunc(this.selectActivePad)) - this.selectActivePad(padpainter, painter, position); - - if (isFunc(this.pad_events_receiver)) - this.pad_events_receiver({ what, padpainter, painter, position }); - } - - /** @summary method redirect call to pad events receiver */ - selectObjectPainter(painter, pos) { - const canp = this.isTopPad() ? this : this.getCanvPainter(); - - if (painter === undefined) - painter = this; - - if (pos && !this.isTopPad()) - pos = getAbsPosInCanvas(this.getPadSvg(), pos); - - selectActivePad({ pp: this, active: true }); - - canp?.producePadEvent('select', this, painter, pos); - } - - /** @summary Draw pad active border - * @private */ - drawActiveBorder(svg_rect, is_active) { - if (is_active !== undefined) { - if (this.is_active_pad === is_active) - return; - this.is_active_pad = is_active; - } - - if (this.is_active_pad === undefined) - return; - - if (!svg_rect) - svg_rect = this.isCanvas() ? this.getCanvSvg().selectChild('.canvas_fillrect') : this.getPadSvg().selectChild('.root_pad_border'); - - const cp = this.getCanvPainter(); - - let lineatt = this.is_active_pad && cp?.highlight_gpad ? new TAttLineHandler({ style: 1, width: 1, color: 'red' }) : this.lineatt; - if (!lineatt) - lineatt = new TAttLineHandler({ color: 'none' }); - - svg_rect.call(lineatt.func); - } - - /** @summary Set fast drawing property depending on the size - * @private */ - setFastDrawing(w, h) { - const was_fast = this.#fast_drawing; - this.#fast_drawing = !this.hasSnapId() && settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); - if (was_fast !== this.#fast_drawing) - this.showPadButtons(); - } - - /** @summary Return fast drawing flag - * @private */ - isFastDrawing() { return this.#fast_drawing; } - - /** @summary Returns true if canvas configured with grayscale - * @private */ - isGrayscale() { - if (!this.isCanvas()) - return false; - return this.#pad?.TestBit(kIsGrayscale) ?? false; - } - - /** @summary Returns true if default pad range is configured - * @private */ - isDefaultPadRange() { - if (!this.#pad) - return true; - return (this.#pad.fX1 === 0) && (this.#pad.fX2 === 1) && (this.#pad.fY1 === 0) && (this.#pad.fY2 === 1); - } - - /** @summary Set grayscale mode for the canvas - * @private */ - setGrayscale(flag) { - if (!this.isTopPad()) - return; - - let changed = false; - - if (flag === undefined) { - flag = this.#pad?.TestBit(kIsGrayscale) ?? false; - changed = (this.#last_grayscale !== undefined) && (this.#last_grayscale !== flag); - } else if (flag !== this.#pad?.TestBit(kIsGrayscale)) { - this.#pad?.InvertBit(kIsGrayscale); - changed = true; - } - - if (changed) { - this.forEachPainter(p => { - if (isFunc(p.clearHistPalette)) - p.clearHistPalette(); - }); - } - - this.setColors(flag ? getGrayColors(this.#custom_colors) : this.#custom_colors); - - this.#last_grayscale = flag; - - this.#custom_palette = this.#custom_palette_colors ? new ColorPalette(this.#custom_palette_colors, flag) : null; - } - - /** @summary Set fixed-size canvas - * @private */ - _setFixedSize(on) { this.#fixed_size = on; } - - /** @summary Create SVG element for canvas */ - createCanvasSvg(check_resize, new_size) { - const is_batch = this.isBatchMode(), lmt = 5; - let factor, svg, rect, btns, info, frect; - - if (check_resize > 0) { - if (this.#fixed_size) - return check_resize > 1; // flag used to force re-drawing of all sub-pads - - svg = this.getCanvSvg(); - if (svg.empty()) - return false; - - factor = svg.property('height_factor'); - - rect = this.testMainResize(check_resize, null, factor); - - if (!rect.changed && (check_resize === 1)) - return false; - - if (!is_batch) - btns = this.getLayerSvg('btns_layer'); - - info = this.getLayerSvg('info_layer'); - frect = svg.selectChild('.canvas_fillrect'); - } else { - const render_to = this.selectDom(); - - if (render_to.style('position') === 'static') - render_to.style('position', 'relative'); - - svg = render_to.append('svg') - .attr('class', 'jsroot root_canvas') - .property('pad_painter', this) // this is custom property - .property('redraw_by_resize', false); // could be enabled to force redraw by each resize - - this.setTopPainter(); // assign canvas as top painter of that element - - if (is_batch) - svg.attr('xmlns', nsSVG); - else if (!this.online_canvas) - svg.append('svg:title').text('ROOT canvas'); - - if (!is_batch) - svg.style('user-select', settings.UserSelect || null); - - if (!is_batch || (this.#pad.fFillStyle > 0)) - frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); - - if (!is_batch) { - frect.style('pointer-events', 'visibleFill') - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter()) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } - - svg.append('svg:g').attr('class', 'primitives_layer'); - info = svg.append('svg:g').attr('class', 'info_layer'); - if (!is_batch) { - btns = svg.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide === 'left') - .property('vertical', settings.ToolBarVert); - } - - factor = 0.66; - if (this.#pad?.fCw && this.#pad?.fCh && (this.#pad?.fCw > 0)) { - factor = this.#pad.fCh / this.#pad.fCw; - if ((factor < 0.1) || (factor > 10)) - factor = 0.66; - } - - if (this.#fixed_size) { - render_to.style('overflow', 'auto'); - rect = { width: this.#pad.fCw, height: this.#pad.fCh }; - if (!rect.width || !rect.height) - rect = getElementRect(render_to); - } else - rect = this.testMainResize(2, new_size, factor); - } - - this.setGrayscale(); - - this.createAttFill({ attr: this.#pad }); - - if ((rect.width <= lmt) || (rect.height <= lmt)) { - if (!this.hasSnapId()) { - svg.style('display', 'none'); - console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); - } - if (this.#pad_width && this.#pad_height) { - // use last valid dimensions - rect.width = this.#pad_width; - rect.height = this.#pad_height; - } else { - // just to complete drawing. - rect.width = 800; - rect.height = 600; - } - } else - svg.style('display', null); - - svg.attr('x', 0).attr('y', 0).style('position', 'absolute'); - - if (this.#fixed_size) - svg.attr('width', rect.width).attr('height', rect.height); - else - svg.style('width', '100%').style('height', '100%').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - - svg.style('filter', settings.DarkMode || this.#pad?.$dark ? 'invert(100%)' : null); - - this.#pad_scale = settings.CanvasScale || 1; - this.#pad_x = 0; - this.#pad_y = 0; - this.#pad_width = rect.width * this.#pad_scale; - this.#pad_height = rect.height * this.#pad_scale; - - svg.attr('viewBox', `0 0 ${this.#pad_width} ${this.#pad_height}`) - .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio - .property('height_factor', factor) - .property('draw_x', this.#pad_x) - .property('draw_y', this.#pad_y) - .property('draw_width', this.#pad_width) - .property('draw_height', this.#pad_height); - - this.addPadBorder(svg, frect); - - this.setFastDrawing(this.#pad_width * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), this.#pad_height * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); - - if (this.alignButtons && btns) - this.alignButtons(btns, this.#pad_width, this.#pad_height); - - let dt = info.selectChild('.canvas_date'); - if (!gStyle.fOptDate) - dt.remove(); - else { - if (dt.empty()) - dt = info.append('text').attr('class', 'canvas_date'); - const posy = Math.round(this.#pad_height * (1 - gStyle.fDateY)), - date = new Date(); - let posx = Math.round(this.#pad_width * gStyle.fDateX); - if (!is_batch && (posx < 25)) - posx = 25; - if (gStyle.fOptDate > 3) - date.setTime(gStyle.fOptDate * 1000); - - makeTranslate(dt, posx, posy) - .style('text-anchor', 'start') - .text(convertDate(date)); - } - - const iname = this.getItemName(); - if (iname) - this.drawItemNameOnCanvas(iname); - else if (!gStyle.fOptFile) - info.selectChild('.canvas_item').remove(); - - return true; - } - - /** @summary Draw item name on canvas if gStyle.fOptFile is configured - * @private */ - drawItemNameOnCanvas(item_name) { - const info = this.getLayerSvg('info_layer'); - let df = info.selectChild('.canvas_item'); - const fitem = getHPainter().findRootFileForItem(item_name), - fname = (gStyle.fOptFile === 3) ? item_name : ((gStyle.fOptFile === 2) ? fitem?._fullurl : fitem?._name); - - if (!gStyle.fOptFile || !fname) - df.remove(); - else { - if (df.empty()) - df = info.append('text').attr('class', 'canvas_item'); - const rect = this.getPadRect(); - makeTranslate(df, Math.round(rect.width * (1 - gStyle.fDateX)), Math.round(rect.height * (1 - gStyle.fDateY))) - .style('text-anchor', 'end') - .text(fname); - } - if (((gStyle.fOptDate === 2) || (gStyle.fOptDate === 3)) && fitem?._file) { - info.selectChild('.canvas_date') - .text(convertDate(getTDatime(gStyle.fOptDate === 2 ? fitem._file.fDatimeC : fitem._file.fDatimeM))); - } - } - - /** @summary Return true if this pad enlarged */ - isPadEnlarged() { - if (this.isTopPad()) - return this.enlargeMain('state') === 'on'; - return this.getCanvSvg().property('pad_enlarged') === this.#pad; - } - - /** @summary Enlarge pad draw element when possible */ - enlargePad(evnt, is_dblclick, is_escape) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - // ignore double click on online canvas itself for enlarge - if (is_dblclick && this.isCanvas(true) && (this.enlargeMain('state') === 'off')) - return; - - const svg_can = this.getCanvSvg(), - pad_enlarged = svg_can.property('pad_enlarged'); - - if (this.isTopPad() || (!pad_enlarged && !this.hasObjectsToDraw() && !this.#painters)) { - if (this.#fixed_size) - return; // canvas cannot be enlarged in such mode - if (!this.enlargeMain(is_escape ? false : 'toggle')) - return; - if (this.enlargeMain('state') === 'off') - svg_can.property('pad_enlarged', null); - else - selectActivePad({ pp: this, active: true }); - } else if (!pad_enlarged && !is_escape) { - this.enlargeMain(true, true); - svg_can.property('pad_enlarged', this.#pad); - selectActivePad({ pp: this, active: true }); - } else if (pad_enlarged === this.#pad) { - this.enlargeMain(false); - svg_can.property('pad_enlarged', null); - } else if (!is_escape && is_dblclick) - console.error('missmatch with pad double click events'); - - return this.checkResize(true); - } - - /** @summary Create main SVG element for pad - * @return true when pad is displayed and all its items should be redrawn */ - createPadSvg(only_resize) { - if (this.isTopPad()) { - this.createCanvasSvg(only_resize ? 2 : 0); - return true; - } - - const svg_can = this.getCanvSvg(), - width = svg_can.property('draw_width'), - height = svg_can.property('draw_height'), - pad_enlarged = svg_can.property('pad_enlarged'), - pad_visible = !this.#pad_draw_disabled && (!pad_enlarged || (pad_enlarged === this.#pad)), - is_batch = this.isBatchMode(); - let w = Math.round(this.#pad.fAbsWNDC * width), - h = Math.round(this.#pad.fAbsHNDC * height), - x = Math.round(this.#pad.fAbsXlowNDC * width), - y = Math.round(height * (1 - this.#pad.fAbsYlowNDC)) - h, - svg_pad, svg_border, btns; - - if (pad_enlarged === this.#pad) { - w = width; - h = height; - x = y = 0; - } - - if (only_resize) { - svg_pad = this.getPadSvg(); - svg_border = svg_pad.selectChild('.root_pad_border'); - if (!is_batch) - btns = this.getLayerSvg('btns_layer'); - this.addPadInteractive(true); - } else { - svg_pad = svg_can.selectChild('.primitives_layer') - .append('svg:svg') // svg used to blend all drawings outside - .classed('__root_pad_' + this.#pad_name, true) - .attr('pad', this.#pad_name) // set extra attribute to mark pad name - .property('pad_painter', this); // this is custom property - - if (!is_batch) - svg_pad.append('svg:title').text('subpad ' + this.#pad_name); - - // need to check attributes directly while attributes objects will be created later - if (!is_batch || (this.#pad.fFillStyle > 0) || ((this.#pad.fLineStyle > 0) && (this.#pad.fLineColor > 0))) - svg_border = svg_pad.append('svg:path').attr('class', 'root_pad_border'); - - if (!is_batch) { - svg_border.style('pointer-events', 'visibleFill') // get events also for not visible rect - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter()) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } - - svg_pad.append('svg:g').attr('class', 'primitives_layer'); - if (!is_batch) { - btns = svg_pad.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide !== 'left') - .property('vertical', settings.ToolBarVert); - } - } - - this.createAttFill({ attr: this.#pad }); - this.createAttLine({ attr: this.#pad, color0: !this.#pad.fBorderMode ? 'none' : '' }); - - svg_pad.style('display', pad_visible ? null : 'none') - .attr('viewBox', `0 0 ${w} ${h}`) // due to svg - .attr('preserveAspectRatio', 'none') // due to svg, we do not preserve relative ratio - .attr('x', x) // due to svg - .attr('y', y) // due to svg - .attr('width', w) // due to svg - .attr('height', h) // due to svg - .property('draw_x', x) // this is to make similar with canvas - .property('draw_y', y) - .property('draw_width', w) - .property('draw_height', h); - - this.#pad_scale = this.getCanvPainter().getPadScale(); - this.#pad_x = x; - this.#pad_y = y; - this.#pad_width = w; - this.#pad_height = h; - - this.addPadBorder(svg_pad, svg_border, true); - - this.setFastDrawing(w * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), h * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); - - // special case of 3D canvas overlay - if (svg_pad.property('can3d') === constants$1.Embed3D.Overlay) { - this.selectDom().select('.draw3d_' + this.#pad_name) - .style('display', pad_visible ? '' : 'none'); - } - - if (this.alignButtons && btns) - this.alignButtons(btns, this.#pad_width, this.#pad_height); - - return pad_visible; - } - - /** @summary Add border decorations - * @private */ - addPadBorder(svg_pad, svg_border, draw_line) { - if (!svg_border) - return; - - svg_border.attr('d', `M0,0H${this.#pad_width}V${this.#pad_height}H0Z`) - .call(this.fillatt.func); - if (draw_line) - svg_border.call(this.lineatt.func); - - this.drawActiveBorder(svg_border); - - let svg_border1 = svg_pad.selectChild('.root_pad_border1'), - svg_border2 = svg_pad.selectChild('.root_pad_border2'); - - if (this.#pad.fBorderMode && this.#pad.fBorderSize) { - const arr = getBoxDecorations(0, 0, this.#pad_width, this.#pad_height, this.#pad.fBorderMode, this.#pad.fBorderSize, this.#pad.fBorderSize); - - if (svg_border2.empty()) - svg_border2 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border2'); - if (svg_border1.empty()) - svg_border1 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border1'); - - svg_border1.attr('d', arr[0]) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).brighter(0.5).formatRgb()); - svg_border2.attr('d', arr[1]) - .call(this.fillatt.func) - .style('fill', rgb(this.fillatt.color).darker(0.5).formatRgb()); - } else { - svg_border1.remove(); - svg_border2.remove(); - } - } - - /** @summary Add pad interactive features like dragging and resize - * @private */ - addPadInteractive(cleanup = false) { - if (isFunc(this.$userInteractive)) { - this.$userInteractive(); - delete this.$userInteractive; - } - - if (this.isBatchMode() || this.isCanvas() || !this.isEditable()) - return; - - const svg_can = this.getCanvSvg(), - width = svg_can.property('draw_width'), - height = svg_can.property('draw_height'); - - addDragHandler(this, { - cleanup, // do cleanup to let assign new handlers later on - x: this.#pad_x, y: this.#pad_y, width: this.#pad_width, height: this.#pad_height, no_transform: true, - only_resize: true, - is_disabled: kind => svg_can.property('pad_enlarged') || this.btns_active_flag || - (kind === 'move' && (this.options._disable_dragging || this.getFramePainter()?.mode3d)), - getDrawG: () => this.getPadSvg(), - pad_rect: { width, height }, - minwidth: 20, minheight: 20, - move_resize: (_x, _y, _w, _h) => { - const x0 = this.#pad.fAbsXlowNDC, - y0 = this.#pad.fAbsYlowNDC, - scale_w = _w / width / this.#pad.fAbsWNDC, - scale_h = _h / height / this.#pad.fAbsHNDC, - shift_x = _x / width - x0, - shift_y = 1 - (_y + _h) / height - y0; - this.forEachPainterInPad(p => { - const subpad = p.getRootPad(); - subpad.fAbsXlowNDC += (subpad.fAbsXlowNDC - x0) * (scale_w - 1) + shift_x; - subpad.fAbsYlowNDC += (subpad.fAbsYlowNDC - y0) * (scale_h - 1) + shift_y; - subpad.fAbsWNDC *= scale_w; - subpad.fAbsHNDC *= scale_h; - }, 'pads'); - }, - redraw: () => this.interactiveRedraw('pad', 'padpos') - }); - } - - /** @summary Disable pad drawing - * @desc Complete SVG element will be hidden */ - disablePadDrawing() { - if (!this.#pad_draw_disabled && !this.isTopPad()) { - this.#pad_draw_disabled = true; - this.createPadSvg(true); - } - } - - /** @summary Check if it is special object, which should be handled separately - * @desc It can be TStyle or list of colors or palette object - * @return {boolean} true if any */ - checkSpecial(obj) { - if (!obj) - return false; - - if (obj._typename === clTStyle) { - Object.assign(gStyle, obj); - return true; - } - - const o = this.getOptions(true); - - if ((obj._typename === clTObjArray) && (obj.name === 'ListOfColors')) { - if (o?.CreatePalette) { - let arr = []; - for (let n = obj.arr.length - o.CreatePalette; n < obj.arr.length; ++n) { - const col = getRGBfromTColor(obj.arr[n]); - if (!col) { - console.log('Fail to create color for palette'); - arr = null; - break; - } - arr.push(col); - } - if (arr.length) - this.#custom_palette = new ColorPalette(arr); - } - - if (!o || o.GlobalColors) // set global list of colors - adoptRootColors(obj); - - // copy existing colors and extend with new values - this.#custom_colors = o?.LocalColors ? extendRootColors(null, obj) : null; - return true; - } - - if ((obj._typename === clTObjArray) && (obj.name === 'CurrentColorPalette')) { - const arr = [], indx = []; - let missing = false; - for (let n = 0; n < obj.arr.length; ++n) { - const col = obj.arr[n]; - if (col?._typename === clTColor) { - indx[n] = col.fNumber; - arr[n] = getRGBfromTColor(col); - } else { - console.log(`Missing color with index ${n}`); - missing = true; - } - } - - const apply = (!o || (!missing && !o.IgnorePalette)); - this.#custom_palette_indexes = apply ? indx : null; - this.#custom_palette_colors = apply ? arr : null; - - return true; - } - - return false; - } - - /** @summary Check if special objects appears in primitives - * @desc it could be list of colors or palette */ - checkSpecialsInPrimitives(can, count_specials) { - const lst = can?.fPrimitives; - if (count_specials) - this.#num_specials = 0; - if (!lst) - return; - for (let i = 0; i < lst.arr?.length; ++i) { - if (lst.arr[i] === lst) { - lst.arr[i] = null; - console.error('list of primitives includes itself - endless recursion'); - } else if (this.checkSpecial(lst.arr[i])) { - lst.arr[i].$special = true; // mark object as special one, do not use in drawing - if (count_specials) - this.#num_specials++; - } - } - } - - /** @summary try to find object by name in list of pad primitives - * @desc used to find title drawing - * @private */ - findInPrimitives(objname, objtype) { - const match = obj => obj && (obj?.fName === objname) && (objtype ? (obj?._typename === objtype) : true), - snap = this.#snap_primitives?.find(s => match((s.fKind === webSnapIds.kObject) ? s.fSnapshot : null)); - - return snap ? snap.fSnapshot : this.#pad?.fPrimitives?.arr.find(match); - } - - /** @summary Try to find painter for specified object - * @desc can be used to find painter for some special objects, registered as - * histogram functions - * @param {object} selobj - object to which painter should be search, set null to ignore parameter - * @param {string} [selname] - object name, set to null to ignore - * @param {string} [seltype] - object type, set to null to ignore - * @return {object} - painter for specified object (if any) - * @private */ - findPainterFor(selobj, selname, seltype) { - return this.#painters.find(p => { - const pobj = p.getObject(); - if (!pobj) - return false; - - if (selobj && (pobj === selobj)) - return true; - if (!selname && !seltype) - return false; - if (selname && (pobj.fName !== selname)) - return false; - if (seltype && (pobj._typename !== seltype)) - return false; - return true; - }); - } - - /** @summary Return true if any objects beside sub-pads exists in the pad */ - hasObjectsToDraw() { - return this.#pad?.fPrimitives?.arr?.find(obj => obj._typename !== clTPad); - } - - /** @summary sync drawing/redrawing/resize of the pad - * @param {string} kind - kind of draw operation, if true - always queued - * @return {Promise} when pad is ready for draw operation or false if operation already queued - * @private */ - syncDraw(kind) { - const entry = { kind: kind || 'redraw' }; - if (this.#doing_draw === undefined) { - this.#doing_draw = [entry]; - return Promise.resolve(true); - } - // if queued operation registered, ignore next calls, indx === 0 is running operation - if ((entry.kind !== true) && (this.#doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) - return false; - this.#doing_draw.push(entry); - return new Promise(resolveFunc => { - entry.func = resolveFunc; - }); - } - - /** @summary indicates if painter performing objects draw - * @private */ - doingDraw() { return this.#doing_draw !== undefined; } - - /** @summary confirms that drawing is completed, may trigger next drawing immediately - * @private */ - confirmDraw() { - if (this.#doing_draw === undefined) - return console.warn('failure, should not happen'); - this.#doing_draw.shift(); - if (!this.#doing_draw.length) - this.#doing_draw = undefined; - else { - const entry = this.#doing_draw[0]; - if (entry.func) { - entry.func(); - delete entry.func; - } - } - } - - /** @summary Draw single primitive */ - async drawObject(/* dom, obj, opt */) { - console.log('Not possible to draw object without loading of draw.mjs'); - return null; - } - - /** @summary Draw pad primitives - * @return {Promise} when drawing completed - * @private */ - async drawPrimitives(indx) { - if (indx === undefined) { - if (this.isCanvas()) - this.#start_draw_tm = new Date().getTime(); - - // set number of primitives - this.#num_primitives = this.#pad?.fPrimitives?.arr?.length || 0; - - // sync to prevent immediate pad redraw during normal drawing sequence - return this.syncDraw(true).then(() => this.drawPrimitives(0)); - } - - if (!this.#pad || (indx >= this.#num_primitives)) { - if (this.#start_draw_tm) { - const spenttm = new Date().getTime() - this.#start_draw_tm; - if (spenttm > 1000) - console.log(`Canvas ${this.#pad?.fName || '---'} drawing took ${(spenttm * 1e-3).toFixed(2)}s`); - this.#start_draw_tm = undefined; - } - - this.confirmDraw(); - return; - } - - const obj = this.#pad.fPrimitives.arr[indx]; - - if (!obj || obj.$special || ((indx > 0) && (obj._typename === clTFrame) && this.getFramePainter())) - return this.drawPrimitives(indx + 1); - - // use of Promise should avoid large call-stack depth when many primitives are drawn - return this.drawObject(this, obj, this.#pad.fPrimitives.opt[indx]).then(op => { - if (isObject(op)) - op._primitive = true; // mark painter as belonging to primitives - - return this.drawPrimitives(indx + 1); - }); - } - - /** @summary Divide pad on sub-pads - * @return {Promise} when finished - * @private */ - async divide(nx, ny, use_existing) { - let color = this.#pad.fFillColor; - if (!use_existing) { - if (color < 15) - color = 19; - else if (color < 20) - color--; - } - - if (nx && !ny && use_existing) { - for (let k = 0; k < nx; ++k) { - if (!this.getSubPadPainter(k + 1)) { - use_existing = false; - break; - } - } - if (use_existing) - return this; - } - - this.cleanPrimitives(isPadPainter); - if (!this.#pad.fPrimitives) - this.#pad.fPrimitives = create$1(clTList); - this.#pad.fPrimitives.Clear(); - - if ((!nx && !ny) || !this.#pad.Divide(nx, ny, 0.01, 0.01, color)) - return this; - - const drawNext = indx => { - if (indx >= this.#pad.fPrimitives.arr.length) - return this; - return this.drawObject(this, this.#pad.fPrimitives.arr[indx]).then(() => drawNext(indx + 1)); - }; - - return drawNext(0); - } - - /** @summary Return sub-pads painter, only direct childs are checked - * @private */ - getSubPadPainter(n) { - for (let k = 0; k < this.#painters.length; ++k) { - const sub = this.#painters[k]; - if (isPadPainter(sub) && (sub.getRootPad()?.fNumber === n)) - return sub; - } - return null; - } - - /** @summary Process tooltip event in the pad - * @private */ - processPadTooltipEvent(pnt) { - const painters = [], hints = []; - - // first count - how many processors are there - this.#painters?.forEach(obj => { - if (isFunc(obj.processTooltipEvent)) - painters.push(obj); - }); - - if (pnt) - pnt.nproc = painters.length; - - painters.forEach(obj => { - const hint = obj.processTooltipEvent(pnt) || { user_info: null }; - hints.push(hint); - if (pnt?.painters) - hint.painter = obj; - }); - - return hints; - } - - /** @summary Changes canvas dark mode - * @private */ - changeDarkMode(mode) { - this.getCanvSvg().style('filter', (mode ?? settings.DarkMode) ? 'invert(100%)' : null); - } - - /** @summary Fill pad context menu - * @private */ - fillContextMenu(menu) { - const pad = this.getRootPad(true); - if (!pad) - return false; - - menu.header(`${pad._typename}::${pad.fName}`, `${urlClassPrefix}${pad._typename}.html`); - - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - - menu.addchk(pad.fGridx, 'Grid x', flag => { - pad.fGridx = flag ? 1 : 0; - this.interactiveRedraw('pad', `exec:SetGridx(${flag ? 1 : 0})`); - }); - menu.addchk(pad.fGridy, 'Grid y', flag => { - pad.fGridy = flag ? 1 : 0; - this.interactiveRedraw('pad', `exec:SetGridy(${flag ? 1 : 0})`); - }); - menu.sub('Ticks x'); - menu.addchk(pad.fTickx === 0, 'normal', () => { - pad.fTickx = 0; - this.interactiveRedraw('pad', 'exec:SetTickx(0)'); - }); - menu.addchk(pad.fTickx === 1, 'ticks on both sides', () => { - pad.fTickx = 1; - this.interactiveRedraw('pad', 'exec:SetTickx(1)'); - }); - menu.addchk(pad.fTickx === 2, 'labels on both sides', () => { - pad.fTickx = 2; - this.interactiveRedraw('pad', 'exec:SetTickx(2)'); - }); - menu.endsub(); - menu.sub('Ticks y'); - menu.addchk(pad.fTicky === 0, 'normal', () => { - pad.fTicky = 0; - this.interactiveRedraw('pad', 'exec:SetTicky(0)'); - }); - menu.addchk(pad.fTicky === 1, 'ticks on both sides', () => { - pad.fTicky = 1; - this.interactiveRedraw('pad', 'exec:SetTicky(1)'); - }); - menu.addchk(pad.fTicky === 2, 'labels on both sides', () => { - pad.fTicky = 2; - this.interactiveRedraw('pad', 'exec:SetTicky(2)'); - }); - menu.endsub(); - - menu.addchk(pad.fEditable, 'Editable', flag => { - pad.fEditable = flag; - this.interactiveRedraw('pad', `exec:SetEditable(${flag})`); - }); - - if (this.isCanvas()) { - menu.addchk(pad.TestBit(kIsGrayscale), 'Gray scale', flag => { - this.setGrayscale(flag); - this.interactiveRedraw('pad', `exec:SetGrayscale(${flag})`); - }); - } - - menu.sub('Border'); - menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], pad.fBorderMode + 1, v => { - pad.fBorderMode = v - 1; - this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); - }, 'Pad border mode'); - menu.addSizeMenu('Size', 0, 20, 2, pad.fBorderSize, v => { - pad.fBorderSize = v; - this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); - }, 'Pad border size'); - menu.endsub(); - - menu.addAttributesMenu(this); - - if (!this.isCanvas(true)) { - // if not online canvas - - const do_divide = arg => { - if (!arg || !isStr(arg)) - return; - // delete auto_canvas flag to prevent deletion - if (this.#iscan === 'auto') - this.#iscan = true; - this.cleanPrimitives(true); - if (arg === 'reset') - return; - const arr = arg.split('x'); - if (arr.length === 1) - this.divide(Number.parseInt(arr[0])); - else if (arr.length === 2) - this.divide(Number.parseInt(arr[0]), Number.parseInt(arr[1])); - }; - - if (isFunc(this.drawObject)) - menu.add('Build legend', () => this.buildLegend()); - - menu.sub('Divide', () => menu.input('Input divide arg', '2x2').then(do_divide), 'Divide on sub-pads'); - ['1x2', '2x1', '2x2', '2x3', '3x2', '3x3', '4x4', 'reset'].forEach(item => menu.add(item, item, do_divide)); - menu.endsub(); - - menu.add('Save to gStyle', () => { - this.fillatt?.saveToStyle(this.isCanvas() ? 'fCanvasColor' : 'fPadColor'); - gStyle.fPadGridX = pad.fGridx; - gStyle.fPadGridY = pad.fGridy; - gStyle.fPadTickX = pad.fTickx; - gStyle.fPadTickY = pad.fTicky; - gStyle.fOptLogx = pad.fLogx; - gStyle.fOptLogy = pad.fLogy; - gStyle.fOptLogz = pad.fLogz; - }, 'Store pad fill attributes, grid, tick and log scale settings to gStyle'); - - if (this.isCanvas()) { - menu.addSettingsMenu(false, false, arg => { - if (arg === 'dark') - this.changeDarkMode(); - }); - } - } - - menu.separator(); - - // keep typo in function name "actiavteMenuBar" while assigned from ROOT - if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) - menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); - - if (isFunc(this.hasEventStatus) && isFunc(this.activateStatusBar) && isFunc(this.canStatusBar)) { - if (this.canStatusBar()) - menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); - } - - if (this.enlargeMain() || (!this.isTopPad() && this.hasObjectsToDraw())) - menu.addchk(this.isPadEnlarged(), 'Enlarge ' + (this.isCanvas() ? 'canvas' : 'pad'), () => this.enlargePad()); - - const fname = this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad'); - menu.sub('Save as'); - const fmts = ['svg', 'png', 'jpeg', 'webp']; - if (internals.makePDF) - fmts.push('pdf'); - fmts.forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.isCanvas(), `${fname}.${fmt}`), `Produce ${fmt} image`)); - if (this.isCanvas()) { - menu.separator(); - menu.add(`${fname}.html`, () => this.saveAs('html', true, `${fname}.html`), 'Produce html with canvas display'); - menu.add(`${fname}0.html`, () => this.saveAs('html', false, `${fname}0.html`), 'Produce compact html with canvas display'); - menu.add(`${fname}.json`, () => this.saveAs('json', true, `${fname}.json`), 'Produce JSON with line spacing'); - menu.add(`${fname}0.json`, () => this.saveAs('json', false, `${fname}0.json`), 'Produce JSON without line spacing'); - } - menu.endsub(); - - return true; - } - - /** @summary Show pad context menu - * @private */ - async padContextMenu(evnt) { - if (evnt.stopPropagation) { - // this is normal event processing and not emulated jsroot event - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - this.getFramePainter()?.setLastEventPos(); - } - - return createMenu(evnt, this).then(menu => { - this.fillContextMenu(menu); - return this.fillObjectExecMenu(menu, ''); - }).then(menu => menu.show()); - } - - /** @summary Redraw TLegend object - * @desc Used when object attributes are changed to ensure that legend is up to date - * @private */ - async redrawLegend() { - return this.findPainterFor(null, '', clTLegend)?.redraw(); - } - - /** @summary Redraw pad means redraw ourself - * @return {Promise} when redrawing ready */ - async redrawPad(reason) { - const sync_promise = this.syncDraw(reason); - if (sync_promise === false) { - console.log(`Prevent redrawing of ${this.#pad.fName}`); - return false; - } - - let showsubitems = true; - const redrawNext = indx => { - while (indx < this.#painters.length) { - const sub = this.#painters[indx++]; - let res = 0; - if (showsubitems || isPadPainter(sub)) - res = sub.redraw(reason); - - if (isPromise(res)) - return res.then(() => redrawNext(indx)); - } - return true; - }; - - return sync_promise.then(() => { - if (this.isCanvas()) - this.createCanvasSvg(2); - else - showsubitems = this.createPadSvg(true); - return redrawNext(0); - }).then(() => { - this.addPadInteractive(); - this.confirmDraw(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - return true; - }); - } - - /** @summary redraw pad */ - redraw(reason) { - // intentionally do not return Promise to let re-draw sub-pads in parallel - this.redrawPad(reason); - } - - /** @summary Checks if pad should be redrawn by resize - * @private */ - needRedrawByResize() { - const elem = this.getPadSvg(); - if (!elem.empty() && elem.property('can3d') === constants$1.Embed3D.Overlay) - return true; - - return this.#painters.findIndex(objp => { - return isFunc(objp.needRedrawByResize) ? objp.needRedrawByResize() : false; - }) >= 0; - } - - /** @summary Check resize of canvas - * @return {Promise} with result or false */ - checkCanvasResize(size, force) { - if (this._ignore_resize || !this.isTopPad()) - return false; - - const sync_promise = this.syncDraw('canvas_resize'); - if (sync_promise === false) - return false; - - if ((size === true) || (size === false)) { - force = size; - size = null; - } - - if (isObject(size) && size.force) - force = true; - - if (!force) - force = this.needRedrawByResize(); - - let changed = false; - const redrawNext = indx => { - if (!changed || (indx >= this.#painters.length)) { - this.confirmDraw(); - return changed; - } - - return getPromise(this.#painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx + 1)); - }; - - // return sync_promise.then(() => this.ensureBrowserSize(this.#pad?.fCw, this.#pad?.fCh)).then(() => { - - return sync_promise.then(() => { - changed = this.createCanvasSvg(force ? 2 : 1, size); - - if (changed && this.isCanvas() && this.#pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { - if (this.#resize_tmout) - clearTimeout(this.#resize_tmout); - this.#resize_tmout = setTimeout(() => { - this.#resize_tmout = undefined; - if (isFunc(this.sendResized)) - this.sendResized(); - }, 1000); // long enough delay to prevent multiple occurrence - } - - // if canvas changed, redraw all its subitems. - // If redrawing was forced for canvas, same applied for sub-elements - return redrawNext(0); - }); - } - - /** @summary Update TPad object */ - updateObject(obj) { - const pad = this.getRootPad(); - if (!obj || !pad) - return false; - - pad.fBits = obj.fBits; - pad.fTitle = obj.fTitle; - - pad.fGridx = obj.fGridx; - pad.fGridy = obj.fGridy; - pad.fTickx = obj.fTickx; - pad.fTicky = obj.fTicky; - pad.fLogx = obj.fLogx; - pad.fLogy = obj.fLogy; - pad.fLogz = obj.fLogz; - - pad.fUxmin = obj.fUxmin; - pad.fUxmax = obj.fUxmax; - pad.fUymin = obj.fUymin; - pad.fUymax = obj.fUymax; - - pad.fX1 = obj.fX1; - pad.fX2 = obj.fX2; - pad.fY1 = obj.fY1; - pad.fY2 = obj.fY2; - - // this is main coordinates for sub-pad relative to canvas - pad.fAbsWNDC = obj.fAbsWNDC; - pad.fAbsHNDC = obj.fAbsHNDC; - pad.fAbsXlowNDC = obj.fAbsXlowNDC; - pad.fAbsYlowNDC = obj.fAbsYlowNDC; - - pad.fLeftMargin = obj.fLeftMargin; - pad.fRightMargin = obj.fRightMargin; - pad.fBottomMargin = obj.fBottomMargin; - pad.fTopMargin = obj.fTopMargin; - - pad.fFillColor = obj.fFillColor; - pad.fFillStyle = obj.fFillStyle; - pad.fLineColor = obj.fLineColor; - pad.fLineStyle = obj.fLineStyle; - pad.fLineWidth = obj.fLineWidth; - - pad.fPhi = obj.fPhi; - pad.fTheta = obj.fTheta; - pad.fEditable = obj.fEditable; - - if (this.isCanvas()) - this.checkSpecialsInPrimitives(obj); - - const fp = this.getFramePainter(); - fp?.updateAttributes(!fp.$modifiedNDC); - - if (!obj.fPrimitives) - return false; - - let isany = false, p = 0; - for (let n = 0; n < obj.fPrimitives.arr?.length; ++n) { - if (obj.fPrimitives.arr[n].$special) - continue; - while (p < this.#painters.length) { - const op = this.#painters[p++]; - if (!op._primitive) - continue; - if (op.updateObject(obj.fPrimitives.arr[n], obj.fPrimitives.opt[n])) - isany = true; - break; - } - } - - return isany; - } - - /** @summary add legend object to the pad and redraw it - * @private */ - async buildLegend(x1, y1, x2, y2, title, opt) { - const lp = this.findPainterFor(null, '', clTLegend); - - if (!lp && !isFunc(this.drawObject)) - return Promise.reject(Error('Not possible to build legend while module draw.mjs was not load')); - - const leg = lp?.getObject() ?? create$1(clTLegend), - pad = this.getRootPad(true); - - leg.fPrimitives.Clear(); - - for (let k = 0; k < this.#painters.length; ++k) { - const painter = this.#painters[k], - obj = painter.getObject(); - if (!obj || obj.fName === kTitle || obj.fName === 'stats' || painter.draw_content === false || - obj._typename === clTLegend || obj._typename === clTHStack || obj._typename === clTMultiGraph) - continue; - - const entry = create$1(clTLegendEntry); - entry.fObject = obj; - entry.fLabel = painter.getItemName(); - if ((opt === 'all') || !entry.fLabel) - entry.fLabel = obj.fName; - entry.fOption = ''; - if (!entry.fLabel) - continue; - - if (painter.lineatt?.used) - entry.fOption += 'l'; - if (painter.fillatt?.used) - entry.fOption += 'f'; - if (painter.markeratt?.used) - entry.fOption += 'p'; - if (!entry.fOption) - entry.fOption = 'l'; - - leg.fPrimitives.Add(entry); - } - - if (lp) - return lp.redraw(); - - const szx = 0.4; - let szy = leg.fPrimitives.arr.length; - // no entries - no need to draw legend - if (!szy) - return null; - if (szy > 8) - szy = 8; - szy *= 0.1; - - if ((x1 === x2) || (y1 === y2)) { - leg.fX1NDC = szx * pad.fLeftMargin + (1 - szx) * (1 - pad.fRightMargin); - leg.fY1NDC = (1 - szy) * (1 - pad.fTopMargin) + szy * pad.fBottomMargin; - leg.fX2NDC = 0.99 - pad.fRightMargin; - leg.fY2NDC = 0.99 - pad.fTopMargin; - if (opt === undefined) - opt = 'autoplace'; - } else { - leg.fX1NDC = x1; - leg.fY1NDC = y1; - leg.fX2NDC = x2; - leg.fY2NDC = y2; - } - leg.fFillStyle = 1001; - leg.fTitle = title ?? ''; - - return this.drawObject(this, leg, opt); - } - - /** @summary Add object painter to list of primitives - * @private */ - addObjectPainter(objpainter, lst, indx) { - if (objpainter && lst && lst[indx] && !objpainter.hasSnapId()) { - // keep snap id in painter, will be used for the - if (this.#painters.indexOf(objpainter) < 0) - this.#painters.push(objpainter); - - objpainter.assignSnapId(lst[indx].fObjectID); - const setSubSnaps = p => { - if (!p.isPrimary()) - return; - for (let k = 0; k < this.#painters.length; ++k) { - const sub = this.#painters[k]; - if (sub.isSecondary(p) && sub.getSecondaryId()) { - sub.assignSnapId(p.getSnapId() + '#' + sub.getSecondaryId()); - setSubSnaps(sub); - } - } - }; - - setSubSnaps(objpainter); - } - } - - /** @summary Process snap with style - * @private */ - processSnapStyle(snap) { - Object.assign(gStyle, snap.fSnapshot); - } - - /** @summary Process snap with colors - * @private */ - processSnapColors(snap) { - const ListOfColors = decodeWebCanvasColors(snap.fSnapshot.fOper), - o = this.getOptions(true); - - // set global list of colors - if (!o || o.GlobalColors) - adoptRootColors(ListOfColors); - - const greyscale = this.#pad?.TestBit(kIsGrayscale) ?? false, - colors = extendRootColors(null, ListOfColors, greyscale); - - // copy existing colors and extend with new values - this.#custom_colors = o?.LocalColors ? colors : null; - - // set palette - if (snap.fSnapshot.fBuf && (!o || !o.IgnorePalette)) { - const indexes = [], palette = []; - for (let n = 0; n < snap.fSnapshot.fBuf.length; ++n) { - indexes[n] = Math.round(snap.fSnapshot.fBuf[n]); - palette[n] = colors[indexes[n]]; - } - this.#custom_palette_indexes = indexes; - this.#custom_palette_colors = palette; - this.#custom_palette = new ColorPalette(palette, greyscale); - } else - this.#custom_palette = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; - } - - /** @summary Process snap with custom font - * @private */ - processSnapFont(snap) { - const arr = snap.fSnapshot.fOper.split(':'); - addCustomFont(Number.parseInt(arr[0]), arr[1], arr[2], arr[3]); - } - - /** @summary Process special snaps like colors or style objects - * @return {Promise} index where processing should start - * @private */ - processSpecialSnaps(lst) { - while (lst?.length) { - const snap = lst[0]; - - // gStyle object - if (snap.fKind === webSnapIds.kStyle) { - lst.shift(); - this.processSnapStyle(snap); - } else if (snap.fKind === webSnapIds.kColors) { - lst.shift(); - this.processSnapColors(snap); - } else if (snap.fKind === webSnapIds.kFont) { - lst.shift(); - this.processSnapFont(snap); - } else - break; - } - } - - /** @summary Function called when drawing next snapshot from the list - * @return {Promise} for drawing of the snap - * @private */ - async drawNextSnap(lst, pindx, indx) { - if (indx === undefined) { - indx = -1; - this.#num_primitives = lst?.length ?? 0; - } - - ++indx; // change to the next snap - - if (!lst || (indx >= lst.length)) - return this; - - const snap = lst[indx], is_subpad = (snap.fKind === webSnapIds.kSubPad); - - // gStyle object - if (snap.fKind === webSnapIds.kStyle) { - this.processSnapStyle(snap); - return this.drawNextSnap(lst, pindx, indx); // call next - } - - // list of colors - if (snap.fKind === webSnapIds.kColors) { - this.processSnapColors(snap); - return this.drawNextSnap(lst, pindx, indx); // call next - } - - // try to locate existing object painter, only allowed when redrawing pad snap - let objpainter, promise; - - while ((pindx !== undefined) && (pindx < this.#painters.length)) { - const subp = this.#painters[pindx++]; - if (subp.getSnapId() === snap.fObjectID) { - objpainter = subp; - break; - } else if (subp.getSnapId() && !subp.isSecondary() && !is_subpad) { - console.warn(`Mismatch in snapid between painter ${subp.getSnapId()} secondary: ${subp.isSecondary()} type: ${subp.getClassName()} and primitive ${snap.fObjectID} kind ${snap.fKind} type ${snap.fSnapshot?._typename}`); - break; - } - } - - if (objpainter) { - // painter exists - try to update drawing - if (is_subpad) - promise = objpainter.redrawPadSnap(snap); - else if (snap.fKind === webSnapIds.kObject) { // object itself - if (objpainter.updateObject(snap.fSnapshot, snap.fOption, true)) - promise = objpainter.redraw(); - } else if (snap.fKind === webSnapIds.kSVG) { // update SVG - if (objpainter.updateObject(snap.fSnapshot)) - promise = objpainter.redraw(); - } - } else if (is_subpad) { - const subpad = snap.fSnapshot; - - subpad.fPrimitives = null; // clear primitives, they just because of I/O - - const padpainter = new TPadPainter(this, subpad, snap.fOption, false, 'webpad'); - padpainter.assignSnapId(snap.fObjectID); - padpainter.is_active_pad = Boolean(snap.fActive); // enforce boolean flag - padpainter.#readonly = snap.fReadOnly ?? false; // readonly flag - padpainter.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive - padpainter.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features - - padpainter.processSpecialSnaps(snap.fPrimitives); // need to process style and colors before creating graph elements - - padpainter.createPadSvg(); - - if (padpainter.matchObjectType(clTPad) && snap.fPrimitives.length) - padpainter.addPadButtons(true); - pindx++; // new painter will be add - promise = padpainter.drawNextSnap(snap.fPrimitives).then(() => padpainter.addPadInteractive()); - } else if (((snap.fKind === webSnapIds.kObject) || (snap.fKind === webSnapIds.kSVG)) && (snap.fOption !== '__ignore_drawing__')) { - // here the case of normal drawing - pindx++; // new painter will be add - promise = this.drawObject(this, snap.fSnapshot, snap.fOption).then(objp => this.addObjectPainter(objp, lst, indx)); - } - - return getPromise(promise).then(() => this.drawNextSnap(lst, pindx, indx)); // call next - } - - /** @summary Return painter with specified id - * @private */ - findSnap(snapid) { - if (this.getSnapId() === snapid) - return this; - - if (!this.#painters) - return null; - - for (let k = 0; k < this.#painters.length; ++k) { - let sub = this.#painters[k]; - - if (isFunc(sub.findSnap)) - sub = sub.findSnap(snapid); - else if (sub.getSnapId() !== snapid) - sub = null; - - if (sub) - return sub; - } - - return null; - } - - /** @summary Redraw pad snap - * @desc Online version of drawing pad primitives - * for the canvas snapshot contains list of objects - * as first entry, graphical properties of canvas itself is provided - * in ROOT6 it also includes primitives, but we ignore them - * @return {Promise} with pad painter when drawing completed - * @private */ - async redrawPadSnap(snap) { - if (!snap?.fPrimitives) - return this; - - this.is_active_pad = Boolean(snap.fActive); // enforce boolean flag - this.#readonly = snap.fReadOnly ?? false; // readonly flag - this.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive - this.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features - - const first = snap.fSnapshot; - first.fPrimitives = null; // primitives are not interesting, they are disabled in IO - - // if there are execs in the pad, deliver events to the server - this.#deliver_move_events = this.#has_execs || first.fExecs?.arr?.length; - - if (!this.hasSnapId()) { - // first time getting snap, create all gui elements first - - this.assignSnapId(snap.fObjectID); - - this.assignObject(first); - this.#pad = first; // first object is pad - - // this._setFixedSize(true); - - // if canvas size not specified in batch mode, temporary use 900x700 size - if (this.isBatchMode() && (!first.fCw || !first.fCh)) { - first.fCw = 900; - first.fCh = 700; - } - - // case of ROOT7 with always dummy TPad as first entry - if (!first.fCw || !first.fCh) - this._setFixedSize(false); - - const mainid = this.selectDom().attr('id'); - - if (!this.isBatchMode() && this.online_canvas && !this.use_openui && !this.brlayout && mainid && isStr(mainid) && !getHPainter()) { - this.brlayout = new BrowserLayout(mainid, null, this); - this.brlayout.create(mainid, true); - this.setDom(this.brlayout.drawing_divid()); // need to create canvas - registerForResize(this.brlayout); - } - - this.processSpecialSnaps(snap.fPrimitives); - - this.createCanvasSvg(0); - - if (!this.isBatchMode()) - this.addPadButtons(true); - - if (typeof snap.fHighlightConnect !== 'undefined') - this._highlight_connect = snap.fHighlightConnect; - - let pr = Promise.resolve(true); - - if (isStr(snap.fScripts) && snap.fScripts) { - let src = '', m = null; - - if (snap.fScripts.indexOf('modules:') === 0) - m = snap.fScripts.slice(8).split(';'); - else if (snap.fScripts.indexOf('load:') === 0) - src = snap.fScripts.slice(5).split(';'); - else if (snap.fScripts.indexOf('assert:') === 0) - src = snap.fScripts.slice(7); - - pr = (m !== null) ? loadModules(m) : (src ? loadScript(src) : injectCode(snap.fScripts)); - } - - return pr.then(() => this.drawNextSnap(snap.fPrimitives)).then(() => { - if (isFunc(this.onCanvasUpdated)) - this.onCanvasUpdated(this); - return this; - }); - } - - this.updateObject(first); // update only object attributes - - // apply all changes in the object (pad or canvas) - if (this.isCanvas()) - this.createCanvasSvg(2); - else - this.createPadSvg(true); - - let missmatch = false; - - // match painters with new list of primitives - if (!snap.fWithoutPrimitives) { - let i = 0, k = 0; - while (k < this.#painters.length) { - const sub = this.#painters[k]; - - // skip check secondary painters or painters without snapid - if (!sub.hasSnapId() || sub.isSecondary()) { - k++; - continue; // look only for painters with snapid - } - - if (i >= snap.fPrimitives.length) - break; - - const prim = snap.fPrimitives[i]; - - // only real objects drawing checked for existing painters - if ((prim.fKind !== webSnapIds.kSubPad) && (prim.fKind !== webSnapIds.kObject) && (prim.fKind !== webSnapIds.kSVG)) { - i++; - continue; // look only for primitives of real objects - } - - if (prim.fObjectID === sub.getSnapId()) { - i++; - k++; - } else { - missmatch = true; - break; - } - } - - let cnt = 1000; - // remove painters without primitives, limit number of checks - while (!missmatch && (k < this.#painters.length) && (--cnt >= 0)) { - if (this.removePrimitive(k) === -111) - missmatch = true; - } - if (cnt < 0) - missmatch = true; - } - - if (missmatch) { - const old_painters = this.#painters; - this.#painters = []; - old_painters.forEach(objp => objp.cleanup()); - this.setMainPainter(undefined, true); - if (isFunc(this.removePadButtons)) - this.removePadButtons(); - this.addPadButtons(true); - } - - return this.drawNextSnap(snap.fPrimitives, missmatch ? undefined : 0).then(() => { - this.addPadInteractive(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - if (isFunc(this.onCanvasUpdated)) - this.onCanvasUpdated(this); - return this; - }); - } - - /** @summary Deliver mouse move or click event to the web canvas - * @private */ - deliverWebCanvasEvent(kind, x, y, snapid) { - if (!this.is_active_pad || this.doingDraw() || x === undefined || y === undefined) - return; - if ((kind === 'move') && !this.#deliver_move_events) - return; - const cp = this.getCanvPainter(); - if (!cp || !cp.canSendWebsocket(2) || cp.isReadonly()) - return; - - const msg = JSON.stringify([this.getSnapId(), kind, x.toString(), y.toString(), snapid || '']); - cp.sendWebsocket(`EVENT:${msg}`); - } - - /** @summary Create image for the pad - * @desc Used with web-based canvas to create images for server side - * @return {Promise} with image data, coded with btoa() function - * @private */ - async createImage(format) { - if ((format === 'png') || (format === 'jpeg') || (format === 'html') || (format === 'svg') || (format === 'webp') || (format === 'pdf')) { - return this.produceImage(true, format).then(res => { - if (!res || (format === 'svg') || (format === 'html')) - return res; - const separ = res.indexOf('base64,'); - return (separ > 0) ? res.slice(separ + 7) : ''; - }); - } - return ''; - } - - /** @summary Collects pad information for TWebCanvas - * @desc need to update different states - * @private */ - getWebPadOptions(arg, cp) { - let is_top = (arg === undefined), elem = null, scan_subpads = true; - // no any options need to be collected in readonly mode - if (is_top && this.isReadonly()) - return ''; - if (arg === 'only_this') { - is_top = true; - scan_subpads = false; - } else if (arg === 'with_subpads') { - is_top = true; - scan_subpads = true; - } - if (is_top) - arg = []; - if (!cp) - cp = this.isCanvas() ? this : this.getCanvPainter(); - - if (this.getSnapId()) { - elem = { - _typename: 'TWebPadOptions', snapid: this.getSnapId(), - active: Boolean(this.is_active_pad), - cw: 0, ch: 0, w: [], - bits: 0, primitives: [], - logx: this.#pad.fLogx, logy: this.#pad.fLogy, logz: this.#pad.fLogz, - gridx: this.#pad.fGridx, gridy: this.#pad.fGridy, - tickx: this.#pad.fTickx, ticky: this.#pad.fTicky, - mleft: this.#pad.fLeftMargin, mright: this.#pad.fRightMargin, - mtop: this.#pad.fTopMargin, mbottom: this.#pad.fBottomMargin, - xlow: 0, ylow: 0, xup: 1, yup: 1, - zx1: 0, zx2: 0, zy1: 0, zy2: 0, zz1: 0, zz2: 0, phi: 0, theta: 0 - }; - - if (this.isCanvas()) { - elem.bits = this.getStatusBits(); - elem.cw = this.getPadWidth(); - elem.ch = this.getPadHeight(); - elem.w = [window.screenLeft, window.screenTop, window.outerWidth, window.outerHeight]; - } else if (cp) { - const cw = cp.getPadWidth(), ch = cp.getPadHeight(), rect = this.getPadRect(); - elem.cw = cw; - elem.ch = ch; - elem.xlow = rect.x / cw; - elem.ylow = 1 - (rect.y + rect.height) / ch; - elem.xup = elem.xlow + rect.width / cw; - elem.yup = elem.ylow + rect.height / ch; - } - - if ((this.#pad.fTheta !== 30) || (this.#pad.fPhi !== 30)) { - elem.phi = this.#pad.fPhi; - elem.theta = this.#pad.fTheta; - } - - if (this.getPadRanges(elem)) - arg.push(elem); - else - console.log(`fail to get ranges for pad ${this.#pad.fName}`); - } - - this.#painters.forEach(sub => { - if (isFunc(sub.getWebPadOptions)) { - if (scan_subpads) - sub.getWebPadOptions(arg, cp); - } else { - const opt = createWebObjectOptions(sub); - if (opt) - elem.primitives.push(opt); - } - }); - - if (is_top) - return toJSON(arg); - } - - /** @summary returns actual ranges in the pad, which can be applied to the server - * @private */ - getPadRanges(r) { - if (!r) - return false; - - const fp = this.getFramePainter(), - p = this.getPadSvg(); - - r.ranges = fp?.ranges_set ?? false; // indicate that ranges are assigned - - r.ux1 = r.px1 = r.ranges ? fp.scale_xmin : 0; // need to initialize for JSON reader - r.uy1 = r.py1 = r.ranges ? fp.scale_ymin : 0; - r.ux2 = r.px2 = r.ranges ? fp.scale_xmax : 0; - r.uy2 = r.py2 = r.ranges ? fp.scale_ymax : 0; - r.uz1 = r.ranges ? (fp.scale_zmin ?? 0) : 0; - r.uz2 = r.ranges ? (fp.scale_zmax ?? 0) : 0; - - if (fp) { - if (fp.zoom_xmin !== fp.zoom_xmax) { - r.zx1 = fp.zoom_xmin; - r.zx2 = fp.zoom_xmax; - } - - if (fp.zoom_ymin !== fp.zoom_ymax) { - r.zy1 = fp.zoom_ymin; - r.zy2 = fp.zoom_ymax; - } - - if (fp.zoom_zmin !== fp.zoom_zmax) { - r.zz1 = fp.zoom_zmin; - r.zz2 = fp.zoom_zmax; - } - } - - if (!r.ranges || p.empty()) - return true; - - // calculate user range for full pad - const func = (log, value, err) => { - if (!log) - return value; - if (value <= 0) - return err; - if (log === 3) - return Math.log(value); - value = Math.log10(value); - if (log > 1) - value /= Math.log10(log); - return value; - }, frect = fp.getFrameRect(); - - r.ux1 = func(fp.logx, r.ux1, 0); - r.ux2 = func(fp.logx, r.ux2, 1); - - let k = (r.ux2 - r.ux1) / (frect.width || 10); - r.px1 = r.ux1 - k * frect.x; - r.px2 = r.px1 + k * this.getPadWidth(); - - r.uy1 = func(fp.logy, r.uy1, 0); - r.uy2 = func(fp.logy, r.uy2, 1); - - k = (r.uy2 - r.uy1) / (frect.height || 10); - r.py1 = r.uy1 - k * frect.y; - r.py2 = r.py1 + k * this.getPadHeight(); - - return true; - } - - /** @summary Show context menu for specified item - * @private */ - itemContextMenu(name) { - const rrr = this.getPadSvg().node().getBoundingClientRect(), - evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; - - // use timeout to avoid conflict with mouse click and automatic menu close - if (name === 'pad') - return postponePromise(() => this.padContextMenu(evnt), 50); - - let selp = null, selkind; - - switch (name) { - case 'xaxis': - case 'yaxis': - case 'zaxis': - selp = this.getFramePainter(); - selkind = name[0]; - break; - case 'frame': - selp = this.getFramePainter(); - break; - default: { - const indx = parseInt(name); - if (Number.isInteger(indx)) - selp = this.#painters[indx]; - } - } - - if (!isFunc(selp?.fillContextMenu)) - return; - - return createMenu(evnt, selp).then(menu => { - const offline_menu = selp.fillContextMenu(menu, selkind); - if (offline_menu || selp.getSnapId()) - return selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); - }); - } - - /** @summary Save pad as image - * @param {string} kind - format of saved image like 'png', 'svg' or 'jpeg' - * @param {boolean} full_canvas - does complete canvas (true) or only frame area (false) should be saved - * @param {string} [filename] - name of the file which should be stored - * @desc Normally used from context menu - * @example - * import { getElementCanvPainter } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; - * let canvas_painter = getElementCanvPainter('drawing_div_id'); - * canvas_painter.saveAs('png', true, 'canvas.png'); */ - saveAs(kind, full_canvas, filename) { - if (!filename) - filename = (this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad')) + '.' + kind; - - this.produceImage(full_canvas, kind).then(imgdata => { - if (!imgdata) - return console.error(`Fail to produce image ${filename}`); - - if ((browser.qt6 || browser.cef3) && this.getSnapId()) { - console.warn(`sending file ${filename} to server`); - let res = imgdata; - if (kind !== 'svg') { - const separ = res.indexOf('base64,'); - res = (separ > 0) ? res.slice(separ + 7) : ''; - } - if (res) - this.getCanvPainter()?.sendWebsocket(`SAVE:${filename}:${res}`); - } else { - let prefix = ''; - switch (kind) { - case 'svg': prefix = prSVG; break; - case 'json': prefix = prJSON; break; - case 'html': prefix = prHTML; break; - } - saveFile(filename, prefix ? prefix + encodeURIComponent(imgdata) : imgdata); - } - }); - } - - /** @summary Search active pad - * @return {object} pad painter for active pad */ - findActivePad() { - let active_pp; - this.forEachPainterInPad(pp => { - if (pp.is_active_pad && !active_pp) - active_pp = pp; - }, 'pads'); - return active_pp; - } - - /** @summary Produce image for the pad - * @return {Promise} with created image */ - async produceImage(full_canvas, file_format, args) { - if ((file_format === 'json') || (file_format === 'html')) { - const json = isFunc(this.produceJSON) ? this.produceJSON(full_canvas ? 2 : 0) : ''; - if (!json || (file_format === 'json')) - return json; - let url = exports.source_dir; - if (url.indexOf('http://localhost') === 0) { - url = 'https://root.cern/js/'; - url += /^\d+\.\d+\.\d+$/.test(version_id) ? version_id : 'latest'; - } - return '\n' + - '\n' + - '\n' + - ' \n' + - ' Dsiplay ROOT canvas\n' + - ` \n` + - ' - - After this several modules are defined and can be used with syntax like: - - require(['JSRootPainter'], function(jsroot) { /*any user code*/}); - - Also inside JSROOT require.js used to load all dependencies. - - -## Changes in 3.6 -1. Try to provide workaround for websites where require.js already loaded. - This makes problem by direct loading of jquery and jquery-ui -2. Provide workaround for older version of jquery-ui -3. Prompt for input of command arguments -4. After command execution one could automatically reload hierarchy (_hreload property) or - update view of displayed object (_update_item property) -5. Use HierarchyPainter for implementing draw.htm. This let us handle - all different kinds of extra attributes in central place -6. Fix problem in tabs layout - new tab should be add to direct child -7. When drawing several tabs, activate frame before drawing - only then - real frame size will be set -8. Fix problem with GetBBox - it only can be used for visible elements in mozilla. -9. Support drawing of fit parameters in stat box, use (as far as possible) stat and - fit format for statistic display -10. Implement 'g' formatting kind for stat box output - one need to checks - significant digits when producing output. -11. Support new draw options for TGraph: 'C', 'B1', '0', '2', '3', '4', '[]' -12. Primary support for STL containers in IO part. Allows to read ROOT6 TF1. -13. Full support of TGraphBentErrors -14. Support objects drawing from JSON files in default user interface, including - monitoring. One could open file from link like: https://root.cern.ch/js/dev/?json=demo/canvas_tf1.json -15. Introduce JSROOT.FFormat function to convert numeric values into string according - format like 6.4g or 5.7e. Used for statistic display. - - -## Changes in 3.5 -1. Fix error in vertical text alignment -2. Many improvements in TPaletteAxis drawing - draw label, avoid too large ticks. -3. Fix error with col drawing - bin with maximum value got wrong color -4. Test for existing jquery.js, jquery-ui.js and d3.js libraries, reuse when provided -5. Fix several I/O problems; now one could read files, produced in Geant4 -6. Implement 'e2' drawing option for TH1 class, - use by default 'e' option when TH1 has non-empty fSumw2 -7. Reuse statistic from histogram itself, when no axis selection done -8. Support log/lin z scale for color drawing -9. Implement interactive z-scale selection on TPaletteAxis -10. Allow to redraw item with other draw options (before one should clear drawings) -11. Several improvements in THttpServer user interface - repair hierarchy reload, - hide unsupported context menu entries, status line update - - -## Changes in 3.4 -1. Support usage of minimized versions of .js and .css files. - Minimized scripts used by default on web servers. -2. Implement JSROOT.extend instead of jQuery.extend, reduce - usage of jquery.js in core JSROOT classes -3. Implement main graphics without jquery at all, - such mode used in `nobrowser` mode. -4. Provide optional latex drawing with MathJax SVG. - TMathText always drawn with MathJax, - other classes require `mathjax` option in URL -5. Improve drawing of different text classes, correctly handle - their alignment and scaling, special handling for IE -6. Fix error with time axes - time offset was not correctly interpreted - - -## Changes in 3.3 -1. Use d3.time.scale for display of time scales -2. Within JSRootCore.js script URL one could specify JSROOT - functionality to be loaded: '2d', '3d', 'io', 'load', 'onload'. - Old method with JSROOT.AssertPrerequisites will also work. -3. With THttpServer JSROOT now provides simple control functionality. - One could publish commands and execute them from the browser -4. One could open several ROOT files simultaneously -5. Add 'simple' layout - drawing uses full space on the right side -6. Allow to open ROOT files in online session (via url parameter) -7. One could monitor simultaneously objects from server and root files -8. Implement 'autocol' draw option - when superimposing histograms, - their line colors will be automatically assigned -9. Implement 'nostat' draw option - disabled stat drawing -10. Using '_same_' identifier in item name, one can easily draw or superimpose - similar items from different files. Could be used in URL like: - `...&files=[file1.root,file2.root]&items=[file1.root/hpx, file2.root/_same_]` - `...&files=[file1.root,file2.root]&item=file1.root/hpx+file2.root/_same_` - Main limitation - file names should have similar length. -11. When 'autozoom' specified in draw options, histogram zoomed into - non-empty content. Same command available via context menu. -12. Item of 'Text' kind can be created. It is displayed as - plain text in the browser. If property 'mathjax' specified, - MathJax.js library will be loaded and used for rendering. - See httpcontrol.C macro for example. -13. When using foreignObject, provide workaround for absolute positioning - problem in Chrome/Safari, see - - -## Changes in 3.2 -1. Support JSON objects embedding in html pages, produced by THttpServer -2. For small resize of canvas use autoscale functionality of SVG. Only when - relative changes too large, redraw complete canvas again. -3. Use touch-punch.min.js to process touch events with jquery-ui -4. Even when several TH1/TGraph/TF1 objects with fill attribute overlap each other, - one able to get tooltip for underlying objects -5. Use jquery-ui menu for context menu -6. From context menu one could select several options for drawing -7. Provide user interface for executing TTree::Draw on THttpServer -8. 3D graphic (three.js) works only with IE11 - - -## Changes in 3.1 -1. Correctly show tooltips in case of overlapped objects -2. Implement JSROOT.create() method to create supported - in JavaScript ROOT classes like TH1 or TGraph -3. Fix problem with JSROOT.draw in HTML element with zero width (display:none) -4. Provide possibility to load user scripts with JSROOT.BuildSimpleGUI - and JSROOT.AssertPrerequisites, also with main index.htm -5. Support of TCutG drawing -6. Implement hierarchy display (former dtree) with jQuery -7. Fix several problems in drawing optimization -8. Implement dragging objects from hierarchy browser into existing canvas - to superimpose several objects -9. Implement col2 and col3 draw options, using html5 canvas -10. Support 'p' and 'p0' draw options for TH1 class - - -## Development of version 3.0 - -### November 2014 -1. Better font size and position in pave stats -2. Resize/move of element only inside correspondent pad -3. Adjust of frame size when Y-axis exceed pad limits -4. Correct values in tooltip for THStack -5. Exclude drawing of markers from TGraph outside visible range -6. Drawing of canvas without TFrame object -7. Many other small bug fixes and improvements, thanks to Maximilian Dietrich - -### October 2014 -1. Add "shortcut icon" -2. Add demo of online THttpServer - shell script copies data from - running httpserver.C macro on Apache webserver -3. Evaluate 'monitoring' parameter for online server like: - - Parameter defines how often displayed objects should be updated. -4. Implement 'opt' and 'opts' URL parameters for main page. -5. Show progress with scripts loading in the browser window -6. When one appends "+" to the filename, its content read completely with first I/O operation. -7. Implement JS custom streamer for TCanvas, restore aspect ratio when drawing -8. Major redesign of drawing classes. Resize and update of TCanvas are implemented. - All major draw functions working with HTML element id as first argument. -9. Extract 3D drawings into separate JSRoot3DPainter.js script -10. Use newest three.min.js (r68) for 3D drawings, solves problem with Firefox. -11. Introduce generic list of draw functions for all supported classes. -12. Add possibility to 'expand' normal objects in the hierarchy browser. - For instance, this gives access to single elements of canvas, - when whole canvas cannot be drawn. -13. Correct usage of colors map, provided with TCanvas. -14. Introduce JSROOT.redraw() function which is capable to create or update object drawing. -15. In main index.htm page browser can be disabled (nobrowser parameter) and - page can be used to display only specified items from the file -16. Add support of TPolyMarker3D in binary I/O - -### September 2014 -1. First try to handle resize of the browser, - for the moment works only with collapsible layout -2. Also first try to interactively move separation line between - browser and drawing field. -3. Small fix of minor ticks drawing on the axis -4. Introduce display class for MDI drawing. Provide two implementations - - 'collapsible' for old kind and 'tabs' for new kinds. -5. Adjust size of color palette drawing when labels would take more place as provided. -6. Add correct filling of statistic for TProfile, - fix small problem with underflow/overflow bins. -7. Provide way to select display kind ('collapsible', 'tabs') in the simple GUI. -8. Implement 'grid' display, one could specify any number of division like - 'grid 3x3' or 'grid 4x2'. -9. MDI display object created at the moment when first draw is performed. -10. Introduce painter class for TCanvas, support resize and update of canvas drawing -11. Resize almost works for all layouts and all objects kinds. -12. Implement JSROOT.GetUrlOption to extract options from document URL. -13. Provide example fileitem.htm how read and display item from ROOT file. -14. In default index.htm page one could specify 'file', 'layout', - 'item' and 'items' parameters like: - -15. Support direct reading of objects from sub-sub-directories. -16. Introduce demo.htm, which demonstrates online usage of JSROOT. -17. One could use demo.htm directly with THttpServer providing address like: - -18. Also for online server process url options like 'item', 'items', 'layout' -19. Possibility to generate URL, which reproduces opened page with layout and drawn items - -### August 2014 -1. All communication between server and browser done with JSON format. -2. Fix small error in dtree.js - one should always set - last sibling (_ls) property while tree can be dynamically changed. -3. In JSRootCore.js provide central function, which handles different kinds - of XMLHttpRequest. Use only async requests, also when getting file header. -4. Fully reorganize data management in file/tree/directory/collection hierarchical - display. Now complete description collected in HPainter class and decoupled from - visualization, performed with dTree.js. -5. Remove all global variables from the code. -6. Automatic scripts/style loading handled via JSROOT.loadScript() function. - One can specify arbitrary scripts list, which asynchronously loaded by browser. -7. Method to build simple GUI changed and more simplified :). The example in index.htm. - While loadScript and AssertPrerequisites functions moved to JSROOT, one - can easily build many different kinds of GUIs, reusing provided JSRootCore.js functions. -8. In example.htm also use AssertPrerequisites to load necessary scripts. - This helps to keep code up-to-date even by big changes in JavaScript code. -9. Provide monitoring of online THttpServer with similar interface as for ROOT files. -10. Fix several errors in TKey Streamer, use member names as in ROOT itself. -11. Keep the only version identifier JSROOT.version for JS code -12. One can specify in JSROOT.AssertPrerequisites functionality which is required. - One could specify '2d', 'io' (default) or '3d'. -13. Use new AssertPrerequisites functionality to load only required functionality. -14. When displaying single element, one could specify draw options and monitor property like: - - Such link is best possibility to integrate display into different HTML pages, - using `` -15. Remove 'JSROOTIO.' prefix from _typename. Now real class name is used. -16. Use in all scripts JSROOT as central 'namespace' -17. Introduce context menu in 3D, use it for switch between 2D/3D modes -18. Use own code to generate hierarchical structure in HTML, replace dtree.js which is - extremely slow for complex hierarchies. Dramatically improve performance for - structures with large (~1000) number of items. -19. Deliver to the server title of the objects, display it as hint in the browser. -20. Better handling of special characters in the hierarchies - allows to display - symbols like ' or " in the file structure. - -### July 2014 -1. Migration to d3.v3.js and jQuery v2.1.1 -2. Fix errors in filling of histogram statbox -3. Possibility of move and resize of statbox, title, color palete -4. Remove many (not all) global variables -5. Example with direct usage of JSRootIO graphics -6. Example of inserting ROOT graphics from THttpServer into `` - -### May 2014 -1. This JSRootIO code together with THttpServer class included - in ROOT repository - -### March 2014 -1. Introduce TBuffer class, which plays similar role - as TBuffer in native ROOT I/O. Simplifies I/O logic, - reduce duplication of code in many places, fix errors. - Main advantage - one could try to keep code synchronous with C++. -2. Avoid objects cloning when object referenced several times. -3. Treat special cases (collection, arrays) in one place. - This is major advantage, while any new classes need to be implemented only once. -4. Object representation, produced by JSRootIO is similar to - objects, produced by TBufferJSON class. By this one can exchange - I/O engine and use same JavaSctript graphic for display. -5. More clear functions to display different elements of the file. - In the future functions should be fully separated from I/O part - and organized in similar way as online part. -6. Eliminate usage of gFile pointer in the I/O part. -7. Provide TBufferJSON::JsonWriteMember method. It allows to stream any - selected data member of the class. Supported are: - basic data types, arrays of basic data types, TString, TArray classes. - Also any object as data member can be streamed. -8. TRootSniffer do not creates sublevels for base classes diff --git a/js/files/draw.htm b/js/files/draw.htm deleted file mode 100644 index 8710b23d0581d..0000000000000 --- a/js/files/draw.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - Draw of single element - - - - -
- loading ... -
- - - diff --git a/js/files/online.htm b/js/files/online.htm deleted file mode 100644 index 578c5227178c7..0000000000000 --- a/js/files/online.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - Online server - - - - -
- loading ... -
- - - diff --git a/js/img/RootIcon.ico b/js/img/RootIcon.ico deleted file mode 100644 index 8bce131f7c53c24170558efd29304a5ebb0a2f9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32038 zcmd6w37BM6b;sW@1LF#!85S9)8yto~7Dq@>h-OB%geW9Lc2Hyqf_^H31PmZ8Ae$0{ z0xB5+6%!B`LXcgcB?g0_1g3lT>FJs2ec$)4m*4-qcZ=%ks;+ufuc|rU_wW1OtGh4f zo_p@Ow>@u;H_xlA^l&S^2j1s-$9bOjK3u(DG|%(8cwV+F{Cz*qJ9UBQtymGfFFnfh z5=VO8Qo<00$7c(NLnQV$(jVXVJunCC4bB92gI7Q!=nc_?`zlxoJ_jnm4j@umsh$-U zDBT0Vji3gMfLtU$YrrkwAP^}Z_*c5ZuHXt#4>C|9!?$Ulw)5p`@Mpl{O)X>kv-5+y z6#N&M0MlrEw9Xguq)Gfe1ymPV^Wc8NJrukKvQWbBZNAZqrQcrnSwAs3=zfagD$~~jjV;nnc!|m319{#8 zW~9Eh@B584m45TugUH7x{E0CQ`+}%}P|JYVSi6VU-SCZ@2D=~jCnq|yMm90J&2QcC zNxyB=@qSO!HGb2Y5Bp<7YR-a59Y}RhF*aON)6@KIzoY*1{y@i1!*We++y3@DDf`~` z`*815epBs-{Gr}o7o0c&*Mr4CJayPljQb-4|KoRVy}-)Y*Ls^jKC+3Q5|*FVjYm=b zzq04C;ni0D{`NcgyWf(@ zVQtXUbd}%N_OJMPt50+{ULK5r@zyM1^ZML@FU_GdQi8sbUe5mZA2SXQ^y#}ZhRyM# z=E|JuXNqIE?-}~w5?^i+r1T|6M5eLfTH{0g9aFk*)Of^j{tz{g{7;Uz*?8=3yt0ts zF3f5kR=SrcZXz+_cWgNwoZ%-D8p;LHF9~%vP&-7X?#6FY2P>!xb>ql1Mu-bRC8&)Y zng9bGE9nCt@yABi6%u?SftCQxzmgRr({SH!7y}3R!~HKr4oiUBK{yASOtLL%Br3Pc zKhSw^)S#NU4+Bp2kb5~E-X7@ux%kx!jF&woC;F1&Hwn5tNJf4= z?+oBkbM`>z&-{U|`~AVL|3EH&i23tT@VGzR`y}Z-3!e8!`d{Mx>DumwuOB+p{eZXb z#bomKkdg<<$VCY@S@N$j_6e8H)YOAF_ZzP2;b{=5Kj1$TDURoX$$Pk&l1_fp?;*5i zx(3WJKlAI%)XdM?$3wtvK=N8Xj8=S2yhQ@TM?s|ZXL_~>uY4J)fqL{5%9) z9P@Zqy2-GTaoaY38or@s%S!5dJHl5!H9oSTRqF&t;y2>IIo84|=drBS?$LThbLQyK zpIApU`<>gqXuPYZ3D%zyuZus$RiC3zOCH`}JhgM{7vQ&76HkK=Ph;)Ylj42aV?AHg zMgfu5=-?|RTT6}+@6MsmkbLr1co$FT_O^TnKKydp*D0?GuYQt|20HG6Z$F&zxA3F# ze{VJq;_>}$Kg{;CzTXejC)@OrPLkoZ&dRdP9yarwSnfFk%9lFKmITE z`$yIWtJTimCxDFwT=m6Yfn*=#nYRgBM~n`>YHMocXMAK`f;d(&f9~Gj@e}oZ2gUI{ zpnPQ`$<<1CcVOY%Kn&k^!aW+OU)2M(kDzDO)v-W+TrjVGzUNiXg{RC(e#-eEzY3$U z;xo&@7eJEFEaoL9QeH!-u0I4$1~-Anf%x@yps^@4fYsm$a4R?s><6sOxH07_cQi^X z#2&b3fL{WQ74g%2Npnygco2LZ>;;nYUG7{&_d)zF-wS!Z9H@>p_ZCZHu(jZ7PzfxZ z=sC)1`bFtpC1Xn86*H|PzC%F9GKAm7 zBQ+mIBjiiTL(02!_RVV#LB0`7kDdOL7b@b7hV-O?o``J~s5G-@NWn^8Z1`?V0Sw{H^V`*%~Qhh*&;eM=X|ac!t-t z?czP#HvP>TJ?K}pR+FA9KT4P`PW|5L=DQm%4R{cHTSe%Jb0sg`NG0&l39_Hqe>VEA zaqXuyuRF{a9~|kg&a}>+67SgXTK0DTg8c4@Uhi!D53|<1-QHU_9$ToMc}f~tUL=2A z2-F7ifbksZ2DHyjTTq`%El)!|kFnP6#>;f-ZMnhvz3mO+PbB}1H5JsuM_E&;no34W z@Et&FkBzCROt42K8K7nT$H+^i-@f_pY~3#%fTQt|jkd?8y4T*aThFWiX|GjfQX7_# zv_3i*Xx)@SU<`B*((x)k;wh~gkGB3H-9c~bt@cgjlU~Hp#8{*4(`igd|64-RAQivY znlC#sH%V6^xvWwA5k8}KX?^}HkP$7-)ra)kC_OH6kWzwAzOQm{|_DEg}t zsCVWmn`}K2^`ZQ1Re2{GYR_Bc;PIOYt$=IA| zEuj6D64GEP@-KUd)(y*zKc(`f5|l&v7axt4G!JX9*?3UP% z>w}Y6n+?Pn_a-9S7r6Y(y(-QG6qoEUBy-EAQ2UlK(#^zWd6yC*e6GZ;i7^ecbCSJ} zY>u=pi-}bCOF>eHrRNY#ma*)q5Dh)v?1hej2Q6Y{Uc(h6CY}NB2QJ%5 z7Z0utwC}M7#6q&wYIt)u>qD}qZr!-T);(>Tjx$}C)^qK3CxMeq=93;qWtLqsdMR`{ zUtv9WDf`vm-i$ur@WJi}J@l2ye5XE;VONGv^V||g0kkI|y_jsk1KZ^5X^Wef6TWM9 z5P`kdoj!Zkq0QHQ(`>V_rReI~e#z;|-{#-XyrCSHVGi!+Kqr`O>LmjggSjBJy0bpZ zAJ_qCUW>E`ga0^5_4h>}9b4A?X8J+?f!aZPXxd9FH%TTt8K@pJWijVBg$r>o?xP@I z#V%e8CMjH=1cw1jKW9c~@ZjRW-5E&ckp5i^trVoW=~A#OC_3+{#d7iCO75rvDu)9RZs)psarNZtEm@C5WcBLm*$?Vnc3LYy6-e4?=|dUF^&MQTVHbhp zz}LaQf#-m9Q*}UNMbPt`;CXNlxEvf0G=6phMfJrp6xs3@=2Q7S5QwKg2*eMiXc9lm zm*lq}Yyhjk*MM{a8q;s9x^VfyeLpw_JOr9S0>nj%s|`E`&H@X-tk#1|A6Ikgxj+i_ z5m0K<9P=u;7|cpNkgn=p{aiBS3qW})H;t3v%RsWEjf+x`bJB6+d=bxg0*&k0ra>}r zFOa^oWMd+XTe8g2;13`&Nq0Ivx+QYBJOix8-w7b^^<2LFg*a8On#(Q(;%xP0^l#bU^vrA(3ifB;I&CcrVCUwpBnZepMbRD zBGtjIAo5rsjLI%(yh{!%igcFI+3(3FPG^70L~6HhfV_1r_;qDJhUbEL9bt_oCwl$% zEvJNbEsFx%19XpNB<+Qt1guE~thC0%_0?!MrRYniEr^p=7d>mdAH zWBv|MG-1Dpt>0P6wzu+Q-=uTJvp~NfP4V)KT|nPMNT%%tMHA(`#|_zYZD+qm)V5b0(EhRoc6O0PCloCL$*p^WDSd#p z2--d|aRI-@(l8_3Wgr`epzSM||31t6Qr;JuZElWpn$b#?zi+LhB_B@d1Kfw>u^;*Y z=BbzCn47g^ZepsQ%u*`NZUSV81VV#PNifAQ~7@&<9wP zdF-vu$6X~kr41Qfd|LaNx~EF^%u!TVl5MrO8MM95j4VsuFV(gE%Y6T!{ic22S)^x4 z``v(XpfehYgLpPsM>dACm2X*pxaC!LR%*Y|q1W@0+jSn>Y>>mYx4WG}Uf(t1PJ(EpsP%YKY^XrI?kV5Y5iFH^s<-e+Wu9$eWI-7z zzidrY&-BY)O7^qr8yXAR=buHoDlEU`os5G~eOc$gwPut2u6hs;@2o$^?j=c|!_-IA z-`%*^K7Z<25uFQ{&56wc@I>9kHjDHOX=@BrfEo9JCCBMpzvgX~O}0ytX=UFq(D@5r zV@6wkI#b}rz3NGGU@A?FZBLm4u)U!^W|36+2ZG~3Uh&TTyW3exxxC*>L8GBJGPw0e4m-O>HfQ* z&X@xlw5?N^j8RCQGlWqG)Ld9FsZS4e2lpHX>s!tZ&2R@uaE`0e>Y#|bHw(^hTEN$M zk95u@EqwA-XTxMOCA;{U?s0SHz|;qVyFVL~am>O)J!x)^FWt#-PS4(_Q_qn#tvS%- zs`%269Fz3r4Ut0?5x|{83+8}*;FFIR5nItgrQ0lP`Ni)f69;qWLe7lEqG~F%B2@mm zLIW2XPGy!1s5i#(MWvQWi_^v#76BFANY5cI!qxszSx9}} zY$F&0+AA)DU6ji8O^|H+v^r29_?N@tY4h31;-ql4)9h|^(N3Q%A`};uW zeL?wc{{Wp(4f~_q@1gxh@kZ^XG}SI)-*A81JJem0S~E*#k}aYBhnM!MwI?NgvewnQ z&nq~Ge7yFX`dD9_{nFi@$NfL9m@~Rz9mrQg*jM_K&|0}1Nt(fk*(_El)(w*qi zWiy~XBb}AD|4os4W|@29^nb+c%rbjP!I|Z|Y(1&F$iX=GS6*N9ls9kNpWZWLnbUs^ z>p*hhO`wEwk#y!mrUMJkxs#Oa1?+AeO~q18NMwmj1Wf#xeX5-^or_N|Z>D!r)OZok z&_-Pe>wwtfB~{u=uq((^w7kD5JWw6n3`#l{h(8WeJKYJ2F1xEF;!+)GUesQ3CwRM( z)^*L0mcR6T z$Ne6r^G@8;f!2&=TQiZCBk?%NXy*cx>u}>PYS#FaR*2nkWytV4VB#t{sjh3m6<{GK zOPgn_2$w$Y9^gFi6ez_)PQ2}Ta4}H1=YVY4D*JckCB(aM4+mEQ-IuWyXx@w}#AsY^ z2hW0Qz>%Qr{k)KhB%iJ>#3K#>T2o&Keg(uY>Oec_0czI{um!vd9s%D0;w=XOo6B$u z$x6;JG>iU&6vPb#yU4B91r3mb;sh5EZ~{#475rxzxroKpa`9Ltm#!#>0CrD7@PDxM zzC65_3iVwD#%JQf9}0H z_&(RU^}+jm=el}uuB#_kZ_ah~>gqXJ@70QscCZL(7mI*)a_#2YF}OH_1|=nHXJTCL G`~MFEQ*4z0 diff --git a/js/index.htm b/js/index.htm deleted file mode 100644 index 95ad61eb541cb..0000000000000 --- a/js/index.htm +++ /dev/null @@ -1,65 +0,0 @@ - - - - - Read a ROOT file - - - - -
- loading modules ... -
- - - - - diff --git a/js/modules/base/BasePainter.mjs b/js/modules/base/BasePainter.mjs deleted file mode 100644 index 0ea10daa968b0..0000000000000 --- a/js/modules/base/BasePainter.mjs +++ /dev/null @@ -1,1004 +0,0 @@ -import { select as d3_select } from '../d3.mjs'; -import { settings, internals, isNodeJs, isFunc, isStr, isObject, btoa_func, getDocument } from '../core.mjs'; -import { getColor, addColor } from './colors.mjs'; - -/** @summary Standard prefix for SVG file context as data url - * @private */ -const prSVG = 'data:image/svg+xml;charset=utf-8,', -/** @summary Standard prefix for JSON file context as data url - * @private */ - prJSON = 'data:application/json;charset=utf-8,', - /** @summary Standard prefix for HTML file context as data url - * @private */ - prHTML = 'data:text/html;charset=utf-8,'; - - -/** @summary Returns visible rect of element - * @param {object} elem - d3.select object with element - * @param {string} [sizearg] - which size method is used - * @desc sizearg = 'bbox' use getBBox, works only with SVG - * sizearg = 'full' - full size of element, using getBoundingClientRect function - * sizearg = 'nopadding' - excludes padding area - * With node.js can use 'width' and 'height' attributes when provided in element - * @private */ -function getElementRect(elem, sizearg) { - if (!elem || elem.empty()) - return { x: 0, y: 0, width: 0, height: 0 }; - - if ((isNodeJs() && (sizearg !== 'bbox')) || elem.property('_batch_mode')) - return { x: 0, y: 0, width: parseInt(elem.attr('width')), height: parseInt(elem.attr('height')) }; - - const styleValue = name => { - let value = elem.style(name); - if (!value || !isStr(value)) - return 0; - value = parseFloat(value.replace('px', '')); - return !Number.isFinite(value) ? 0 : Math.round(value); - }; - - let rect = elem.node().getBoundingClientRect(); - if ((sizearg === 'bbox') && (parseFloat(rect.width) > 0)) - rect = elem.node().getBBox(); - - const res = { x: 0, y: 0, width: parseInt(rect.width), height: parseInt(rect.height) }; - if (rect.left !== undefined) { - res.x = parseInt(rect.left); - res.y = parseInt(rect.top); - } else if (rect.x !== undefined) { - res.x = parseInt(rect.x); - res.y = parseInt(rect.y); - } - - if ((sizearg === undefined) || (sizearg === 'nopadding')) { - // this is size exclude padding area - res.width -= styleValue('padding-left') + styleValue('padding-right'); - res.height -= styleValue('padding-top') + styleValue('padding-bottom'); - } - - return res; -} - - -/** @summary Calculate absolute position of provided element in canvas - * @private */ -function getAbsPosInCanvas(sel, pos) { - if (!pos) - return pos; - - while (!sel.empty() && !sel.classed('root_canvas')) { - const cl = sel.attr('class'); - if (cl && ((cl.indexOf('root_frame') >= 0) || (cl.indexOf('__root_pad_') >= 0))) { - pos.x += sel.property('draw_x') || 0; - pos.y += sel.property('draw_y') || 0; - } - sel = d3_select(sel.node().parentNode); - } - return pos; -} - - -/** @summary Converts numeric value to string according to specified format. - * @param {number} value - value to convert - * @param {string} [fmt='6.4g'] - format can be like 5.4g or 4.2e or 6.4f - * @param {boolean} [ret_fmt] - when true returns array with value and actual format like ['0.1','6.4f'] - * @return {string|Array} - converted value or array with value and actual format - * @private */ -function floatToString(value, fmt, ret_fmt) { - if ((fmt === true) || (fmt === 'double')) - fmt = settings.DoubleFormat || '10.8g'; - else if ((fmt === false) || (fmt === 'float')) - fmt = settings.FloatFormat || '8.6g'; - else if (fmt === 'g') - fmt = '7.5g'; - else if (!fmt || !isStr(fmt)) - fmt = '6.4g'; - - fmt = fmt.trim(); - const len = fmt.length; - if (len < 2) - return ret_fmt ? [value.toFixed(4), '6.4f'] : value.toFixed(4); - - const kind = fmt[len - 1].toLowerCase(), - compact = (len > 1) && (fmt[len - 2] === 'c') ? 'c' : ''; - fmt = fmt.slice(0, len - (compact ? 2 : 1)); - - if (kind === 'g') { - const se = floatToString(value, fmt + 'ce', true), - sg = floatToString(value, fmt + 'cf', true), - res = se[0].length < sg[0].length || ((sg[0] === '0') && value) ? se : sg; - return ret_fmt ? res : res[0]; - } - - let isexp, prec = fmt.indexOf('.'); - prec = (prec < 0) ? 4 : parseInt(fmt.slice(prec + 1)); - if (!Number.isInteger(prec) || (prec <= 0)) - prec = 4; - - switch (kind) { - case 'e': - isexp = true; - break; - case 'f': - isexp = false; - break; - default: - isexp = false; - prec = 4; - } - - if (isexp) { - let se = value.toExponential(prec); - - if (compact) { - const pnt = se.indexOf('.'), - pe = se.toLowerCase().indexOf('e'); - if ((pnt > 0) && (pe > pnt)) { - let p = pe; - while ((p > pnt) && (se[p - 1] === '0')) - p--; - if (p === pnt + 1) - p--; - if (p !== pe) - se = se.slice(0, p) + se.slice(pe); - } - } - - return ret_fmt ? [se, `${prec + 2}.${prec}${compact}e`] : se; - } - - let sg = value.toFixed(prec); - - if (compact) { - let l = 0; - while ((l < sg.length) && (sg[l] === '0' || sg[l] === '-' || sg[l] === '.')) - l++; - - let diff = sg.length - l - prec; - if (sg.indexOf('.') > l) - diff--; - - if (diff) { - prec -= diff; - if (prec < 0) - prec = 0; - else if (prec > 20) - prec = 20; - sg = value.toFixed(prec); - } - - const pnt = sg.indexOf('.'); - if (pnt > 0) { - let p = sg.length; - while ((p > pnt) && (sg[p - 1] === '0')) - p--; - if (p === pnt + 1) - p--; - sg = sg.slice(0, p); - } - - if (sg === '-0') - sg = '0'; - } - - return ret_fmt ? [sg, `${prec + 2}.${prec}${compact}f`] : sg; -} - - -/** @summary Draw options interpreter - * @private */ -class DrawOptions { - - constructor(opt) { - if (isStr(opt)) { - this.origin = opt.trim(); - this.opt = this.origin.toUpperCase(); - } else - this.opt = this.origin = ''; - this.part = this.partO = ''; - } - - /** @summary Returns true if remaining options are empty or contain only separators symbols. */ - empty() { return !this.opt ? true : !this.opt.replace(/[ ;_,]/g, ''); } - - /** @summary Returns remaining part of the draw options. */ - remain() { return this.opt; } - - /** @summary Remove [pos, pos2) part from the string */ - #cut(pos, pos2) { - this.opt = this.opt.slice(0, pos) + this.opt.slice(pos2); - this.origin = this.origin.slice(0, pos) + this.origin.slice(pos2); - } - - /** @summary Checks if given option exists */ - check(name, postpart) { - const pos = this.opt.indexOf(name); - if (pos < 0) - return false; - this.#cut(pos, pos + name.length); - this.part = ''; - if (!postpart) - return true; - - let pos2 = pos; - const is_array = postpart === 'array'; - if (is_array) { - if (this.opt[pos2] !== '[') - return false; - while ((pos2 < this.opt.length) && (this.opt[pos2] !== ']')) - pos2++; - if (++pos2 > this.opt.length) - return false; - } else { - while ((pos2 < this.opt.length) && (this.opt[pos2] !== ' ') && (this.opt[pos2] !== ',') && (this.opt[pos2] !== ';')) - pos2++; - } - if (pos2 > pos) { - this.part = this.opt.slice(pos, pos2); - this.partO = this.origin.slice(pos, pos2); - this.#cut(pos, pos2); - } - - if (is_array) { - try { - this.array = JSON.parse(this.part); - } catch { - this.array = undefined; - } - return this.array?.length !== undefined; - } - - if (postpart !== 'color') - return true; - - if (((this.part.length === 6) || (this.part.length === 8)) && this.part.match(/^[a-fA-F0-9]+/)) { - this.color = addColor('#' + this.part); - return true; - } - - this.color = this.partAsInt(1) - 1; - if (this.color >= 0) - return true; - for (let col = 0; col < 8; ++col) { - if (getColor(col).toUpperCase() === this.part) { - this.color = col; - return true; - } - } - return false; - } - - /** @summary Returns (original) part after found options. */ - getPart(origin) { return origin ? this.partO : this.part; } - - /** @summary Returns remaining part of found option as integer. */ - partAsInt(offset, dflt) { - let mult = 1; - const last = this.part ? this.part.at(-1) : ''; - if (last === 'K') - mult = 1e3; - else if (last === 'M') - mult = 1e6; - else if (last === 'G') - mult = 1e9; - let val = this.part.replace(/^\D+/g, ''); - val = val ? parseInt(val, 10) : Number.NaN; - return !Number.isInteger(val) ? (dflt || 0) : mult * val + (offset || 0); - } - - /** @summary Returns remaining part of found option as float. */ - partAsFloat(offset, dflt) { - let val = this.part.replace(/^\D+/g, ''); - val = val ? parseFloat(val) : Number.NaN; - return !Number.isFinite(val) ? (dflt || 0) : val + (offset || 0); - } - -} // class DrawOptions - - -/** @summary Simple random generator with controlled seed - * @private */ -class TRandom { - - constructor(i) { - if (i !== undefined) - this.seed(i); - } - - /** @summary Seed simple random generator */ - seed(i) { - i = Math.abs(i); - if (i > 1e8) - i = Math.abs(1e8 * Math.sin(i)); - else if (i < 1) - i *= 1e8; - this.m_w = Math.round(i); - this.m_z = 987654321; - } - - /** @summary Produce random value between 0 and 1 */ - random() { - if (this.m_z === undefined) - return Math.random(); - this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >> 16)) & 0xffffffff; - this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >> 16)) & 0xffffffff; - let result = ((this.m_z << 16) + this.m_w) & 0xffffffff; - result /= 4294967296; - return result + 0.5; - } - -} // class TRandom - - -/** @summary Build smooth SVG curve using Bezier - * @desc Reuse code from https://stackoverflow.com/questions/62855310 - * @private */ -function buildSvgCurve(p, args) { - if (!args) - args = {}; - if (!args.line) - args.calc = true; - else if (args.ndig === undefined) - args.ndig = 0; - - let npnts = p.length; - if (npnts < 3) - args.line = true; - - args.t = args.t ?? 0.2; - - if ((args.ndig === undefined) || args.height) { - args.maxy = p[0].gry; - args.mindiff = 100; - for (let i = 1; i < npnts; i++) { - args.maxy = Math.max(args.maxy, p[i].gry); - args.mindiff = Math.min(args.mindiff, Math.abs(p[i].grx - p[i - 1].grx), Math.abs(p[i].gry - p[i - 1].gry)); - } - if (args.ndig === undefined) - args.ndig = args.mindiff > 20 ? 0 : (args.mindiff > 5 ? 1 : 2); - } - - const end_point = (pnt1, pnt2, sign) => { - const len = Math.sqrt((pnt2.gry - pnt1.gry) ** 2 + (pnt2.grx - pnt1.grx) ** 2) * args.t, - a2 = Math.atan2(pnt2.dgry, pnt2.dgrx), - a1 = Math.atan2(sign * (pnt2.gry - pnt1.gry), sign * (pnt2.grx - pnt1.grx)); - - pnt1.dgrx = len * Math.cos(2 * a1 - a2); - pnt1.dgry = len * Math.sin(2 * a1 - a2); - }, conv = val => { - if (!args.ndig || (Math.round(val) === val)) - return val.toFixed(0); - let s = val.toFixed(args.ndig), p1 = s.length - 1; - while (s[p1] === '0') - p1--; - if (s[p1] === '.') - p1--; - s = s.slice(0, p1 + 1); - return (s === '-0') ? '0' : s; - }; - - if (args.calc) { - for (let i = 1; i < npnts - 1; i++) { - p[i].dgrx = (p[i + 1].grx - p[i - 1].grx) * args.t; - p[i].dgry = (p[i + 1].gry - p[i - 1].gry) * args.t; - } - - if (npnts > 2) { - end_point(p[0], p[1], 1); - end_point(p[npnts - 1], p[npnts - 2], -1); - } else if (p.length === 2) { - p[0].dgrx = (p[1].grx - p[0].grx) * args.t; - p[0].dgry = (p[1].gry - p[0].gry) * args.t; - p[1].dgrx = -p[0].dgrx; - p[1].dgry = -p[0].dgry; - } - } - - let path = `${args.cmd ?? 'M'}${conv(p[0].grx)},${conv(p[0].gry)}`; - - if (!args.line) { - let i0 = 1; - if (args.qubic) { - npnts--; i0++; - path += `Q${conv(p[1].grx - p[1].dgrx)},${conv(p[1].gry - p[1].dgry)},${conv(p[1].grx)},${conv(p[1].gry)}`; - } - path += `C${conv(p[i0 - 1].grx + p[i0 - 1].dgrx)},${conv(p[i0 - 1].gry + p[i0 - 1].dgry)},${conv(p[i0].grx - p[i0].dgrx)},${conv(p[i0].gry - p[i0].dgry)},${conv(p[i0].grx)},${conv(p[i0].gry)}`; - - // continue with simpler points - for (let i = i0 + 1; i < npnts; i++) - path += `S${conv(p[i].grx - p[i].dgrx)},${conv(p[i].gry - p[i].dgry)},${conv(p[i].grx)},${conv(p[i].gry)}`; - - if (args.qubic) - path += `Q${conv(p[npnts].grx - p[npnts].dgrx)},${conv(p[npnts].gry - p[npnts].dgry)},${conv(p[npnts].grx)},${conv(p[npnts].gry)}`; - } else if (npnts < 10000) { - // build simple curve - - let acc_x = 0, acc_y = 0, currx = Math.round(p[0].grx), curry = Math.round(p[0].gry); - - const flush = () => { - if (acc_x) { - path += 'h' + acc_x; - acc_x = 0; - } - if (acc_y) { - path += 'v' + acc_y; - acc_y = 0; - } - }; - - for (let n = 1; n < npnts; ++n) { - const bin = p[n], - dx = Math.round(bin.grx) - currx, - dy = Math.round(bin.gry) - curry; - if (dx && dy) { - flush(); - path += `l${dx},${dy}`; - } else if (!dx && dy) { - if ((acc_y === 0) || ((dy < 0) !== (acc_y < 0))) - flush(); - acc_y += dy; - } else if (dx && !dy) { - if ((acc_x === 0) || ((dx < 0) !== (acc_x < 0))) - flush(); - acc_x += dx; - } - currx += dx; - curry += dy; - } - - flush(); - } else { - // build line with trying optimize many vertical moves - let currx = Math.round(p[0].grx), curry = Math.round(p[0].gry), - cminy = curry, cmaxy = curry, prevy = curry; - - for (let n = 1; n < npnts; ++n) { - const bin = p[n], - lastx = Math.round(bin.grx), - lasty = Math.round(bin.gry), - dx = lastx - currx; - if (dx === 0) { - // if X not change, just remember amplitude and - cminy = Math.min(cminy, lasty); - cmaxy = Math.max(cmaxy, lasty); - prevy = lasty; - continue; - } - - if (cminy !== cmaxy) { - if (cminy !== curry) - path += `v${cminy - curry}`; - path += `v${cmaxy - cminy}`; - if (cmaxy !== prevy) - path += `v${prevy - cmaxy}`; - curry = prevy; - } - const dy = lasty - curry; - if (dy) - path += `l${dx},${dy}`; - else - path += `h${dx}`; - currx = lastx; - curry = lasty; - prevy = cminy = cmaxy = lasty; - } - - if (cminy !== cmaxy) { - if (cminy !== curry) - path += `v${cminy - curry}`; - path += `v${cmaxy - cminy}`; - if (cmaxy !== prevy) - path += `v${prevy - cmaxy}`; - } - } - - if (args.height) - args.close = `L${conv(p.at(-1).grx)},${conv(Math.max(args.maxy, args.height))}H${conv(p[0].grx)}Z`; - - return path; -} - -/** @summary Compress SVG code, produced from drawing - * @desc removes extra info or empty elements - * @private */ -function compressSVG(svg) { - svg = svg.replace(/url\("#(\w+)"\)/g, 'url(#$1)') // decode all URL - .replace(/ class="\w*"/g, '') // remove all classes - .replace(/ pad="\w*"/g, '') // remove all pad ids - .replace(/ title=""/g, '') // remove all empty titles - .replace(/ style=""/g, '') // remove all empty styles - .replace(/<\/g>/g, '') // remove all empty groups with transform - .replace(/<\/g>/g, '') // remove hidden title - .replace(/<\/g>/g, ''); // remove all empty groups - - // remove all empty frame svg, typically appears in 3D drawings, maybe should be improved in frame painter itself - svg = svg.replace(/<\/svg>/g, ''); - - return svg; -} - - -/** - * @summary Base painter class - * - */ - -class BasePainter { - - #divid; // either id of DOM element or element itself - #selected_main; // d3.select for dom elements - #hitemname; // item name in the hpainter - #hdrawopt; // draw option in the hpainter - #hpainter; // assigned hpainter - - /** @summary constructor - * @param {object|string} [dom] - dom element or id of dom element */ - constructor(dom) { - this.#divid = null; // either id of DOM element or element itself - if (dom) - this.setDom(dom); - } - - /** @summary Assign painter to specified DOM element - * @param {string|object} elem - element ID or DOM Element - * @desc Normally DOM element should be already assigned in constructor - * @protected */ - setDom(elem) { - if (elem !== undefined) { - this.#divid = elem; - this.#selected_main = null; - } - } - - /** @summary Returns assigned dom element */ - getDom() { return this.#divid; } - - /** @summary Returns argument for draw function */ - getDrawDom() { return this.#divid; } - - /** @summary Selects main HTML element assigned for drawing - * @desc if main element was layout, returns main element inside layout - * @param {string} [is_direct] - if 'origin' specified, returns original element even if actual drawing moved to some other place - * @return {object} d3.select object for main element for drawing */ - selectDom(is_direct) { - if (!this.#divid) - return d3_select(null); - - let res = this.#selected_main; - if (!res) { - if (isStr(this.#divid)) { - let id = this.#divid; - if (id[0] !== '#') - id = '#' + id; - res = d3_select(id); - if (!res.empty()) - this.#divid = res.node(); - } else - res = d3_select(this.#divid); - this.#selected_main = res; - } - - if (!res || res.empty() || (is_direct === 'origin')) - return res; - - const use_enlarge = res.property('use_enlarge'), - layout = res.property('layout') || 'simple', - layout_selector = (layout === 'simple') ? '' : res.property('layout_selector'); - - if (layout_selector) - res = res.select(layout_selector); - - // one could redirect here - if (!is_direct && !res.empty() && use_enlarge) - res = d3_select(getDocument().getElementById('jsroot_enlarge_div')); - - return res; - } - - /** @summary Access/change top painter - * @private */ - #accessTopPainter(on) { - const chld = this.selectDom().node()?.firstChild; - if (!chld) - return null; - if (on === true) - chld.painter = this; - else if ((on === false) && (chld.painter === this)) - delete chld.painter; - return chld.painter; - } - - /** @summary Set painter, stored in first child element - * @desc Only make sense after first drawing is completed and any child element add to configured DOM - * @protected */ - setTopPainter() { this.#accessTopPainter(true); } - - /** @summary Return top painter set for the selected dom element - * @protected */ - getTopPainter() { return this.#accessTopPainter(); } - - /** @summary Clear reference on top painter - * @protected */ - clearTopPainter() { this.#accessTopPainter(false); } - - /** @summary Generic method to cleanup painter - * @desc Removes all visible elements and all internal data */ - cleanup(keep_origin) { - this.clearTopPainter(); - const origin = this.selectDom('origin'); - if (!origin.empty() && !keep_origin) - origin.html(''); - this.#divid = null; - this.#selected_main = undefined; - - if (isFunc(this.#hpainter?.removePainter)) - this.#hpainter.removePainter(this); - - this.#hitemname = undefined; - this.#hdrawopt = undefined; - this.#hpainter = undefined; - } - - /** @summary Checks if draw elements were resized and drawing should be updated - * @return {boolean} true if resize was detected - * @protected - * @abstract */ - checkResize(/* arg */) {} - - /** @summary Function checks if geometry of main div was changed. - * @desc take into account enlarge state, used only in PadPainter class - * @return size of area when main div is drawn - * @private */ - testMainResize(check_level, new_size, height_factor) { - const enlarge = this.enlargeMain('state'), - origin = this.selectDom('origin'), - main = this.selectDom(), - lmt = 5; // minimal size - - if ((enlarge !== 'on') && new_size?.width && new_size?.height) { - origin.style('width', new_size.width + 'px') - .style('height', new_size.height + 'px'); - } - - const rect_origin = getElementRect(origin, true), - can_resize = origin.attr('can_resize'); - let do_resize = false; - - if ((can_resize === 'height') && height_factor && Math.abs(rect_origin.width * height_factor - rect_origin.height) > 0.1 * rect_origin.width) - do_resize = true; - - if (((rect_origin.height <= lmt) || (rect_origin.width <= lmt)) && can_resize && (can_resize !== 'false')) - do_resize = true; - - if (do_resize && (enlarge !== 'on')) { - // if zero size and can_resize attribute set, change container size - - if (rect_origin.width > lmt) { - height_factor = height_factor || 0.66; - origin.style('height', Math.round(rect_origin.width * height_factor) + 'px'); - } else if (can_resize !== 'height') - origin.style('width', '200px').style('height', '100px'); - } - - const rect = getElementRect(main), - old_h = main.property('_jsroot_height'), - old_w = main.property('_jsroot_width'); - - rect.changed = false; - - if (!rect.width && !rect.height && !main.empty() && main.attr('style')) { - const ws = main.style('width'), hs = main.style('height'); - if (isStr(ws) && isStr(hs) && ws.match(/^\d+px$/) && hs.match(/^\d+px$/)) { - rect.width = parseInt(ws.slice(0, ws.length - 2)); - rect.height = parseInt(hs.slice(0, hs.length - 2)); - } - } - - if (old_h && old_w && (old_h > 0) && (old_w > 0)) { - if ((old_h !== rect.height) || (old_w !== rect.width)) - rect.changed = (check_level > 1) || (rect.width / old_w < 0.99) || (rect.width / old_w > 1.01) || (rect.height / old_h < 0.99) || (rect.height / old_h > 1.01); - } else - rect.changed = true; - - if (rect.changed) - main.property('_jsroot_height', rect.height).property('_jsroot_width', rect.width); - - // after change enlarge state always mark main element as resized - if (origin.property('did_enlarge')) { - rect.changed = true; - origin.property('did_enlarge', false); - } - - return rect; - } - - /** @summary Try enlarge main drawing element to full HTML page. - * @param {string|boolean} action - defines that should be done - * @desc Possible values for action parameter: - * - true - try to enlarge - * - false - revert enlarge state - * - 'toggle' - toggle enlarge state - * - 'state' - only returns current enlarge state - * - 'verify' - check if element can be enlarged - * if action not specified, just return possibility to enlarge main div - * @protected */ - enlargeMain(action, skip_warning) { - const main = this.selectDom(true), - origin = this.selectDom('origin'), - doc = getDocument(); - - if (main.empty() || !settings.CanEnlarge || (origin.property('can_enlarge') === false)) - return false; - - if ((action === undefined) || (action === 'verify')) - return true; - - const state = origin.property('use_enlarge') ? 'on' : 'off'; - - if (action === 'state') - return state; - - if (action === 'toggle') - action = (state === 'off'); - - let enlarge = d3_select(doc.getElementById('jsroot_enlarge_div')); - - if ((action === true) && (state !== 'on')) { - if (!enlarge.empty()) - return false; - - enlarge = d3_select(doc.body) - .append('div') - .attr('id', 'jsroot_enlarge_div') - .attr('style', 'position: fixed; margin: 0px; border: 0px; padding: 0px; left: 1px; top: 1px; bottom: 1px; right: 1px; background: white; opacity: 0.95; z-index: 100; overflow: hidden;'); - - const rect1 = getElementRect(main), - rect2 = getElementRect(enlarge); - - // if new enlarge area not big enough, do not do it - if ((rect2.width <= rect1.width) || (rect2.height <= rect1.height)) { - if (rect2.width * rect2.height < rect1.width * rect1.height) { - if (!skip_warning) - console.log(`Enlarged area ${rect2.width} x ${rect2.height} smaller then original drawing ${rect1.width} x ${rect1.height}`); - enlarge.remove(); - return false; - } - } - - while (main.node().childNodes.length) - enlarge.node().appendChild(main.node().firstChild); - - origin.property('use_enlarge', true); - origin.property('did_enlarge', true); - return true; - } - if ((action === false) && (state !== 'off')) { - while (enlarge.node()?.childNodes.length) - main.node().appendChild(enlarge.node().firstChild); - - enlarge.remove(); - origin.property('use_enlarge', false); - origin.property('did_enlarge', true); - return true; - } - - return false; - } - - /** @summary Set item name, associated with the painter - * @desc Used by {@link HierarchyPainter} - * @private */ - setItemName(name, opt, hpainter) { - this.#hitemname = isStr(name) ? name : undefined; - // only update draw option, never delete. - if (isStr(opt)) - this.#hdrawopt = opt; - - this.#hpainter = hpainter; - } - - /** @summary Returns assigned histogram painter */ - getHPainter() { return this.#hpainter; } - - /** @summary Returns assigned item name - * @desc Used with {@link HierarchyPainter} to identify drawn item name */ - getItemName() { return this.#hitemname ?? null; } - - /** @summary Returns assigned item draw option - * @desc Used with {@link HierarchyPainter} to identify drawn item option */ - getItemDrawOpt() { return this.#hdrawopt ?? ''; } - -} // class BasePainter - -/** @summary Load and initialize JSDOM from nodes - * @return {Promise} with d3 selection for d3_body - * @private */ -async function _loadJSDOM() { - return import('jsdom').then(handle => { - if (!internals.nodejs_window) { - internals.nodejs_window = (new handle.JSDOM('hello')).window; - internals.nodejs_document = internals.nodejs_window.document; // used with three.js - internals.nodejs_body = d3_select(internals.nodejs_document).select('body'); // get d3 handle for body - } - - return { JSDOM: handle.JSDOM, doc: internals.nodejs_document, body: internals.nodejs_body }; - }); -} - -/** @summary Return translate string for transform attribute of some svg element - * @return string or null if x and y are zeros - * @private */ -function makeTranslate(g, x, y, scale = 1) { - if (!isObject(g)) { - scale = y; - y = x; - x = g; - g = null; - } - let res = y ? `translate(${x},${y})` : (x ? `translate(${x})` : null); - if (scale && scale !== 1) { - if (res) - res += ' '; - else - res = ''; - res += `scale(${scale.toFixed(3)})`; - } - - return g ? g.attr('transform', res) : res; -} - - -/** @summary Configure special style used for highlight or dragging elements - * @private */ -function addHighlightStyle(elem, drag) { - if (drag) { - elem.style('stroke', 'steelblue') - .style('fill-opacity', '0.1'); - } else { - elem.style('stroke', '#4572A7') - .style('fill', '#4572A7') - .style('opacity', '0'); - } -} - -/** @summary Create image based on SVG - * @param {string} svg - svg code of the image - * @param {string} [image_format] - image format like 'png', 'jpeg' or 'webp' - * @param {Objects} [args] - optional arguments - * @param {boolean} [args.as_buffer] - return image as buffer - * @return {Promise} with produced image in base64 form or as Buffer (or canvas when no image_format specified) - * @private */ -async function svgToImage(svg, image_format, args) { - if ((args === true) || (args === false)) - args = { as_buffer: args }; - - if (image_format === 'svg') - return svg; - - if (image_format === 'pdf') - return internals.makePDF ? internals.makePDF(svg, args) : null; - - // required with df104.py/df105.py example with RCanvas or any special symbols in TLatex - const doctype = '', - is_rgba = image_format === 'rgba'; - - if (isNodeJs()) { - if (image_format === 'jpeg') { - console.log('JPEG image format not supported in node.js, use PNG'); - return null; - } - svg = encodeURIComponent(doctype + svg); - svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { - const c = String.fromCharCode('0x' + p1); - return c === '%' ? '%25' : c; - }); - - const img_src = 'data:image/svg+xml;base64,' + btoa_func(decodeURIComponent(svg)); - - // Use the newer and stabler `resvg-js` backend for converting SVG to PNG - if (settings.UseResvgJs) { - return import('@resvg/resvg-js').then(({ Resvg }) => { - const rawSvg = decodeURIComponent(svg), // raw SVG XML - resvg = new Resvg(rawSvg), // Initialize Resvg and create the PNG buffer - renderData = resvg.render(), - pngBuffer = renderData.asPng(); - - // Return raw RGBA pixels if caller requested it - if (is_rgba) { - return { - width: renderData.width, - height: renderData.height, - data: renderData.pixels - }; - } - - if (args?.as_buffer) - return pngBuffer; - - return 'data:image/png;base64,' + pngBuffer.toString('base64'); - }); - } - - // Fallback to `node-canvas` - return import('canvas').then(async handle => { - return handle.default.loadImage(img_src).then(img => { - const canvas = handle.default.createCanvas(img.width, img.height); - - canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height); - - if (args?.as_buffer) - return canvas.toBuffer('image/' + image_format); - - return image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas; - }); - }); - } - - const img_src = URL.createObjectURL(new Blob([doctype + svg], { type: 'image/svg+xml;charset=utf-8' })); - - return new Promise(resolveFunc => { - const image = document.createElement('img'); - - image.onload = function() { - URL.revokeObjectURL(img_src); - - const canvas = document.createElement('canvas'); - canvas.width = image.width; - canvas.height = image.height; - - canvas.getContext('2d').drawImage(image, 0, 0); - - if (args?.as_buffer && image_format) - canvas.toBlob(blob => blob.arrayBuffer().then(resolveFunc), 'image/' + image_format); - else - resolveFunc(image_format && !is_rgba ? canvas.toDataURL('image/' + image_format) : canvas); - }; - image.onerror = function(arg) { - URL.revokeObjectURL(img_src); - console.log(`IMAGE ERROR ${arg}`); - resolveFunc(null); - }; - - image.setAttribute('src', img_src); - }); -} - -/** @summary Convert ROOT TDatime object into Date - * @desc Always use UTC to avoid any variation between timezones */ -function getTDatime(dt) { - const y = (dt.fDatime >>> 26) + 1995, - m = ((dt.fDatime << 6) >>> 28) - 1, - d = (dt.fDatime << 10) >>> 27, - h = (dt.fDatime << 15) >>> 27, - min = (dt.fDatime << 20) >>> 26, - s = (dt.fDatime << 26) >>> 26; - return new Date(Date.UTC(y, m, d, h, min, s)); -} - -/** @summary Convert Date object into string used configured time zone - * @desc Time zone stored in settings.TimeZone */ -function convertDate(dt) { - let res = ''; - - if (settings.TimeZone && isStr(settings.TimeZone)) { - try { - res = dt.toLocaleString('en-GB', { timeZone: settings.TimeZone }); - } catch { - res = ''; - } - } - return res || dt.toLocaleString('en-GB'); -} - -/** @summary Box decorations - * @private */ -function getBoxDecorations(xx, yy, ww, hh, bmode, pww, phh) { - const side1 = `M${xx},${yy}h${ww}l${-pww},${phh}h${2 * pww - ww}v${hh - 2 * phh}l${-pww},${phh}z`, - side2 = `M${xx + ww},${yy + hh}v${-hh}l${-pww},${phh}v${hh - 2 * phh}h${2 * pww - ww}l${-pww},${phh}z`; - return bmode > 0 ? [side1, side2] : [side2, side1]; -} - - -export { prSVG, prJSON, prHTML, getElementRect, getAbsPosInCanvas, getTDatime, convertDate, - DrawOptions, TRandom, floatToString, buildSvgCurve, compressSVG, getBoxDecorations, - BasePainter, _loadJSDOM, makeTranslate, addHighlightStyle, svgToImage }; diff --git a/js/modules/base/FontHandler.mjs b/js/modules/base/FontHandler.mjs deleted file mode 100644 index 14e1fa4564633..0000000000000 --- a/js/modules/base/FontHandler.mjs +++ /dev/null @@ -1,308 +0,0 @@ -import { isNodeJs, httpRequest, btoa_func, source_dir, settings, isObject } from '../core.mjs'; - - -const kArial = 'Arial', kTimes = 'Times New Roman', kCourier = 'Courier New', kVerdana = 'Verdana', kSymbol = 'RootSymbol', kWingdings = 'Wingdings', - // average width taken from symbols.html, counted only for letters and digits - root_fonts = [null, // index 0 not exists - { n: kTimes, s: 'italic', aw: 0.5314 }, - { n: kTimes, w: 'bold', aw: 0.5809 }, - { n: kTimes, s: 'italic', w: 'bold', aw: 0.5540 }, - { n: kArial, aw: 0.5778 }, - { n: kArial, s: 'oblique', aw: 0.5783 }, - { n: kArial, w: 'bold', aw: 0.6034 }, - { n: kArial, s: 'oblique', w: 'bold', aw: 0.6030 }, - { n: kCourier, aw: 0.6003 }, - { n: kCourier, s: 'oblique', aw: 0.6004 }, - { n: kCourier, w: 'bold', aw: 0.6003 }, - { n: kCourier, s: 'oblique', w: 'bold', aw: 0.6005 }, - { n: kSymbol, aw: 0.5521, file: 'symbol.ttf' }, - { n: kTimes, aw: 0.5521 }, - { n: kWingdings, aw: 0.5664, file: 'wingding.ttf' }, - { n: kSymbol, s: 'oblique', aw: 0.5314, file: 'symbol.ttf' }, - { n: kVerdana, aw: 0.5664 }, - { n: kVerdana, s: 'italic', aw: 0.5495 }, - { n: kVerdana, w: 'bold', aw: 0.5748 }, - { n: kVerdana, s: 'italic', w: 'bold', aw: 0.5578 }], - // list of loaded fonts including handling of multiple simultaneous requests - gFontFiles = {}; - -/** @summary Read font file from some pre-configured locations - * @return {Promise} with base64 code of the font - * @private */ -async function loadFontFile(fname) { - let entry = gFontFiles[fname]; - if (entry?.base64) - return entry?.base64; - - if (entry?.promises !== undefined) { - return new Promise(resolveFunc => { - entry.promises.push(resolveFunc); - }); - } - - entry = gFontFiles[fname] = { promises: [] }; - - const locations = []; - if (fname.indexOf('/') >= 0) - locations.push(''); // just use file name as is - else { - locations.push(source_dir + 'fonts/'); - if (isNodeJs()) - locations.push('../../fonts/'); - else if (source_dir.indexOf('jsrootsys/') >= 0) { - locations.unshift(source_dir.replace(/jsrootsys/g, 'rootsys_fonts')); - locations.unshift(source_dir.replace(/jsrootsys/g, 'rootsys/fonts')); - } - } - - function completeReading(base64) { - entry.base64 = base64; - const arr = entry.promises; - delete entry.promises; - arr.forEach(func => func(base64)); - return base64; - } - - async function tryNext() { - if (!locations.length) { - completeReading(null); - throw new Error(`Fail to load ${fname} font`); - } - let path = locations.shift() + fname; - console.log('loading font', path); - const pr = isNodeJs() ? import('fs').then(fs => { - const prefix = 'file://' + (process?.platform === 'win32' ? '/' : ''); - if (path.indexOf(prefix) === 0) - path = path.slice(prefix.length); - return fs.readFileSync(path).toString('base64'); - }) : httpRequest(path, 'bin').then(buf => btoa_func(buf)); - - return pr.then(res => { return res ? completeReading(res) : tryNext();}).catch(() => tryNext()); - } - - return tryNext(); -} - - -/** - * @summary Helper class for font handling - * @private - */ - -class FontHandler { - - /** @summary constructor */ - constructor(fontIndex, size, scale) { - if (scale && (size < 1)) { - size *= scale; - this.scaled = true; - } - - this.size = Math.round(size); - this.scale = scale; - this.index = 0; - - this.func = this.setFont.bind(this); - - let cfg; - - if (fontIndex && isObject(fontIndex)) - cfg = fontIndex; - else { - if (fontIndex && Number.isInteger(fontIndex)) - this.index = Math.floor(fontIndex / 10); - cfg = root_fonts[this.index]; - } - - if (cfg) { - this.cfg = cfg; - this.setNameStyleWeight(cfg.n, cfg.s, cfg.w, cfg.aw, cfg.format, cfg.base64); - } else - this.setNameStyleWeight(kArial); - } - - /** @summary Should returns true if font has to be loaded before - * @private */ - needLoad() { return this.cfg?.file && !this.isSymbol && !this.base64; } - - /** @summary Async function to load font - * @private */ - async load() { - if (!this.needLoad()) - return true; - return loadFontFile(this.cfg.file).then(base64 => { - this.cfg.base64 = this.base64 = base64; - this.format = 'ttf'; - return Boolean(base64); - }); - } - - /** @summary Directly set name, style and weight for the font - * @private */ - setNameStyleWeight(name, style, weight, aver_width, format, base64) { - this.name = name; - this.style = style || null; - this.weight = weight || null; - this.aver_width = aver_width || (weight ? 0.58 : 0.55); - this.format = format; // format of custom font, ttf by default - this.base64 = base64; // indication of custom font - if (!settings.LoadSymbolTtf && ((this.name === kSymbol) || (this.name === kWingdings))) { - this.isSymbol = this.name; - this.name = kTimes; - } else - this.isSymbol = ''; - } - - /** @summary Set painter for which font will be applied */ - setPainter(painter) { - this.painter = painter; - } - - /** @summary Force setting of style and weight, used in latex */ - setUseFullStyle(flag) { - this.full_style = flag; - } - - /** @summary Assigns font-related attributes */ - addCustomFontToSvg(svg) { - if (!this.base64 || !this.name) - return; - const clname = 'custom_font_' + this.name.replace(/[^a-zA-Z0-9]/g, '_'), fmt = 'ttf'; - let defs = svg.selectChild('.canvas_defs'); - if (defs.empty()) - defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); - const entry = defs.selectChild('.' + clname); - if (entry.empty()) { - defs.append('style') - .attr('class', clname) - .property('$fontcfg', this.cfg || null) - .text(`@font-face { font-family: "${this.name}"; font-weight: normal; font-style: normal; src: url(data:application/font-${fmt};charset=utf-8;base64,${this.base64}); }`); - } - } - - /** @summary Assigns font-related attributes */ - setFont(selection) { - if (this.base64 && this.painter) - this.addCustomFontToSvg(this.painter.getCanvSvg()); - - selection.attr('font-family', this.name) - .attr('font-size', this.size) - .attr(':xml:space', 'preserve'); - this.setFontStyle(selection); - } - - /** @summary Assigns only font style attributes */ - setFontStyle(selection) { - selection.attr('font-weight', this.weight || (this.full_style ? 'normal' : null)) - .attr('font-style', this.style || (this.full_style ? 'normal' : null)); - } - - /** @summary Set font size (optional) */ - setSize(size) { this.size = Math.round(size); } - - /** @summary Set text color (optional) */ - setColor(color) { this.color = color; } - - /** @summary Set text align (optional) */ - setAlign(align) { this.align = align; } - - /** @summary Set text angle (optional) */ - setAngle(angle) { this.angle = angle; } - - /** @summary Align angle to step raster, add optional offset */ - roundAngle(step, offset) { - this.angle = parseInt(this.angle || 0); - if (!Number.isInteger(this.angle)) - this.angle = 0; - this.angle = Math.round(this.angle / step) * step + (offset || 0); - if (this.angle < 0) - this.angle += 360; - else if (this.angle >= 360) - this.angle -= 360; - } - - /** @summary Clears all font-related attributes */ - clearFont(selection) { - selection.attr('font-family', null) - .attr('font-size', null) - .attr(':xml:space', null) - .attr('font-weight', null) - .attr('font-style', null); - } - - /** @summary Returns true in case of monospace font - * @private */ - isMonospace() { - const n = this.name.toLowerCase(); - return (n.indexOf('courier') === 0) || (n === 'monospace') || (n === 'monaco'); - } - - /** @summary Return full font declaration which can be set as font property like '12pt Arial bold' - * @private */ - getFontHtml() { - let res = Math.round(this.size) + 'pt ' + this.name; - if (this.weight) - res += ' ' + this.weight; - if (this.style) - res += ' ' + this.style; - return res; - } - - /** @summary Returns font name */ - getFontName() { - return this.isSymbol || this.name || 'none'; - } - -} // class FontHandler - -/** @summary Register custom font - * @private */ -function addCustomFont(index, name, format, base64) { - if (!Number.isInteger(index)) - console.error(`Wrong index ${index} for custom font`); - else - root_fonts[index] = { n: name, format, base64 }; -} - -/** @summary Return handle with custom font - * @private */ -function getCustomFont(name) { - return root_fonts.find(h => (h?.n === name) && h?.base64); -} - -/** @summary Try to detect and create font handler for SVG text node - * @private */ -function detectPdfFont(node) { - const sz = node.getAttribute('font-size'), - p = sz.indexOf('px'), - sz_pixels = p > 0 ? Number.parseInt(sz.slice(0, p)) : 12; - - let family = node.getAttribute('font-family'), - style = node.getAttribute('font-style'), - weight = node.getAttribute('font-weight'); - - if (family === 'times') - family = kTimes; - else if (family === 'symbol') - family = kSymbol; - else if (family === 'arial') - family = kArial; - else if (family === 'verdana') - family = kVerdana; - if (weight === 'normal') - weight = ''; - if (style === 'normal') - style = ''; - - const fcfg = root_fonts.find(elem => { - return (elem?.n === family) && - ((!weight && !elem.w) || (elem.w === weight)) && - ((!style && !elem.s) || (elem.s === style)); - }); - - return new FontHandler(fcfg || root_fonts[13], sz_pixels); -} - - -export { kArial, kCourier, kSymbol, kWingdings, kTimes, - FontHandler, addCustomFont, getCustomFont, detectPdfFont }; diff --git a/js/modules/base/ObjectPainter.mjs b/js/modules/base/ObjectPainter.mjs deleted file mode 100644 index 0c5339c9186f4..0000000000000 --- a/js/modules/base/ObjectPainter.mjs +++ /dev/null @@ -1,1831 +0,0 @@ -import { pointer as d3_pointer } from '../d3.mjs'; -import { settings, constants, internals, isNodeJs, isBatchMode, getPromise, BIT, - getKindForType, clTObjString, clTAxis, isObject, isFunc, isStr, getDocument, urlClassPrefix } from '../core.mjs'; -import { isPlainText, producePlainText, produceLatex, produceMathjax, typesetMathjax, approximateLabelWidth } from './latex.mjs'; -import { getElementRect, BasePainter, makeTranslate } from './BasePainter.mjs'; -import { TAttMarkerHandler } from './TAttMarkerHandler.mjs'; -import { TAttFillHandler } from './TAttFillHandler.mjs'; -import { TAttLineHandler } from './TAttLineHandler.mjs'; -import { TAttTextHandler } from './TAttTextHandler.mjs'; -import { FontHandler } from './FontHandler.mjs'; -import { getRootColors } from './colors.mjs'; - - -/** @summary returns true if pad painter - * @private */ -function isPadPainter(p) { - return isFunc(p?.getRootPad) && isFunc(p?.forEachPainterInPad); -} - -/** @summary returns canvas painter from DOM element - * @private */ -function getDomCanvasPainter(dom) { - const elem = dom?.select('.root_canvas'); - return !elem || elem.empty() ? null : elem.property('pad_painter'); -} - -/** - * @summary Painter class for ROOT objects - * - */ - -class ObjectPainter extends BasePainter { - - #draw_object; // drawn object - #draw_g; // element for object drawing - #pad_painter_ref; // reference of pad painter - #main_painter; // WeakRef to main painter in the pad - #primary_ref; // reference of primary painter - if any - #snapid; // assigned online identifier - #is_primary; // if primary painter - #secondary_id; // id of this painter in relation to primary painter - #options; // current options object - #options_store; // stored draw options used to check changes - #user_tooltip_handler; // configured user tooltip handler - #user_tooltip_timeout; // timeout configured with tooltip handler - #user_tooltip_timerid; // setTimemout id for processing user tooltip - #user_context_menu; // function for user context menu - #special_draw_area; // current special draw area like projection - #root_colors; // custom colors list - #fillatt; // fill attribute - #lineatt; // line attribute - #markeratt; // marker attribute - #textatt; // text attribute - - /** @summary constructor - * @param {object|string} dom - dom element or identifier or pad painter - * @param {object} obj - object to draw - * @param {string} [opt] - object draw options */ - constructor(dom, obj, opt) { - const pp = isPadPainter(dom) ? dom : null; - - super(pp?.getDom() ?? dom); - - this.setPadPainter(pp); - - this.#draw_g = undefined; // container for all drawn objects - this.assignObject(obj); - if (isStr(opt)) - this.#options = { original: opt }; - } - - /** @summary Assign object to the painter - * @protected */ - assignObject(obj) { this.#draw_object = isObject(obj) ? obj : null; } - - /** @summary Returns drawn object */ - getObject() { return this.#draw_object; } - - /** @summary Assign new pad painter - * @protected */ - setPadPainter(pp) { this.#pad_painter_ref = pp ? new WeakRef(pp) : undefined; } - - /** @summary returns pad painter where object is drawn - * @protected */ - getPadPainter() { return this.#pad_painter_ref?.deref(); } - - /** @summary returns canvas painter - * @protected */ - getCanvPainter() { - let pp = this.getPadPainter(); - while (pp && !pp.isCanvas()) - pp = pp.getPadPainter(); - return pp; - } - - /** @summary Indicates that drawing runs in batch mode - * @private */ - isBatchMode() { return isBatchMode() ? true : (this.getCanvPainter()?.isBatchMode() ?? false); } - - /** @summary Assign snapid to the painter - * @desc Identifier used to communicate with server side and identifies object on the server - * @private */ - assignSnapId(id) { this.#snapid = id; } - - /** @summary Provides identifier on server for requested sub-element */ - getSnapId(subelem) { return !this.#snapid ? '' : (this.#snapid + (subelem ? '#' + subelem : '')); } - - /** @summary Returns true if snapid was assigned */ - hasSnapId() { return this.#snapid !== undefined; } - - /** @summary Generic method to cleanup painter. - * @desc Remove object drawing and (in case of main painter) also main HTML components - * @protected */ - cleanup() { - this.removeG(); - - let keep_origin = true; - - if (this.isMainPainter()) { - const pp = this.getPadPainter(); - if (!pp || pp.isCanvas('auto')) - keep_origin = false; - } - - // cleanup all existing references - this.#pad_painter_ref = undefined; - this.#main_painter = null; - this.#draw_object = null; - this.#snapid = undefined; - this.#is_primary = undefined; - this.#primary_ref = undefined; - this.#secondary_id = undefined; - - // remove attributes objects (if any) - this.deleteAttr(); - this.#root_colors = undefined; - this.#options = undefined; - this.#options_store = undefined; - - // remove extra fields from v7 painters - delete this.rstyle; - delete this.csstype; - - super.cleanup(keep_origin); - } - - /** @summary Returns drawn object name */ - getObjectName() { return this.getObject()?.fName ?? ''; } - - /** @summary Returns drawn object class name */ - getClassName() { return this.getObject()?._typename ?? ''; } - - /** @summary Checks if drawn object matches with provided typename - * @param {string|object} arg - typename (or object with _typename member) - * @protected */ - matchObjectType(arg) { - const clname = this.getClassName(); - if (!arg || !clname) - return false; - if (isStr(arg)) - return arg === clname; - if (isStr(arg._typename)) - return arg._typename === clname; - return Boolean(clname.match(arg)); - } - - /** @summary Change item name - * @desc When available, used for svg:title property - * @private */ - setItemName(name, opt, hpainter) { - super.setItemName(name, opt, hpainter); - if (this._no_default_title || !name) - return; - const can = this.getCanvSvg(); - if (!can.empty()) - can.select('title').text(name); - else - this.selectDom().attr('title', name); - const cp = this.getCanvPainter(); - if (cp && ((cp === this) || (this.isMainPainter() && (cp === this.getPadPainter())))) - cp.drawItemNameOnCanvas(name); - } - - /** @summary Create options and copy new args - * @return options - * @private */ - setOptions(new_options, as_is) { - if (as_is) - this.#options = new_options; - else { - if (!this.#options) - this.#options = {}; - Object.assign(this.#options, new_options); - } - return this.#options; - } - - /** @summary Return actual options */ - getOptions(as_is) { - if (!as_is && !this.#options) - this.#options = {}; - return this.#options; - } - - /** @summary Emulate old options property */ - get options() { return this.getOptions(); } - - /** @summary Store actual options together with original string - * @private */ - storeDrawOpt(original) { - if (!this.#options) - return; - if (!original) - original = ''; - const pp = original.indexOf(';;'); - if (pp >= 0) - original = original.slice(0, pp); - this.#options.original = original; - this.#options_store = Object.assign({}, this.#options); - } - - /** @summary Return dom argument for object drawing - * @desc Can be used to draw other objects on same pad / same dom element - * @protected */ - getDrawDom() { return this.getPadPainter() || this.getDom(); } - - /** @summary Return actual draw options as string - * @param {boolean} ignore_pad - do not include pad settings into histogram draw options - * @desc if options are not modified - returns original string which was specified for object draw */ - getDrawOpt(ignore_pad) { - if (!this.#options) - return ''; - - if (isFunc(this.#options.asString)) { - let changed = false; - const pp = this.getPadPainter(); - if (!this.#options_store || pp?.options._interactively_changed) - changed = true; - else { - for (const k in this.#options_store) { - if (this.#options[k] !== this.#options_store[k]) { - if ((k[0] !== '_') && (k[0] !== '$') && (k[0].toLowerCase() !== k[0])) - changed = true; - } - } - } - - if (changed && isFunc(this.#options.asString)) - return this.#options.asString(this.isMainPainter(), ignore_pad ? null : pp?.getRootPad()); - } - - return this.#options.original || ''; // nothing better, return original draw option - } - - /** @summary Returns array with supported draw options as configured in draw.mjs - * @desc works via pad painter and only when module was loaded */ - getSupportedDrawOptions() { - const pp = this.getPadPainter(), - cl = this.getClassName(); - - if (!cl || !isFunc(pp?.getObjectDrawSettings)) - return []; - - return pp.getObjectDrawSettings(getKindForType(cl), 'nosame')?.opts; - } - - /** @summary Central place to update objects drawing - * @param {object} obj - new version of object, values will be updated in original object - * @param {string} [opt] - when specified, new draw options - * @return {boolean|Promise} for object redraw - * @desc Two actions typically done by redraw - update object content via {@link ObjectPainter#updateObject} and - * then redraw correspondent pad via {@link ObjectPainter#redrawPad}. If possible one should redefine - * only updateObject function and keep this function unchanged. But for some special painters this function is the - * only way to control how object can be update while requested from the server - * @protected */ - redrawObject(obj, opt) { - if (!this.updateObject(obj, opt)) - return false; - const doc = this.isBatchMode() ? null : getDocument(), - current = doc?.body.style.cursor; - if (doc) - doc.body.style.cursor = 'wait'; - return this.redrawPad().then(res => { - if (doc) - doc.body.style.cursor = current; - return res; - }); - } - - /** @summary Generic method to update object content. - * @desc Default implementation just copies first-level members to current object - * @param {object} obj - object with new data - * @param {string} [_opt] - option which will be used for redrawing - * @protected */ - updateObject(obj, _opt) { - if (!this.matchObjectType(obj)) - return false; - Object.assign(this.getObject(), obj); - return true; - } - - /** @summary Returns string with object hint - * @desc It is either item name or object name or class name. - * Such string typically used as object tooltip. - * If result string larger than 20 symbols, it will be shorten. */ - getObjectHint() { - const iname = this.getItemName(); - if (iname) - return (iname.length > 20) ? '...' + iname.slice(iname.length - 17) : iname; - return this.getObjectName() || this.getClassName() || ''; - } - - /** @summary Set colors list - * @protected */ - setColors(lst) { this.#root_colors = lst; } - - /** @summary Return colors list - * @protected */ - getColors(force) { - if (!this.#root_colors && force) - this.setColors(this.getCanvPainter()?.getColors() || getRootColors()); - return this.#root_colors; - } - - /** @summary returns color from current list of colors - * @desc First checks canvas painter and then just access global list of colors - * @param {number} indx - color index - * @return {string} with SVG color name or rgb() - * @protected */ - getColor(indx) { return this.getColors(true)[indx]; } - - /** @summary Add color to list of colors - * @desc Returned color index can be used as color number in all other draw functions - * @return {number} new color index - * @protected */ - addColor(color) { - const lst = this.getColors(true), - indx = lst.indexOf(color); - if (indx >= 0) - return indx; - lst.push(color); - return lst.length - 1; - } - - /** @summary returns tooltip allowed flag - * @desc If available, checks in canvas painter - * @private */ - isTooltipAllowed() { - const src = this.getCanvPainter() || this; - return src.tooltip_allowed; - } - - /** @summary change tooltip allowed flag - * @param {boolean|string} [on = true] set tooltip allowed state or 'toggle' - * @private */ - setTooltipAllowed(on = true) { - const src = this.getCanvPainter() || this; - src.tooltip_allowed = (on === 'toggle') ? !src.tooltip_allowed : on; - } - - /** @summary Checks if draw elements were resized and drawing should be updated. - * @desc Redirects to {@link TPadPainter#checkCanvasResize} - * @private */ - checkResize(arg) { - return this.getCanvPainter()?.checkCanvasResize(arg); - } - - /** @summary removes element with object drawing - * @desc generic method to delete all graphical elements, associated with the painter - * @protected */ - removeG() { - this.#draw_g?.remove(); - this.#draw_g = undefined; - } - - /** @summary Returns created element used for object drawing - * @desc Element should be created by {@link ObjectPainter#createG} - * @protected */ - getG() { return this.#draw_g; } - - /** @summary introduce property for backward compatibility */ - get draw_g() { return this.#draw_g; } - - /** @summary Assign G element used for object drawing - * @protected */ - setG(g) { - this.#draw_g = g; - return g; - } - - /** @summary Append svg::path to G - * @protected */ - appendPath(d) { return this.#draw_g.append('svg:path').attr('d', d); } - - /** @summary (re)creates svg:g element for object drawings - * @desc either one attach svg:g to pad primitives (default) - * or svg:g element created in specified frame layer ('main_layer' will be used when true specified) - * @param {boolean|string} [frame_layer] - when specified, element will be created inside frame layer, otherwise in the pad - * @protected */ - createG(frame_layer, use_a = false) { - let layer; - - const pp = this.getPadPainter(); - - if (frame_layer === 'frame2d') { - const fp = this.getFramePainter(); - frame_layer = fp && !fp.mode3d; - } - - if (frame_layer) { - const frame = pp.getFrameSvg(); - if (frame.empty()) { - console.error('Not found frame to create g element inside'); - return frame; - } - if (!isStr(frame_layer)) - frame_layer = 'main_layer'; - layer = frame.selectChild('.' + frame_layer); - } else - layer = pp.getLayerSvg('primitives_layer'); - - if (this.#draw_g && this.#draw_g.node().parentNode !== layer.node()) { - console.log('g element changes its layer!'); - this.removeG(); - } - - if (this.#draw_g) { - // clear all elements, keep g element on its place - this.#draw_g.selectAll('*').remove(); - } else { - this.#draw_g = layer.append(use_a ? 'svg:a' : 'svg:g'); - - if (!frame_layer) - layer.selectChildren('.most_upper_primitives').raise(); - } - - // set attributes for debugging, both should be there for opt out them later - const clname = this.getClassName(), objname = this.getObjectName(); - if (objname || clname) { - this.#draw_g.attr('objname', (objname || 'name').replace(/[^\w]/g, '_')) - .attr('objtype', (clname || 'type').replace(/[^\w]/g, '_')); - } - - this.#draw_g.property('in_frame', Boolean(frame_layer)); // indicates coordinate system - - return this.#draw_g; - } - - /** @summary Bring draw element to the front */ - bringToFront(check_online) { - if (!this.#draw_g) - return; - const prnt = this.#draw_g.node().parentNode; - prnt?.appendChild(this.#draw_g.node()); - - if (check_online && this.getSnapId()) { - const pp = this.getPadPainter(); - if (pp?.getSnapId()) - this.getCanvPainter()?.sendWebsocket('POPOBJ:' + JSON.stringify([pp.getSnapId(), this.getSnapId()])); - } - } - - /** @summary Assign is_primary flag - * @private */ - setPrimary(flag = true) { this.#is_primary = flag; } - - /** @summary Return is_primary flag - * @private */ - isPrimary() { return this.#is_primary; } - - /** @summary Assign secondary id - * @private */ - setSecondaryId(primary, name) { - primary.setPrimary(true); // mark as primary, used later - this.#primary_ref = new WeakRef(primary); - this.#secondary_id = name; - } - - /** @summary Returns secondary id - * @private */ - getSecondaryId() { return this.#secondary_id; } - - /** @summary Check if this is secondary painter - * @desc if primary painter provided - check if this really main for this - * @private */ - isSecondary(primary) { - if (!this.#primary_ref) - return false; - return !isObject(primary) ? true : this.#primary_ref.deref() === primary; - } - - /** @summary Return primary object - * @private */ - getPrimary() { return this.#primary_ref?.deref(); } - - /** @summary Canvas main svg element - * @return {object} d3 selection with canvas svg - * @protected */ - getCanvSvg() { return this.selectDom().select('.root_canvas'); } - - /** @summary Method did selects current pad name, now does nothing - * @private - * @deprecated to be removed in v8 */ - selectCurrentPad() { - console.warn('selectCurrentPad is deprecated, will be removed in v8'); - return ''; - } - - /** @summary Return functor, which can convert x and y coordinates into pixels, used for drawing in the pad - * @desc X and Y coordinates can be converted by calling func.x(x) and func.y(y) - * Only can be used for painting in the pad, means CreateG() should be called without arguments - * @param {boolean} isndc - if NDC coordinates will be used - * @param {boolean} [nornd] - if set, return coordinates will not be rounded - * @param {boolean} [use_frame_coordinates] - use frame coordinates even when drawing on the pad - * @protected */ - getAxisToSvgFunc(isndc, nornd, use_frame_coordinates) { - const func = { isndc, nornd }, - use_frame = this.getG()?.property('in_frame'); - if (use_frame || (use_frame_coordinates && !isndc)) - func.fp = this.getFramePainter(); - if (func.fp?.grx && func.fp?.gry) { - func.x0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameX() : 0; - func.y0 = (use_frame_coordinates && !isndc) ? func.fp.getFrameY() : 0; - if (nornd) { - func.x = function(x) { return this.x0 + this.fp.grx(x); }; - func.y = function(y) { return this.y0 + this.fp.gry(y); }; - } else { - func.x = function(x) { return this.x0 + Math.round(this.fp.grx(x)); }; - func.y = function(y) { return this.y0 + Math.round(this.fp.gry(y)); }; - } - } else if (!use_frame) { - const pp = this.getPadPainter(); - func.pad = isndc ? null : pp?.getRootPad(true); // need for NDC conversion - func.padw = pp?.getPadWidth() ?? 10; - func.x = function(value) { - if (this.pad) { - if (this.pad.fLogx) - value = (value > 0) ? Math.log10(value) : this.pad.fUxmin; - value = (value - this.pad.fX1) / (this.pad.fX2 - this.pad.fX1); - } - value *= this.padw; - return this.nornd ? value : Math.round(value); - }; - func.padh = pp?.getPadHeight() ?? 10; - func.y = function(value) { - if (this.pad) { - if (this.pad.fLogy) - value = (value > 0) ? Math.log10(value) : this.pad.fUymin; - value = (value - this.pad.fY1) / (this.pad.fY2 - this.pad.fY1); - } - value = (1 - value) * this.padh; - return this.nornd ? value : Math.round(value); - }; - } else { - console.error(`Problem to create functor for ${this.getClassName()}`); - func.x = () => 0; - func.y = () => 0; - } - return func; - } - - /** @summary Converts x or y coordinate into pad SVG coordinates. - * @desc Only can be used for painting in the pad, means CreateG() should be called without arguments - * @param {string} axis - name like 'x' or 'y' - * @param {number} value - axis value to convert. - * @param {boolean} ndc - is value in NDC coordinates - * @param {boolean} [noround] - skip rounding - * @return {number} value of requested coordinates - * @protected */ - axisToSvg(axis, value, ndc, noround) { - const func = this.getAxisToSvgFunc(ndc, noround); - return func[axis](value); - } - - /** @summary Converts pad SVG x or y coordinates into axis values. - * @desc Reverse transformation for {@link ObjectPainter#axisToSvg} - * @param {string} axis - name like 'x' or 'y' - * @param {number} coord - graphics coordinate. - * @param {boolean} ndc - kind of return value - * @return {number} value of requested coordinates - * @protected */ - svgToAxis(axis, coord, ndc) { - const use_frame = this.getG()?.property('in_frame'); - - if (use_frame) - return this.getFramePainter()?.revertAxis(axis, coord) ?? 0; - - const pp = this.getPadPainter(), - pad = (ndc || !pp) ? null : pp.getRootPad(true); - let value = !pp ? 0 : ((axis === 'y') ? (1 - coord / pp.getPadHeight()) : coord / pp.getPadWidth()); - - if (pad) { - if (axis === 'y') { - value = pad.fY1 + value * (pad.fY2 - pad.fY1); - if (pad.fLogy) - value = Math.pow(10, value); - } else { - value = pad.fX1 + value * (pad.fX2 - pad.fX1); - if (pad.fLogx) - value = Math.pow(10, value); - } - } - - return value; - } - - /** @summary Returns frame painter for current pad - * @desc Pad has direct reference on frame if any - * @protected */ - getFramePainter() { - return this.getPadPainter()?.getFramePainter(); - } - - /** @summary Returns painter for main object on the pad. - * @desc Typically it is first histogram drawn on the pad and which draws frame axes - * But it also can be special use-case as TASImage or TGraphPolargram - * @param {boolean} [not_store] - if true, prevent temporary storage of main painter reference - * @protected */ - getMainPainter(not_store) { - let res = this.#main_painter?.deref(); - if (!res) { - const pp = this.getPadPainter(); - res = pp ? pp.getMainPainter() : this.getTopPainter(); - this.#main_painter = not_store || !res ? null : new WeakRef(res); - } - return res || null; - } - - /** @summary Returns true if this is main painter - * @protected */ - isMainPainter() { return this === this.getMainPainter(); } - - /** @summary Assign this as main painter on the pad - * @desc Main painter typically responsible for axes drawing - * Should not be used by pad/canvas painters, but rather by objects which are drawing axis - * @protected */ - setAsMainPainter(force) { - const pp = this.getPadPainter(); - if (!pp) - this.setTopPainter(); // fallback on BasePainter method - else - pp.setMainPainter(this, force); - } - - /** @summary Add painter to pad list of painters - * @desc Normally called from {@link ensureTCanvas} function when new painter is created - * @protected */ - addToPadPrimitives(pad_painter) { - if (this.#pad_painter_ref) - pad_painter = this.#pad_painter_ref.deref(); - else { - if (!pad_painter) - pad_painter = getDomCanvasPainter(this.selectDom()); // try to detect in DOM - if (pad_painter) - this.#pad_painter_ref = new WeakRef(pad_painter); - } - - if (!pad_painter || (pad_painter === this)) - return null; - - return pad_painter.addToPrimitives(this); - } - - /** @summary Remove painter from pad list of painters - * @desc Can be used from external frameworks to add/remove painters - * @protected */ - removeFromPadPrimitives() { this.getPadPainter()?.removePrimitive(this); } - - /** @summary Creates marker attributes object - * @desc Can be used to produce markers in painter. - * See {@link TAttMarkerHandler} for more info. - * Instance assigned as this.markeratt data member, recognized by GED editor - * @param {object} args - either TAttMarker or see arguments of {@link TAttMarkerHandler} - * @return {object} created handler - * @protected */ - createAttMarker(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args.fMarkerColor !== undefined && args.fMarkerStyle !== undefined && args.fMarkerSize !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#markeratt : null; - - if (!handler) - handler = new TAttMarkerHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#markeratt = handler; - return handler; - } - - /** @summary Creates line attributes object. - * @desc Can be used to produce lines in painter. - * See {@link TAttLineHandler} for more info. - * Instance assigned as this.lineatt data member, recognized by GED editor - * @param {object} args - either TAttLine or see constructor arguments of {@link TAttLineHandler} - * @protected */ - createAttLine(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args.fLineColor !== undefined && args.fLineStyle !== undefined && args.fLineWidth !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#lineatt : null; - - if (!handler) - handler = new TAttLineHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#lineatt = handler; - return handler; - } - - /** @summary Creates text attributes object. - * @param {object} args - either TAttText or see constructor arguments of {@link TAttTextHandler} - * @protected */ - createAttText(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args.fTextFont !== undefined && args.fTextSize !== undefined && args.fTextColor !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#textatt : null; - - if (!handler) - handler = new TAttTextHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#textatt = handler; - return handler; - } - - /** @summary Creates fill attributes object. - * @desc Method dedicated to create fill attributes, bound to canvas SVG - * otherwise newly created patters will not be usable in the canvas - * See {@link TAttFillHandler} for more info. - * Instance assigned as this.fillatt data member, recognized by GED editors - * @param {object} [args] - for special cases one can specify TAttFill as args or number of parameters - * @param {boolean} [args.std = true] - this is standard fill attribute for object and should be used as this.fillatt - * @param {object} [args.attr = null] - object, derived from TAttFill - * @param {number} [args.pattern = undefined] - integer index of fill pattern - * @param {number} [args.color = undefined] - integer index of fill color - * @param {string} [args.color_as_svg = undefined] - color will be specified as SVG string, not as index from color palette - * @param {number} [args.kind = undefined] - some special kind which is handled differently from normal patterns - * @return created handle - * @protected */ - createAttFill(args) { - if (args === undefined) - args = { attr: this.getObject() }; - else if (!isObject(args)) - args = { std: true }; - else if (args._typename && args.fFillColor !== undefined && args.fFillStyle !== undefined) - args = { attr: args, std: false }; - - if (args.std === undefined) - args.std = true; - if (args.painter === undefined) - args.painter = this; - - let handler = args.std ? this.#fillatt : null; - - if (!args.svg) - args.svg = this.getCanvSvg(); - - if (!handler) - handler = new TAttFillHandler(args); - else if (!handler.changed || args.force) - handler.setArgs(args); - - if (args.std) - this.#fillatt = handler; - return handler; - } - - get fillatt() { return this.#fillatt; } - get lineatt() { return this.#lineatt; } - get markeratt() { return this.#markeratt; } - get textatt() { return this.#textatt; } - - /** @summary call function for each painter in the pad - * @desc Iterate over all known painters - * @private */ - forEachPainter(userfunc, kind) { - // iterate over all painters from pad list - let pp = this.getPadPainter(), top = null; - if (!pp) { - top = this.getTopPainter(); - if (isPadPainter(top)) - pp = top; - } - if (pp) - pp.forEachPainterInPad(userfunc, kind); - else if (top && (kind !== 'pads')) - userfunc(top); - } - - /** @summary indicate that redraw was invoked via interactive action (like context menu or zooming) - * @desc Use to catch such action by GED and by server-side - * @return {Promise} when completed - * @private */ - async interactiveRedraw(arg, info, subelem) { - let reason, res; - if (isStr(info) && info.indexOf('exec:')) - reason = info; - - if (arg === 'pad') - res = this.redrawPad(reason); - else if (arg !== false) - res = this.redraw(reason); - - return getPromise(res).then(() => { - if (arg === 'attribute') - return this.getPadPainter()?.redrawLegend(); - }).then(() => { - // inform GED that something changes - const canp = this.getCanvPainter(); - - if (isFunc(canp?.producePadEvent)) - canp.producePadEvent('redraw', this.getPadPainter(), this, null, subelem); - - // inform server that draw options changes - if (isFunc(canp?.processChanges)) - canp.processChanges(info, this, subelem); - - return this; - }); - } - - /** @summary Redraw all objects in the current pad - * @param {string} [reason] - like 'resize' or 'zoom' - * @return {Promise} when pad redraw completed - * @protected */ - async redrawPad(reason) { - return this.getPadPainter()?.redrawPad(reason) ?? false; - } - - /** @summary execute selected menu command, either locally or remotely - * @private */ - executeMenuCommand(method) { - if (method.fName === 'Inspect') - // primitive inspector, keep it here - return this.showInspector(); - - return false; - } - - /** @summary Invoke method for object via WebCanvas functionality - * @desc Requires that painter marked with object identifier (this.#snapid) or identifier provided as second argument - * Canvas painter should exists and in non-readonly mode - * Execution string can look like 'Print()'. - * Many methods call can be chained with 'Print();;Update();;Clear()' - * @private */ - submitCanvExec(exec, snapid) { - if (!exec || !isStr(exec)) - return; - - const canp = this.getCanvPainter(); - if (isFunc(canp?.submitExec)) - canp.submitExec(this, exec, snapid); - } - - /** @summary remove all created draw attributes - * @protected */ - deleteAttr(name) { - if (!name || name === 'line') - this.#lineatt = undefined; - if (!name || name === 'fill') - this.#fillatt = undefined; - if (!name || name === 'marker') - this.#markeratt = undefined; - if (!name || name === 'text') - this.#textatt = undefined; - } - - /** @summary Show object in inspector for provided object - * @protected */ - showInspector(/* opt */) { - return false; - } - - /** @summary Fill context menu for the object - * @private */ - fillContextMenu(menu) { - const cl = this.getClassName(), - name = this.getObjectName(), - p = cl.lastIndexOf('::'), - cl0 = (p > 0) ? cl.slice(p + 2) : cl, - hdr = (cl0 && name) ? `${cl0}:${name}` : (cl0 || name || 'object'), - url = cl ? `${urlClassPrefix}${cl.replaceAll('::', '_1_1')}.html` : ''; - - menu.header(hdr, url); - - const size0 = menu.size(); - - if (isFunc(this.fillContextMenuItems)) - this.fillContextMenuItems(menu); - - if ((menu.size() > size0) && this.showInspector('check')) - menu.add('Inspect', this.showInspector); - - menu.addAttributesMenu(this); - - return menu.size() > size0; - } - - /** @summary shows objects status - * @desc Either used canvas painter method or globally assigned - * When no parameters are specified, just basic object properties are shown - * @private */ - showObjectStatus(name, title, info, info2) { - let cp = this.getCanvPainter(); - - if (!isFunc(cp?.showCanvasStatus)) - cp = null; - - if (!cp && !isFunc(internals.showStatus)) - return false; - - if (this.enlargeMain('state') === 'on') - return false; - - if ((name === undefined) && (title === undefined)) { - const obj = this.getObject(); - if (!obj) - return; - name = this.getItemName() || obj.fName; - title = obj.fTitle || obj._typename; - info = obj._typename; - } - - if (cp) - cp.showCanvasStatus(name, title, info, info2); - else - internals.showStatus(name, title, info, info2); - } - - /** @summary Redraw object - * @desc Basic method, should be reimplemented in all derived objects - * for the case when drawing should be repeated - * @abstract - * @protected */ - redraw(/* reason */) {} - - /** @summary Start text drawing - * @desc required before any text can be drawn - * @param {number} font_face - font id as used in ROOT font attributes - * @param {number} font_size - font size as used in ROOT font attributes - * @param {object} [draw_g] - element where text drawn, by default using main object element - * @param {number} [max_font_size] - maximal font size, used when text can be scaled - * @protected */ - startTextDrawing(font_face, font_size, draw_g, max_font_size, can_async) { - if (!draw_g) - draw_g = this.getG(); - if (!draw_g || draw_g.empty()) - return false; - - const font = (font_size === 'font') ? font_face : new FontHandler(font_face, font_size); - if (can_async && font.needLoad()) - return font; - - font.setPainter(this); // may be required when custom font is used - - draw_g.call(font.func); - - draw_g.property('draw_text_completed', false) // indicate that draw operations submitted - .property('all_args', []) // array of all submitted args, makes easier to analyze them - .property('text_font', font) - .property('text_factor', 0) - .property('max_text_width', 0) // keep maximal text width, use it later - .property('max_font_size', max_font_size) - .property('_fast_drawing', this.getPadPainter()?.isFastDrawing() ?? false); - - if (draw_g.property('_fast_drawing')) - draw_g.property('_font_too_small', (max_font_size && (max_font_size < 5)) || (font.size < 4)); - - return true; - } - - /** @summary Start async text drawing - * @return {Promise} for loading of font if necessary - * @private */ - async startTextDrawingAsync(font_face, font_size, draw_g, max_font_size) { - const font = this.startTextDrawing(font_face, font_size, draw_g, max_font_size, true); - if ((font === true) || (font === false)) - return font; - return font.load().then(res => { - if (!res) - return false; - - return this.startTextDrawing(font, 'font', draw_g, max_font_size); - }); - } - - /** @summary Apply scaling factor to all drawn text in the element - * @desc Can be applied at any time before finishTextDrawing is called - even in the postprocess callbacks of text draw - * @param {number} factor - scaling factor - * @param {object} [draw_g] - drawing element for the text - * @protected */ - scaleTextDrawing(factor, draw_g) { - if (!draw_g) - draw_g = this.getG(); - if (!draw_g || draw_g.empty()) - return; - if (factor && (factor > draw_g.property('text_factor'))) - draw_g.property('text_factor', factor); - } - - /** @summary Analyze if all text draw operations are completed - * @private */ - #checkAllTextDrawing(draw_g, resolveFunc, try_optimize) { - const all_args = draw_g.property('all_args') || []; - let missing = 0; - - all_args.forEach(arg => { - if (!arg.ready) - missing++; - }); - - if (missing > 0) { - if (isFunc(resolveFunc)) { - draw_g.node().textResolveFunc = resolveFunc; - draw_g.node().try_optimize = try_optimize; - } - return; - } - - draw_g.property('all_args', null); // clear all_args property - - // adjust font size (if there are normal text) - const f = draw_g.property('text_factor'), - font = draw_g.property('text_font'), - max_sz = draw_g.property('max_font_size'); - let font_size = font.size, any_text = false, only_text = true; - - if ((f > 0) && ((f < 0.95) || (f > 1.05))) - font.size = Math.max(1, Math.floor(font.size / f)); - - if (max_sz && (font.size > max_sz)) - font.size = max_sz; - - if (font.size !== font_size) { - draw_g.call(font.func); - font_size = font.size; - } - - all_args.forEach(arg => { - if (arg.mj_node && arg.mj_func) { - const svg = arg.mj_node.select('svg'); // MathJax svg - arg.mj_func(this, arg.mj_node, svg, arg, font_size, f); - delete arg.mj_node; // remove reference - only_text = false; - } else if (arg.txt_g) - only_text = false; - }); - - if (!resolveFunc) { - resolveFunc = draw_g.node().textResolveFunc; - try_optimize = draw_g.node().try_optimize; - delete draw_g.node().textResolveFunc; - delete draw_g.node().try_optimize; - } - - const optimize_arr = (try_optimize && only_text) ? [] : null; - - // now process text and latex drawings - all_args.forEach(arg => { - let txt, is_txt, scale = 1; - if (arg.txt_node) { - txt = arg.txt_node; - delete arg.txt_node; - is_txt = true; - if (optimize_arr !== null) - optimize_arr.push(txt); - } else if (arg.txt_g) { - txt = arg.txt_g; - delete arg.txt_g; - is_txt = false; - } else - return; - - txt.attr('visibility', null); - - any_text = true; - - if (arg.width) { - // adjust x position when scale into specified rectangle - if (arg.align[0] === 'middle') - arg.x += arg.width / 2; - else if (arg.align[0] === 'end') - arg.x += arg.width; - } - - if (arg.height) { - if (arg.align[1].indexOf('bottom') === 0) - arg.y += arg.height; - else if (arg.align[1] === 'middle') - arg.y += arg.height / 2; - } - - let dx = 0, dy = 0; - - if (is_txt) { - // handle simple text drawing - - if (isNodeJs()) { - if (arg.scale && (f > 0)) { - arg.box.width *= 1 / f; - arg.box.height *= 1 / f; - } - } else if (!arg.plain && !arg.fast) { - // exact box dimension only required when complex text was build - arg.box = getElementRect(txt, 'bbox'); - } - - if (arg.plain) { - txt.attr('text-anchor', arg.align[0]); - if (arg.align[1] === 'top') - txt.attr('dy', '.8em'); - else if (arg.align[1] === 'middle') { - // if (isNodeJs()) txt.attr('dy', '.4em'); else // old workaround for node.js - txt.attr('dominant-baseline', 'middle'); - } - } else { - txt.attr('text-anchor', 'start'); - dx = ((arg.align[0] === 'middle') ? -0.5 : ((arg.align[0] === 'end') ? -1 : 0)) * arg.box.width; - dy = ((arg.align[1] === 'top') ? (arg.top_shift || 1) : (arg.align[1] === 'middle') ? (arg.mid_shift || 0.5) : 0) * arg.box.height; - } - } else if (arg.text_rect) { - // handle latex drawing - const box = arg.text_rect; - - scale = (f > 0) && (Math.abs(1 - f) > 0.01) ? 1 / f : 1; - - dx = ((arg.align[0] === 'middle') ? -0.5 : ((arg.align[0] === 'end') ? -1 : 0)) * box.width * scale; - - if (arg.align[1] === 'top') - dy = -box.y1 * scale; - else if (arg.align[1] === 'bottom') - dy = -box.y2 * scale; - else if (arg.align[1] === 'middle') - dy = -0.5 * (box.y1 + box.y2) * scale; - } else - console.error('text rect not calcualted - please check code'); - - if (!arg.rotate) { - arg.x += dx; - arg.y += dy; - dx = dy = 0; - } - - // use translate and then rotate to avoid complex sign calculations - let trans = makeTranslate(Math.round(arg.x), Math.round(arg.y)) || ''; - const dtrans = makeTranslate(Math.round(dx), Math.round(dy)), - append = aaa => { - if (trans) - trans += ' '; - trans += aaa; - }; - - if (arg.rotate) - append(`rotate(${Math.round(arg.rotate)})`); - if (scale !== 1) - append(`scale(${scale.toFixed(3)})`); - if (dtrans) - append(dtrans); - if (trans) - txt.attr('transform', trans); - }); - - - // when no any normal text drawn - remove font attributes - if (!any_text) - font.clearFont(draw_g); - - if ((optimize_arr !== null) && (optimize_arr.length > 1)) { - ['fill', 'text-anchor'].forEach(name => { - let first = optimize_arr[0].attr(name); - optimize_arr.forEach(txt_node => { - const value = txt_node.attr(name); - if (!value || (value !== first)) - first = undefined; - }); - if (first) { - draw_g.attr(name, first); - optimize_arr.forEach(txt_node => { txt_node.attr(name, null); }); - } - }); - } - - // if specified, call resolve function - if (resolveFunc) - resolveFunc(this); // IMPORTANT - return painter, may use in draw methods - } - - /** @summary Post-process plain text drawing - * @private */ - #postprocessDrawText(arg, txt_node) { - // complete rectangle with very rough size estimations - arg.box = !isNodeJs() && !settings.ApproxTextSize && !arg.fast - ? getElementRect(txt_node, 'bbox') - : (arg.text_rect || { height: Math.round(1.15 * arg.font_size), width: approximateLabelWidth(arg.text, arg.font, arg.font_size) }); - - txt_node.attr('visibility', 'hidden'); // hide elements until text drawing is finished - - if (arg.box.width > arg.draw_g.property('max_text_width')) - arg.draw_g.property('max_text_width', arg.box.width); - if (arg.scale) - this.scaleTextDrawing(Math.max(1.05 * arg.box.width / arg.width, arg.box.height / arg.height), arg.draw_g); - - arg.result_width = arg.box.width; - arg.result_height = arg.box.height; - - if (isFunc(arg.post_process)) - arg.post_process(this); - - return arg.box.width; - } - - /** @summary Draw text - * @desc The only legal way to draw text, support plain, latex and math text output - * @param {object} arg - different text draw options - * @param {string} arg.text - text to draw - * @param {number|string} [arg.align = 12] - int value like 12 or 31 or string 'end;bottom' - * @param {number} [arg.x = 0] - x position - * @param {number} [arg.y = 0] - y position - * @param {number} [arg.width] - when specified, adjust font size in the specified box - * @param {number} [arg.height] - when specified, adjust font size in the specified box - * @param {boolean} [arg.scale = true] - scale into draw box when width and height parameters are specified - * @param {number} [arg.latex] - 0 - plain text, 1 - normal TLatex, 2 - math - * @param {string} [arg.color=black] - text color - * @param {number} [arg.rotate] - rotation angle - * @param {number} [arg.font_size] - fixed font size - * @param {object} [arg.draw_g] - element where to place text, if not specified central draw_g container is used - * @param {Function} [arg.post_process] - optional function called when specified text is drawn - * @protected */ - drawText(arg) { - if (!arg.text) - arg.text = ''; - - arg.draw_g = arg.draw_g || this.getG(); - if (!arg.draw_g || arg.draw_g.empty()) - return; - - const font = arg.draw_g.property('text_font'); - arg.font = font; // use in latex conversion - - if (font) { - arg.color = arg.color || font.color; - arg.align = arg.align || font.align; - arg.rotate = arg.rotate || font.angle; - } - - let align = ['start', 'middle']; - - if (isStr(arg.align)) { - align = arg.align.split(';'); - if (align.length === 1) - align.push('middle'); - } else if (typeof arg.align === 'number') { - if ((arg.align / 10) >= 3) - align[0] = 'end'; - else if ((arg.align / 10) >= 2) - align[0] = 'middle'; - if ((arg.align % 10) === 0) - align[1] = 'bottom'; - else if ((arg.align % 10) === 1) - align[1] = 'bottom-base'; - else if ((arg.align % 10) === 3) - align[1] = 'top'; - } else if (isObject(arg.align) && (arg.align.length === 2)) - align = arg.align; - - if (arg.latex === undefined) - arg.latex = 1; // 0: text, 1: latex, 2: math - arg.align = align; - arg.x = arg.x || 0; - arg.y = arg.y || 0; - if (arg.scale !== false) - arg.scale = arg.width && arg.height && !arg.font_size; - arg.width = arg.width || 0; - arg.height = arg.height || 0; - - if (arg.draw_g.property('_fast_drawing')) { - if (arg.scale) { - // area too small - ignore such drawing - if (arg.height < 4) - return 0; - } else if (arg.font_size) { - // font size too small - if (arg.font_size < 4) - return 0; - } else if (arg.draw_g.property('_font_too_small')) { - // configure font is too small - ignore drawing - return 0; - } - } - - // include drawing into list of all args - arg.draw_g.property('all_args').push(arg); - arg.ready = false; // indicates if drawing is ready for post-processing - - let use_mathjax = (arg.latex === 2); - const cl = constants.Latex; - - if (arg.latex === 1) { - use_mathjax = (settings.Latex === cl.AlwaysMathJax) || - ((settings.Latex === cl.MathJax) && arg.text.match(/[#{\\]/g)) || - arg.text.match(/[\\]/g); - } - - if (!use_mathjax || arg.nomathjax) { - arg.txt_node = arg.draw_g.append('svg:text'); - - if (arg.color) - arg.txt_node.attr('fill', arg.color); - - if (arg.font_size) - arg.txt_node.attr('font-size', arg.font_size); - else - arg.font_size = font.size; - - arg.plain = !arg.latex || (settings.Latex === cl.Off) || (settings.Latex === cl.Symbols); - - arg.simple_latex = arg.latex && (settings.Latex === cl.Symbols); - - if (!arg.plain || arg.simple_latex || arg.font?.isSymbol) { - if (arg.simple_latex || isPlainText(arg.text) || arg.plain) { - arg.simple_latex = true; - producePlainText(this, arg.txt_node, arg); - } else { - arg.txt_node.remove(); // just remove text node - delete arg.txt_node; - arg.txt_g = arg.draw_g.append('svg:g'); - produceLatex(this, arg.txt_g, arg); - } - arg.ready = true; - this.#postprocessDrawText(arg, arg.txt_g || arg.txt_node); - - if (arg.draw_g.property('draw_text_completed')) - this.#checkAllTextDrawing(arg.draw_g); // check if all other elements are completed - return 0; - } - - arg.plain = true; - arg.txt_node.text(arg.text); - arg.ready = true; - - return this.#postprocessDrawText(arg, arg.txt_node); - } - - arg.mj_node = arg.draw_g.append('svg:g').attr('visibility', 'hidden'); // hide text until drawing is finished - - produceMathjax(this, arg.mj_node, arg).then(() => { - arg.ready = true; - if (arg.draw_g.property('draw_text_completed')) - this.#checkAllTextDrawing(arg.draw_g); - }); - - return 0; - } - - /** @summary Finish text drawing - * @desc Should be called to complete all text drawing operations - * @param {Function} [draw_g] - element for text drawing, default is getG() - * @return {Promise} when text drawing completed - * @protected */ - async finishTextDrawing(draw_g, try_optimize) { - if (!draw_g) - draw_g = this.getG(); - if (!draw_g || draw_g.empty()) - return false; - - draw_g.property('draw_text_completed', true); // mark that text drawing is completed - - return new Promise(resolveFunc => { - this.#checkAllTextDrawing(draw_g, resolveFunc, try_optimize); - }); - } - - /** @summary Configure user-defined context menu for the object - * @desc fillmenu_func will be called when context menu is activated - * Arguments fillmenu_func are (menu,kind) - * First is menu object, second is object sub-element like axis 'x' or 'y' - * Function should return promise with menu when items are filled - * @param {Function} fillmenu_func - function to fill custom context menu for object */ - configureUserContextMenu(fillmenu_func) { - this.#user_context_menu = isFunc(fillmenu_func) ? fillmenu_func : undefined; - } - - /** @summary Fill object menu in web canvas - * @private */ - async fillObjectExecMenu(menu, kind) { - if (isFunc(this.#user_context_menu)) - return this.#user_context_menu(menu, kind); - - const canvp = this.getCanvPainter(); - - if (!this.getSnapId() || !canvp || canvp?.isReadonly() || !canvp?.getWebsocket()) - return menu; - - function doExecMenu(arg) { - const execp = menu.exec_painter || this, - cp = execp.getCanvPainter(), - item = menu.exec_items[parseInt(arg)]; - - if (!item?.fName) - return; - - // this is special entry, produced by TWebMenuItem, which recognizes editor entries itself - if (item.fExec === 'Show:Editor') { - if (isFunc(cp?.activateGed)) - cp.activateGed(execp); - return; - } - - if (isFunc(cp?.executeObjectMethod) && cp.executeObjectMethod(execp, item, item.$execid)) - return; - - item.fClassName = execp.getClassName(); - if ((item.$execid.indexOf('#x') > 0) || (item.$execid.indexOf('#y') > 0) || (item.$execid.indexOf('#z') > 0)) - item.fClassName = clTAxis; - - if (execp.executeMenuCommand(item)) - return; - - if (!item.$execid) - return; - - if (!item.fArgs) { - return cp?.v7canvas ? cp.submitExec(execp, item.fExec, kind) - : execp.submitCanvExec(item.fExec, item.$execid); - } - - menu.showMethodArgsDialog(item).then(args => { - if (!args || execp.executeMenuCommand(item, args)) - return; - - const exec = item.fExec.slice(0, item.fExec.length - 1) + args + ')'; - if (cp?.v7canvas) - cp.submitExec(execp, exec, kind); - else - cp?.sendWebsocket(`OBJEXEC:${item.$execid}:${exec}`); - }); - } - - const doFillMenu = (_menu, _reqid, _resolveFunc, reply) => { - // avoid multiple call of the callback after timeout - if (menu._got_menu) - return; - menu._got_menu = true; - - if (reply && (_reqid !== reply.fId)) - console.error(`missmatch between request ${_reqid} and reply ${reply.fId} identifiers`); - - menu.exec_items = reply?.fItems; - - if (menu.exec_items?.length) { - if (_menu.size() > 0) - _menu.separator(); - - let lastclname; - - for (let n = 0; n < menu.exec_items.length; ++n) { - const item = menu.exec_items[n]; - item.$execid = reply.fId; - item.$menu = menu; - - if (item.fClassName && lastclname && (lastclname !== item.fClassName)) { - _menu.endsub(); - lastclname = ''; - } - if (lastclname !== item.fClassName) { - lastclname = item.fClassName; - const p = lastclname.lastIndexOf('::'), - shortname = (p > 0) ? lastclname.slice(p + 2) : lastclname; - - _menu.sub(shortname.replace(/[<>]/g, '_')); - } - - if ((item.fChecked === undefined) || (item.fChecked < 0)) - _menu.add(item.fName, n, doExecMenu); - else - _menu.addchk(item.fChecked, item.fName, n, doExecMenu); - } - - if (lastclname) - _menu.endsub(); - } - - _resolveFunc(_menu); - }, - reqid = this.getSnapId(kind); - - menu._got_menu = false; - - // if menu painter differs from this, remember it for further usage - if (menu.painter) - menu.exec_painter = (menu.painter !== this) ? this : undefined; - - return new Promise(resolveFunc => { - let did_resolve = false; - - function handleResolve(res) { - if (did_resolve) - return; - did_resolve = true; - resolveFunc(res); - } - - // set timeout to avoid menu hanging - setTimeout(() => doFillMenu(menu, reqid, handleResolve), 2000); - - canvp.submitMenuRequest(this, kind, reqid).then(lst => doFillMenu(menu, reqid, handleResolve, lst)); - }); - } - - /** @summary Configure user-defined tooltip handler - * @desc Hook for the users to get tooltip information when mouse cursor moves over then object - * Handler function will be called every time when new data is selected - * @param {Function} handler - function called when tooltip is produced - * @param {number} [tmout = 100] - delay in ms before tooltip delivered */ - configureUserTooltipHandler(handler, tmout = 100) { - if (!handler || !isFunc(handler)) { - this.#user_tooltip_handler = undefined; - this.#user_tooltip_timeout = undefined; - } else { - this.#user_tooltip_handler = handler; - this.#user_tooltip_timeout = tmout; - } - } - - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was performed - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of click will be disabled - * @param {Function} handler - function called when mouse click is done */ - configureUserClickHandler(handler) { - const fp = this.getFramePainter(); - if (isFunc(fp?.configureUserClickHandler)) - fp.configureUserClickHandler(handler); - } - - /** @summary Configure user-defined dblclick handler - * @desc Function will be called every time when double click was called - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of dblclick (unzoom) will be disabled - * @param {Function} handler - function called when mouse double click is done */ - configureUserDblclickHandler(handler) { - const fp = this.getFramePainter(); - if (isFunc(fp?.configureUserDblclickHandler)) - fp.configureUserDblclickHandler(handler); - } - - /** @summary Check if user-defined tooltip function was configured - * @return {boolean} flag is user tooltip handler was configured */ - hasUserTooltip() { - return isFunc(this.#user_tooltip_handler); - } - - /** @summary Provide tooltips data to user-defined function - * @param {object} data - tooltip data - * @private */ - provideUserTooltip(data) { - if (!this.hasUserTooltip()) - return; - - if (this.#user_tooltip_timeout <= 0) - return this.#user_tooltip_handler(data); - - if (this.#user_tooltip_timerid) { - clearTimeout(this.#user_tooltip_timerid); - this.#user_tooltip_timerid = undefined; - } - - if (!data) - return this.#user_tooltip_handler(data); - - // only after timeout user function will be called - this.#user_tooltip_timerid = setTimeout(() => { - this.#user_tooltip_timerid = undefined; - if (this.#user_tooltip_handler) - this.#user_tooltip_handler(data); - }, this.#user_tooltip_timeout); - } - - /** @summary Provide projection areas - * @param {string} kind - 'X', 'Y', 'XY' or '' - * @private */ - async provideSpecialDrawArea(kind) { - if (kind === this.#special_draw_area) - return true; - - return this.getCanvPainter().toggleProjection(kind).then(() => { - this.#special_draw_area = kind; - return true; - }); - } - - /** @summary Draw in special projection areas - * @param {object} obj - object to draw - * @param {string} opt - draw option - * @param {string} kind - '', 'X', 'Y' - * @private */ - async drawInSpecialArea(obj, opt, kind) { - const canp = this.getCanvPainter(); - if (this.#special_draw_area && isFunc(canp?.drawProjection)) - return canp.drawProjection(kind || this.#special_draw_area, obj, opt); - - return false; - } - - /** @summary Get tooltip for painter and specified event position - * @param {object} evnt - object with clientX and clientY positions - * @private */ - getToolTip(evnt) { - if ((evnt?.clientX === undefined) || (evnt?.clientY === undefined)) - return null; - - const frame = this.getPadPainter()?.getFrameSvg(); - if (!frame || frame.empty()) - return null; - const layer = frame.selectChild('.main_layer'); - if (layer.empty()) - return null; - - const pos = d3_pointer(evnt, layer.node()), - pnt = { touch: false, x: pos[0], y: pos[1] }; - - if (isFunc(this.extractToolTip)) - return this.extractToolTip(pnt); - - pnt.disabled = true; - - const res = isFunc(this.processTooltipEvent) ? this.processTooltipEvent(pnt) : null; - - return res?.user_info || res; - } - -} // class ObjectPainter - - -/** @summary Generic text drawing - * @private */ -function drawRawText(dom, txt /* , opt */) { - const painter = new BasePainter(dom); - painter.txt = txt; - - painter.redrawObject = async function(obj) { - this.txt = obj; - return this.drawText(); - }; - - painter.drawText = async function() { - let stxt = (this.txt._typename === clTObjString) ? this.txt.fString : this.txt.value; - if (!isStr(stxt)) - stxt = ''; - - const mathjax = this.txt.mathjax || (settings.Latex === constants.Latex.AlwaysMathJax), - frame = this.selectDom(); - let main = frame.select('div'); - if (main.empty()) - main = frame.append('div').attr('style', 'max-width:100%;max-height:100%;overflow:auto'); - else - main.html(''); - - // (re) set painter to first child element, base painter not requires canvas - this.setTopPainter(); - - if (!mathjax && !('as_is' in this.txt)) { - const arr = stxt.split('\n'); - for (let i = 0; i < arr.length; ++i) - main.append('pre').style('margin', '0').text(arr[i]); - } else - main.text(stxt); - - if (mathjax) - typesetMathjax(frame.node()); - - return this; - }; - - return painter.drawText(); -} - -/** @summary Returns canvas painter (if any) for specified DOM element - * @param {string|object} dom - id or DOM element or pad painter - * @private */ -function getElementCanvPainter(dom) { - return isPadPainter(dom) ? dom.getCanvPainter() : getDomCanvasPainter(new ObjectPainter(dom).selectDom()); -} - -/** @summary Returns pad painter (if any) for specified DOM element - * @param {string|object} dom - id or DOM element or pad painter - * @private */ -function getElementPadPainter(dom) { - return isPadPainter(dom) ? dom : new ObjectPainter(dom).getPadPainter(); -} - -/** @summary Returns main painter (if any) for specified HTML element - typically histogram painter - * @param {string|object} dom - id or DOM element or pad painter - * @private */ -function getElementMainPainter(dom) { - return isPadPainter(dom) ? dom.getMainPainter() : new ObjectPainter(dom).getMainPainter(true); -} - -/** @summary Save object, drawn in specified element, as JSON. - * @desc Normally it is TCanvas object with list of primitives - * @param {string|object} dom - id of top div element or directly DOMElement - * @return {string} produced JSON string */ -function drawingJSON(dom) { - return getElementCanvPainter(dom)?.produceJSON() || ''; -} - -let $active_pp = null; - -/** @summary Set active pad painter - * @desc Normally be used to handle key press events, which are global in the web browser - * @param {object} args - functions arguments - * @param {object} args.pp - pad painter - * @param {boolean} [args.active] - is pad activated or not - * @private */ -function selectActivePad(args) { - if (args.active) { - $active_pp?.getFramePainter()?.setFrameActive(false); - $active_pp = args.pp; - $active_pp?.getFramePainter()?.setFrameActive(true); - } else if ($active_pp === args.pp) - $active_pp = null; -} - -/** @summary Returns current active pad - * @desc Should be used only for keyboard handling - * @private */ -function getActivePad() { - return $active_pp; -} - -/** @summary Check resize of drawn element - * @param {string|object} dom - id or DOM element - * @param {boolean|object} arg - options on how to resize - * @desc As first argument dom one should use same argument as for the drawing - * As second argument, one could specify 'true' value to force redrawing of - * the element even after minimal resize - * Or one just supply object with exact sizes like { width:300, height:200, force:true }; - * @example - * import { resize } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; - * resize('drawing', { width: 500, height: 200 }); - * resize(document.querySelector('#drawing'), true); */ -function resize(dom, arg) { - if (arg === true) - arg = { force: true }; - else if (!isObject(arg)) - arg = null; - let done = false; - new ObjectPainter(dom).forEachPainter(painter => { - if (!done && isFunc(painter.checkResize)) - done = painter.checkResize(arg); - }); - return done; -} - - -/** @summary Safely remove all drawings from specified element - * @param {string|object} dom - id or DOM element - * @public - * @example - * import { cleanup } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; - * cleanup('drawing'); - * cleanup(document.querySelector('#drawing')); */ -function cleanup(dom) { - const dummy = new ObjectPainter(dom), lst = []; - dummy.forEachPainter(p => { - if (lst.indexOf(p) < 0) - lst.push(p); - }); - lst.forEach(p => p.cleanup()); - dummy.selectDom().html(''); - return lst; -} - -const EAxisBits = { - kDecimals: BIT(7), - kTickPlus: BIT(9), - kTickMinus: BIT(10), - kAxisRange: BIT(11), - kCenterTitle: BIT(12), - kCenterLabels: BIT(14), - kRotateTitle: BIT(15), - kPalette: BIT(16), - kNoExponent: BIT(17), - kLabelsHori: BIT(18), - kLabelsVert: BIT(19), - kLabelsDown: BIT(20), - kLabelsUp: BIT(21), - kIsInteger: BIT(22), - kMoreLogLabels: BIT(23), - kOppositeTitle: BIT(32) // artificial bit, not possible to set in ROOT -}, kAxisLabels = 'labels', kAxisNormal = 'normal', kAxisFunc = 'func', kAxisTime = 'time'; - -Object.assign(internals.jsroot, { ObjectPainter, cleanup, resize }); - -export { isPadPainter, getDomCanvasPainter, getElementPadPainter, getElementCanvPainter, getElementMainPainter, drawingJSON, - selectActivePad, getActivePad, cleanup, resize, drawRawText, - ObjectPainter, EAxisBits, kAxisLabels, kAxisNormal, kAxisFunc, kAxisTime }; diff --git a/js/modules/base/RObjectPainter.mjs b/js/modules/base/RObjectPainter.mjs deleted file mode 100644 index 1dd2a6d467a9f..0000000000000 --- a/js/modules/base/RObjectPainter.mjs +++ /dev/null @@ -1,398 +0,0 @@ -import { isStr, isFunc, nsREX, settings, isNodeJs, isBatchMode } from '../core.mjs'; -import { FontHandler } from './FontHandler.mjs'; -import { ObjectPainter } from './ObjectPainter.mjs'; -import { color as d3_color } from '../d3.mjs'; - - -const kNormal = 1, /* kLessTraffic = 2, */ kOffline = 3; - -class RObjectPainter extends ObjectPainter { - - #pending_request; - #auto_colors; // handle for auto colors - - constructor(dom, obj, opt, csstype) { - super(dom, obj, opt); - this.csstype = csstype; - } - - /** @summary Add painter to pad list of painters - * @desc For RCanvas also handles common style - * @protected */ - addToPadPrimitives(pad_painter) { - const pp = super.addToPadPrimitives(pad_painter); - - if (pp && !this.rstyle && pp.next_rstyle) - this.rstyle = pp.next_rstyle; - - return pp; - } - - /** @summary Evaluate v7 attributes using fAttr storage and configured RStyle */ - v7EvalAttr(name, dflt) { - const obj = this.getObject(); - if (!obj) - return dflt; - if (this.cssprefix) - name = this.cssprefix + name; - - const type_check = res => { - if (dflt === undefined) - return res; - const typ1 = typeof dflt, typ2 = typeof res; - if (typ1 === typ2) - return res; - if (typ1 === 'boolean') { - if (typ2 === 'string') - return (res !== '') && (res !== '0') && (res !== 'no') && (res !== 'off'); - return Boolean(res); - } - if ((typ1 === 'number') && (typ2 === 'string')) - return parseFloat(res); - return res; - }; - - if (obj.fAttr?.m) { - const value = obj.fAttr.m[name]; - if (value) - return type_check(value.v); // found value direct in attributes - } - - if (this.rstyle?.fBlocks) { - const blks = this.rstyle.fBlocks; - for (let k = 0; k < blks.length; ++k) { - const block = blks[k], - match = (this.csstype && (block.selector === this.csstype)) || - (obj.fId && (block.selector === ('#' + obj.fId))) || - (obj.fCssClass && (block.selector === ('.' + obj.fCssClass))); - - if (match && block.map?.m) { - const value = block.map.m[name.toLowerCase()]; - if (value) - return type_check(value.v); - } - } - } - - return dflt; - } - - /** @summary Set v7 attributes value */ - v7SetAttr(name, value) { - const obj = this.getObject(); - if (this.cssprefix) - name = this.cssprefix + name; - - if (obj?.fAttr?.m) - obj.fAttr.m[name] = { v: value }; - } - - /** @summary Decode pad length from string, return pixel value */ - v7EvalLength(name, sizepx, dflt) { - if (sizepx <= 0) - sizepx = 1; - - const value = this.v7EvalAttr(name); - - if (value === undefined) - return Math.round(dflt * sizepx); - - if (typeof value === 'number') - return Math.round(value * sizepx); - - if (value === null) - return 0; - - let norm = 0, px = 0, val = value, operand = 0, pos = 0; - - while (val) { - // skip empty spaces - while ((pos < val.length) && ((val[pos] === ' ') || (val[pos] === '\t'))) - ++pos; - - if (pos >= val.length) - break; - - if ((val[pos] === '-') || (val[pos] === '+')) { - if (operand) { - console.log(`Fail to parse RPadLength ${value}`); - return dflt; - } - operand = (val[pos] === '-') ? -1 : 1; - pos++; - continue; - } - - if (pos > 0) { - val = val.slice(pos); - pos = 0; - } - - while ((pos < val.length) && (((val[pos] >= '0') && (val[pos] <= '9')) || (val[pos] === '.'))) - pos++; - - const v = parseFloat(val.slice(0, pos)); - if (!Number.isFinite(v)) { - console.log(`Fail to parse RPadLength ${value}`); - return Math.round(dflt * sizepx); - } - - val = val.slice(pos); - pos = 0; - if (!operand) - operand = 1; - if (val && (val[0] === '%')) { - val = val.slice(1); - norm += operand * v * 0.01; - } else if ((val.length > 1) && (val[0] === 'p') && (val[1] === 'x')) { - val = val.slice(2); - px += operand * v; - } else - norm += operand * v; - - operand = 0; - } - - return Math.round(norm * sizepx + px); - } - - /** @summary Evaluate RColor using attribute storage and configured RStyle */ - v7EvalColor(name, dflt) { - let val = this.v7EvalAttr(name, ''); - if (!val || !isStr(val)) - return dflt; - - if (val === 'auto') { - const pp = this.getPadPainter(); - if (pp) { - val = pp.getAutoColor(); - if (!this.#auto_colors) - this.#auto_colors = {}; - this.#auto_colors[name] = val; - } else if (this.#auto_colors && this.#auto_colors[name]) - val = this.#auto_colors[name]; - else { - console.error(`Autocolor ${name} not defined yet - please check code`); - val = ''; - } - } else if (val[0] === '[') { - const ordinal = parseFloat(val.slice(1, val.length - 1)); - val = 'black'; - if (Number.isFinite(ordinal)) { - const pal = this.getPadPainter()?.getHistPalette(); - if (pal) - val = pal.getColorOrdinal(ordinal); - } - } - - // to make colors similar in node and in pupperteer - if ((val[0] === '#') && (isNodeJs() || (isBatchMode() && settings.ApproxTextSize))) { - const col = d3_color(val); - if (col.opacity !== 1) - col.opacity = col.opacity.toFixed(2); - return col.formatRgb(); - } - - return val; - } - - /** @summary Evaluate RAttrText properties - * @return {object} FontHandler, can be used directly for the text drawing */ - v7EvalFont(name, dflts, fontScale) { - if (!dflts) - dflts = {}; - else if (typeof dflts === 'number') - dflts = { size: dflts }; - - const pp = this.getPadPainter(), - rfont = pp?._dfltRFont || { fFamily: 'Arial', fStyle: '', fWeight: '' }, - text_angle = this.v7EvalAttr(name + '_angle', 0), - text_align = this.v7EvalAttr(name + '_align', dflts.align || 'none'), - text_color = this.v7EvalColor(name + '_color', dflts.color || 'none'), - font_family = this.v7EvalAttr(name + '_font_family', rfont.fFamily || 'Arial'), - font_style = this.v7EvalAttr(name + '_font_style', rfont.fStyle || ''), - font_weight = this.v7EvalAttr(name + '_font_weight', rfont.fWeight || ''); - let text_size = this.v7EvalAttr(name + '_size', dflts.size || 12); - - if (isStr(text_size)) - text_size = parseFloat(text_size); - if (!Number.isFinite(text_size) || (text_size <= 0)) - text_size = 12; - if (!fontScale) - fontScale = pp?.getPadHeight() || 100; - - const handler = new FontHandler(null, text_size, fontScale); - handler.setNameStyleWeight(font_family, font_style, font_weight); - - if (text_angle) - handler.setAngle(360 - text_angle); - if (text_align !== 'none') - handler.setAlign(text_align); - if (text_color !== 'none') - handler.setColor(text_color); - - return handler; - } - - /** @summary Create this.fillatt object based on v7 fill attributes */ - createv7AttFill(prefix) { - if (!prefix || !isStr(prefix)) - prefix = 'fill_'; - - const color = this.v7EvalColor(prefix + 'color', ''), - pattern = this.v7EvalAttr(prefix + 'style', 0); - - this.createAttFill({ pattern, color, color_as_svg: true }); - } - - /** @summary Create this.lineatt object based on v7 line attributes */ - createv7AttLine(prefix) { - if (!prefix || !isStr(prefix)) - prefix = 'line_'; - - const color = this.v7EvalColor(prefix + 'color', 'black'), - width = this.v7EvalAttr(prefix + 'width', 1), - style = this.v7EvalAttr(prefix + 'style', 1); - let pattern = this.v7EvalAttr(prefix + 'pattern'); - if (pattern && isNodeJs()) - pattern = pattern.split(',').join(', '); - - this.createAttLine({ color, width, style, pattern }); - - if (prefix === 'border_') - this.lineatt.setBorder(this.v7EvalAttr(prefix + 'rx', 0), this.v7EvalAttr(prefix + 'ry', 0)); - } - - /** @summary Create this.markeratt object based on v7 attributes */ - createv7AttMarker(prefix) { - if (!prefix || !isStr(prefix)) - prefix = 'marker_'; - - const color = this.v7EvalColor(prefix + 'color', 'black'), - size = this.v7EvalAttr(prefix + 'size', 0.01), - style = this.v7EvalAttr(prefix + 'style', 1), - refsize = (size >= 1) ? 1 : (this.getPadPainter()?.getPadHeight() || 100); - - this.createAttMarker({ color, size, style, refsize }); - } - - /** @summary Create RChangeAttr, which can be applied on the server side - * @private */ - v7AttrChange(req, name, value, kind) { - if (!this.getSnapId()) - return false; - - if (!req._typename) { - req._typename = `${nsREX}RChangeAttrRequest`; - req.ids = []; - req.names = []; - req.values = []; - req.update = true; - } - - if (this.cssprefix) - name = this.cssprefix + name; - req.ids.push(this.getSnapId()); - req.names.push(name); - - if ((value === null) || (value === undefined)) { - if (!kind) - kind = 'none'; - if (kind !== 'none') - console.error(`Trying to set ${kind} for none value`); - } - - if (!kind) { - switch (typeof value) { - case 'number': kind = 'double'; break; - case 'boolean': kind = 'boolean'; break; - } - } - - const obj = { _typename: `${nsREX}RAttrMap::` }; - switch (kind) { - case 'none': - obj._typename += 'NoValue_t'; - break; - case 'boolean': - obj._typename += 'BoolValue_t'; - obj.v = Boolean(value); - break; - case 'int': - obj._typename += 'IntValue_t'; - obj.v = parseInt(value); - break; - case 'double': - obj._typename += 'DoubleValue_t'; - obj.v = parseFloat(value); - break; - default: - obj._typename += 'StringValue_t'; - obj.v = isStr(value) ? value : JSON.stringify(value); - break; - } - - req.values.push(obj); - return true; - } - - /** @summary Sends accumulated attribute changes to server */ - v7SendAttrChanges(req, do_update) { - const canp = this.getCanvPainter(); - if (canp && req?._typename) { - if (do_update !== undefined) - req.update = Boolean(do_update); - canp.v7SubmitRequest('', req); - } - } - - /** @summary Submit request to server-side drawable - * @param {string} kind defines request kind, only single request a time can be submitted - * @param {object} req is object derived from DrawableRequest, including correct _typename - * @param {string} method is method of painter object which will be called when getting reply */ - v7SubmitRequest(kind, req, method) { - const canp = this.getCanvPainter(); - if (!isFunc(canp?.submitDrawableRequest)) - return null; - - // special situation when snapid not yet assigned - just keep ref until snapid is there - // maybe keep full list - for now not clear if really needed - if (!this.getSnapId()) { - this.#pending_request = { kind, req, method }; - return req; - } - - return canp.submitDrawableRequest(kind, req, this, method); - } - - /** @summary Assign snapid to the painter - * @desc Overwrite default method */ - assignSnapId(id) { - super.assignSnapId(id); - if (this.getSnapId() && this.#pending_request) { - const p = this.#pending_request; - this.#pending_request = undefined; - this.v7SubmitRequest(p.kind, p.req, p.method); - } - } - - /** @summary Return communication mode with the server - * @desc - * kOffline means no server there, - * kLessTraffic advise not to send commands if offline functionality available - * kNormal is standard functionality with RCanvas on server side */ - v7CommMode() { - const canp = this.getCanvPainter(); - if (!canp || !canp.submitDrawableRequest || !canp.getWebsocket()) - return kOffline; - - return kNormal; - } - - v7NormalMode() { return this.v7CommMode() === kNormal; } - - v7OfflineMode() { return this.v7CommMode() === kOffline; } - -} // class RObjectPainter - -export { RObjectPainter }; diff --git a/js/modules/base/TAttFillHandler.mjs b/js/modules/base/TAttFillHandler.mjs deleted file mode 100644 index 96a906168061d..0000000000000 --- a/js/modules/base/TAttFillHandler.mjs +++ /dev/null @@ -1,587 +0,0 @@ -import { gStyle, isObject, isStr } from '../core.mjs'; -import { color as d3_color, rgb as d3_rgb, select as d3_select } from '../d3.mjs'; -import { getColor, findColor, clTLinearGradient, clTRadialGradient, toColor } from './colors.mjs'; - - -/** - * @summary Handle for fill attributes - * @private - */ - -class TAttFillHandler { - - #disabled; // if fill disabled - - /** @summary constructor - * @param {object} args - arguments see {@link TAttFillHandler#setArgs} for more info - * @param {number} [args.kind = 2] - 1 means object drawing where combination fillcolor == 0 and fillstyle == 1001 means no filling, - * 2 means all other objects where such combination is white-color filling */ - constructor(args) { - this.color = 'none'; - this.colorindx = 0; - this.pattern = 0; - this.used = true; - this.kind = args.kind || 2; - this.changed = false; - this.func = this.apply.bind(this); - this.setArgs(args); - this.changed = false; // unset change property - } - - /** @summary Set fill style as arguments - * @param {object} args - different arguments to set fill attributes - * @param {object} [args.attr] - TAttFill object - * @param {number} [args.color] - color id - * @param {number} [args.pattern] - fill pattern id - * @param {object} [args.svg] - SVG element to store newly created patterns - * @param {string} [args.color_as_svg] - color in SVG format */ - setArgs(args) { - if (isObject(args.attr)) { - args.pattern ??= args.attr.fFillStyle; - args.color ??= args.attr.fFillColor; - } - - if (args.enable !== undefined) - this.enable(args.enable); - - const was_changed = this.changed; // preserve changed state - this.change(args.color, args.pattern, args.svg, args.color_as_svg, args.painter); - this.changed = was_changed; - } - - /** @summary Apply fill style to selection */ - apply(selection) { - if (this.#disabled) { - selection.style('fill', 'none'); - return; - } - - this.used = true; - - selection.style('fill', this.getFillColor()); - - if ('opacity' in this) - selection.style('opacity', this.opacity); - - if ('antialias' in this) - selection.style('antialias', this.antialias); - } - - /** @summary Returns fill color (or pattern url) */ - getFillColor() { return this.pattern_url || this.color; } - - /** @summary Returns fill color without pattern url. - * @desc If empty, alternative color will be provided - * @param {string} [alt] - alternative color which returned when fill color not exists - * @private */ - getFillColorAlt(alt) { return this.color && (this.color !== 'none') ? this.color : alt; } - - /** @summary Returns true if color not specified or fill style not specified */ - empty() { - const fill = this.getFillColor(); - return !fill || (fill === 'none'); - } - - /** @summary Enable or disable fill usage - if disabled only 'fill: none' will be applied */ - enable(on) { - this.#disabled = ((on === undefined) || on) ? undefined : true; - } - - /** @summary Set usage flag of attribute */ - setUsed(flag) { - this.used = flag; - } - - /** @summary Returns true if fill attributes has real color */ - hasColor() { - return this.color && (this.color !== 'none'); - } - - /** @summary Set solid fill color as fill pattern - * @param {string} col - solid color */ - setSolidColor(col) { - delete this.pattern_url; - this.color = col; - this.pattern = 1001; - } - - /** @summary Set fill color opacity */ - setOpacity(o) { - this.opacity = o; - } - - /** @summary Check if solid fill is used, also color can be checked - * @param {string} [solid_color] - when specified, checks if fill color matches */ - isSolid(solid_color) { - if ((this.pattern !== 1001) || this.gradient) - return false; - return !solid_color || (solid_color === this.color); - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets - * @private */ - verifyDirectChange(painter) { - if (isStr(this.pattern)) - this.pattern = parseInt(this.pattern); - if (!Number.isInteger(this.pattern)) - this.pattern = 0; - - this.change(this.color, this.pattern, painter?.getCanvSvg(), true, painter); - } - - /** @summary Method to change fill attributes. - * @param {number} color - color index - * @param {number} pattern - pattern index - * @param {selection} svg - top canvas element for pattern storages - * @param {string} [color_as_svg] - when color is string, interpret as normal SVG color - * @param {object} [painter] - when specified, used to extract color by index */ - change(color, pattern, svg, color_as_svg, painter) { - delete this.pattern_url; - delete this.gradient; - this.changed = true; - - if ((color !== undefined) && Number.isInteger(parseInt(color)) && !color_as_svg) - this.colorindx = parseInt(color); - - if ((pattern !== undefined) && Number.isInteger(parseInt(pattern))) { - this.pattern = parseInt(pattern); - delete this.opacity; - delete this.antialias; - } - - if ((this.pattern === 1000) && (this.colorindx === 0)) { - this.pattern_url = 'white'; - return true; - } - - if (this.pattern === 1000) - this.pattern = 1001; - - if (this.pattern < 1001) { - this.pattern_url = 'none'; - return true; - } - - if (this.isSolid() && (this.colorindx === 0) && (this.kind === 1) && !color_as_svg) { - this.pattern_url = 'none'; - return true; - } - - let indx = this.colorindx; - - if (color_as_svg) { - this.color = color; - if (color !== 'none') - indx = d3_color(color).hex().slice(1); // fictional index produced from color code - } else - this.color = painter ? painter.getColor(indx) : getColor(indx); - - if (!isStr(this.color)) { - if (isObject(this.color) && (this.color?._typename === clTLinearGradient || this.color?._typename === clTRadialGradient)) - this.gradient = this.color; - this.color = 'none'; - } - - if (this.isSolid()) - return true; - - if (!this.gradient) { - if ((this.pattern >= 4000) && (this.pattern <= 4100)) { - // special transparent colors (use for sub-pads) - this.opacity = (this.pattern - 4000) / 100; - return true; - } - if ((this.pattern < 3000) || (this.color === 'none')) - return false; - } - - if (!svg || svg.empty()) - return false; - - let id, lines = '', lfill = null, fills = '', fills2 = '', w = 2, h = 2; - - if (this.gradient) - id = `grad_${this.gradient.fNumber}`; - else { - id = `pat_${this.pattern}_${indx}`; - - switch (this.pattern) { - case 3001: - w = h = 2; - fills = 'M0,0h1v1h-1zM1,1h1v1h-1z'; - break; - case 3002: - w = 4; - h = 2; - fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; - break; - case 3003: - w = h = 4; - fills = 'M2,1h1v1h-1zM0,3h1v1h-1z'; - break; - case 3004: - w = h = 8; - lines = 'M8,0L0,8'; - break; - case 3005: - w = h = 8; - lines = 'M0,0L8,8'; - break; - case 3006: - w = h = 4; - lines = 'M1,0v4'; - break; - case 3007: - w = h = 4; - lines = 'M0,1h4'; - break; - case 3008: - w = h = 10; - fills = 'M0,3v-3h3ZM7,0h3v3ZM0,7v3h3ZM7,10h3v-3ZM5,2l3,3l-3,3l-3,-3Z'; - lines = 'M0,3l5,5M3,10l5,-5M10,7l-5,-5M7,0l-5,5'; - break; - case 3009: - w = h = 12; - lines = 'M0,0A6,6,0,0,0,12,0M6,6A6,6,0,0,0,12,12M6,6A6,6,0,0,1,0,12'; - lfill = 'none'; - break; - case 3010: // bricks - w = h = 10; - lines = 'M0,2h10M0,7h10M2,0v2M7,2v5M2,7v3'; - break; - case 3011: - w = 9; - h = 18; - lines = 'M5,0v8M2,1l6,6M8,1l-6,6M9,9v8M6,10l3,3l-3,3M0,9v8M3,10l-3,3l3,3'; - lfill = 'none'; - break; - case 3012: - w = 10; - h = 20; - lines = 'M5,1A4,4,0,0,0,5,9A4,4,0,0,0,5,1M0,11A4,4,0,0,1,0,19M10,11A4,4,0,0,0,10,19'; - lfill = 'none'; - break; - case 3013: - w = h = 7; - lines = 'M0,0L7,7M7,0L0,7'; - lfill = 'none'; - break; - case 3014: - w = h = 16; - lines = 'M0,0h16v16h-16v-16M0,12h16M12,0v16M4,0v8M4,4h8M0,8h8M8,4v8'; - lfill = 'none'; - break; - case 3015: - w = 6; - h = 12; - lines = 'M2,1A2,2,0,0,0,2,5A2,2,0,0,0,2,1M0,7A2,2,0,0,1,0,11M6,7A2,2,0,0,0,6,11'; - lfill = 'none'; - break; - case 3016: - w = 12; - h = 7; - lines = 'M0,1A3,2,0,0,1,3,3A3,2,0,0,0,9,3A3,2,0,0,1,12,1'; - lfill = 'none'; - break; - case 3017: - w = h = 4; - lines = 'M3,1l-2,2'; - break; - case 3018: - w = h = 4; - lines = 'M1,1l2,2'; - break; - case 3019: - w = h = 12; - lines = 'M1,6A5,5,0,0,0,11,6A5,5,0,0,0,1,6h-1h1A5,5,0,0,1,6,11v1v-1A5,5,0,0,1,11,6h1h-1A5,5,0,0,1,6,1v-1v1A5,5,0,0,1,1,6'; - lfill = 'none'; - break; - case 3020: - w = 7; - h = 12; - lines = 'M1,0A2,3,0,0,0,3,3A2,3,0,0,1,3,9A2,3,0,0,0,1,12'; - lfill = 'none'; - break; - case 3021: // left stairs - w = h = 8; - lines = 'M8,2h-2v4h-4v2M2,0v2h-2'; - lfill = 'none'; - break; - case 3022: // right stairs - w = h = 8; - lines = 'M0,2h2v4h4v2M6,0v2h2'; - lfill = 'none'; - break; - case 3023: - w = h = 8; - fills = 'M4,0h4v4zM8,4v4h-4z'; - fills2 = 'M4,0L0,4L4,8L8,4Z'; - break; - case 3024: - w = h = 16; - fills = 'M0,8v8h2v-8zM8,0v8h2v-8M4,14v2h12v-2z'; - fills2 = 'M0,2h8v6h4v-6h4v12h-12v-6h-4z'; - break; - case 3025: - w = h = 18; - fills = 'M5,13v-8h8ZM18,0v18h-18l5,-5h8v-8Z'; - break; - default: { - if ((this.pattern > 3025) && (this.pattern < 3100)) { - // same as 3002, see TGX11.cxx, line 2234 - w = 4; - h = 2; - fills = 'M1,0h1v1h-1zM3,1h1v1h-1z'; - break; - } - - const code = this.pattern % 1000, - k = code % 10, - j = ((code - k) % 100) / 10, - i = (code - j * 10 - k) / 100; - if (!i) - break; - - // use flexible hatches only possible when single pattern is used, - // otherwise it is not possible to adjust pattern dimension that both hatches match with each other - const use_new = (j === k) || (j === 0) || (j === 5) || (j === 9) || (k === 0) || (k === 5) || (k === 9), - pp = painter?.getPadPainter(), - scale_size = pp ? Math.max(pp.getPadWidth(), pp.getPadHeight()) : 600, - spacing_original = Math.max(0.1, gStyle.fHatchesSpacing * scale_size * 0.001), - hatches_spacing = Math.max(1, Math.round(spacing_original)) * 6, - sz = i * hatches_spacing; // axis distance between lines - - id += use_new ? `_hn${Math.round(spacing_original * 100)}` : `_ho${hatches_spacing}`; - - w = h = 6 * sz; // we use at least 6 steps - - const produce_old = (dy, swap) => { - const pos = []; - let step = sz, y1 = 0, max = h, y2, x1, x2; - - // reduce step for smaller angles to keep normal distance approx same - if (Math.abs(dy) < 3) - step = Math.round(sz / 12 * 9); - if (dy === 0) { - step = Math.round(sz / 12 * 8); - y1 = step / 2; - } else if (dy > 0) - max -= step; - else - y1 = step; - - while (y1 <= max) { - y2 = y1 + dy * step; - if (y2 < 0) { - x2 = Math.round(y1 / (y1 - y2) * w); - pos.push(0, y1, x2, 0); - pos.push(w, h - y1, w - x2, h); - } else if (y2 > h) { - x2 = Math.round((h - y1) / (y2 - y1) * w); - pos.push(0, y1, x2, h); - pos.push(w, h - y1, w - x2, 0); - } else - pos.push(0, y1, w, y2); - y1 += step; - } - for (let b = 0; b < pos.length; b += 4) { - if (swap) { - x1 = pos[b + 1]; - y1 = pos[b]; - x2 = pos[b + 3]; - y2 = pos[b + 2]; - } else { - x1 = pos[b]; - y1 = pos[b + 1]; - x2 = pos[b + 2]; - y2 = pos[b + 3]; - } - lines += `M${x1},${y1}`; - if (y2 === y1) - lines += `h${x2 - x1}`; - else if (x2 === x1) - lines += `v${y2 - y1}`; - else - lines += `L${x2},${y2}`; - } - }; - - /* eslint-disable-next-line one-var */ - const produce_new = (_aa, _bb, angle, swapx) => { - if ((angle === 0) || (angle === 90)) { - const dy = i * spacing_original * 3, - nsteps = Math.round(h / dy), - dyreal = h / nsteps; - let yy = dyreal / 2; - - while (yy < h) { - if (angle === 0) - lines += `M0,${Math.round(yy)}h${w}`; - else - lines += `M${Math.round(yy)},0v${h}`; - yy += dyreal; - } - - return; - } - - const a = angle / 180 * Math.PI, - dy = i * spacing_original * 3 / Math.cos(a), - hside = Math.tan(a) * w, - hside_steps = Math.round(hside / dy), - dyreal = hside / hside_steps, - nsteps = Math.floor(h / dyreal); - - h = Math.round(nsteps * dyreal); - - let yy = nsteps * dyreal; - - while (Math.abs(yy - h) < 0.1) - yy -= dyreal; - - while (yy + hside > 0) { - let x1 = 0, y1 = yy, x2 = w, y2 = yy + hside; - - if (y1 < -0.00001) { - // cut at the begin - x1 = -y1 / hside * w; - y1 = 0; - } else if (y2 > h) { - // cut at the end - x2 = (h - y1) / hside * w; - y2 = h; - } - - if (swapx) { - x1 = w - x1; - x2 = w - x2; - } - - lines += `M${Math.round(x1)},${Math.round(y1)}L${Math.round(x2)},${Math.round(y2)}`; - yy -= dyreal; - } - }; - - /* eslint-disable-next-line one-var */ - const func = use_new ? produce_new : produce_old; - - let horiz = false, vertical = false; - - switch (j) { - case 0: horiz = true; break; - case 1: func(1, false, 10); break; - case 2: func(2, false, 20); break; - case 3: func(3, false, 30); break; - case 4: func(6, false, 45); break; - case 6: func(3, true, 60); break; - case 7: func(2, true, 70); break; - case 8: func(1, true, 80); break; - case 9: vertical = true; break; - } - - switch (k) { - case 0: horiz = true; break; - case 1: func(-1, false, 10, true); break; - case 2: func(-2, false, 20, true); break; - case 3: func(-3, false, 30, true); break; - case 4: func(-6, false, 45, true); break; - case 6: func(-3, true, 60, true); break; - case 7: func(-2, true, 70, true); break; - case 8: func(-1, true, 80, true); break; - case 9: vertical = true; break; - } - - if (horiz) - func(0, false, 0); - if (vertical) - func(0, true, 90); - - break; - } - } - - if (!fills && !lines) - return false; - } - - this.pattern_url = `url(#${id})`; - this.antialias = false; - - let defs = svg.selectChild('.canvas_defs'); - if (defs.empty()) - defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); - - if (defs.selectChild('.' + id).empty()) { - if (this.gradient) { - const is_linear = this.gradient._typename === clTLinearGradient, - grad = defs.append(is_linear ? 'svg:linearGradient' : 'svg:radialGradient') - .attr('id', id).attr('class', id), - conv = v => { return v === Math.round(v) ? v.toFixed(0) : v.toFixed(2); }; - if (is_linear) { - grad.attr('x1', conv(this.gradient.fStart.fX)) - .attr('y1', conv(1 - this.gradient.fStart.fY)) - .attr('x2', conv(this.gradient.fEnd.fX)) - .attr('y2', conv(1 - this.gradient.fEnd.fY)); - } else { - grad.attr('cx', conv(this.gradient.fStart.fX)) - .attr('cy', conv(1 - this.gradient.fStart.fY)) - .attr('cr', conv(this.gradient.fR1)); - } - for (let n = 0; n < this.gradient.fColorPositions.length; ++n) { - const pos = this.gradient.fColorPositions[n], - col = toColor(this.gradient.fColors[n * 4], this.gradient.fColors[n * 4 + 1], this.gradient.fColors[n * 4 + 2]); - grad.append('svg:stop').attr('offset', `${Math.round(pos * 100)}%`) - .attr('stop-color', col) - .attr('stop-opacity', `${Math.round(this.gradient.fColors[n * 4 + 3] * 100)}%`); - } - } else { - const patt = defs.append('svg:pattern') - .attr('id', id).attr('class', id).attr('patternUnits', 'userSpaceOnUse') - .attr('width', w).attr('height', h); - - if (fills2) { - const col = d3_rgb(this.color); - col.r = Math.round((col.r + 255) / 2); - col.g = Math.round((col.g + 255) / 2); - col.b = Math.round((col.b + 255) / 2); - patt.append('svg:path').attr('d', fills2).style('fill', col); - } - if (fills) - patt.append('svg:path').attr('d', fills).style('fill', this.color); - if (lines) - patt.append('svg:path').attr('d', lines).style('stroke', this.color).style('stroke-width', gStyle.fHatchesLineWidth || 1).style('fill', lfill); - } - } - - return true; - } - - /** @summary Create sample of fill pattern inside SVG - * @private */ - createSample(svg, width, height, plain) { - // we need to create extra handle to change - if (plain) - svg = d3_select(svg); - - const sample = new TAttFillHandler({ svg, pattern: this.pattern, color: this.color, color_as_svg: true }); - - svg.append('path') - .attr('d', `M0,0h${width}v${height}h${-width}z`) - .call(sample.func); - } - - /** @summary Save fill attributes to style - * @private */ - saveToStyle(name_color, name_pattern) { - if (name_color) { - const indx = this.colorindx ?? findColor(this.color); - if (indx >= 0) - gStyle[name_color] = indx; - } - if (name_pattern) - gStyle[name_pattern] = this.pattern; - } - -} // class TAttFillHandler - -export { TAttFillHandler }; diff --git a/js/modules/base/TAttLineHandler.mjs b/js/modules/base/TAttLineHandler.mjs deleted file mode 100644 index cbe0f528e957f..0000000000000 --- a/js/modules/base/TAttLineHandler.mjs +++ /dev/null @@ -1,187 +0,0 @@ -import { select as d3_select } from '../d3.mjs'; -import { gStyle, isStr } from '../core.mjs'; -import { getColor, findColor } from './colors.mjs'; - - -const root_line_styles = [ - '', '', '3, 3', '1, 2', - '3, 4, 1, 4', '5, 3, 1, 3', '5, 3, 1, 3, 1, 3, 1, 3', '5, 5', - '5, 3, 1, 3, 1, 3', '20, 5', '20, 10, 1, 10', '1, 3']; - -/** - * @summary Handle for line attributes - * @private - */ - -class TAttLineHandler { - - /** @summary constructor - * @param {object} args - attributes or arguments of {@link TAttLineHandler#setArgs} */ - constructor(args) { - this.func = this.apply.bind(this); - this.used = true; - if (args._typename && (args.fLineStyle !== undefined)) - args = { attr: args }; - this.setArgs(args); - } - - /** @summary Set line attributes. - * @param {object} args - specify attributes by different ways - * @param {object} args.attr - TAttLine object with appropriate data members or - * @param {string} args.color - color in html like rgb(255,0,0) or 'red' or '#ff0000' - * @param {number} args.style - line style number - * @param {number} args.width - line width */ - setArgs(args) { - if (args.attr) { - this.color_index = args.attr.fLineColor; - args.color = args.color0 || (args.painter?.getColor(this.color_index) ?? getColor(this.color_index)); - args.width ??= args.attr.fLineWidth; - args.style ??= args.attr.fLineStyle; - } else if (isStr(args.color)) { - if ((args.color !== 'none') && !args.width) - args.width = 1; - } else if (typeof args.color === 'number') { - this.color_index = args.color; - args.color = args.painter?.getColor(args.color) ?? getColor(args.color); - } - - if (args.width === undefined) - args.width = (args.color && args.color !== 'none') ? 1 : 0; - - this.nocolor = args.nocolor; - this.color = (args.width === 0) || this.nocolor ? 'none' : args.color; - this.width = args.width; - this.style = args.style; - this.pattern = args.pattern || root_line_styles[this.style] || null; - - if (args.can_excl) { - this.excl_side = this.excl_width = 0; - if (Math.abs(this.width) > 99) { - // exclusion graph - this.excl_side = (this.width < 0) ? -1 : 1; - this.excl_width = Math.floor(Math.abs(this.width) / 100) * 5; - this.width = Math.abs(this.width % 100); // line width - } - } - - // if custom color number used, use lightgrey color to show lines - if (!this.color && (this.width > 0)) - this.color = 'lightgrey'; - } - - /** @summary Change exclusion attributes */ - changeExcl(side, width) { - if (width !== undefined) - this.excl_width = width; - if (side !== undefined) { - this.excl_side = side; - if (side && !this.excl_width) - this.excl_width = 20; - } - this.changed = true; - } - - /** @summary returns true if line attribute is empty and will not be applied. */ - empty() { return this.color === 'none'; } - - /** @summary Set usage flag of attribute */ - setUsed(flag) { - this.used = flag; - } - - /** @summary set border parameters, used for rect drawing */ - setBorder(rx, ry) { - this.rx = rx; - this.ry = ry; - this.func = this.applyBorder.bind(this); - } - - /** @summary Applies line attribute to selection. - * @param {object} selection - d3.js selection */ - apply(selection) { - this.used = true; - if (this.empty()) { - selection.style('stroke', null) - .style('stroke-width', null) - .style('stroke-dasharray', null); - } else { - selection.style('stroke', this.color) - .style('stroke-width', this.width) - .style('stroke-dasharray', this.pattern); - } - } - - /** @summary Applies line and border attribute to selection. - * @param {object} selection - d3.js selection */ - applyBorder(selection) { - this.used = true; - if (this.empty()) { - selection.attr('rx', null) - .attr('ry', null) - .style('stroke', null) - .style('stroke-width', null) - .style('stroke-dasharray', null); - } else { - selection.attr('rx', this.rx || null) - .attr('ry', this.ry || null) - .style('stroke', this.color) - .style('stroke-width', this.width) - .style('stroke-dasharray', this.pattern); - } - } - - /** @summary Change line attributes */ - change(color, width, style) { - if (color !== undefined) { - if (this.color !== color) - delete this.color_index; - this.color = color; - } - if (width !== undefined) - this.width = width; - if (style !== undefined) { - this.style = style; - this.pattern = root_line_styles[this.style] || null; - } - this.changed = true; - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets. - * @private */ - verifyDirectChange(/* painter */) { - this.change(this.color, parseInt(this.width), parseInt(this.style)); - } - - /** @summary Create sample element inside primitive SVG - used in context menu */ - createSample(svg, width, height, plain) { - if (plain) - svg = d3_select(svg); - svg.append('path') - .attr('d', `M0,${height / 2}h${width}`) - .call(this.func); - } - - /** @summary Save attributes values to gStyle */ - saveToStyle(name_color, name_width, name_style) { - if (name_color) { - const indx = (this.color_index !== undefined) ? this.color_index : findColor(this.color); - if (indx >= 0) - gStyle[name_color] = indx; - } - if (name_width) - gStyle[name_width] = this.width; - if (name_style) - gStyle[name_style] = this.style; - } - -} // class TAttLineHandler - -/** @summary Get svg string for specified line style - * @private */ -function getSvgLineStyle(indx) { - if ((indx < 0) || (indx >= root_line_styles.length)) - indx = 11; - return root_line_styles[indx]; -} - -export { TAttLineHandler, getSvgLineStyle, root_line_styles }; diff --git a/js/modules/base/TAttMarkerHandler.mjs b/js/modules/base/TAttMarkerHandler.mjs deleted file mode 100644 index ec62942ea749e..0000000000000 --- a/js/modules/base/TAttMarkerHandler.mjs +++ /dev/null @@ -1,327 +0,0 @@ -import { isObject } from '../core.mjs'; -import { select as d3_select } from '../d3.mjs'; -import { getColor } from './colors.mjs'; - - -// list of marker types which can have line widths -const root_50_67 = [2, 3, 5, 4, 25, 26, 27, 28, 30, 32, 35, 36, 37, 38, 40, 42, 44, 46], - // internal recoding of root markers - root_markers = [ - 0, 1, 2, 3, 4, // 0..4 - 5, 106, 107, 104, 1, // 5..9 - 1, 1, 1, 1, 1, // 10..14 - 1, 1, 1, 1, 1, // 15..19 - 104, 125, 126, 132, 4, // 20..24 - 25, 26, 27, 28, 130, // 25..29 - 30, 3, 32, 127, 128, // 30..34 - 35, 36, 37, 38, 137, // 35..39 - 40, 140, 42, 142, 44, // 40..44 - 144, 46, 146, 148, 149]; // 45..49 - - -/** - * @summary Handle for marker attributes - * @private - */ - -class TAttMarkerHandler { - - /** @summary constructor - * @param {object} args - attributes or arguments of {@link TAttMarkerHandler#setArgs} */ - constructor(args) { - this.x0 = this.y0 = 0; - this.color = 'black'; - this.style = 1; - this.size = 8; - this.scale = 1; - this.stroke = true; - this.fill = true; - this.marker = ''; - this.ndig = 0; - this.used = true; - this.changed = false; - this.func = this.apply.bind(this); - this.setArgs(args); - this.changed = false; - } - - /** @summary Set marker attributes. - * @param {object} args - arguments can be - * @param {object} args.attr - instance of TAttrMarker (or derived class) or - * @param {string} args.color - color in HTML form like grb(1,4,5) or 'green' - * @param {number} args.style - marker style - * @param {number} args.size - marker size - * @param {number} [args.refsize] - when specified and marker size < 1, marker size will be calculated relative to that size */ - setArgs(args) { - if (isObject(args) && (typeof args.fMarkerStyle === 'number')) - args = { attr: args }; - - if (args.attr) { - args.color ??= args.painter ? args.painter.getColor(args.attr.fMarkerColor) : getColor(args.attr.fMarkerColor); - if (!args.style || (args.style < 0)) - args.style = args.attr.fMarkerStyle; - args.size ??= args.attr.fMarkerSize; - } - - this.color = args.color; - this.style = args.style; - this.size = args.size; - this.refsize = args.refsize; - - this.#configure(); - } - - /** @summary Set usage flag of attribute */ - setUsed(flag) { - this.used = flag; - } - - /** @summary Reset position, used for optimization of drawing of multiple markers - * @private */ - resetPos() { this.lastx = this.lasty = null; } - - /** @summary Create marker path for given position. - * @desc When drawing many elementary points, created path may depend from previously produced markers. - * @param {number} x - first coordinate - * @param {number} y - second coordinate - * @return {string} path string */ - create(x, y) { - if (!this.optimized) - return `M${(x + this.x0).toFixed(this.ndig)},${(y + this.y0).toFixed(this.ndig)}${this.marker}`; - - // use optimized handling with relative position - const xx = Math.round(x), yy = Math.round(y); - let mv = `M${xx},${yy}`; - if (this.lastx !== null) { - if ((xx === this.lastx) && (yy === this.lasty)) - mv = ''; // pathological case, but let exclude it - else { - const m2 = `m${xx - this.lastx},${yy - this.lasty}`; - if (m2.length < mv.length) - mv = m2; - } - } - this.lastx = xx + 1; - this.lasty = yy; - return mv + 'h1'; - } - - /** @summary Returns full size of marker */ - getFullSize() { return this.scale * this.size; } - - /** @summary Returns approximate length of produced marker string */ - getMarkerLength() { return this.marker ? this.marker.length : 10; } - - /** @summary Change marker attributes. - * @param {string} color - marker color - * @param {number} style - marker style - * @param {number} size - marker size */ - change(color, style, size) { - this.changed = true; - - if (color !== undefined) - this.color = color; - if ((style !== undefined) && (style >= 0)) - this.style = style; - if (size !== undefined) - this.size = size; - - this.#configure(); - } - - /** @summary Prepare object to create marker - * @private */ - #configure() { - this.x0 = this.y0 = 0; - - if ((this.style === 1) || (this.style === 777)) { - this.fill = false; - this.marker = 'h1'; - this.size = 1; - this.optimized = true; - this.resetPos(); - return true; - } - - this.optimized = false; - this.lwidth = 1; - - let style = this.style; - if (style >= 50) { - this.lwidth = 2 + Math.floor((style - 50) / root_50_67.length); - style = root_50_67[(style - 50) % root_50_67.length]; - } - - const marker_kind = root_markers[style] ?? 104, - shape = marker_kind % 100; - - this.fill = (marker_kind >= 100); - - this.scale = this.refsize || 8; // v7 defines refsize as 1 or pad height - - const size = this.getFullSize(); - - this.ndig = (size > 7) ? 0 : ((size > 2) ? 1 : 2); - if (shape === 30) - this.ndig++; // increase precision for star - let s1 = size.toFixed(this.ndig); - const s2 = (size / 2).toFixed(this.ndig), - s3 = (size / 3).toFixed(this.ndig), - s4 = (size / 4).toFixed(this.ndig), - s8 = (size / 8).toFixed(this.ndig), - s38 = (size * 3 / 8).toFixed(this.ndig), - s34 = (size * 3 / 4).toFixed(this.ndig); - - switch (shape) { - case 0: // empty - this.marker = ''; - break; - case 1: // dot - this.marker = 'h1'; - break; - case 2: // plus - this.y0 = -size / 2; - this.marker = `v${s1}m-${s2},-${s2}h${s1}`; - break; - case 3: // asterisk - this.y0 = -size / 2; - this.marker = `v${s1}m-${s2},-${s2}h${s1}m-${s8},-${s38}l-${s34},${s34}m${s34},0l-${s34},-${s34}`; - break; - case 4: // circle - this.x0 = -parseFloat(s2); - s1 = (parseFloat(s2) * 2).toFixed(this.ndig); - this.marker = `a${s2},${s2},0,1,0,${s1},0a${s2},${s2},0,1,0,-${s1},0z`; - break; - case 5: // multiply - this.x0 = this.y0 = -3 / 8 * size; - this.marker = `l${s34},${s34}m0,-${s34}l-${s34},${s34}`; - break; - case 6: // small dot - this.x0 = -1; - this.marker = 'a1,1,0,1,0,2,0a1,1,0,1,0,-2,0z'; - break; - case 7: // medium dot - this.x0 = -1.5; - this.marker = 'a1.5,1.5,0,1,0,3,0a1.5,1.5,0,1,0,-3,0z'; - break; - case 25: // square - this.x0 = this.y0 = -size / 2; - this.marker = `v${s1}h${s1}v-${s1}z`; - break; - case 26: // triangle-up - this.y0 = -size / 2; - this.marker = `l-${s2},${s1}h${s1}z`; - break; - case 27: // diamond - this.y0 = -size / 2; - this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; - break; - case 28: // cross - this.x0 = this.y0 = size / 6; - this.marker = `h${s3}v-${s3}h-${s3}v-${s3}h-${s3}v${s3}h-${s3}v${s3}h${s3}v${s3}h${s3}z`; - break; - case 30: { // star - this.y0 = -size / 2; - const s56 = (size * 5 / 6).toFixed(this.ndig), s58 = (size * 5 / 8).toFixed(this.ndig); - this.marker = `l${s3},${s1}l-${s56},-${s58}h${s1}l-${s56},${s58}z`; - break; - } - case 32: // triangle-down - this.y0 = size / 2; - this.marker = `l-${s2},-${s1}h${s1}z`; - break; - case 35: - this.x0 = -size / 2; - this.marker = `l${s2},${s2}l${s2},-${s2}l-${s2},-${s2}zh${s1}m-${s2},-${s2}v${s1}`; - break; - case 36: - this.x0 = this.y0 = -size / 2; - this.marker = `h${s1}v${s1}h-${s1}zl${s1},${s1}m0,-${s1}l-${s1},${s1}`; - break; - case 37: - this.x0 = -size / 2; - this.marker = `h${s1}l-${s4},-${s2}l-${s2},${s1}h${s2}l-${s2},-${s1}z`; - break; - case 38: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `h${s2}l${s4},${s4}v${s2}l-${s4},${s4}h-${s2}l-${s4},-${s4}v-${s2}zm${s4},0v${s1}m-${s2},-${s2}h${s1}`; - break; - case 40: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `l${s2},${s1}l${s4},-${s4}l-${s1},-${s2}zm${s2},0l-${s2},${s1}l-${s4},-${s4}l${s1},-${s2}z`; - break; - case 42: - this.y0 = -size / 2; - this.marker = `l${s8},${s38}l${s38},${s8}l-${s38},${s8}l-${s8},${s38}l-${s8},-${s38}l-${s38},-${s8}l${s38},-${s8}z`; - break; - case 44: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `h${s2}l-${s8},${s38}l${s38},-${s8}v${s2}l-${s38},-${s8}l${s8},${s38}h-${s2}l${s8},-${s38}l-${s38},${s8}v-${s2}l${s38},${s8}z`; - break; - case 46: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `l${s4},${s4}l${s4},-${s4}l${s4},${s4}l-${s4},${s4}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}l-${s4},${s4}l-${s4},-${s4}l${s4},-${s4}l-${s4},-${s4}z`; - break; - case 48: - this.x0 = -size / 4; - this.y0 = -size / 2; - this.marker = `l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm0,${s2}l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}zm-${s2},0l${s4},${s4}l-${s4},${s4}l-${s4},-${s4}z`; - break; - case 49: - this.x0 = -size / 6; - this.y0 = -size / 2; - this.marker = `h${s3}v${s3}h-${s3}zm${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},${s3}h${s3}v${s3}h-${s3}zm-${s3},-${s3}h${s3}v${s3}h-${s3}z`; - break; - default: // diamond - this.y0 = -size / 2; - this.marker = `l${s3},${s2}l-${s3},${s2}l-${s3},-${s2}z`; - break; - } - - return true; - } - - /** @summary get stroke color */ - getStrokeColor() { return this.stroke ? this.color : 'none'; } - - /** @summary get fill color */ - getFillColor() { return this.fill ? this.color : 'none'; } - - /** @summary returns true if marker attributes will produce empty (invisible) output */ - empty() { return (this.color === 'none') || (!this.fill && !this.stroke) || !this.marker; } - - /** @summary Apply marker styles to created element */ - apply(selection) { - this.used = true; - selection.style('stroke', this.stroke ? this.color : 'none') - .style('stroke-width', this.stroke && (this.lwidth > 1) ? this.lwidth : null) - .style('fill', this.fill ? this.color : 'none'); - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets. - * @private */ - verifyDirectChange(/* painter */) { - this.change(this.color, parseInt(this.style), parseFloat(this.size)); - } - - /** @summary Create sample with marker in given SVG element - * @param {selection} svg - SVG element - * @param {number} width - width of sample SVG - * @param {number} height - height of sample SVG - * @private */ - createSample(svg, width, height, plain) { - if (plain) - svg = d3_select(svg); - this.resetPos(); - svg.append('path') - .attr('d', this.create(width / 2, height / 2)) - .call(this.func); - } - -} // class TAttMarkerHandler - -export { TAttMarkerHandler }; diff --git a/js/modules/base/TAttTextHandler.mjs b/js/modules/base/TAttTextHandler.mjs deleted file mode 100644 index cf20b26b768d2..0000000000000 --- a/js/modules/base/TAttTextHandler.mjs +++ /dev/null @@ -1,123 +0,0 @@ -import { getColor } from './colors.mjs'; - - -function calcTextSize(sz, sz0, fact, pp) { - if (!sz) - sz = sz0 || 0; - - if (sz >= 1) - return Math.round(sz * (pp?.getPadScale() || 1)); - - return Math.round(sz * Math.min(pp?.getPadWidth() ?? 1000, pp?.getPadHeight() ?? 1000) * (fact || 1)); -} - -/** - * @summary Handle for text attributes - * @private - */ - -class TAttTextHandler { - - /** @summary constructor - * @param {object} args - attributes or arguments of {@link TAttTextHandler#setArgs} */ - constructor(args) { - this.used = true; - if (args._typename && (args.fTextFont !== undefined)) - args = { attr: args }; - this.setArgs(args); - } - - /** @summary Set text attributes. - * @param {object} args - specify attributes by different ways - * @param {object} args.attr - TAttText object with appropriate data members or - * @param {object} args.attr_alt - alternative TAttText object with appropriate data members if values are 0 - * @param {string} args.color - color in html like rgb(255,0,0) or 'red' or '#ff0000' - * @param {number} args.align - text align - * @param {number} args.angle - text angle - * @param {number} args.font - font index - * @param {number} args.size - text size */ - setArgs(args) { - if (args.attr) { - args.font = args.attr.fTextFont || args.attr_alt?.fTextFont || 0; - args.size = args.attr.fTextSize || args.attr_alt?.fTextSize || 0; - this.color_index = args.attr.fTextColor || args.attr_alt?.fTextColor || 0; - args.color = args.painter?.getColor(this.color_index) ?? getColor(this.color_index); - args.align = args.attr.fTextAlign || args.attr_alt?.fTextAlign || 0; - args.angle = args.attr.fTextAngle || args.attr_alt?.fTextAngle || 0; - } else if (typeof args.color === 'number') { - this.color_index = args.color; - args.color = args.painter?.getColor(args.color) ?? getColor(args.color); - } - - this.font = args.font; - this.size = args.size; - this.color = args.color; - this.align = args.align; - this.angle = args.angle; - - this.can_rotate = args.can_rotate ?? true; - this.angle_used = false; - this.align_used = false; - } - - /** @summary returns true if line attribute is empty and will not be applied. */ - empty() { return this.color === 'none'; } - - /** @summary Change text attributes */ - change(font, size, color, align, angle) { - if (font !== undefined) - this.font = font; - if (size !== undefined) - this.size = size; - if (color !== undefined) { - if (this.color !== color) - delete this.color_index; - this.color = color; - } - if (align !== undefined) - this.align = align; - if (angle !== undefined) - this.angle = angle; - this.changed = true; - } - - /** @summary Method used when color or pattern were changed with OpenUi5 widgets. - * @private */ - verifyDirectChange(/* painter */) { - this.change(parseInt(this.font), parseFloat(this.size), this.color, parseInt(this.align), parseInt(this.angle)); - } - - /** @summary Create argument for drawText method */ - createArg(arg) { - if (!arg) - arg = {}; - this.align_used = !arg.noalign && !arg.align; - if (this.align_used) - arg.align = this.align; - this.angle_used = !arg.norotate && this.can_rotate; - if (this.angle_used && this.angle) - arg.rotate = -this.angle; // SVG rotation angle has different sign - arg.color = this.color || 'black'; - return arg; - } - - /** @summary Provides pixel size */ - getSize(pp, fact, zero_size) { return calcTextSize(this.size, zero_size, fact, pp); } - - /** @summary Returns alternating size - which defined by sz1 variable */ - getAltSize(sz1, pp) { return calcTextSize(sz1, this.size, 1, pp); } - - /** @summary Get font index - without precision */ - getGedFont() { return Math.floor(this.font / 10); } - - /** @summary Change text font from GED */ - setGedFont(value) { - const v = parseInt(value); - if ((v > 0) && (v < 17)) - this.change(v * 10 + (this.font % 10)); - return this.font; - } - -} // class TAttTextHandler - -export { TAttTextHandler }; diff --git a/js/modules/base/base3d.mjs b/js/modules/base/base3d.mjs deleted file mode 100644 index a599b4747e29b..0000000000000 --- a/js/modules/base/base3d.mjs +++ /dev/null @@ -1,1755 +0,0 @@ -import { select as d3_select, color as d3_color } from '../d3.mjs'; -import { browser, settings, internals, constants, isBatchMode, nsSVG, - isNodeJs, isObject, isFunc, isStr, getDocument } from '../core.mjs'; -import { WebGLRenderer, WebGLRenderTarget, CanvasTexture, TextureLoader, Raycaster, - BufferGeometry, BufferAttribute, Float32BufferAttribute, - Vector2, Vector3, Color, Points, PointsMaterial, - LineSegments, LineDashedMaterial, LineBasicMaterial, - REVISION, DoubleSide, Object3D, Matrix4, Line3, Mesh, MeshBasicMaterial, MeshLambertMaterial, - Plane, Scene, PerspectiveCamera, OrthographicCamera, ShapeUtils, - FrontSide, Box3, InstancedMesh, MeshStandardMaterial, MeshNormalMaterial, - MeshPhysicalMaterial, MeshPhongMaterial, MeshDepthMaterial, MeshMatcapMaterial, MeshToonMaterial, - Group, PlaneHelper, Euler, Quaternion, BoxGeometry, CircleGeometry, SphereGeometry, Fog, - AmbientLight, HemisphereLight, DirectionalLight } from '../three.mjs'; -import { Font, OrbitControls, SVGRenderer, TextGeometry, EffectComposer, RenderPass, UnrealBloomPass } from '../three_addons.mjs'; -import { prSVG, getElementRect, getAbsPosInCanvas, makeTranslate } from './BasePainter.mjs'; -import { TAttMarkerHandler } from './TAttMarkerHandler.mjs'; -import { getSvgLineStyle } from './TAttLineHandler.mjs'; - - -const originalTHREE = { - REVISION, DoubleSide, FrontSide, Object3D, Color, Vector2, Vector3, Matrix4, Line3, Raycaster, - WebGLRenderer, WebGLRenderTarget, - BufferGeometry, BufferAttribute, Float32BufferAttribute, Mesh, MeshBasicMaterial, MeshLambertMaterial, - LineSegments, LineDashedMaterial, LineBasicMaterial, Points, PointsMaterial, - Plane, Scene, PerspectiveCamera, OrthographicCamera, ShapeUtils, - Box3, InstancedMesh, MeshStandardMaterial, MeshNormalMaterial, - MeshPhysicalMaterial, MeshPhongMaterial, MeshDepthMaterial, MeshMatcapMaterial, MeshToonMaterial, - Group, PlaneHelper, Euler, Quaternion, BoxGeometry, CircleGeometry, SphereGeometry, Fog, - AmbientLight, HemisphereLight, DirectionalLight, - CanvasTexture, TextureLoader, - - Font, OrbitControls, SVGRenderer, TextGeometry, EffectComposer, RenderPass, UnrealBloomPass -}, THREE = Object.assign({}, originalTHREE); - - -/** @summary Import proper three.js version - * @desc in node.js only r162 supports WebGL1 which can be emulated with "gl" package. - * Therefore only this version can be used for working in node.js - * @private */ -async function importThreeJs(original) { - if (original) { - Object.keys(THREE).forEach(key => delete THREE[key]); - Object.assign(THREE, originalTHREE); - return THREE; - } - - if (!isNodeJs() || (THREE.REVISION <= 162)) - return THREE; - return import('three').then(h1 => { - Object.assign(THREE, h1); - return import('three/addons'); - }).then(h2 => { - Object.assign(THREE, h2); - return THREE; - }); -} - -let _hfont; - -/** @summary Create three.js Helvetica Regular Font instance - * @private */ -function getHelveticaFont() { - if (_hfont && _hfont instanceof THREE.Font) - return _hfont; - - // eslint-disable-next-line - const glyphs={"0":{x_min:73,x_max:715,ha:792,o:"m 394 -29 q 153 129 242 -29 q 73 479 73 272 q 152 829 73 687 q 394 989 241 989 q 634 829 545 989 q 715 479 715 684 q 635 129 715 270 q 394 -29 546 -29 m 394 89 q 546 211 489 89 q 598 479 598 322 q 548 748 598 640 q 394 871 491 871 q 241 748 298 871 q 190 479 190 637 q 239 211 190 319 q 394 89 296 89 "},"1":{x_min:215.671875,x_max:574,ha:792,o:"m 574 0 l 442 0 l 442 697 l 215 697 l 215 796 q 386 833 330 796 q 475 986 447 875 l 574 986 l 574 0 "},"2":{x_min:59,x_max:731,ha:792,o:"m 731 0 l 59 0 q 197 314 59 188 q 457 487 199 315 q 598 691 598 580 q 543 819 598 772 q 411 867 488 867 q 272 811 328 867 q 209 630 209 747 l 81 630 q 182 901 81 805 q 408 986 271 986 q 629 909 536 986 q 731 694 731 826 q 613 449 731 541 q 378 316 495 383 q 201 122 235 234 l 731 122 l 731 0 "},"3":{x_min:54,x_max:737,ha:792,o:"m 737 284 q 635 55 737 141 q 399 -25 541 -25 q 156 52 248 -25 q 54 308 54 140 l 185 308 q 245 147 185 202 q 395 96 302 96 q 539 140 484 96 q 602 280 602 190 q 510 429 602 390 q 324 454 451 454 l 324 565 q 487 584 441 565 q 565 719 565 617 q 515 835 565 791 q 395 879 466 879 q 255 824 307 879 q 203 661 203 769 l 78 661 q 166 909 78 822 q 387 992 250 992 q 603 921 513 992 q 701 723 701 844 q 669 607 701 656 q 578 524 637 558 q 696 434 655 499 q 737 284 737 369 "},"4":{x_min:48,x_max:742.453125,ha:792,o:"m 742 243 l 602 243 l 602 0 l 476 0 l 476 243 l 48 243 l 48 368 l 476 958 l 602 958 l 602 354 l 742 354 l 742 243 m 476 354 l 476 792 l 162 354 l 476 354 "},"5":{x_min:54.171875,x_max:738,ha:792,o:"m 738 314 q 626 60 738 153 q 382 -23 526 -23 q 155 47 248 -23 q 54 256 54 125 l 183 256 q 259 132 204 174 q 382 91 314 91 q 533 149 471 91 q 602 314 602 213 q 538 469 602 411 q 386 528 475 528 q 284 506 332 528 q 197 439 237 484 l 81 439 l 159 958 l 684 958 l 684 840 l 254 840 l 214 579 q 306 627 258 612 q 407 643 354 643 q 636 552 540 643 q 738 314 738 457 "},"6":{x_min:53,x_max:739,ha:792,o:"m 739 312 q 633 62 739 162 q 400 -31 534 -31 q 162 78 257 -31 q 53 439 53 206 q 178 859 53 712 q 441 986 284 986 q 643 912 559 986 q 732 713 732 833 l 601 713 q 544 830 594 786 q 426 875 494 875 q 268 793 331 875 q 193 517 193 697 q 301 597 240 570 q 427 624 362 624 q 643 540 552 624 q 739 312 739 451 m 603 298 q 540 461 603 400 q 404 516 484 516 q 268 461 323 516 q 207 300 207 401 q 269 137 207 198 q 405 83 325 83 q 541 137 486 83 q 603 298 603 197 "},"7":{x_min:58.71875,x_max:730.953125,ha:792,o:"m 730 839 q 469 448 560 641 q 335 0 378 255 l 192 0 q 328 441 235 252 q 593 830 421 630 l 58 830 l 58 958 l 730 958 l 730 839 "},"8":{x_min:55,x_max:736,ha:792,o:"m 571 527 q 694 424 652 491 q 736 280 736 358 q 648 71 736 158 q 395 -26 551 -26 q 142 69 238 -26 q 55 279 55 157 q 96 425 55 359 q 220 527 138 491 q 120 615 153 562 q 88 726 88 668 q 171 904 88 827 q 395 986 261 986 q 618 905 529 986 q 702 727 702 830 q 670 616 702 667 q 571 527 638 565 m 394 565 q 519 610 475 565 q 563 717 563 655 q 521 823 563 781 q 392 872 474 872 q 265 824 312 872 q 224 720 224 783 q 265 613 224 656 q 394 565 312 565 m 395 91 q 545 150 488 91 q 597 280 597 204 q 546 408 597 355 q 395 465 492 465 q 244 408 299 465 q 194 280 194 356 q 244 150 194 203 q 395 91 299 91 "},"9":{x_min:53,x_max:739,ha:792,o:"m 739 524 q 619 94 739 241 q 362 -32 516 -32 q 150 47 242 -32 q 59 244 59 126 l 191 244 q 246 129 191 176 q 373 82 301 82 q 526 161 466 82 q 597 440 597 255 q 363 334 501 334 q 130 432 216 334 q 53 650 53 521 q 134 880 53 786 q 383 986 226 986 q 659 841 566 986 q 739 524 739 719 m 388 449 q 535 514 480 449 q 585 658 585 573 q 535 805 585 744 q 388 873 480 873 q 242 809 294 873 q 191 658 191 745 q 239 514 191 572 q 388 449 292 449 "},"ο":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 96 88 192 -25 q 0 368 0 201 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 "},S:{x_min:0,x_max:788,ha:890,o:"m 788 291 q 662 54 788 144 q 397 -26 550 -26 q 116 68 226 -26 q 0 337 0 168 l 131 337 q 200 152 131 220 q 384 85 269 85 q 557 129 479 85 q 650 270 650 183 q 490 429 650 379 q 194 513 341 470 q 33 739 33 584 q 142 964 33 881 q 388 1041 242 1041 q 644 957 543 1041 q 756 716 756 867 l 625 716 q 561 874 625 816 q 395 933 497 933 q 243 891 309 933 q 164 759 164 841 q 325 609 164 656 q 625 526 475 568 q 788 291 788 454 "},"¦":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"/":{x_min:183.25,x_max:608.328125,ha:792,o:"m 608 1041 l 266 -129 l 183 -129 l 520 1041 l 608 1041 "},"Τ":{x_min:-0.4375,x_max:777.453125,ha:839,o:"m 777 893 l 458 893 l 458 0 l 319 0 l 319 892 l 0 892 l 0 1013 l 777 1013 l 777 893 "},y:{x_min:0,x_max:684.78125,ha:771,o:"m 684 738 l 388 -83 q 311 -216 356 -167 q 173 -279 252 -279 q 97 -266 133 -279 l 97 -149 q 132 -155 109 -151 q 168 -160 155 -160 q 240 -114 213 -160 q 274 -26 248 -98 l 0 738 l 137 737 l 341 139 l 548 737 l 684 738 "},"Π":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 803 1012 l 803 0 "},"ΐ":{x_min:-111,x_max:339,ha:361,o:"m 339 800 l 229 800 l 229 925 l 339 925 l 339 800 m -1 800 l -111 800 l -111 925 l -1 925 l -1 800 m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 m 302 1040 l 113 819 l 30 819 l 165 1040 l 302 1040 "},g:{x_min:0,x_max:686,ha:838,o:"m 686 34 q 586 -213 686 -121 q 331 -306 487 -306 q 131 -252 216 -306 q 31 -84 31 -190 l 155 -84 q 228 -174 166 -138 q 345 -207 284 -207 q 514 -109 454 -207 q 564 89 564 -27 q 461 6 521 36 q 335 -23 401 -23 q 88 100 184 -23 q 0 370 0 215 q 87 634 0 522 q 330 758 183 758 q 457 728 398 758 q 564 644 515 699 l 564 737 l 686 737 l 686 34 m 582 367 q 529 560 582 481 q 358 652 468 652 q 189 561 250 652 q 135 369 135 482 q 189 176 135 255 q 361 85 251 85 q 529 176 468 85 q 582 367 582 255 "},"²":{x_min:0,x_max:442,ha:539,o:"m 442 383 l 0 383 q 91 566 0 492 q 260 668 176 617 q 354 798 354 727 q 315 875 354 845 q 227 905 277 905 q 136 869 173 905 q 99 761 99 833 l 14 761 q 82 922 14 864 q 232 974 141 974 q 379 926 316 974 q 442 797 442 878 q 351 635 442 704 q 183 539 321 611 q 92 455 92 491 l 442 455 l 442 383 "},"–":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},"Κ":{x_min:0,x_max:819.5625,ha:893,o:"m 819 0 l 650 0 l 294 509 l 139 356 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},"ƒ":{x_min:-46.265625,x_max:392,ha:513,o:"m 392 651 l 259 651 l 79 -279 l -46 -278 l 134 651 l 14 651 l 14 751 l 135 751 q 151 948 135 900 q 304 1041 185 1041 q 334 1040 319 1041 q 392 1034 348 1039 l 392 922 q 337 931 360 931 q 271 883 287 931 q 260 793 260 853 l 260 751 l 392 751 l 392 651 "},e:{x_min:0,x_max:714,ha:813,o:"m 714 326 l 140 326 q 200 157 140 227 q 359 87 260 87 q 488 130 431 87 q 561 245 545 174 l 697 245 q 577 48 670 123 q 358 -26 484 -26 q 97 85 195 -26 q 0 363 0 197 q 94 642 0 529 q 358 765 195 765 q 626 627 529 765 q 714 326 714 503 m 576 429 q 507 583 564 522 q 355 650 445 650 q 206 583 266 650 q 140 429 152 522 l 576 429 "},"ό":{x_min:0,x_max:712,ha:815,o:"m 356 -25 q 94 91 194 -25 q 0 368 0 202 q 92 642 0 533 q 356 761 192 761 q 617 644 517 761 q 712 368 712 533 q 619 91 712 201 q 356 -25 520 -25 m 356 85 q 527 175 465 85 q 583 369 583 255 q 528 562 583 484 q 356 651 466 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 356 85 250 85 m 576 1040 l 387 819 l 303 819 l 438 1040 l 576 1040 "},J:{x_min:0,x_max:588,ha:699,o:"m 588 279 q 287 -26 588 -26 q 58 73 126 -26 q 0 327 0 158 l 133 327 q 160 172 133 227 q 288 96 198 96 q 426 171 391 96 q 449 336 449 219 l 449 1013 l 588 1013 l 588 279 "},"»":{x_min:-1,x_max:503,ha:601,o:"m 503 302 l 280 136 l 281 256 l 429 373 l 281 486 l 280 608 l 503 440 l 503 302 m 221 302 l 0 136 l 0 255 l 145 372 l 0 486 l -1 608 l 221 440 l 221 302 "},"©":{x_min:-3,x_max:1008,ha:1106,o:"m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 741 394 q 661 246 731 302 q 496 190 591 190 q 294 285 369 190 q 228 497 228 370 q 295 714 228 625 q 499 813 370 813 q 656 762 588 813 q 733 625 724 711 l 634 625 q 589 704 629 673 q 498 735 550 735 q 377 666 421 735 q 334 504 334 597 q 374 340 334 408 q 490 272 415 272 q 589 304 549 272 q 638 394 628 337 l 741 394 "},"ώ":{x_min:0,x_max:922,ha:1030,o:"m 687 1040 l 498 819 l 415 819 l 549 1040 l 687 1040 m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 338 0 202 q 45 551 0 444 q 161 737 84 643 l 302 737 q 175 552 219 647 q 124 336 124 446 q 155 179 124 248 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 341 797 257 q 745 555 797 450 q 619 737 705 637 l 760 737 q 874 551 835 640 q 922 339 922 444 "},"^":{x_min:193.0625,x_max:598.609375,ha:792,o:"m 598 772 l 515 772 l 395 931 l 277 772 l 193 772 l 326 1013 l 462 1013 l 598 772 "},"«":{x_min:0,x_max:507.203125,ha:604,o:"m 506 136 l 284 302 l 284 440 l 506 608 l 507 485 l 360 371 l 506 255 l 506 136 m 222 136 l 0 302 l 0 440 l 222 608 l 221 486 l 73 373 l 222 256 l 222 136 "},D:{x_min:0,x_max:828,ha:935,o:"m 389 1013 q 714 867 593 1013 q 828 521 828 729 q 712 161 828 309 q 382 0 587 0 l 0 0 l 0 1013 l 389 1013 m 376 124 q 607 247 523 124 q 681 510 681 355 q 607 771 681 662 q 376 896 522 896 l 139 896 l 139 124 l 376 124 "},"∙":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"ÿ":{x_min:0,x_max:47,ha:125,o:"m 47 3 q 37 -7 47 -7 q 28 0 30 -7 q 39 -4 32 -4 q 45 3 45 -1 l 37 0 q 28 9 28 0 q 39 19 28 19 l 47 16 l 47 19 l 47 3 m 37 1 q 44 8 44 1 q 37 16 44 16 q 30 8 30 16 q 37 1 30 1 m 26 1 l 23 22 l 14 0 l 3 22 l 3 3 l 0 25 l 13 1 l 22 25 l 26 1 "},w:{x_min:0,x_max:1009.71875,ha:1100,o:"m 1009 738 l 783 0 l 658 0 l 501 567 l 345 0 l 222 0 l 0 738 l 130 738 l 284 174 l 432 737 l 576 738 l 721 173 l 881 737 l 1009 738 "},$:{x_min:0,x_max:700,ha:793,o:"m 664 717 l 542 717 q 490 825 531 785 q 381 872 450 865 l 381 551 q 620 446 540 522 q 700 241 700 370 q 618 45 700 116 q 381 -25 536 -25 l 381 -152 l 307 -152 l 307 -25 q 81 62 162 -25 q 0 297 0 149 l 124 297 q 169 146 124 204 q 307 81 215 89 l 307 441 q 80 536 148 469 q 13 725 13 603 q 96 910 13 839 q 307 982 180 982 l 307 1077 l 381 1077 l 381 982 q 574 917 494 982 q 664 717 664 845 m 307 565 l 307 872 q 187 831 233 872 q 142 724 142 791 q 180 618 142 656 q 307 565 218 580 m 381 76 q 562 237 562 96 q 517 361 562 313 q 381 423 472 409 l 381 76 "},"\\":{x_min:-0.015625,x_max:425.0625,ha:522,o:"m 425 -129 l 337 -129 l 0 1041 l 83 1041 l 425 -129 "},"µ":{x_min:0,x_max:697.21875,ha:747,o:"m 697 -4 q 629 -14 658 -14 q 498 97 513 -14 q 422 9 470 41 q 313 -23 374 -23 q 207 4 258 -23 q 119 81 156 32 l 119 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 173 124 246 q 308 83 216 83 q 452 178 402 83 q 493 359 493 255 l 493 738 l 617 738 l 617 214 q 623 136 617 160 q 673 92 637 92 q 697 96 684 92 l 697 -4 "},"Ι":{x_min:42,x_max:181,ha:297,o:"m 181 0 l 42 0 l 42 1013 l 181 1013 l 181 0 "},"Ύ":{x_min:0,x_max:1144.5,ha:1214,o:"m 1144 1012 l 807 416 l 807 0 l 667 0 l 667 416 l 325 1012 l 465 1012 l 736 533 l 1004 1012 l 1144 1012 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"’":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"Ν":{x_min:0,x_max:801,ha:915,o:"m 801 0 l 651 0 l 131 822 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 191 l 670 1013 l 801 1013 l 801 0 "},"-":{x_min:8.71875,x_max:350.390625,ha:478,o:"m 350 317 l 8 317 l 8 428 l 350 428 l 350 317 "},Q:{x_min:0,x_max:968,ha:1072,o:"m 954 5 l 887 -79 l 744 35 q 622 -11 687 2 q 483 -26 556 -26 q 127 130 262 -26 q 0 504 0 279 q 127 880 0 728 q 484 1041 262 1041 q 841 884 708 1041 q 968 507 968 735 q 933 293 968 398 q 832 104 899 188 l 954 5 m 723 191 q 802 330 777 248 q 828 499 828 412 q 744 790 828 673 q 483 922 650 922 q 228 791 322 922 q 142 505 142 673 q 227 221 142 337 q 487 91 323 91 q 632 123 566 91 l 520 215 l 587 301 l 723 191 "},"ς":{x_min:1,x_max:676.28125,ha:740,o:"m 676 460 l 551 460 q 498 595 542 546 q 365 651 448 651 q 199 578 263 651 q 136 401 136 505 q 266 178 136 241 q 508 106 387 142 q 640 -50 640 62 q 625 -158 640 -105 q 583 -278 611 -211 l 465 -278 q 498 -182 490 -211 q 515 -80 515 -126 q 381 12 515 -15 q 134 91 197 51 q 1 388 1 179 q 100 651 1 542 q 354 761 199 761 q 587 680 498 761 q 676 460 676 599 "},M:{x_min:0,x_max:954,ha:1067,o:"m 954 0 l 819 0 l 819 869 l 537 0 l 405 0 l 128 866 l 128 0 l 0 0 l 0 1013 l 200 1013 l 472 160 l 757 1013 l 954 1013 l 954 0 "},"Ψ":{x_min:0,x_max:1006,ha:1094,o:"m 1006 678 q 914 319 1006 429 q 571 200 814 200 l 571 0 l 433 0 l 433 200 q 92 319 194 200 q 0 678 0 429 l 0 1013 l 139 1013 l 139 679 q 191 417 139 492 q 433 326 255 326 l 433 1013 l 571 1013 l 571 326 l 580 326 q 813 423 747 326 q 868 679 868 502 l 868 1013 l 1006 1013 l 1006 678 "},C:{x_min:0,x_max:886,ha:944,o:"m 886 379 q 760 87 886 201 q 455 -26 634 -26 q 112 136 236 -26 q 0 509 0 283 q 118 882 0 737 q 469 1041 245 1041 q 748 955 630 1041 q 879 708 879 859 l 745 708 q 649 862 724 805 q 473 920 573 920 q 219 791 312 920 q 136 509 136 675 q 217 229 136 344 q 470 99 311 99 q 672 179 591 99 q 753 379 753 259 l 886 379 "},"!":{x_min:0,x_max:138,ha:236,o:"m 138 684 q 116 409 138 629 q 105 244 105 299 l 33 244 q 16 465 33 313 q 0 684 0 616 l 0 1013 l 138 1013 l 138 684 m 138 0 l 0 0 l 0 151 l 138 151 l 138 0 "},"{":{x_min:0,x_max:480.5625,ha:578,o:"m 480 -286 q 237 -213 303 -286 q 187 -45 187 -159 q 194 48 187 -15 q 201 141 201 112 q 164 264 201 225 q 0 314 118 314 l 0 417 q 164 471 119 417 q 201 605 201 514 q 199 665 201 644 q 193 772 193 769 q 241 941 193 887 q 480 1015 308 1015 l 480 915 q 336 866 375 915 q 306 742 306 828 q 310 662 306 717 q 314 577 314 606 q 288 452 314 500 q 176 365 256 391 q 289 275 257 337 q 314 143 314 226 q 313 84 314 107 q 310 -11 310 -5 q 339 -131 310 -94 q 480 -182 377 -182 l 480 -286 "},X:{x_min:-0.015625,x_max:854.15625,ha:940,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 428 637 l 675 1013 l 836 1013 l 504 520 l 854 0 "},"#":{x_min:0,x_max:963.890625,ha:1061,o:"m 963 690 l 927 590 l 719 590 l 655 410 l 876 410 l 840 310 l 618 310 l 508 -3 l 393 -2 l 506 309 l 329 310 l 215 -2 l 102 -3 l 212 310 l 0 310 l 36 410 l 248 409 l 312 590 l 86 590 l 120 690 l 347 690 l 459 1006 l 573 1006 l 462 690 l 640 690 l 751 1006 l 865 1006 l 754 690 l 963 690 m 606 590 l 425 590 l 362 410 l 543 410 l 606 590 "},"ι":{x_min:42,x_max:284,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 738 l 167 738 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 103 239 101 q 284 112 257 104 l 284 3 "},"Ά":{x_min:0,x_max:906.953125,ha:982,o:"m 283 1040 l 88 799 l 5 799 l 145 1040 l 283 1040 m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1012 l 529 1012 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},")":{x_min:0,x_max:318,ha:415,o:"m 318 365 q 257 25 318 191 q 87 -290 197 -141 l 0 -290 q 140 21 93 -128 q 193 360 193 189 q 141 704 193 537 q 0 1024 97 850 l 87 1024 q 257 706 197 871 q 318 365 318 542 "},"ε":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 314 0 265 q 128 390 67 353 q 56 460 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 "},"Δ":{x_min:0,x_max:952.78125,ha:1028,o:"m 952 0 l 0 0 l 400 1013 l 551 1013 l 952 0 m 762 124 l 476 867 l 187 124 l 762 124 "},"}":{x_min:0,x_max:481,ha:578,o:"m 481 314 q 318 262 364 314 q 282 136 282 222 q 284 65 282 97 q 293 -58 293 -48 q 241 -217 293 -166 q 0 -286 174 -286 l 0 -182 q 143 -130 105 -182 q 171 -2 171 -93 q 168 81 171 22 q 165 144 165 140 q 188 275 165 229 q 306 365 220 339 q 191 455 224 391 q 165 588 165 505 q 168 681 165 624 q 171 742 171 737 q 141 865 171 827 q 0 915 102 915 l 0 1015 q 243 942 176 1015 q 293 773 293 888 q 287 675 293 741 q 282 590 282 608 q 318 466 282 505 q 481 417 364 417 l 481 314 "},"‰":{x_min:-3,x_max:1672,ha:1821,o:"m 846 0 q 664 76 732 0 q 603 244 603 145 q 662 412 603 344 q 846 489 729 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 846 0 962 0 m 845 103 q 945 143 910 103 q 981 243 981 184 q 947 340 981 301 q 845 385 910 385 q 745 342 782 385 q 709 243 709 300 q 742 147 709 186 q 845 103 781 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 m 1428 0 q 1246 76 1314 0 q 1185 244 1185 145 q 1244 412 1185 344 q 1428 489 1311 489 q 1610 412 1542 489 q 1672 244 1672 343 q 1612 76 1672 144 q 1428 0 1545 0 m 1427 103 q 1528 143 1492 103 q 1564 243 1564 184 q 1530 340 1564 301 q 1427 385 1492 385 q 1327 342 1364 385 q 1291 243 1291 300 q 1324 147 1291 186 q 1427 103 1363 103 "},a:{x_min:0,x_max:698.609375,ha:794,o:"m 698 0 q 661 -12 679 -7 q 615 -17 643 -17 q 536 12 564 -17 q 500 96 508 41 q 384 6 456 37 q 236 -25 312 -25 q 65 31 130 -25 q 0 194 0 88 q 118 390 0 334 q 328 435 180 420 q 488 483 476 451 q 495 523 495 504 q 442 619 495 584 q 325 654 389 654 q 209 617 257 654 q 152 513 161 580 l 33 513 q 123 705 33 633 q 332 772 207 772 q 528 712 448 772 q 617 531 617 645 l 617 163 q 624 108 617 126 q 664 90 632 90 l 698 94 l 698 0 m 491 262 l 491 372 q 272 329 350 347 q 128 201 128 294 q 166 113 128 144 q 264 83 205 83 q 414 130 346 83 q 491 262 491 183 "},"—":{x_min:0,x_max:941.671875,ha:1039,o:"m 941 334 l 0 334 l 0 410 l 941 410 l 941 334 "},"=":{x_min:8.71875,x_max:780.953125,ha:792,o:"m 780 510 l 8 510 l 8 606 l 780 606 l 780 510 m 780 235 l 8 235 l 8 332 l 780 332 l 780 235 "},N:{x_min:0,x_max:801,ha:914,o:"m 801 0 l 651 0 l 131 823 l 131 0 l 0 0 l 0 1013 l 151 1013 l 670 193 l 670 1013 l 801 1013 l 801 0 "},"ρ":{x_min:0,x_max:712,ha:797,o:"m 712 369 q 620 94 712 207 q 362 -26 521 -26 q 230 2 292 -26 q 119 83 167 30 l 119 -278 l 0 -278 l 0 362 q 91 643 0 531 q 355 764 190 764 q 617 647 517 764 q 712 369 712 536 m 583 366 q 530 559 583 480 q 359 651 469 651 q 190 562 252 651 q 135 370 135 483 q 189 176 135 257 q 359 85 250 85 q 528 175 466 85 q 583 366 583 254 "},"¯":{x_min:0,x_max:941.671875,ha:938,o:"m 941 1033 l 0 1033 l 0 1109 l 941 1109 l 941 1033 "},Z:{x_min:0,x_max:779,ha:849,o:"m 779 0 l 0 0 l 0 113 l 621 896 l 40 896 l 40 1013 l 779 1013 l 778 887 l 171 124 l 779 124 l 779 0 "},u:{x_min:0,x_max:617,ha:729,o:"m 617 0 l 499 0 l 499 110 q 391 10 460 45 q 246 -25 322 -25 q 61 58 127 -25 q 0 258 0 136 l 0 738 l 125 738 l 125 284 q 156 148 125 202 q 273 82 197 82 q 433 165 369 82 q 493 340 493 243 l 493 738 l 617 738 l 617 0 "},k:{x_min:0,x_max:612.484375,ha:697,o:"m 612 738 l 338 465 l 608 0 l 469 0 l 251 382 l 121 251 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 402 l 456 738 l 612 738 "},"Η":{x_min:0,x_max:803,ha:917,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"Α":{x_min:0,x_max:906.953125,ha:985,o:"m 906 0 l 756 0 l 650 303 l 251 303 l 143 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 609 421 l 452 866 l 293 421 l 609 421 "},s:{x_min:0,x_max:604,ha:697,o:"m 604 217 q 501 36 604 104 q 292 -23 411 -23 q 86 43 166 -23 q 0 238 0 114 l 121 237 q 175 122 121 164 q 300 85 223 85 q 415 112 363 85 q 479 207 479 147 q 361 309 479 276 q 140 372 141 370 q 21 544 21 426 q 111 708 21 647 q 298 761 190 761 q 492 705 413 761 q 583 531 583 643 l 462 531 q 412 625 462 594 q 298 657 363 657 q 199 636 242 657 q 143 558 143 608 q 262 454 143 486 q 484 394 479 397 q 604 217 604 341 "},B:{x_min:0,x_max:778,ha:876,o:"m 580 546 q 724 469 670 535 q 778 311 778 403 q 673 83 778 171 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 892 q 691 633 732 693 q 580 546 650 572 m 393 899 l 139 899 l 139 588 l 379 588 q 521 624 462 588 q 592 744 592 667 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 303 635 219 q 559 436 635 389 q 402 477 494 477 l 139 477 l 139 124 l 419 124 "},"…":{x_min:0,x_max:614,ha:708,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 m 378 0 l 236 0 l 236 151 l 378 151 l 378 0 m 614 0 l 472 0 l 472 151 l 614 151 l 614 0 "},"?":{x_min:0,x_max:607,ha:704,o:"m 607 777 q 543 599 607 674 q 422 474 482 537 q 357 272 357 391 l 236 272 q 297 487 236 395 q 411 619 298 490 q 474 762 474 691 q 422 885 474 838 q 301 933 371 933 q 179 880 228 933 q 124 706 124 819 l 0 706 q 94 963 0 872 q 302 1044 177 1044 q 511 973 423 1044 q 607 777 607 895 m 370 0 l 230 0 l 230 151 l 370 151 l 370 0 "},H:{x_min:0,x_max:803,ha:915,o:"m 803 0 l 667 0 l 667 475 l 140 475 l 140 0 l 0 0 l 0 1013 l 140 1013 l 140 599 l 667 599 l 667 1013 l 803 1013 l 803 0 "},"ν":{x_min:0,x_max:675,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 738 l 340 147 l 541 738 l 675 738 "},c:{x_min:1,x_max:701.390625,ha:775,o:"m 701 264 q 584 53 681 133 q 353 -26 487 -26 q 91 91 188 -26 q 1 370 1 201 q 92 645 1 537 q 353 761 190 761 q 572 688 479 761 q 690 493 666 615 l 556 493 q 487 606 545 562 q 356 650 428 650 q 186 563 246 650 q 134 372 134 487 q 188 179 134 258 q 359 88 250 88 q 492 136 437 88 q 566 264 548 185 l 701 264 "},"¶":{x_min:0,x_max:566.671875,ha:678,o:"m 21 892 l 52 892 l 98 761 l 145 892 l 176 892 l 178 741 l 157 741 l 157 867 l 108 741 l 88 741 l 40 871 l 40 741 l 21 741 l 21 892 m 308 854 l 308 731 q 252 691 308 691 q 227 691 240 691 q 207 696 213 695 l 207 712 l 253 706 q 288 733 288 706 l 288 763 q 244 741 279 741 q 193 797 193 741 q 261 860 193 860 q 287 860 273 860 q 308 854 302 855 m 288 842 l 263 843 q 213 796 213 843 q 248 756 213 756 q 288 796 288 756 l 288 842 m 566 988 l 502 988 l 502 -1 l 439 -1 l 439 988 l 317 988 l 317 -1 l 252 -1 l 252 602 q 81 653 155 602 q 0 805 0 711 q 101 989 0 918 q 309 1053 194 1053 l 566 1053 l 566 988 "},"β":{x_min:0,x_max:660,ha:745,o:"m 471 550 q 610 450 561 522 q 660 280 660 378 q 578 64 660 151 q 367 -22 497 -22 q 239 5 299 -22 q 126 82 178 32 l 126 -278 l 0 -278 l 0 593 q 54 903 0 801 q 318 1042 127 1042 q 519 964 436 1042 q 603 771 603 887 q 567 644 603 701 q 471 550 532 586 m 337 79 q 476 138 418 79 q 535 279 535 198 q 427 437 535 386 q 226 477 344 477 l 226 583 q 398 620 329 583 q 486 762 486 668 q 435 884 486 833 q 312 935 384 935 q 169 861 219 935 q 126 698 126 797 l 126 362 q 170 169 126 242 q 337 79 224 79 "},"Μ":{x_min:0,x_max:954,ha:1068,o:"m 954 0 l 819 0 l 819 868 l 537 0 l 405 0 l 128 865 l 128 0 l 0 0 l 0 1013 l 199 1013 l 472 158 l 758 1013 l 954 1013 l 954 0 "},"Ό":{x_min:0.109375,x_max:1120,ha:1217,o:"m 1120 505 q 994 132 1120 282 q 642 -29 861 -29 q 290 130 422 -29 q 167 505 167 280 q 294 883 167 730 q 650 1046 430 1046 q 999 882 868 1046 q 1120 505 1120 730 m 977 504 q 896 784 977 669 q 644 915 804 915 q 391 785 484 915 q 307 504 307 669 q 391 224 307 339 q 644 95 486 95 q 894 224 803 95 q 977 504 977 339 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ή":{x_min:0,x_max:1158,ha:1275,o:"m 1158 0 l 1022 0 l 1022 475 l 496 475 l 496 0 l 356 0 l 356 1012 l 496 1012 l 496 599 l 1022 599 l 1022 1012 l 1158 1012 l 1158 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"•":{x_min:0,x_max:663.890625,ha:775,o:"m 663 529 q 566 293 663 391 q 331 196 469 196 q 97 294 194 196 q 0 529 0 393 q 96 763 0 665 q 331 861 193 861 q 566 763 469 861 q 663 529 663 665 "},"¥":{x_min:0.1875,x_max:819.546875,ha:886,o:"m 563 561 l 697 561 l 696 487 l 520 487 l 482 416 l 482 380 l 697 380 l 695 308 l 482 308 l 482 0 l 342 0 l 342 308 l 125 308 l 125 380 l 342 380 l 342 417 l 303 487 l 125 487 l 125 561 l 258 561 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 l 563 561 "},"(":{x_min:0,x_max:318.0625,ha:415,o:"m 318 -290 l 230 -290 q 61 23 122 -142 q 0 365 0 190 q 62 712 0 540 q 230 1024 119 869 l 318 1024 q 175 705 219 853 q 125 360 125 542 q 176 22 125 187 q 318 -290 223 -127 "},U:{x_min:0,x_max:796,ha:904,o:"m 796 393 q 681 93 796 212 q 386 -25 566 -25 q 101 95 208 -25 q 0 393 0 211 l 0 1013 l 138 1013 l 138 391 q 204 191 138 270 q 394 107 276 107 q 586 191 512 107 q 656 391 656 270 l 656 1013 l 796 1013 l 796 393 "},"γ":{x_min:0.5,x_max:744.953125,ha:822,o:"m 744 737 l 463 54 l 463 -278 l 338 -278 l 338 54 l 154 495 q 104 597 124 569 q 13 651 67 651 l 0 651 l 0 751 l 39 753 q 168 711 121 753 q 242 594 207 676 l 403 208 l 617 737 l 744 737 "},"α":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 728 407 760 q 563 637 524 696 l 563 739 l 685 739 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 96 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 "},F:{x_min:0,x_max:683.328125,ha:717,o:"m 683 888 l 140 888 l 140 583 l 613 583 l 613 458 l 140 458 l 140 0 l 0 0 l 0 1013 l 683 1013 l 683 888 "},"­":{x_min:0,x_max:705.5625,ha:803,o:"m 705 334 l 0 334 l 0 410 l 705 410 l 705 334 "},":":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"Χ":{x_min:0,x_max:854.171875,ha:935,o:"m 854 0 l 683 0 l 423 409 l 166 0 l 0 0 l 347 519 l 18 1013 l 186 1013 l 427 637 l 675 1013 l 836 1013 l 504 521 l 854 0 "},"*":{x_min:116,x_max:674,ha:792,o:"m 674 768 l 475 713 l 610 544 l 517 477 l 394 652 l 272 478 l 178 544 l 314 713 l 116 766 l 153 876 l 341 812 l 342 1013 l 446 1013 l 446 811 l 635 874 l 674 768 "},"†":{x_min:0,x_max:777,ha:835,o:"m 458 804 l 777 804 l 777 683 l 458 683 l 458 0 l 319 0 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 "},"°":{x_min:0,x_max:347,ha:444,o:"m 173 802 q 43 856 91 802 q 0 977 0 905 q 45 1101 0 1049 q 173 1153 90 1153 q 303 1098 255 1153 q 347 977 347 1049 q 303 856 347 905 q 173 802 256 802 m 173 884 q 238 910 214 884 q 262 973 262 937 q 239 1038 262 1012 q 173 1064 217 1064 q 108 1037 132 1064 q 85 973 85 1010 q 108 910 85 937 q 173 884 132 884 "},V:{x_min:0,x_max:862.71875,ha:940,o:"m 862 1013 l 505 0 l 361 0 l 0 1013 l 143 1013 l 434 165 l 718 1012 l 862 1013 "},"Ξ":{x_min:0,x_max:734.71875,ha:763,o:"m 723 889 l 9 889 l 9 1013 l 723 1013 l 723 889 m 673 463 l 61 463 l 61 589 l 673 589 l 673 463 m 734 0 l 0 0 l 0 124 l 734 124 l 734 0 "}," ":{x_min:0,x_max:0,ha:853},"Ϋ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 588 1046 l 460 1046 l 460 1189 l 588 1189 l 588 1046 m 360 1046 l 232 1046 l 232 1189 l 360 1189 l 360 1046 m 819 1012 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1012 l 140 1012 l 411 533 l 679 1012 l 819 1012 "},"”":{x_min:0,x_max:347,ha:454,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 m 347 851 q 310 737 347 784 q 208 669 273 690 l 208 734 q 267 787 250 741 q 280 873 280 821 l 208 873 l 208 1013 l 347 1013 l 347 851 "},"@":{x_min:0,x_max:1260,ha:1357,o:"m 1098 -45 q 877 -160 1001 -117 q 633 -203 752 -203 q 155 -29 327 -203 q 0 360 0 127 q 176 802 0 616 q 687 1008 372 1008 q 1123 854 969 1008 q 1260 517 1260 718 q 1155 216 1260 341 q 868 82 1044 82 q 772 106 801 82 q 737 202 737 135 q 647 113 700 144 q 527 82 594 82 q 367 147 420 82 q 314 312 314 212 q 401 565 314 452 q 639 690 498 690 q 810 588 760 690 l 849 668 l 938 668 q 877 441 900 532 q 833 226 833 268 q 853 182 833 198 q 902 167 873 167 q 1088 272 1012 167 q 1159 512 1159 372 q 1051 793 1159 681 q 687 925 925 925 q 248 747 415 925 q 97 361 97 586 q 226 26 97 159 q 627 -122 370 -122 q 856 -87 737 -122 q 1061 8 976 -53 l 1098 -45 m 786 488 q 738 580 777 545 q 643 615 700 615 q 483 517 548 615 q 425 322 425 430 q 457 203 425 250 q 552 156 490 156 q 722 273 665 156 q 786 488 738 309 "},"Ί":{x_min:0,x_max:499,ha:613,o:"m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 m 499 0 l 360 0 l 360 1012 l 499 1012 l 499 0 "},i:{x_min:14,x_max:136,ha:275,o:"m 136 873 l 14 873 l 14 1013 l 136 1013 l 136 873 m 136 0 l 14 0 l 14 737 l 136 737 l 136 0 "},"Β":{x_min:0,x_max:778,ha:877,o:"m 580 545 q 724 468 671 534 q 778 310 778 402 q 673 83 778 170 q 432 0 575 0 l 0 0 l 0 1013 l 411 1013 q 629 957 541 1013 q 732 768 732 891 q 691 632 732 692 q 580 545 650 571 m 393 899 l 139 899 l 139 587 l 379 587 q 521 623 462 587 q 592 744 592 666 q 531 859 592 819 q 393 899 471 899 m 419 124 q 566 169 504 124 q 635 302 635 219 q 559 435 635 388 q 402 476 494 476 l 139 476 l 139 124 l 419 124 "},"υ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 94 617 199 q 308 -24 455 -24 q 76 94 161 -24 q 0 352 0 199 l 0 739 l 126 739 l 126 355 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 355 492 257 l 492 739 l 617 739 l 617 352 "},"]":{x_min:0,x_max:275,ha:372,o:"m 275 -281 l 0 -281 l 0 -187 l 151 -187 l 151 920 l 0 920 l 0 1013 l 275 1013 l 275 -281 "},m:{x_min:0,x_max:1019,ha:1128,o:"m 1019 0 l 897 0 l 897 454 q 860 591 897 536 q 739 660 816 660 q 613 586 659 660 q 573 436 573 522 l 573 0 l 447 0 l 447 455 q 412 591 447 535 q 294 657 372 657 q 165 586 213 657 q 122 437 122 521 l 122 0 l 0 0 l 0 738 l 117 738 l 117 640 q 202 730 150 697 q 316 763 254 763 q 437 730 381 763 q 525 642 494 697 q 621 731 559 700 q 753 763 682 763 q 943 694 867 763 q 1019 512 1019 625 l 1019 0 "},"χ":{x_min:8.328125,x_max:780.5625,ha:815,o:"m 780 -278 q 715 -294 747 -294 q 616 -257 663 -294 q 548 -175 576 -227 l 379 133 l 143 -277 l 9 -277 l 313 254 l 163 522 q 127 586 131 580 q 36 640 91 640 q 8 637 27 640 l 8 752 l 52 757 q 162 719 113 757 q 236 627 200 690 l 383 372 l 594 737 l 726 737 l 448 250 l 625 -69 q 670 -153 647 -110 q 743 -188 695 -188 q 780 -184 759 -188 l 780 -278 "},"ί":{x_min:42,x_max:326.71875,ha:361,o:"m 284 3 q 233 -10 258 -5 q 182 -15 207 -15 q 85 26 119 -15 q 42 200 42 79 l 42 737 l 167 737 l 168 215 q 172 141 168 157 q 226 101 183 101 q 248 102 239 101 q 284 112 257 104 l 284 3 m 326 1040 l 137 819 l 54 819 l 189 1040 l 326 1040 "},"Ζ":{x_min:0,x_max:779.171875,ha:850,o:"m 779 0 l 0 0 l 0 113 l 620 896 l 40 896 l 40 1013 l 779 1013 l 779 887 l 170 124 l 779 124 l 779 0 "},R:{x_min:0,x_max:781.953125,ha:907,o:"m 781 0 l 623 0 q 587 242 590 52 q 407 433 585 433 l 138 433 l 138 0 l 0 0 l 0 1013 l 396 1013 q 636 946 539 1013 q 749 731 749 868 q 711 597 749 659 q 608 502 674 534 q 718 370 696 474 q 729 207 722 352 q 781 26 736 62 l 781 0 m 373 551 q 533 594 465 551 q 614 731 614 645 q 532 859 614 815 q 373 896 465 896 l 138 896 l 138 551 l 373 551 "},o:{x_min:0,x_max:713,ha:821,o:"m 357 -25 q 94 91 194 -25 q 0 368 0 202 q 93 642 0 533 q 357 761 193 761 q 618 644 518 761 q 713 368 713 533 q 619 91 713 201 q 357 -25 521 -25 m 357 85 q 528 175 465 85 q 584 369 584 255 q 529 562 584 484 q 357 651 467 651 q 189 560 250 651 q 135 369 135 481 q 187 177 135 257 q 357 85 250 85 "},K:{x_min:0,x_max:819.46875,ha:906,o:"m 819 0 l 649 0 l 294 509 l 139 355 l 139 0 l 0 0 l 0 1013 l 139 1013 l 139 526 l 626 1013 l 809 1013 l 395 600 l 819 0 "},",":{x_min:0,x_max:142,ha:239,o:"m 142 -12 q 105 -132 142 -82 q 0 -205 68 -182 l 0 -138 q 57 -82 40 -124 q 70 0 70 -51 l 0 0 l 0 151 l 142 151 l 142 -12 "},d:{x_min:0,x_max:683,ha:796,o:"m 683 0 l 564 0 l 564 93 q 456 6 516 38 q 327 -25 395 -25 q 87 100 181 -25 q 0 365 0 215 q 90 639 0 525 q 343 763 187 763 q 564 647 486 763 l 564 1013 l 683 1013 l 683 0 m 582 373 q 529 562 582 484 q 361 653 468 653 q 190 561 253 653 q 135 365 135 479 q 189 175 135 254 q 358 85 251 85 q 529 178 468 85 q 582 373 582 258 "},"¨":{x_min:-109,x_max:247,ha:232,o:"m 247 1046 l 119 1046 l 119 1189 l 247 1189 l 247 1046 m 19 1046 l -109 1046 l -109 1189 l 19 1189 l 19 1046 "},E:{x_min:0,x_max:736.109375,ha:789,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},Y:{x_min:0,x_max:820,ha:886,o:"m 820 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 534 l 679 1012 l 820 1013 "},"\"":{x_min:0,x_max:299,ha:396,o:"m 299 606 l 203 606 l 203 988 l 299 988 l 299 606 m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"‹":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"„":{x_min:0,x_max:364,ha:467,o:"m 141 -12 q 104 -132 141 -82 q 0 -205 67 -182 l 0 -138 q 56 -82 40 -124 q 69 0 69 -51 l 0 0 l 0 151 l 141 151 l 141 -12 m 364 -12 q 327 -132 364 -82 q 222 -205 290 -182 l 222 -138 q 279 -82 262 -124 q 292 0 292 -51 l 222 0 l 222 151 l 364 151 l 364 -12 "},"δ":{x_min:1,x_max:710,ha:810,o:"m 710 360 q 616 87 710 196 q 356 -28 518 -28 q 99 82 197 -28 q 1 356 1 192 q 100 606 1 509 q 355 703 199 703 q 180 829 288 754 q 70 903 124 866 l 70 1012 l 643 1012 l 643 901 l 258 901 q 462 763 422 794 q 636 592 577 677 q 710 360 710 485 m 584 365 q 552 501 584 447 q 451 602 521 555 q 372 611 411 611 q 197 541 258 611 q 136 355 136 472 q 190 171 136 245 q 358 85 252 85 q 528 173 465 85 q 584 365 584 252 "},"έ":{x_min:0,x_max:634.71875,ha:714,o:"m 634 234 q 527 38 634 110 q 300 -25 433 -25 q 98 29 183 -25 q 0 204 0 93 q 37 313 0 265 q 128 390 67 352 q 56 459 82 419 q 26 555 26 505 q 114 712 26 654 q 295 763 191 763 q 499 700 416 763 q 589 515 589 631 l 478 515 q 419 618 464 580 q 307 657 374 657 q 207 630 253 657 q 151 547 151 598 q 238 445 151 469 q 389 434 280 434 l 389 331 l 349 331 q 206 315 255 331 q 125 210 125 287 q 183 107 125 145 q 302 76 233 76 q 436 117 379 76 q 509 234 493 159 l 634 234 m 520 1040 l 331 819 l 248 819 l 383 1040 l 520 1040 "},"ω":{x_min:0,x_max:922,ha:1031,o:"m 922 339 q 856 97 922 203 q 650 -26 780 -26 q 538 9 587 -26 q 461 103 489 44 q 387 12 436 46 q 277 -22 339 -22 q 69 97 147 -22 q 0 339 0 203 q 45 551 0 444 q 161 738 84 643 l 302 738 q 175 553 219 647 q 124 336 124 446 q 155 179 124 249 q 275 88 197 88 q 375 163 341 88 q 400 294 400 219 l 400 572 l 524 572 l 524 294 q 561 135 524 192 q 643 88 591 88 q 762 182 719 88 q 797 342 797 257 q 745 556 797 450 q 619 738 705 638 l 760 738 q 874 551 835 640 q 922 339 922 444 "},"´":{x_min:0,x_max:96,ha:251,o:"m 96 606 l 0 606 l 0 988 l 96 988 l 96 606 "},"±":{x_min:11,x_max:781,ha:792,o:"m 781 490 l 446 490 l 446 255 l 349 255 l 349 490 l 11 490 l 11 586 l 349 586 l 349 819 l 446 819 l 446 586 l 781 586 l 781 490 m 781 21 l 11 21 l 11 115 l 781 115 l 781 21 "},"|":{x_min:343,x_max:449,ha:792,o:"m 449 462 l 343 462 l 343 986 l 449 986 l 449 462 m 449 -242 l 343 -242 l 343 280 l 449 280 l 449 -242 "},"ϋ":{x_min:0,x_max:617,ha:725,o:"m 482 800 l 372 800 l 372 925 l 482 925 l 482 800 m 239 800 l 129 800 l 129 925 l 239 925 l 239 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 "},"§":{x_min:0,x_max:593,ha:690,o:"m 593 425 q 554 312 593 369 q 467 233 516 254 q 537 83 537 172 q 459 -74 537 -12 q 288 -133 387 -133 q 115 -69 184 -133 q 47 96 47 -6 l 166 96 q 199 7 166 40 q 288 -26 232 -26 q 371 -5 332 -26 q 420 60 420 21 q 311 201 420 139 q 108 309 210 255 q 0 490 0 383 q 33 602 0 551 q 124 687 66 654 q 75 743 93 712 q 58 812 58 773 q 133 984 58 920 q 300 1043 201 1043 q 458 987 394 1043 q 529 814 529 925 l 411 814 q 370 908 404 877 q 289 939 336 939 q 213 911 246 939 q 180 841 180 883 q 286 720 180 779 q 484 612 480 615 q 593 425 593 534 m 467 409 q 355 544 467 473 q 196 630 228 612 q 146 587 162 609 q 124 525 124 558 q 239 387 124 462 q 398 298 369 315 q 448 345 429 316 q 467 409 467 375 "},b:{x_min:0,x_max:685,ha:783,o:"m 685 372 q 597 99 685 213 q 347 -25 501 -25 q 219 5 277 -25 q 121 93 161 36 l 121 0 l 0 0 l 0 1013 l 121 1013 l 121 634 q 214 723 157 692 q 341 754 272 754 q 591 637 493 754 q 685 372 685 526 m 554 356 q 499 550 554 470 q 328 644 437 644 q 162 556 223 644 q 108 369 108 478 q 160 176 108 256 q 330 83 221 83 q 498 169 435 83 q 554 356 554 245 "},q:{x_min:0,x_max:683,ha:876,o:"m 683 -278 l 564 -278 l 564 97 q 474 8 533 39 q 345 -23 415 -23 q 91 93 188 -23 q 0 364 0 203 q 87 635 0 522 q 337 760 184 760 q 466 727 408 760 q 564 637 523 695 l 564 737 l 683 737 l 683 -278 m 582 375 q 527 564 582 488 q 358 652 466 652 q 190 565 253 652 q 135 377 135 488 q 189 179 135 261 q 361 84 251 84 q 530 179 469 84 q 582 375 582 260 "},"Ω":{x_min:-0.171875,x_max:969.5625,ha:1068,o:"m 969 0 l 555 0 l 555 123 q 744 308 675 194 q 814 558 814 423 q 726 812 814 709 q 484 922 633 922 q 244 820 334 922 q 154 567 154 719 q 223 316 154 433 q 412 123 292 199 l 412 0 l 0 0 l 0 124 l 217 124 q 68 327 122 210 q 15 572 15 444 q 144 911 15 781 q 484 1041 274 1041 q 822 909 691 1041 q 953 569 953 777 q 899 326 953 443 q 750 124 846 210 l 969 124 l 969 0 "},"ύ":{x_min:0,x_max:617,ha:725,o:"m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 535 1040 l 346 819 l 262 819 l 397 1040 l 535 1040 "},z:{x_min:-0.015625,x_max:613.890625,ha:697,o:"m 613 0 l 0 0 l 0 100 l 433 630 l 20 630 l 20 738 l 594 738 l 593 636 l 163 110 l 613 110 l 613 0 "},"™":{x_min:0,x_max:894,ha:1000,o:"m 389 951 l 229 951 l 229 503 l 160 503 l 160 951 l 0 951 l 0 1011 l 389 1011 l 389 951 m 894 503 l 827 503 l 827 939 l 685 503 l 620 503 l 481 937 l 481 503 l 417 503 l 417 1011 l 517 1011 l 653 580 l 796 1010 l 894 1011 l 894 503 "},"ή":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 721 124 755 q 200 630 193 687 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 m 479 1040 l 290 819 l 207 819 l 341 1040 l 479 1040 "},"Θ":{x_min:0,x_max:960,ha:1056,o:"m 960 507 q 833 129 960 280 q 476 -32 698 -32 q 123 129 255 -32 q 0 507 0 280 q 123 883 0 732 q 476 1045 255 1045 q 832 883 696 1045 q 960 507 960 732 m 817 500 q 733 789 817 669 q 476 924 639 924 q 223 792 317 924 q 142 507 142 675 q 222 222 142 339 q 476 89 315 89 q 730 218 636 89 q 817 500 817 334 m 716 449 l 243 449 l 243 571 l 716 571 l 716 449 "},"®":{x_min:-3,x_max:1008,ha:1106,o:"m 503 532 q 614 562 566 532 q 672 658 672 598 q 614 747 672 716 q 503 772 569 772 l 338 772 l 338 532 l 503 532 m 502 -7 q 123 151 263 -7 q -3 501 -3 294 q 123 851 -3 706 q 502 1011 263 1011 q 881 851 739 1011 q 1008 501 1008 708 q 883 151 1008 292 q 502 -7 744 -7 m 502 60 q 830 197 709 60 q 940 501 940 322 q 831 805 940 681 q 502 944 709 944 q 174 805 296 944 q 65 501 65 680 q 173 197 65 320 q 502 60 294 60 m 788 146 l 678 146 q 653 316 655 183 q 527 449 652 449 l 338 449 l 338 146 l 241 146 l 241 854 l 518 854 q 688 808 621 854 q 766 658 766 755 q 739 563 766 607 q 668 497 713 519 q 751 331 747 472 q 788 164 756 190 l 788 146 "},"~":{x_min:0,x_max:833,ha:931,o:"m 833 958 q 778 753 833 831 q 594 665 716 665 q 402 761 502 665 q 240 857 302 857 q 131 795 166 857 q 104 665 104 745 l 0 665 q 54 867 0 789 q 237 958 116 958 q 429 861 331 958 q 594 765 527 765 q 704 827 670 765 q 729 958 729 874 l 833 958 "},"Ε":{x_min:0,x_max:736.21875,ha:778,o:"m 736 0 l 0 0 l 0 1013 l 725 1013 l 725 889 l 139 889 l 139 585 l 677 585 l 677 467 l 139 467 l 139 125 l 736 125 l 736 0 "},"³":{x_min:0,x_max:450,ha:547,o:"m 450 552 q 379 413 450 464 q 220 366 313 366 q 69 414 130 366 q 0 567 0 470 l 85 567 q 126 470 85 504 q 225 437 168 437 q 320 467 280 437 q 360 552 360 498 q 318 632 360 608 q 213 657 276 657 q 195 657 203 657 q 176 657 181 657 l 176 722 q 279 733 249 722 q 334 815 334 752 q 300 881 334 856 q 220 907 267 907 q 133 875 169 907 q 97 781 97 844 l 15 781 q 78 926 15 875 q 220 972 135 972 q 364 930 303 972 q 426 817 426 888 q 344 697 426 733 q 421 642 392 681 q 450 552 450 603 "},"[":{x_min:0,x_max:273.609375,ha:371,o:"m 273 -281 l 0 -281 l 0 1013 l 273 1013 l 273 920 l 124 920 l 124 -187 l 273 -187 l 273 -281 "},L:{x_min:0,x_max:645.828125,ha:696,o:"m 645 0 l 0 0 l 0 1013 l 140 1013 l 140 126 l 645 126 l 645 0 "},"σ":{x_min:0,x_max:803.390625,ha:894,o:"m 803 628 l 633 628 q 713 368 713 512 q 618 93 713 204 q 357 -25 518 -25 q 94 91 194 -25 q 0 368 0 201 q 94 644 0 533 q 356 761 194 761 q 481 750 398 761 q 608 739 564 739 l 803 739 l 803 628 m 360 85 q 529 180 467 85 q 584 374 584 262 q 527 566 584 490 q 352 651 463 651 q 187 559 247 651 q 135 368 135 478 q 189 175 135 254 q 360 85 251 85 "},"ζ":{x_min:0,x_max:573,ha:642,o:"m 573 -40 q 553 -162 573 -97 q 510 -278 543 -193 l 400 -278 q 441 -187 428 -219 q 462 -90 462 -132 q 378 -14 462 -14 q 108 45 197 -14 q 0 290 0 117 q 108 631 0 462 q 353 901 194 767 l 55 901 l 55 1012 l 561 1012 l 561 924 q 261 669 382 831 q 128 301 128 489 q 243 117 128 149 q 458 98 350 108 q 573 -40 573 80 "},"θ":{x_min:0,x_max:674,ha:778,o:"m 674 496 q 601 160 674 304 q 336 -26 508 -26 q 73 153 165 -26 q 0 485 0 296 q 72 840 0 683 q 343 1045 166 1045 q 605 844 516 1045 q 674 496 674 692 m 546 579 q 498 798 546 691 q 336 935 437 935 q 178 798 237 935 q 126 579 137 701 l 546 579 m 546 475 l 126 475 q 170 233 126 348 q 338 80 230 80 q 504 233 447 80 q 546 475 546 346 "},"Ο":{x_min:0,x_max:958,ha:1054,o:"m 485 1042 q 834 883 703 1042 q 958 511 958 735 q 834 136 958 287 q 481 -26 701 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 729 q 485 1042 263 1042 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 670 q 480 913 640 913 q 226 785 321 913 q 142 504 142 671 q 226 224 142 339 q 480 98 319 98 "},"Γ":{x_min:0,x_max:705.28125,ha:749,o:"m 705 886 l 140 886 l 140 0 l 0 0 l 0 1012 l 705 1012 l 705 886 "}," ":{x_min:0,x_max:0,ha:375},"%":{x_min:-3,x_max:1089,ha:1186,o:"m 845 0 q 663 76 731 0 q 602 244 602 145 q 661 412 602 344 q 845 489 728 489 q 1027 412 959 489 q 1089 244 1089 343 q 1029 76 1089 144 q 845 0 962 0 m 844 103 q 945 143 909 103 q 981 243 981 184 q 947 340 981 301 q 844 385 909 385 q 744 342 781 385 q 708 243 708 300 q 741 147 708 186 q 844 103 780 103 m 888 986 l 284 -25 l 199 -25 l 803 986 l 888 986 m 241 468 q 58 545 126 468 q -3 715 -3 615 q 56 881 -3 813 q 238 958 124 958 q 421 881 353 958 q 483 712 483 813 q 423 544 483 612 q 241 468 356 468 m 241 855 q 137 811 175 855 q 100 710 100 768 q 136 612 100 653 q 240 572 172 572 q 344 614 306 572 q 382 713 382 656 q 347 810 382 771 q 241 855 308 855 "},P:{x_min:0,x_max:726,ha:806,o:"m 424 1013 q 640 931 555 1013 q 726 719 726 850 q 637 506 726 587 q 413 426 548 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 379 889 l 140 889 l 140 548 l 372 548 q 522 589 459 548 q 593 720 593 637 q 528 845 593 801 q 379 889 463 889 "},"Έ":{x_min:0,x_max:1078.21875,ha:1118,o:"m 1078 0 l 342 0 l 342 1013 l 1067 1013 l 1067 889 l 481 889 l 481 585 l 1019 585 l 1019 467 l 481 467 l 481 125 l 1078 125 l 1078 0 m 277 1040 l 83 799 l 0 799 l 140 1040 l 277 1040 "},"Ώ":{x_min:0.125,x_max:1136.546875,ha:1235,o:"m 1136 0 l 722 0 l 722 123 q 911 309 842 194 q 981 558 981 423 q 893 813 981 710 q 651 923 800 923 q 411 821 501 923 q 321 568 321 720 q 390 316 321 433 q 579 123 459 200 l 579 0 l 166 0 l 166 124 l 384 124 q 235 327 289 210 q 182 572 182 444 q 311 912 182 782 q 651 1042 441 1042 q 989 910 858 1042 q 1120 569 1120 778 q 1066 326 1120 443 q 917 124 1013 210 l 1136 124 l 1136 0 m 277 1040 l 83 800 l 0 800 l 140 1041 l 277 1040 "},_:{x_min:0,x_max:705.5625,ha:803,o:"m 705 -334 l 0 -334 l 0 -234 l 705 -234 l 705 -334 "},"Ϊ":{x_min:-110,x_max:246,ha:275,o:"m 246 1046 l 118 1046 l 118 1189 l 246 1189 l 246 1046 m 18 1046 l -110 1046 l -110 1189 l 18 1189 l 18 1046 m 136 0 l 0 0 l 0 1012 l 136 1012 l 136 0 "},"+":{x_min:23,x_max:768,ha:792,o:"m 768 372 l 444 372 l 444 0 l 347 0 l 347 372 l 23 372 l 23 468 l 347 468 l 347 840 l 444 840 l 444 468 l 768 468 l 768 372 "},"½":{x_min:0,x_max:1050,ha:1149,o:"m 1050 0 l 625 0 q 712 178 625 108 q 878 277 722 187 q 967 385 967 328 q 932 456 967 429 q 850 484 897 484 q 759 450 798 484 q 721 352 721 416 l 640 352 q 706 502 640 448 q 851 551 766 551 q 987 509 931 551 q 1050 385 1050 462 q 976 251 1050 301 q 829 179 902 215 q 717 68 740 133 l 1050 68 l 1050 0 m 834 985 l 215 -28 l 130 -28 l 750 984 l 834 985 m 224 422 l 142 422 l 142 811 l 0 811 l 0 867 q 104 889 62 867 q 164 973 157 916 l 224 973 l 224 422 "},"Ρ":{x_min:0,x_max:720,ha:783,o:"m 424 1013 q 637 933 554 1013 q 720 723 720 853 q 633 508 720 591 q 413 426 546 426 l 140 426 l 140 0 l 0 0 l 0 1013 l 424 1013 m 378 889 l 140 889 l 140 548 l 371 548 q 521 589 458 548 q 592 720 592 637 q 527 845 592 801 q 378 889 463 889 "},"'":{x_min:0,x_max:139,ha:236,o:"m 139 851 q 102 737 139 784 q 0 669 65 690 l 0 734 q 59 787 42 741 q 72 873 72 821 l 0 873 l 0 1013 l 139 1013 l 139 851 "},"ª":{x_min:0,x_max:350,ha:397,o:"m 350 625 q 307 616 328 616 q 266 631 281 616 q 247 673 251 645 q 190 628 225 644 q 116 613 156 613 q 32 641 64 613 q 0 722 0 669 q 72 826 0 800 q 247 866 159 846 l 247 887 q 220 934 247 916 q 162 953 194 953 q 104 934 129 953 q 76 882 80 915 l 16 882 q 60 976 16 941 q 166 1011 104 1011 q 266 979 224 1011 q 308 891 308 948 l 308 706 q 311 679 308 688 q 331 670 315 670 l 350 672 l 350 625 m 247 757 l 247 811 q 136 790 175 798 q 64 726 64 773 q 83 682 64 697 q 132 667 103 667 q 207 690 174 667 q 247 757 247 718 "},"΅":{x_min:0,x_max:450,ha:553,o:"m 450 800 l 340 800 l 340 925 l 450 925 l 450 800 m 406 1040 l 212 800 l 129 800 l 269 1040 l 406 1040 m 110 800 l 0 800 l 0 925 l 110 925 l 110 800 "},T:{x_min:0,x_max:777,ha:835,o:"m 777 894 l 458 894 l 458 0 l 319 0 l 319 894 l 0 894 l 0 1013 l 777 1013 l 777 894 "},"Φ":{x_min:0,x_max:915,ha:997,o:"m 527 0 l 389 0 l 389 122 q 110 231 220 122 q 0 509 0 340 q 110 785 0 677 q 389 893 220 893 l 389 1013 l 527 1013 l 527 893 q 804 786 693 893 q 915 509 915 679 q 805 231 915 341 q 527 122 696 122 l 527 0 m 527 226 q 712 310 641 226 q 779 507 779 389 q 712 705 779 627 q 527 787 641 787 l 527 226 m 389 226 l 389 787 q 205 698 275 775 q 136 505 136 620 q 206 308 136 391 q 389 226 276 226 "},"⁋":{x_min:0,x_max:0,ha:694},j:{x_min:-77.78125,x_max:167,ha:349,o:"m 167 871 l 42 871 l 42 1013 l 167 1013 l 167 871 m 167 -80 q 121 -231 167 -184 q -26 -278 76 -278 l -77 -278 l -77 -164 l -41 -164 q 26 -143 11 -164 q 42 -65 42 -122 l 42 737 l 167 737 l 167 -80 "},"Σ":{x_min:0,x_max:756.953125,ha:819,o:"m 756 0 l 0 0 l 0 107 l 395 523 l 22 904 l 22 1013 l 745 1013 l 745 889 l 209 889 l 566 523 l 187 125 l 756 125 l 756 0 "},"›":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},"<":{x_min:17.984375,x_max:773.609375,ha:792,o:"m 773 40 l 18 376 l 17 465 l 773 799 l 773 692 l 159 420 l 773 149 l 773 40 "},"£":{x_min:0,x_max:704.484375,ha:801,o:"m 704 41 q 623 -10 664 5 q 543 -26 583 -26 q 359 15 501 -26 q 243 36 288 36 q 158 23 197 36 q 73 -21 119 10 l 6 76 q 125 195 90 150 q 175 331 175 262 q 147 443 175 383 l 0 443 l 0 512 l 108 512 q 43 734 43 623 q 120 929 43 854 q 358 1010 204 1010 q 579 936 487 1010 q 678 729 678 857 l 678 684 l 552 684 q 504 838 552 780 q 362 896 457 896 q 216 852 263 896 q 176 747 176 815 q 199 627 176 697 q 248 512 217 574 l 468 512 l 468 443 l 279 443 q 297 356 297 398 q 230 194 297 279 q 153 107 211 170 q 227 133 190 125 q 293 142 264 142 q 410 119 339 142 q 516 96 482 96 q 579 105 550 96 q 648 142 608 115 l 704 41 "},t:{x_min:0,x_max:367,ha:458,o:"m 367 0 q 312 -5 339 -2 q 262 -8 284 -8 q 145 28 183 -8 q 108 143 108 64 l 108 638 l 0 638 l 0 738 l 108 738 l 108 944 l 232 944 l 232 738 l 367 738 l 367 638 l 232 638 l 232 185 q 248 121 232 140 q 307 102 264 102 q 345 104 330 102 q 367 107 360 107 l 367 0 "},"¬":{x_min:0,x_max:706,ha:803,o:"m 706 411 l 706 158 l 630 158 l 630 335 l 0 335 l 0 411 l 706 411 "},"λ":{x_min:0,x_max:750,ha:803,o:"m 750 -7 q 679 -15 716 -15 q 538 59 591 -15 q 466 214 512 97 l 336 551 l 126 0 l 0 0 l 270 705 q 223 837 247 770 q 116 899 190 899 q 90 898 100 899 l 90 1004 q 152 1011 125 1011 q 298 938 244 1011 q 373 783 326 901 l 605 192 q 649 115 629 136 q 716 95 669 95 l 736 95 q 750 97 745 97 l 750 -7 "},W:{x_min:0,x_max:1263.890625,ha:1351,o:"m 1263 1013 l 995 0 l 859 0 l 627 837 l 405 0 l 265 0 l 0 1013 l 136 1013 l 342 202 l 556 1013 l 701 1013 l 921 207 l 1133 1012 l 1263 1013 "},">":{x_min:18.0625,x_max:774,ha:792,o:"m 774 376 l 18 40 l 18 149 l 631 421 l 18 692 l 18 799 l 774 465 l 774 376 "},v:{x_min:0,x_max:675.15625,ha:761,o:"m 675 738 l 404 0 l 272 0 l 0 738 l 133 737 l 340 147 l 541 737 l 675 738 "},"τ":{x_min:0.28125,x_max:644.5,ha:703,o:"m 644 628 l 382 628 l 382 179 q 388 120 382 137 q 436 91 401 91 q 474 94 447 91 q 504 97 501 97 l 504 0 q 454 -9 482 -5 q 401 -14 426 -14 q 278 67 308 -14 q 260 233 260 118 l 260 628 l 0 628 l 0 739 l 644 739 l 644 628 "},"ξ":{x_min:0,x_max:624.9375,ha:699,o:"m 624 -37 q 608 -153 624 -96 q 563 -278 593 -211 l 454 -278 q 491 -183 486 -200 q 511 -83 511 -126 q 484 -23 511 -44 q 370 1 452 1 q 323 0 354 1 q 283 -1 293 -1 q 84 76 169 -1 q 0 266 0 154 q 56 431 0 358 q 197 538 108 498 q 94 613 134 562 q 54 730 54 665 q 77 823 54 780 q 143 901 101 867 l 27 901 l 27 1012 l 576 1012 l 576 901 l 380 901 q 244 863 303 901 q 178 745 178 820 q 312 600 178 636 q 532 582 380 582 l 532 479 q 276 455 361 479 q 118 281 118 410 q 165 173 118 217 q 274 120 208 133 q 494 101 384 110 q 624 -37 624 76 "},"&":{x_min:-3,x_max:894.25,ha:992,o:"m 894 0 l 725 0 l 624 123 q 471 0 553 40 q 306 -41 390 -41 q 168 -7 231 -41 q 62 92 105 26 q 14 187 31 139 q -3 276 -3 235 q 55 433 -3 358 q 248 581 114 508 q 170 689 196 640 q 137 817 137 751 q 214 985 137 922 q 384 1041 284 1041 q 548 988 483 1041 q 622 824 622 928 q 563 666 622 739 q 431 556 516 608 l 621 326 q 649 407 639 361 q 663 493 653 426 l 781 493 q 703 229 781 352 l 894 0 m 504 818 q 468 908 504 877 q 384 940 433 940 q 293 907 331 940 q 255 818 255 875 q 289 714 255 767 q 363 628 313 678 q 477 729 446 682 q 504 818 504 771 m 556 209 l 314 499 q 179 395 223 449 q 135 283 135 341 q 146 222 135 253 q 183 158 158 192 q 333 80 241 80 q 556 209 448 80 "},"Λ":{x_min:0,x_max:862.5,ha:942,o:"m 862 0 l 719 0 l 426 847 l 143 0 l 0 0 l 356 1013 l 501 1013 l 862 0 "},I:{x_min:41,x_max:180,ha:293,o:"m 180 0 l 41 0 l 41 1013 l 180 1013 l 180 0 "},G:{x_min:0,x_max:921,ha:1011,o:"m 921 0 l 832 0 l 801 136 q 655 15 741 58 q 470 -28 568 -28 q 126 133 259 -28 q 0 499 0 284 q 125 881 0 731 q 486 1043 259 1043 q 763 957 647 1043 q 905 709 890 864 l 772 709 q 668 866 747 807 q 486 926 589 926 q 228 795 322 926 q 142 507 142 677 q 228 224 142 342 q 483 94 323 94 q 712 195 625 94 q 796 435 796 291 l 477 435 l 477 549 l 921 549 l 921 0 "},"ΰ":{x_min:0,x_max:617,ha:725,o:"m 524 800 l 414 800 l 414 925 l 524 925 l 524 800 m 183 800 l 73 800 l 73 925 l 183 925 l 183 800 m 617 352 q 540 93 617 199 q 308 -24 455 -24 q 76 93 161 -24 q 0 352 0 199 l 0 738 l 126 738 l 126 354 q 169 185 126 257 q 312 98 220 98 q 451 185 402 98 q 492 354 492 257 l 492 738 l 617 738 l 617 352 m 489 1040 l 300 819 l 216 819 l 351 1040 l 489 1040 "},"`":{x_min:0,x_max:138.890625,ha:236,o:"m 138 699 l 0 699 l 0 861 q 36 974 0 929 q 138 1041 72 1020 l 138 977 q 82 931 95 969 q 69 839 69 893 l 138 839 l 138 699 "},"·":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 "},"Υ":{x_min:0.328125,x_max:819.515625,ha:889,o:"m 819 1013 l 482 416 l 482 0 l 342 0 l 342 416 l 0 1013 l 140 1013 l 411 533 l 679 1013 l 819 1013 "},r:{x_min:0,x_max:355.5625,ha:432,o:"m 355 621 l 343 621 q 179 569 236 621 q 122 411 122 518 l 122 0 l 0 0 l 0 737 l 117 737 l 117 604 q 204 719 146 686 q 355 753 262 753 l 355 621 "},x:{x_min:0,x_max:675,ha:764,o:"m 675 0 l 525 0 l 331 286 l 144 0 l 0 0 l 256 379 l 12 738 l 157 737 l 336 473 l 516 738 l 661 738 l 412 380 l 675 0 "},"μ":{x_min:0,x_max:696.609375,ha:747,o:"m 696 -4 q 628 -14 657 -14 q 498 97 513 -14 q 422 8 470 41 q 313 -24 374 -24 q 207 3 258 -24 q 120 80 157 31 l 120 -278 l 0 -278 l 0 738 l 124 738 l 124 343 q 165 172 124 246 q 308 82 216 82 q 451 177 402 82 q 492 358 492 254 l 492 738 l 616 738 l 616 214 q 623 136 616 160 q 673 92 636 92 q 696 95 684 92 l 696 -4 "},h:{x_min:0,x_max:615,ha:724,o:"m 615 472 l 615 0 l 490 0 l 490 454 q 456 590 490 535 q 338 654 416 654 q 186 588 251 654 q 122 436 122 522 l 122 0 l 0 0 l 0 1013 l 122 1013 l 122 633 q 218 727 149 694 q 362 760 287 760 q 552 676 484 760 q 615 472 615 600 "},".":{x_min:0,x_max:142,ha:239,o:"m 142 0 l 0 0 l 0 151 l 142 151 l 142 0 "},"φ":{x_min:-2,x_max:878,ha:974,o:"m 496 -279 l 378 -279 l 378 -17 q 101 88 204 -17 q -2 367 -2 194 q 68 626 -2 510 q 283 758 151 758 l 283 646 q 167 537 209 626 q 133 373 133 462 q 192 177 133 254 q 378 93 259 93 l 378 758 q 445 764 426 763 q 476 765 464 765 q 765 659 653 765 q 878 377 878 553 q 771 96 878 209 q 496 -17 665 -17 l 496 -279 m 496 93 l 514 93 q 687 183 623 93 q 746 380 746 265 q 691 569 746 491 q 522 658 629 658 l 496 656 l 496 93 "},";":{x_min:0,x_max:142,ha:239,o:"m 142 585 l 0 585 l 0 738 l 142 738 l 142 585 m 142 -12 q 105 -132 142 -82 q 0 -206 68 -182 l 0 -138 q 58 -82 43 -123 q 68 0 68 -56 l 0 0 l 0 151 l 142 151 l 142 -12 "},f:{x_min:0,x_max:378,ha:472,o:"m 378 638 l 246 638 l 246 0 l 121 0 l 121 638 l 0 638 l 0 738 l 121 738 q 137 935 121 887 q 290 1028 171 1028 q 320 1027 305 1028 q 378 1021 334 1026 l 378 908 q 323 918 346 918 q 257 870 273 918 q 246 780 246 840 l 246 738 l 378 738 l 378 638 "},"“":{x_min:1,x_max:348.21875,ha:454,o:"m 140 670 l 1 670 l 1 830 q 37 943 1 897 q 140 1011 74 990 l 140 947 q 82 900 97 940 q 68 810 68 861 l 140 810 l 140 670 m 348 670 l 209 670 l 209 830 q 245 943 209 897 q 348 1011 282 990 l 348 947 q 290 900 305 940 q 276 810 276 861 l 348 810 l 348 670 "},A:{x_min:0.03125,x_max:906.953125,ha:1008,o:"m 906 0 l 756 0 l 648 303 l 251 303 l 142 0 l 0 0 l 376 1013 l 529 1013 l 906 0 m 610 421 l 452 867 l 293 421 l 610 421 "},"‘":{x_min:1,x_max:139.890625,ha:236,o:"m 139 670 l 1 670 l 1 830 q 37 943 1 897 q 139 1011 74 990 l 139 947 q 82 900 97 940 q 68 810 68 861 l 139 810 l 139 670 "},"ϊ":{x_min:-70,x_max:283,ha:361,o:"m 283 800 l 173 800 l 173 925 l 283 925 l 283 800 m 40 800 l -70 800 l -70 925 l 40 925 l 40 800 m 283 3 q 232 -10 257 -5 q 181 -15 206 -15 q 84 26 118 -15 q 41 200 41 79 l 41 737 l 166 737 l 167 215 q 171 141 167 157 q 225 101 182 101 q 247 103 238 101 q 283 112 256 104 l 283 3 "},"π":{x_min:-0.21875,x_max:773.21875,ha:857,o:"m 773 -7 l 707 -11 q 575 40 607 -11 q 552 174 552 77 l 552 226 l 552 626 l 222 626 l 222 0 l 97 0 l 97 626 l 0 626 l 0 737 l 773 737 l 773 626 l 676 626 l 676 171 q 695 103 676 117 q 773 90 714 90 l 773 -7 "},"ά":{x_min:0,x_max:765.5625,ha:809,o:"m 765 -4 q 698 -14 726 -14 q 564 97 586 -14 q 466 7 525 40 q 337 -26 407 -26 q 88 98 186 -26 q 0 369 0 212 q 88 637 0 525 q 337 760 184 760 q 465 727 407 760 q 563 637 524 695 l 563 738 l 685 738 l 685 222 q 693 141 685 168 q 748 94 708 94 q 765 95 760 94 l 765 -4 m 584 371 q 531 562 584 485 q 360 653 470 653 q 192 566 254 653 q 135 379 135 489 q 186 181 135 261 q 358 84 247 84 q 528 176 465 84 q 584 371 584 260 m 604 1040 l 415 819 l 332 819 l 466 1040 l 604 1040 "},O:{x_min:0,x_max:958,ha:1057,o:"m 485 1041 q 834 882 702 1041 q 958 512 958 734 q 834 136 958 287 q 481 -26 702 -26 q 126 130 261 -26 q 0 504 0 279 q 127 880 0 728 q 485 1041 263 1041 m 480 98 q 731 225 638 98 q 815 504 815 340 q 733 783 815 669 q 480 912 640 912 q 226 784 321 912 q 142 504 142 670 q 226 224 142 339 q 480 98 319 98 "},n:{x_min:0,x_max:615,ha:724,o:"m 615 463 l 615 0 l 490 0 l 490 454 q 453 592 490 537 q 331 656 410 656 q 178 585 240 656 q 117 421 117 514 l 117 0 l 0 0 l 0 738 l 117 738 l 117 630 q 218 728 150 693 q 359 764 286 764 q 552 675 484 764 q 615 463 615 593 "},l:{x_min:41,x_max:166,ha:279,o:"m 166 0 l 41 0 l 41 1013 l 166 1013 l 166 0 "},"¤":{x_min:40.09375,x_max:728.796875,ha:825,o:"m 728 304 l 649 224 l 512 363 q 383 331 458 331 q 256 363 310 331 l 119 224 l 40 304 l 177 441 q 150 553 150 493 q 184 673 150 621 l 40 818 l 119 898 l 267 749 q 321 766 291 759 q 384 773 351 773 q 447 766 417 773 q 501 749 477 759 l 649 898 l 728 818 l 585 675 q 612 618 604 648 q 621 553 621 587 q 591 441 621 491 l 728 304 m 384 682 q 280 643 318 682 q 243 551 243 604 q 279 461 243 499 q 383 423 316 423 q 487 461 449 423 q 525 553 525 500 q 490 641 525 605 q 384 682 451 682 "},"κ":{x_min:0,x_max:632.328125,ha:679,o:"m 632 0 l 482 0 l 225 384 l 124 288 l 124 0 l 0 0 l 0 738 l 124 738 l 124 446 l 433 738 l 596 738 l 312 466 l 632 0 "},p:{x_min:0,x_max:685,ha:786,o:"m 685 364 q 598 96 685 205 q 350 -23 504 -23 q 121 89 205 -23 l 121 -278 l 0 -278 l 0 738 l 121 738 l 121 633 q 220 726 159 691 q 351 761 280 761 q 598 636 504 761 q 685 364 685 522 m 557 371 q 501 560 557 481 q 330 651 437 651 q 162 559 223 651 q 108 366 108 479 q 162 177 108 254 q 333 87 224 87 q 502 178 441 87 q 557 371 557 258 "},"‡":{x_min:0,x_max:777,ha:835,o:"m 458 238 l 458 0 l 319 0 l 319 238 l 0 238 l 0 360 l 319 360 l 319 681 l 0 683 l 0 804 l 319 804 l 319 1015 l 458 1013 l 458 804 l 777 804 l 777 683 l 458 683 l 458 360 l 777 360 l 777 238 l 458 238 "},"ψ":{x_min:0,x_max:808,ha:907,o:"m 465 -278 l 341 -278 l 341 -15 q 87 102 180 -15 q 0 378 0 210 l 0 739 l 133 739 l 133 379 q 182 195 133 275 q 341 98 242 98 l 341 922 l 465 922 l 465 98 q 623 195 563 98 q 675 382 675 278 l 675 742 l 808 742 l 808 381 q 720 104 808 213 q 466 -13 627 -13 l 465 -278 "},"η":{x_min:0.78125,x_max:697,ha:810,o:"m 697 -278 l 572 -278 l 572 454 q 540 587 572 536 q 425 650 501 650 q 271 579 337 650 q 206 420 206 509 l 206 0 l 81 0 l 81 489 q 73 588 81 562 q 0 644 56 644 l 0 741 q 68 755 38 755 q 158 720 124 755 q 200 630 193 686 q 297 726 234 692 q 434 761 359 761 q 620 692 544 761 q 697 516 697 624 l 697 -278 "}}; - // eslint-disable-next-line - const cssFontWeight='normal', ascender=1189, underlinePosition=-100, cssFontStyle='normal', boundingBox={yMin:-334,xMin:-111,yMax:1189,xMax:1672}, resolution = 1000, original_font_information={postscript_name:"Helvetiker-Regular",version_string:"Version 1.00 2004 initial release",vendor_url:"http://www.magenta.gr/",full_font_name:"Helvetiker",font_family_name:"Helvetiker",copyright:"Copyright (c) Μagenta ltd, 2004",description:"",trademark:"",designer:"",designer_url:"",unique_font_identifier:"Μagenta ltd:Helvetiker:22-10-104",license_url:"http://www.ellak.gr/fonts/MgOpen/license.html",license_description:"Copyright (c) 2004 by MAGENTA Ltd. All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: \r\n\r\nThe above copyright and this permission notice shall be included in all copies of one or more of the Font Software typefaces.\r\n\r\nThe Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing the word \"MgOpen\", or if the modifications are accepted for inclusion in the Font Software itself by the each appointed Administrator.\r\n\r\nThis License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the \"MgOpen\" name.\r\n\r\nThe Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. \r\n\r\nTHE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL MAGENTA OR PERSONS OR BODIES IN CHARGE OF ADMINISTRATION AND MAINTENANCE OF THE FONT SOFTWARE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.",manufacturer_name:"Μagenta ltd",font_sub_family_name:"Regular"}, descender = -334, familyName = 'Helvetiker', lineHeight = 1522, underlineThickness = 50, helvetiker_regular_typeface = {glyphs, cssFontWeight, ascender, underlinePosition, cssFontStyle, boundingBox, resolution,original_font_information, descender, familyName, lineHeight, underlineThickness}; - - // segment in clockwise - holes, otherwise - solid - // provide infinity symbol - glyphs['\u221E'] = { x_min: 100, x_max: 700, ha: 800, o: 'm 150 200 l 350 300 l 150 400 l 150 200 m 450 300 l 650 200 l 650 400 l 450 300 m 100 100 l 100 500 l 400 350 l 700 500 l 700 100 l 400 250 l 100 100 ' }; - - _hfont = new THREE.Font({ - ascender, boundingBox, cssFontStyle, cssFontWeight, default: helvetiker_regular_typeface, descender, familyName, glyphs, - lineHeight, original_font_information, resolution, underlinePosition, underlineThickness - }); - - return _hfont; -} - -/** @summary Create three.js Color instance, handles optional opacity - * @private */ -function getMaterialArgs(color, args) { - if (!args || !isObject(args)) - args = {}; - - if (isStr(color) && (((color[0] === '#') && (color.length === 9)) || (color.indexOf('rgba') >= 0))) { - const col = d3_color(color); - args.color = new THREE.Color(col.r / 255, col.g / 255, col.b / 255); - args.opacity = col.opacity ?? 1; - args.transparent = args.opacity < 1; - } else - args.color = new THREE.Color(color); - return args; -} - -function createSVGRenderer(as_is, precision, doc) { - if (as_is) { - if (doc !== undefined) - globalThis.document = doc; - const rndr = new THREE.SVGRenderer(); - rndr.setPrecision(precision); - return rndr; - } - - const excl_style1 = ';stroke-opacity:1;stroke-width:1;stroke-linecap:round', - excl_style2 = ';fill-opacity:1'; - /* eslint-disable-next-line one-var */ - const doc_wrapper = { - svg_attr: {}, - svg_style: {}, - path_attr: {}, - accPath: '', - createElementNS(ns, kind) { - if (kind === 'path') { - return { - _wrapper: this, - setAttribute(name, value) { - // cut useless fill-opacity:1 at the end of many SVG attributes - if ((name === 'style') && value) { - const pos1 = value.indexOf(excl_style1); - if ((pos1 >= 0) && (pos1 === value.length - excl_style1.length)) - value = value.slice(0, value.length - excl_style1.length); - const pos2 = value.indexOf(excl_style2); - if ((pos2 >= 0) && (pos2 === value.length - excl_style2.length)) - value = value.slice(0, value.length - excl_style2.length); - } - this._wrapper.path_attr[name] = value; - } - }; - } - - if (kind !== 'svg') { - console.error(`not supported element for SVGRenderer ${kind}`); - return null; - } - - return { - _wrapper: this, - childNodes: [], // may be accessed - make dummy - style: this.svg_style, // for background color - setAttribute(name, value) { - this._wrapper.svg_attr[name] = value; - }, - appendChild(/* node */) { - this._wrapper.accPath += ``; - this._wrapper.path_attr = {}; - }, - removeChild(/* node */) { - this.childNodes = []; - } - }; - } - }; - - let originalDocument; - - if (isNodeJs()) { - originalDocument = globalThis.document; - globalThis.document = doc_wrapper; - } - - const rndr = new THREE.SVGRenderer(); - - if (isNodeJs()) - globalThis.document = originalDocument; - - rndr.doc_wrapper = doc_wrapper; // use it to get final SVG code - - rndr.originalRender = rndr.render; - - rndr.render = function(scene, camera) { - const _doc = globalThis.document; - if (isNodeJs()) - globalThis.document = this.doc_wrapper; - - this.originalRender(scene, camera); - - if (isNodeJs()) - globalThis.document = _doc; - }; - - rndr.clearHTML = function() { - this.doc_wrapper.accPath = ''; - }; - - rndr.makeOuterHTML = function() { - const wrap = this.doc_wrapper, - _textSizeAttr = `viewBox="${wrap.svg_attr.viewBox}" width="${wrap.svg_attr.width}" height="${wrap.svg_attr.height}"`, - _textClearAttr = wrap.svg_style.backgroundColor ? ` style="background:${wrap.svg_style.backgroundColor}"` : ''; - - return `${wrap.accPath}`; - }; - - rndr.fillTargetSVG = function(svg) { - if (isNodeJs()) { - const wrap = this.doc_wrapper; - - svg.setAttribute('viewBox', wrap.svg_attr.viewBox); - svg.setAttribute('width', wrap.svg_attr.width); - svg.setAttribute('height', wrap.svg_attr.height); - svg.style.background = wrap.svg_style.backgroundColor || ''; - - svg.innerHTML = wrap.accPath; - } else { - const src = this.domElement; - - svg.setAttribute('viewBox', src.getAttribute('viewBox')); - svg.setAttribute('width', src.getAttribute('width')); - svg.setAttribute('height', src.getAttribute('height')); - svg.style.background = src.style.backgroundColor; - - while (src.firstChild) { - const elem = src.firstChild; - src.removeChild(elem); - svg.appendChild(elem); - } - } - }; - - rndr.setPrecision(precision); - - return rndr; -} - - -/** @summary Define rendering kind which will be used for rendering of 3D elements - * @param {value} [render3d] - pre-configured value, will be used if applicable - * @param {value} [is_batch] - is batch mode is configured - * @return {value} - rendering kind, see constants.Render3D - * @private */ -function getRender3DKind(render3d, is_batch) { - if (is_batch === undefined) - is_batch = isBatchMode(); - - if (!render3d) - render3d = is_batch ? settings.Render3DBatch : settings.Render3D; - const rc = constants.Render3D; - - if (render3d === rc.Default) - render3d = is_batch ? rc.WebGLImage : rc.WebGL; - if (is_batch && (render3d === rc.WebGL)) - render3d = rc.WebGLImage; - - return render3d; -} - -const Handling3DDrawings = { - - /** @summary Access current 3d mode - * @param {string} [new_value] - when specified, set new 3d mode - * @return current value - * @private */ - access3dKind(new_value) { - const svg = this.getPadPainter()?.getPadSvg(); - if (!svg || svg.empty()) - return -1; - - // returns kind of currently created 3d canvas - const kind = svg.property('can3d'); - if (new_value !== undefined) - svg.property('can3d', new_value); - return ((kind === null) || (kind === undefined)) ? -1 : kind; - }, - - /** @summary Returns size which available for 3D drawing. - * @desc One uses frame sizes for the 3D drawing - like TH2/TH3 objects - * @private */ - getSizeFor3d(can3d /* , render3d */) { - if (can3d === undefined) { - // analyze which render/embed mode can be used - can3d = getRender3DKind(); - // all non-webgl elements can be embedded into SVG as is - if (can3d !== constants.Render3D.WebGL) - can3d = constants.Embed3D.EmbedSVG; - else if (settings.Embed3D !== constants.Embed3D.Default) - can3d = settings.Embed3D; - else if (browser.isFirefox) - can3d = constants.Embed3D.Embed; - else if (browser.chromeVersion > 95) - // version 96 works partially, 97 works fine - can3d = constants.Embed3D.Embed; - else - can3d = constants.Embed3D.Overlay; - } - - const pp = this.getPadPainter(), - pad = pp?.getPadSvg(), - clname = 'draw3d_' + (pp?.getPadName() || 'canvas'); - - if (!pad || pad.empty()) { - // this is a case when object drawn without canvas - - const rect = getElementRect(this.selectDom()); - if ((rect.height < 10) && (rect.width > 10)) { - rect.height = Math.round(0.66 * rect.width); - this.selectDom().style('height', rect.height + 'px'); - } - rect.x = rect.y = 0; - rect.clname = clname; - rect.can3d = -1; - return rect; - } - - const fp = this.getFramePainter(); - let size; - - if (fp?.mode3d && (can3d > 0)) - size = fp.getFrameRect(); - else { - let elem = (can3d > 0) ? pad : this.getCanvSvg(); - size = { x: 0, y: 0, width: elem.property('draw_width'), height: elem.property('draw_height') }; - if (Number.isNaN(size.width) || Number.isNaN(size.height)) { - size.width = pp.getPadWidth(); - size.height = pp.getPadHeight(); - } else if (fp && !fp.mode3d) { - elem = pp.getFrameSvg(); - size.x = elem.property('draw_x'); - size.y = elem.property('draw_y'); - } - } - - size.clname = clname; - size.can3d = can3d; - - const rect = pp?.getPadRect(); - if (rect) { - // while 3D canvas uses area also for the axis labels, extend area relative to normal frame - const dx = Math.round(size.width * 0.07), dy = Math.round(size.height * 0.05); - - size.x = Math.max(0, size.x - dx); - size.y = Math.max(0, size.y - dy); - size.width = Math.min(size.width + 2 * dx, rect.width - size.x); - size.height = Math.min(size.height + 2 * dy, rect.height - size.y); - } - - if (can3d === constants.Embed3D.Overlay) { - size = getAbsPosInCanvas(pad, size); - const scale = this.getCanvPainter().getPadScale(); - if (scale && scale !== 1) { - size.x /= scale; - size.y /= scale; - size.width /= scale; - size.height /= scale; - } - } - - return size; - }, - - /** @summary Clear all 3D drawings - * @return can3d value - how webgl canvas was placed - * @private */ - clear3dCanvas() { - const can3d = this.access3dKind(null); - if (can3d < 0) { - // remove first child from main element - if it is canvas - const main = this.selectDom().node(); - let chld = main?.firstChild; - - while (chld && !chld.$jsroot) - chld = chld.nextSibling; - - if (chld?.$jsroot === '3d') { - delete chld.painter; - main.removeChild(chld); - } - return can3d; - } - - const size = this.getSizeFor3d(can3d); - if (size.can3d === 0) { - d3_select(this.getCanvSvg().node().nextSibling).remove(); // remove html5 canvas - this.getCanvSvg().style('display', null); // show SVG canvas - } else { - const pp = this.getPadPainter(); - if (!pp || pp.getPadSvg().empty()) - return; - - this.apply3dSize(size).remove(); - - pp.getFrameSvg().style('display', null); // clear display property - } - return can3d; - }, - - /** @summary Add 3D canvas - * @private */ - add3dCanvas(size, canv, webgl) { - if (!canv || (size.can3d < -1)) - return; - - if (size.can3d === -1) { - // case when 3D object drawn without canvas - - const main = this.selectDom().node(); - if (main) { - main.appendChild(canv); - canv.painter = this; - canv.$jsroot = '3d'; // mark canvas as added by jsroot - } - - return; - } - - if ((size.can3d > 0) && !webgl) - size.can3d = constants.Embed3D.EmbedSVG; - - this.access3dKind(size.can3d); - - if (size.can3d === 0) { - this.getCanvSvg().style('display', 'none'); // hide SVG canvas - - this.getCanvSvg().node().parentNode.appendChild(canv); // add directly - } else { - const pp = this.getPadPainter(); - if (!pp || pp.getPadSvg().empty()) - return; - - // first hide normal frame - pp.getFrameSvg().style('display', 'none'); - - const elem = this.apply3dSize(size); - elem.attr('title', '').node().appendChild(canv); - } - }, - - /** @summary Apply size to 3D elements - * @private */ - apply3dSize(size, onlyget) { - if (size.can3d < 0) - return d3_select(null); - - let elem; - - if (size.can3d > 1) { - const pp = this.getPadPainter(); - - elem = pp.getLayerSvg(size.clname); - if (onlyget) - return elem; - - const svg = pp.getPadSvg(); - - if (size.can3d === constants.Embed3D.EmbedSVG) { - // this is SVG mode or image mode - just create group to hold element - - if (elem.empty()) - elem = svg.insert('g', '.primitives_layer').attr('class', size.clname); - - makeTranslate(elem, size.x, size.y); - } else { - if (elem.empty()) - elem = svg.insert('foreignObject', '.primitives_layer').attr('class', size.clname); - - elem.attr('x', size.x) - .attr('y', size.y) - .attr('width', size.width) - .attr('height', size.height) - .attr('viewBox', `0 0 ${size.width} ${size.height}`) - .attr('preserveAspectRatio', 'xMidYMid'); - } - } else { - let prnt = this.getCanvSvg().node().parentNode; - - elem = d3_select(prnt).select('.' + size.clname); - if (onlyget) - return elem; - - // force redraw by resize - this.getCanvSvg().property('redraw_by_resize', true); - - if (elem.empty()) { - elem = d3_select(prnt).append('div').attr('class', size.clname) - .style('user-select', 'none'); - } - - // our position inside canvas, but to set 'absolute' position we should use - // canvas element offset relative to first parent with non-static position - // now try to use getBoundingClientRect - it should be more precise - - const pos0 = prnt.getBoundingClientRect(), doc = getDocument(); - - while (prnt) { - if (prnt === doc) { - prnt = null; - break; - } - try { - if (getComputedStyle(prnt).position !== 'static') - break; - } catch { - break; - } - prnt = prnt.parentNode; - } - - const pos1 = prnt?.getBoundingClientRect() ?? { top: 0, left: 0 }, - offx = Math.round(pos0.left - pos1.left), - offy = Math.round(pos0.top - pos1.top); - - elem.style('position', 'absolute').style('left', (size.x + offx) + 'px').style('top', (size.y + offy) + 'px').style('width', size.width + 'px').style('height', size.height + 'px'); - } - - return elem; - } - -}; // Handling3DDrawings - - -/** @summary Assigns method to handle 3D drawings inside SVG - * @private */ -function assign3DHandler(painter) { - Object.assign(painter, Handling3DDrawings); -} - - -/** @summary Creates renderer for the 3D drawings - * @param {value} width - rendering width - * @param {value} height - rendering height - * @param {value} render3d - render type, see {@link constants.Render3D} - * @param {object} args - different arguments for creating 3D renderer - * @return {Promise} with renderer object - * @private */ -async function createRender3D(width, height, render3d, args) { - const rc = constants.Render3D, doc = getDocument(); - - render3d = getRender3DKind(render3d); - - if (!args) - args = { antialias: true, alpha: true }; - - let promise; - - if (render3d === rc.None) - promise = Promise.resolve(null); - else if (render3d === rc.SVG) { - // SVG rendering - const r = createSVGRenderer(false, 0, doc); - r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); - promise = Promise.resolve(r); - } else if (isNodeJs()) { - // try to use WebGL inside node.js - need to create headless context - promise = import('canvas').then(node_canvas => { - args.canvas = node_canvas.default.createCanvas(width, height); - args.canvas.addEventListener = () => {}; // dummy - args.canvas.removeEventListener = () => {}; // dummy - args.canvas.style = {}; - return internals._node_gl || import('gl'); - }).then(node_gl => { - internals._node_gl = node_gl; - const gl = node_gl?.default(width, height, { preserveDrawingBuffer: true }); - if (!gl) - throw Error('Fail to create headless-gl'); - args.context = gl; - gl.canvas = args.canvas; - - const r = new THREE.WebGLRenderer(args); - r.jsroot_output = new THREE.WebGLRenderTarget(width, height); - r.setRenderTarget(r.jsroot_output); - r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); - return r; - }).catch(() => { - console.log('gl module is not available - fallback to SVGRenderer'); - render3d = rc.SVG; - const r = createSVGRenderer(false, 0, doc); - r.jsroot_dom = doc.createElementNS(nsSVG, 'svg'); - return r; - }); - } else if (render3d === rc.WebGL) { - // interactive WebGL Rendering - promise = Promise.resolve(new THREE.WebGLRenderer(args)); - } else { - // rendering with WebGL directly into svg image - const r = new THREE.WebGLRenderer(args); - r.jsroot_dom = doc.createElementNS(nsSVG, 'image'); - promise = Promise.resolve(r); - } - - return promise.then(renderer => { - if (!renderer) - return renderer; - - if (!renderer.jsroot_dom) - renderer.jsroot_dom = renderer.domElement; - else - renderer.jsroot_custom_dom = true; - - // res.renderer.setClearColor('#000000', 1); - // res.renderer.setClearColor(0x0, 0); - renderer.jsroot_render3d = render3d; - - // which format used to convert into images - renderer.jsroot_image_format = 'png'; - - renderer.originalSetSize = renderer.setSize; - - // apply size to dom element - renderer.setSize = function(w, h, updateStyle) { - if (this.jsroot_custom_dom) { - this.jsroot_dom.setAttribute('width', w); - this.jsroot_dom.setAttribute('height', h); - } - - this.originalSetSize(w, h, updateStyle); - }; - - renderer.setSize(width, height); - - return renderer; - }); -} - - -/** @summary Cleanup created renderer object - * @private */ -function cleanupRender3D(renderer) { - if (!renderer) - return; - - if (isNodeJs()) { - const ctxt = isFunc(renderer.getContext) ? renderer.getContext() : null, - ext = ctxt?.getExtension('STACKGL_destroy_context'); - if (isFunc(ext?.destroy)) - ext.destroy(); - } else { - // suppress warnings in Chrome about lost webgl context, not required in firefox - if (browser.isChrome && isFunc(renderer.forceContextLoss)) - renderer.forceContextLoss(); - - if (isFunc(renderer.dispose)) - renderer.dispose(); - } -} - -/** @summary Cleanup previous renderings before doing next one - * @desc used together with SVG - * @private */ -function beforeRender3D(renderer) { - if (isFunc(renderer.clearHTML)) - renderer.clearHTML(); -} - -/** @summary Post-process result of rendering - * @desc used together with SVG or node.js image rendering - * @private */ -function afterRender3D(renderer) { - const rc = constants.Render3D; - - if (renderer.jsroot_render3d === rc.WebGL) - return; - - if (renderer.jsroot_render3d === rc.SVG) { - // case of SVGRenderer - renderer.fillTargetSVG(renderer.jsroot_dom); - } else if (isNodeJs()) { - // this is WebGL rendering in node.js - const canvas = renderer.domElement, - context = canvas.getContext('2d'), - pixels = new Uint8Array(4 * canvas.width * canvas.height); - - renderer.readRenderTargetPixels(renderer.jsroot_output, 0, 0, canvas.width, canvas.height, pixels); - - // small code to flip Y scale - let indx1 = 0, indx2 = (canvas.height - 1) * 4 * canvas.width, k, d; - while (indx1 < indx2) { - for (k = 0; k < 4 * canvas.width; ++k) { - d = pixels[indx1 + k]; - pixels[indx1 + k] = pixels[indx2 + k]; - pixels[indx2 + k] = d; - } - indx1 += 4 * canvas.width; - indx2 -= 4 * canvas.width; - } - - const imageData = context.createImageData(canvas.width, canvas.height); - imageData.data.set(pixels); - context.putImageData(imageData, 0, 0); - - const format = 'image/' + renderer.jsroot_image_format, - dataUrl = canvas.toDataURL(format); - - renderer.jsroot_dom.setAttribute('href', dataUrl); - } else { - const dataUrl = renderer.domElement.toDataURL('image/' + renderer.jsroot_image_format); - renderer.jsroot_dom.setAttribute('href', dataUrl); - } -} - -// ======================================================================================================== - -/** - * @summary Tooltip handler for 3D drawings - * - * @private - */ - -class TooltipFor3D { - - /** @summary constructor - * @param {object} prnt - parent tooltip object - * @param {object} canvas - canvas for 3D rendering */ - constructor(prnt, canvas) { - this.tt = null; - this.cont = null; - this.lastlbl = ''; - this.parent = prnt || getDocument().body; - this.canvas = canvas; // we need canvas to recalculate mouse events - this.abspos = !prnt; - this.scale = 1; - } - - /** @summary check parent */ - checkParent(prnt) { - if (prnt && (this.parent !== prnt)) { - this.hide(); - this.parent = prnt; - } - } - - /** @summary set scaling factor */ - setScale(v) { - this.scale = v; - } - - /** @summary extract position from event - * @desc can be used to process it later when event is gone */ - extract_pos(e) { - if (isObject(e) && (e.u !== undefined) && (e.l !== undefined)) - return e; - const res = { u: 0, l: 0 }; - if (this.abspos) { - res.l = e.pageX; - res.u = e.pageY; - } else { - res.l = e.offsetX; - res.u = e.offsetY; - } - res.l /= this.scale; - res.u /= this.scale; - return res; - } - - /** @summary Method used to define position of next tooltip - * @desc event is delivered from canvas, - * but position should be calculated relative to the element where tooltip is placed */ - pos(e) { - if (!this.tt) - return; - - const pos = this.extract_pos(e); - if (!this.abspos) { - const rect1 = this.parent.getBoundingClientRect(), - rect2 = this.canvas.getBoundingClientRect(); - - if ((rect1.left !== undefined) && (rect2.left !== undefined)) - pos.l += (rect2.left - rect1.left); - - if ((rect1.top !== undefined) && (rect2.top !== undefined)) - pos.u += rect2.top - rect1.top; - - if (pos.l + this.tt.offsetWidth + 3 >= this.parent.offsetWidth) - pos.l = this.parent.offsetWidth - this.tt.offsetWidth - 3; - - if (pos.u + this.tt.offsetHeight + 15 >= this.parent.offsetHeight) - pos.u = this.parent.offsetHeight - this.tt.offsetHeight - 15; - - // one should find parent with non-static position, - // all absolute coordinates calculated relative to such node - let abs_parent = this.parent; - while (abs_parent) { - const style = getComputedStyle(abs_parent); - if (!style || (style.position !== 'static')) - break; - if (!abs_parent.parentNode || (abs_parent.parentNode.nodeType !== 1)) - break; - abs_parent = abs_parent.parentNode; - } - - if (abs_parent && (abs_parent !== this.parent)) { - const rect0 = abs_parent.getBoundingClientRect(); - pos.l += (rect1.left - rect0.left); - pos.u += (rect1.top - rect0.top); - } - } - - this.tt.style.top = `${pos.u + 15}px`; - this.tt.style.left = `${pos.l + 3}px`; - } - - /** @summary Show tooltip */ - show(v /* , mouse_pos, status_func */) { - let lines; - if (v && isObject(v) && (v.lines || v.line)) { - if (!v.only_status) - lines = v.line ? [v.line] : v.lines; - } else if (isStr(v)) - lines = [v]; - - const doc = this.parent.ownerDocument; - - if (!lines || !doc) - return this.hide(); - - if (!this.tt) { - this.tt = doc.createElement('div'); - this.tt.setAttribute('style', 'opacity: 1; filter: alpha(opacity=1); position: absolute; display: block; width: auto; overflow: hidden; z-index: 101;'); - this.cont = doc.createElement('div'); - this.cont.setAttribute('style', 'display: block; padding: 5px; margin-left: 5px; font-size: 11px; line-height: 18px; background: #777; color: #fff;'); - this.tt.appendChild(this.cont); - this.parent.appendChild(this.tt); - } - - this.cont.innerText = ''; - lines.forEach(lbl => { - const p = doc.createElement('p'); - p.innerText = lbl; - p.setAttribute('style', 'padding: 0px; margin: 1px;'); - this.cont.appendChild(p); - }); - } - - /** @summary Hide tooltip */ - hide() { - if (this.tt) - this.parent.removeChild(this.tt); - - this.tt = this.cont = null; - } - -} // class TooltipFor3D - -/** @summary Create OrbitControls for painter - * @private */ -function createOrbitControl(painter, camera, scene, renderer, lookat) { - const enable_zoom = settings.Zooming && settings.ZoomMouse, - enable_wheel = settings.Zooming && settings.ZoomWheel, - enable_keys = settings.HandleKeys && !isNodeJs() && (typeof window !== 'undefined'), - enable_select = isFunc(painter.processMouseClick); - - let control = null; - - function control_mousedown(evnt) { - if (!control) - return; - - // function used to hide some events from orbit control and redirect them to zooming rect - if (control.mouse_zoom_mesh) { - evnt.stopImmediatePropagation(); - evnt.stopPropagation(); - return; - } - - // only left-button is considered - if ((evnt.button !== undefined) && (evnt.button !== 0)) - return; - if ((evnt.buttons !== undefined) && (evnt.buttons !== 1)) - return; - - if (control.enable_zoom) { - control.mouse_zoom_mesh = control.detectZoomMesh(evnt); - if (control.mouse_zoom_mesh) { - // just block orbit control - evnt.stopImmediatePropagation(); - evnt.stopPropagation(); - return; - } - } - - if (control.enable_select) - control.mouse_select_pnt = control.getMousePos(evnt, {}); - } - - function control_mouseup(evnt) { - if (!control) - return; - - if (control.mouse_zoom_mesh && control.mouse_zoom_mesh.point2 && control.painter.get3dZoomCoord) { - let kind = control.mouse_zoom_mesh.object.zoom, - pos1 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point, kind), - pos2 = control.painter.get3dZoomCoord(control.mouse_zoom_mesh.point2, kind); - - if (pos1 > pos2) - [pos1, pos2] = [pos2, pos1]; - - if ((kind === 'z') && control.mouse_zoom_mesh.object.use_y_for_z) - kind = 'y'; - - // try to zoom - if ((pos1 < pos2) && control.painter.zoom(kind, pos1, pos2)) - control.mouse_zoom_mesh = null; - } - - // if selection was drawn, it should be removed and picture rendered again - if (control.enable_zoom) - control.removeZoomMesh(); - - if (control.enable_select && control.mouse_select_pnt) { - const pnt = control.getMousePos(evnt, {}), - same_pnt = (pnt.x === control.mouse_select_pnt.x) && (pnt.y === control.mouse_select_pnt.y); - delete control.mouse_select_pnt; - - if (same_pnt) { - const intersects = control.getMouseIntersects(pnt); - control.painter.processMouseClick(pnt, intersects, evnt); - } - } - } - - function render3DFired(_painter) { - if (_painter?.renderer === undefined) - return false; - // when timeout configured, object is prepared for rendering - return _painter.render_tmout !== undefined; - } - - function control_mousewheel(evnt) { - if (!control) - return; - - // try to handle zoom extra - if (render3DFired(control.painter) || control.mouse_zoom_mesh) { - evnt.preventDefault(); - evnt.stopPropagation(); - evnt.stopImmediatePropagation(); - return; // already fired redraw, do not react on the mouse wheel - } - - const intersect = control.detectZoomMesh(evnt); - if (!intersect) - return; - - evnt.preventDefault(); - evnt.stopPropagation(); - evnt.stopImmediatePropagation(); - - if (isFunc(control.painter?.analyzeMouseWheelEvent)) { - let kind = intersect.object.zoom, - position = intersect.point[kind]; - const item = { name: kind, ignore: false }; - - // z changes from 0..2*size_z3d, others -size_x3d..+size_x3d - switch (kind) { - case 'x': - position = (position + control.painter.size_x3d) / 2 / control.painter.size_x3d; - break; - case 'y': - position = (position + control.painter.size_y3d) / 2 / control.painter.size_y3d; - break; - case 'z': - position = position / 2 / control.painter.size_z3d; - break; - } - - control.painter.analyzeMouseWheelEvent(evnt, item, position, false); - - if ((kind === 'z') && intersect.object.use_y_for_z) - kind = 'y'; - - control.painter.zoom(kind, item.min, item.max); - } - } - - // assign own handler before creating OrbitControl - - if (enable_wheel) - renderer.domElement.addEventListener('wheel', control_mousewheel); - - if (enable_zoom || enable_select) { - renderer.domElement.addEventListener('pointerdown', control_mousedown); - renderer.domElement.addEventListener('pointerup', control_mouseup); - } - - control = new THREE.OrbitControls(camera, renderer.domElement); - - control.enableDamping = false; - control.dampingFactor = 1.0; - control.enableZoom = true; - if (enable_keys) - control.listenToKeyEvents(window); - - if (lookat) { - control.target.copy(lookat); - control.target0.copy(lookat); - control.update(); - } - - control.tooltip = new TooltipFor3D(painter.selectDom().node(), renderer.domElement); - - control.painter = painter; - control.camera = camera; - control.scene = scene; - control.renderer = renderer; - control.raycaster = new THREE.Raycaster(); - control.raycaster.params.Line.threshold = 10; - control.raycaster.params.Points.threshold = 5; - control.mouse_zoom_mesh = null; // zoom mesh, currently used in the zooming - control.block_ctxt = false; // require to block context menu command appearing after control ends, required in chrome which inject contextmenu when key released - control.block_mousemove = false; // when true, tooltip or cursor will not react on mouse move - control.cursor_changed = false; - control.control_changed = false; - control.control_active = false; - control.mouse_ctxt = { x: 0, y: 0, on: false }; - control.enable_zoom = enable_zoom; - control.enable_select = enable_select; - control.enable_wheel = enable_wheel; - control.enable_keys = enable_keys; - - control.cleanup = function() { - if (this.enable_wheel) - this.domElement.removeEventListener('wheel', control_mousewheel); - if (this.enable_zoom || this.enable_select) { - this.domElement.removeEventListener('pointerdown', control_mousedown); - this.domElement.removeEventListener('pointerup', control_mouseup); - } - if (this.enable_keys) - this.stopListenToKeyEvents(); - - this.domElement.removeEventListener('click', this.lstn_click); - this.domElement.removeEventListener('dblclick', this.lstn_dblclick); - this.domElement.removeEventListener('contextmenu', this.lstn_contextmenu); - this.domElement.removeEventListener('mousemove', this.lstn_mousemove); - this.domElement.removeEventListener('mouseleave', this.lstn_mouseleave); - - this.dispose(); // this is from OrbitControl itself - - this.tooltip.hide(); - delete this.tooltip; - delete this.painter; - delete this.camera; - delete this.scene; - delete this.renderer; - delete this.raycaster; - delete this.mouse_zoom_mesh; - }; - - control.hideTooltip = function() { - this.tooltip.hide(); - }; - - control.getMousePos = function(evnt, mouse) { - mouse.x = ('offsetX' in evnt) ? evnt.offsetX : evnt.layerX; - mouse.y = ('offsetY' in evnt) ? evnt.offsetY : evnt.layerY; - mouse.clientX = evnt.clientX; - mouse.clientY = evnt.clientY; - return mouse; - }; - - control.getOriginDirectionIntersects = function(origin, direction) { - this.raycaster.set(origin, direction); - let intersects = this.raycaster.intersectObjects(this.scene.children, true); - // painter may want to filter intersects - if (isFunc(this.painter.filterIntersects)) - intersects = this.painter.filterIntersects(intersects); - return intersects; - }; - - control.getMouseIntersects = function(mouse) { - // domElement gives correct coordinate with canvas render, but isn't always right for webgl renderer - if (!this.renderer) - return []; - - const sz = (this.renderer instanceof THREE.SVGRenderer) ? this.renderer.domElement : this.renderer.getSize(new THREE.Vector2()), - pnt = { x: mouse.x / sz.width * 2 - 1, y: -mouse.y / sz.height * 2 + 1 }; - - this.camera.updateMatrix(); - this.camera.updateMatrixWorld(); - this.raycaster.setFromCamera(pnt, this.camera); - let intersects = this.raycaster.intersectObjects(this.scene.children, true); - - // painter may want to filter intersects - if (isFunc(this.painter.filterIntersects)) - intersects = this.painter.filterIntersects(intersects); - - return intersects; - }; - - control.detectZoomMesh = function(evnt) { - const mouse = this.getMousePos(evnt, {}), - intersects = this.getMouseIntersects(mouse); - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) - return intersects[n]; - } - } - - return null; - }; - - control.getInfoAtMousePosition = function(mouse_pos) { - const intersects = this.getMouseIntersects(mouse_pos); - let tip = null, p = null; - - for (let i = 0; i < intersects.length; ++i) { - const obj3d = intersects[i].object; - if (isFunc(obj3d?.tooltip)) { - tip = obj3d.tooltip(intersects[i]); - p = obj3d.tip_painter || obj3d.painter || tip?.$painter; - break; - } - } - - if (tip && p) { - return { - obj: p.getObject(), - name: p.getObject().fName, - bin: tip.bin, cont: tip.value, - binx: tip.ix, biny: tip.iy, binz: tip.iz, - grx: (tip.x1 + tip.x2) / 2, gry: (tip.y1 + tip.y2) / 2, grz: (tip.z1 + tip.z2) / 2 - }; - } - }; - - control.processDblClick = function(evnt) { - // first check if zoom mesh clicked - const zoom_intersect = this.detectZoomMesh(evnt); - if (zoom_intersect && this.painter) { - this.painter.unzoom(zoom_intersect.object.use_y_for_z ? 'y' : zoom_intersect.object.zoom); - return; - } - - // then check if double-click handler assigned - const handler = this.painter?.getFramePainter()?.getDblclickHandler(); - - if (isFunc(handler)) { - const info = this.getInfoAtMousePosition(this.getMousePos(evnt, {})); - if (info) { - handler(info); - return; - } - } - - this.reset(); - }; - - control.changeEvent = function() { - this.mouse_ctxt.on = false; // disable context menu if any changes where done by orbit control - this.painter.render3D(0); - this.control_changed = true; - }; - - control.startEvent = function() { - this.control_active = true; - this.block_ctxt = false; - this.mouse_ctxt.on = false; - - this.tooltip.hide(); - - // do not reset here, problem of events sequence in orbitcontrol - // it issue change/start/stop event when do zooming - // control.control_changed = false; - }; - - control.endEvent = function() { - this.control_active = false; - if (this.mouse_ctxt.on) { - this.mouse_ctxt.on = false; - this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); - } /* else if (this.control_changed) { - // react on camera change when required - } */ - this.control_changed = false; - }; - - control.mainProcessContextMenu = function(evnt) { - evnt.preventDefault(); - this.getMousePos(evnt, this.mouse_ctxt); - if (this.control_active) - this.mouse_ctxt.on = true; - else if (this.block_ctxt) - this.block_ctxt = false; - else - this.contextMenu(this.mouse_ctxt, this.getMouseIntersects(this.mouse_ctxt)); - }; - - control.contextMenu = function(/* pos, intersects */) { - // do nothing, function called when context menu want to be activated - }; - - control.setTooltipEnabled = function(on) { - this.block_mousemove = !on; - if (on === false) { - this.tooltip.hide(); - this.removeZoomMesh(); - } - }; - - control.removeZoomMesh = function() { - if (this.mouse_zoom_mesh?.object.showSelection()) - this.painter.render3D(); - this.mouse_zoom_mesh = null; // in any case clear mesh, enable orbit control again - }; - - control.mainProcessMouseMove = function(evnt) { - if (!this.painter) - return; // protect when cleanup - - if (this.control_active && evnt.buttons && (evnt.buttons & 2)) - this.block_ctxt = true; // if right button in control was active, block next context menu - - if (this.control_active || this.block_mousemove || !isFunc(this.processMouseMove)) - return; - - if (this.mouse_zoom_mesh) { - // when working with zoom mesh, need special handling - - const zoom2 = this.detectZoomMesh(evnt), - pnt2 = (zoom2?.object === this.mouse_zoom_mesh.object) ? zoom2.point : this.mouse_zoom_mesh.object.globalIntersect(this.raycaster); - - if (pnt2) - this.mouse_zoom_mesh.point2 = pnt2; - - if (pnt2 && this.painter.enable_highlight) { - if (this.mouse_zoom_mesh.object.showSelection(this.mouse_zoom_mesh.point, pnt2)) - this.painter.render3D(0); - } - - this.tooltip.hide(); - return; - } - - evnt.preventDefault(); - - // extract mouse position - this.tmout_mouse = this.getMousePos(evnt, {}); - this.tmout_ttpos = this.tooltip?.extract_pos(evnt); - - if (this.tmout_handle) { - clearTimeout(this.tmout_handle); - delete this.tmout_handle; - } - - if (!this.mouse_tmout) - this.delayedProcessMouseMove(); - else - this.tmout_handle = setTimeout(() => this.delayedProcessMouseMove(), this.mouse_tmout); - }; - - control.delayedProcessMouseMove = function() { - // remove handle - allow to trigger new timeout - delete this.tmout_handle; - if (!this.painter) - return; // protect when cleanup - - const mouse = this.tmout_mouse, - intersects = this.getMouseIntersects(mouse), - tip = this.processMouseMove(intersects); - - if (tip) { - let name = '', title = '', info = ''; - const coord = mouse ? mouse.x.toFixed(0) + ',' + mouse.y.toFixed(0) : ''; - if (isStr(tip)) - info = tip; - else { - name = tip.name; - title = tip.title; - if (tip.line) - info = tip.line; - else if (tip.lines) { - info = tip.lines.slice(1).join(' '); - name = tip.lines[0]; - } - } - this.painter.showObjectStatus(name, title, info, coord); - } - - this.cursor_changed = false; - if (tip && this.painter?.isTooltipAllowed()) { - this.tooltip.checkParent(this.painter.selectDom().node()); - this.tooltip.show(tip, mouse); - this.tooltip.pos(this.tmout_ttpos); - } else { - this.tooltip.hide(); - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.zoom && !intersects[n].object.zoom_disabled) - this.cursor_changed = true; - } - } - } - - getDocument().body.style.cursor = this.cursor_changed ? 'pointer' : 'auto'; - }; - - control.mainProcessMouseLeave = function() { - if (!this.painter) - return; // protect when cleanup - - // do not enter main event at all - if (this.tmout_handle) { - clearTimeout(this.tmout_handle); - delete this.tmout_handle; - } - this.tooltip.hide(); - if (isFunc(this.processMouseLeave)) - this.processMouseLeave(); - if (this.cursor_changed) { - getDocument().body.style.cursor = 'auto'; - this.cursor_changed = false; - } - }; - - control.mainProcessDblClick = function(evnt) { - // suppress simple click handler if double click detected - if (this.single_click_tm) { - clearTimeout(this.single_click_tm); - delete this.single_click_tm; - } - this.processDblClick(evnt); - }; - - control.processClick = function(mouse_pos, kind) { - delete this.single_click_tm; - - if (kind === 1) { - const handler = this.painter?.getFramePainter()?.getClickHandler(); - if (isFunc(handler)) { - const info = this.getInfoAtMousePosition(mouse_pos); - if (info) { - handler(info); - return; - } - } - } - - // method assigned in the Eve7 and used for object selection - if ((kind === 2) && isFunc(this.processSingleClick)) { - const intersects = this.getMouseIntersects(mouse_pos); - this.processSingleClick(intersects); - } - - if (kind === 3) { - const intersects = this.getMouseIntersects(mouse_pos); - let objpainter = null; - for (let i = 0; !objpainter && (i < intersects.length); ++i) { - const obj3d = intersects[i].object; - objpainter = obj3d.painter || obj3d.parent?.painter; // check one top level - } - if (objpainter) { - // while axis painter not directly appears in the list of primitives, pad and canvas take from frame - const padp = this.painter?.getPadPainter(), - canvp = this.painter?.getCanvPainter(); - canvp?.producePadEvent('select', padp, objpainter); - } - } - }; - - control.lstn_click = function(evnt) { - // ignore right-mouse click - if (evnt.detail === 2) - return; - - if (this.single_click_tm) { - clearTimeout(this.single_click_tm); - delete this.single_click_tm; - } - - let kind = 0; - if (this.painter?.getFramePainter()?.getClickHandler()) - kind = 1; // user click handler - else if (this.processSingleClick && this.painter?.options?.mouse_click) - kind = 2; // eve7 click handler - else if (this.painter?.getCanvPainter()) - kind = 3; // select event for GED - - // if normal event, set longer timeout waiting if double click not detected - if (kind) - this.single_click_tm = setTimeout(this.processClick.bind(this, this.getMousePos(evnt, {}), kind), 300); - }.bind(control); - - control.addEventListener('change', () => control.changeEvent()); - control.addEventListener('start', () => control.startEvent()); - control.addEventListener('end', () => control.endEvent()); - - control.lstn_contextmenu = evnt => control.mainProcessContextMenu(evnt); - control.lstn_dblclick = evnt => control.mainProcessDblClick(evnt); - control.lstn_mousemove = evnt => control.mainProcessMouseMove(evnt); - control.lstn_mouseleave = () => control.mainProcessMouseLeave(); - - renderer.domElement.addEventListener('click', control.lstn_click); - renderer.domElement.addEventListener('dblclick', control.lstn_dblclick); - renderer.domElement.addEventListener('contextmenu', control.lstn_contextmenu); - renderer.domElement.addEventListener('mousemove', control.lstn_mousemove); - renderer.domElement.addEventListener('mouseleave', control.lstn_mouseleave); - - return control; -} - -/** @summary Method cleanup three.js object as much as possible. - * @desc Simplify JS engine to remove it from memory - * @private */ -function disposeThreejsObject(obj, only_childs) { - if (!obj) - return; - - if (obj.children) { - for (let i = 0; i < obj.children.length; i++) - disposeThreejsObject(obj.children[i]); - } - - if (only_childs) { - obj.children = []; - return; - } - - obj.children = undefined; - - if (obj.geometry) { - obj.geometry.dispose(); - obj.geometry = undefined; - } - if (obj.material) { - if (obj.material.map) { - obj.material.map.dispose(); - obj.material.map = undefined; - } - obj.material.dispose(); - obj.material = undefined; - } - - // cleanup jsroot fields to simplify browser cleanup job - delete obj.painter; - delete obj.bins_index; - delete obj.tooltip; - delete obj.stack; // used in geom painter - delete obj.drawn_highlight; // special highlight object - // used in lego tooltips - delete obj.face_to_bins_index; - delete obj.tip_painter; - delete obj.handle; -} - - -/** @summary Create LineSegments mesh (or only geometry) - * @desc If required, calculates lineDistance attribute for dashed geometries - * @private */ -function createLineSegments(arr, material, index = undefined, only_geometry = false) { - const geom = new THREE.BufferGeometry(); - - geom.setAttribute('position', arr instanceof Float32Array ? new THREE.BufferAttribute(arr, 3) : new THREE.Float32BufferAttribute(arr, 3)); - if (index) - geom.setIndex(new THREE.BufferAttribute(index, 1)); - - if (material.isLineDashedMaterial) { - const v1 = new THREE.Vector3(), - v2 = new THREE.Vector3(); - let d = 0, distances; - - if (index) { - distances = new Float32Array(index.length); - for (let n = 0; n < index.length; n += 2) { - const i1 = index[n], i2 = index[n + 1]; - v1.set(arr[i1], arr[i1 + 1], arr[i1 + 2]); - v2.set(arr[i2], arr[i2 + 1], arr[i2 + 2]); - distances[n] = d; - d += v2.distanceTo(v1); - distances[n + 1] = d; - } - } else { - distances = new Float32Array(arr.length / 3); - for (let n = 0; n < arr.length; n += 6) { - v1.set(arr[n], arr[n + 1], arr[n + 2]); - v2.set(arr[n + 3], arr[n + 4], arr[n + 5]); - distances[n / 3] = d; - d += v2.distanceTo(v1); - distances[n / 3 + 1] = d; - } - } - geom.setAttribute('lineDistance', new THREE.BufferAttribute(distances, 1)); - } - - return only_geometry ? geom : new THREE.LineSegments(geom, material); -} - -/** @summary Help structures for calculating Box mesh - * @private */ -const Box3D = { - Vertices: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 0), - new THREE.Vector3(1, 0, 1), new THREE.Vector3(1, 0, 0), - new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 1, 1), - new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, 1)], - Indexes: [0, 2, 1, 2, 3, 1, 4, 6, 5, 6, 7, 5, 4, 5, 1, 5, 0, 1, - 7, 6, 2, 6, 3, 2, 5, 7, 0, 7, 2, 0, 1, 3, 4, 3, 6, 4], - Normals: [1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1], - Segments: [0, 2, 2, 7, 7, 5, 5, 0, 1, 3, 3, 6, 6, 4, 4, 1, 1, 0, 3, 2, 6, 7, 4, 5], // segments addresses Vertices - Crosses: [0, 7, 2, 5, 0, 3, 1, 2, 7, 3, 2, 6, 5, 6, 4, 7, 5, 1, 0, 4, 3, 4, 1, 6], // addresses Vertices - MeshSegments: undefined -}; - -// these segments address vertices from the mesh, we can use positions from box mesh -Box3D.MeshSegments = (function() { - const arr = new Int32Array(Box3D.Segments.length); - - for (let n = 0; n < arr.length; ++n) { - for (let k = 0; k < Box3D.Indexes.length; ++k) { - if (Box3D.Segments[n] === Box3D.Indexes[k]) { - arr[n] = k; - break; - } - } - } - return arr; -})(); - - -/** - * @summary Abstract interactive control interface for 3D objects - * - * @abstract - * @private - */ - -class InteractiveControl { - - cleanup() {} - extractIndex(/* intersect */) {} - setSelected(/* col, indx */) {} - setHighlight(/* col, indx */) {} - checkHighlightIndex(/* indx */) {} - -} // class InteractiveControl - - -/** - * @summary Special class to control highliht and selection of single points, used in geo painter - * - * @private - */ - -class PointsControl extends InteractiveControl { - - /** @summary constructor - * @param {object} mesh - draw object */ - constructor(mesh) { - super(); - this.mesh = mesh; - } - - /** @summary cleanup object */ - cleanup() { - if (!this.mesh) - return; - delete this.mesh.is_selected; - this.createSpecial(null); - delete this.mesh; - } - - /** @summary extract intersect index */ - extractIndex(intersect) { return intersect?.index; } - - /** @summary set selection */ - setSelected(col, indx) { - const m = this.mesh; - if ((m.select_col === col) && (m.select_indx === indx)) { - col = null; - indx = undefined; - } - m.select_col = col; - m.select_indx = indx; - this.createSpecial(col, indx); - return true; - } - - /** @summary set highlight */ - setHighlight(col, indx) { - const m = this.mesh; - m.h_index = indx; - if (col) - this.createSpecial(col, indx); - else - this.createSpecial(m.select_col, m.select_indx); - return true; - } - - /** @summary create special object */ - createSpecial(color, index) { - const m = this.mesh; - if (!color) { - if (m.js_special) { - m.remove(m.js_special); - disposeThreejsObject(m.js_special); - delete m.js_special; - } - return; - } - - if (!m.js_special) { - const geom = new THREE.BufferGeometry(); - geom.setAttribute('position', m.geometry.getAttribute('position')); - const material = new THREE.PointsMaterial({ size: m.material.size * 2, color }); - material.sizeAttenuation = m.material.sizeAttenuation; - - m.js_special = new THREE.Points(geom, material); - m.js_special.jsroot_special = true; // special object, exclude from intersections - m.add(m.js_special); - } - - m.js_special.material.color = new THREE.Color(color); - if (index !== undefined) - m.js_special.geometry.setDrawRange(index, 1); - } - -} // class PointsControl - - -/** - * @summary Class for creation of 3D points - * - * @private - */ - -class PointsCreator { - - /** @summary constructor - * @param {number} number - number of points - * @param {boolean} [iswebgl] - if WebGL is used - * @param {number} [scale] - scale factor */ - constructor(number, iswebgl = true, scale = 1) { - this.webgl = iswebgl; - this.scale = scale || 1; - - this.pos = new Float32Array(number * 3); - this.geom = new THREE.BufferGeometry(); - this.geom.setAttribute('position', new THREE.BufferAttribute(this.pos, 3)); - this.indx = 0; - } - - /** @summary Add point */ - addPoint(x, y, z) { - this.pos[this.indx] = x; - this.pos[this.indx + 1] = y; - this.pos[this.indx + 2] = z; - this.indx += 3; - } - - /** @summary Create points */ - createPoints(args) { - if (!isObject(args)) - args = { color: args }; - if (!args.color) - args.color = 'black'; - - let k = 1; - - // special dots - if (!args.style) - k = 1.1; - else if (args.style === 1) - k = 0.3; - else if (args.style === 6) - k = 0.5; - else if (args.style === 7) - k = 0.7; - - const makePoints = texture => { - const material_args = { size: 3 * this.scale * k }; - if (texture) { - material_args.map = texture; - material_args.transparent = true; - } else - material_args.color = args.color || 'black'; - - const pnts = new THREE.Points(this.geom, new THREE.PointsMaterial(material_args)); - pnts.nvertex = 1; - return pnts; - }; - - // this is plain creation of points, no need for texture loading - - if (k !== 1) { - const res = makePoints(); - return this.noPromise ? res : Promise.resolve(res); - } - - const handler = new TAttMarkerHandler({ style: args.style, color: args.color, size: 7 }), - w = handler.fill ? 1 : 7, - imgdata = `` + - `` + - '', - dataUrl = prSVG + (isNodeJs() ? imgdata : encodeURIComponent(imgdata)); - let promise; - - if (isNodeJs()) { - promise = import('canvas').then(handle => handle.default.loadImage(dataUrl).then(img => { - const canvas = handle.default.createCanvas(64, 64), - ctx = canvas.getContext('2d'); - ctx.drawImage(img, 0, 0, 64, 64); - return new THREE.CanvasTexture(canvas); - })); - } else if (this.noPromise) { - // only for v6 support - return makePoints(new THREE.TextureLoader().load(dataUrl)); - } else { - promise = new Promise((resolveFunc, rejectFunc) => { - const loader = new THREE.TextureLoader(); - loader.load(dataUrl, res => resolveFunc(res), undefined, () => rejectFunc(Error(`Fail to load ${dataUrl}`))); - }); - } - - return promise.then(makePoints); - } - -} // class PointsCreator - - -/** @summary Create material for 3D line - * @desc Takes into account dashed properties - * @private */ -function create3DLineMaterial(painter, arg, is_v7 = false) { - if (!painter || !arg) - return null; - - let color, lstyle, lwidth; - if (isStr(arg) || is_v7) { - color = painter.v7EvalColor(arg + 'color', 'black'); - lstyle = parseInt(painter.v7EvalAttr(arg + 'style', 0)); - lwidth = parseInt(painter.v7EvalAttr(arg + 'width', 1)); - } else { - color = painter.getColor(arg.fLineColor); - lstyle = arg.fLineStyle; - lwidth = arg.fLineWidth; - } - - const style = lstyle ? getSvgLineStyle(lstyle) : '', - dash = style ? style.split(',') : [], - material = (dash && dash.length >= 2) - ? new THREE.LineDashedMaterial({ color, dashSize: parseInt(dash[0]), gapSize: parseInt(dash[1]) }) - : new THREE.LineBasicMaterial({ color }); - - if (lwidth && (lwidth > 1)) - material.linewidth = lwidth; - - return material; -} - -/** @summary Create plain text geometry - * @private */ -function createTextGeometry(lbl, size) { - const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }; - if (THREE.REVISION > 162) - geom_args.depth = 0; - else - geom_args.height = 0; - - return new THREE.TextGeometry(lbl, geom_args); -} - - -export { THREE, importThreeJs, assign3DHandler, disposeThreejsObject, createOrbitControl, - createLineSegments, create3DLineMaterial, Box3D, getMaterialArgs, - createRender3D, beforeRender3D, afterRender3D, getRender3DKind, cleanupRender3D, - getHelveticaFont, createTextGeometry, InteractiveControl, PointsControl, PointsCreator, createSVGRenderer }; diff --git a/js/modules/base/colors.mjs b/js/modules/base/colors.mjs deleted file mode 100644 index 83a7146e41cee..0000000000000 --- a/js/modules/base/colors.mjs +++ /dev/null @@ -1,528 +0,0 @@ -import { clTColor, isBatchMode, isNodeJs, settings } from '../core.mjs'; -import { color as d3_color } from '../d3.mjs'; - -const clTLinearGradient = 'TLinearGradient', clTRadialGradient = 'TRadialGradient', - kWhite = 0, kBlack = 1, kRed = 2, kGreen = 3, kBlue = 4, kYellow = 5, kMagenta = 6, kCyan = 7; - -/** @summary Covert value between 0 and 1 into decimal string using scale factor, used for colors coding - * @private */ -function toDec(num, scale = 255) { - return Math.round(num * scale).toString(10); -} - -/** @summary Convert alfa value from rgba to string - * @private */ -function toAlfa(a) { - const res = a.toFixed(2); - if ((res.length === 4) && (res[3] === '0')) - return res.slice(0, 3); - return res; -} - -/** @summary Convert r,g,b,a values to string - * @private */ -function toColor(r, g, b, a = 1) { - return (a !== undefined) && (a !== 1) - ? `rgba(${toDec(r)}, ${toDec(g)}, ${toDec(b)}, ${toAlfa(a)})` - : `rgb(${toDec(r)}, ${toDec(g)}, ${toDec(b)})`; -} - -/** @summary Convert color string to unify node.js and browser - * @private */ -function convertColor(col) { - return (isNodeJs() || (isBatchMode() && settings.ApproxTextSize)) && (col[0] === '#' || col[0] === 'r') ? d3_color(col).formatRgb() : col; -} - -/** @summary list of global root colors - * @private */ -let gbl_colors_list = []; - -/** @summary Generates all root colors, used also in jstests to reset colors - * @private */ -function createRootColors() { - function conv(arg) { - const r = Number.parseInt(arg.slice(0, 2), 16), - g = Number.parseInt(arg.slice(2, 4), 16), - b = Number.parseInt(arg.slice(4, 6), 16); - return `rgb(${r}, ${g}, ${b})`; - } - - const colorMap = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'magenta', 'cyan', conv('59d454'), conv('5954d9'), 'white']; - colorMap[110] = 'white'; - - const moreCol = [ - { n: 11, s: 'c1b7ad4d4d4d6666668080809a9a9ab3b3b3cdcdcde6e6e6f3f3f3cdc8accdc8acc3c0a9bbb6a4b3a697b8a49cae9a8d9c8f83886657b1cfc885c3a48aa9a1839f8daebdc87b8f9a768a926983976e7b857d9ad280809caca6c0d4cf88dfbb88bd9f83c89a7dc08378cf5f61ac8f94a6787b946971d45a549300ff7b00ff6300ff4b00ff3300ff1b00ff0300ff0014ff002cff0044ff005cff0074ff008cff00a4ff00bcff00d4ff00ecff00fffd00ffe500ffcd00ffb500ff9d00ff8500ff6d00ff5500ff3d00ff2600ff0e0aff0022ff003aff0052ff006aff0082ff009aff00b1ff00c9ff00e1ff00f9ff00ffef00ffd700ffbf00ffa700ff8f00ff7700ff6000ff4800ff3000ff18006f2da8a52a2ab2beb55790fcf89c20e42536964a8b9c9ca17a21dd1845fbff5e02c91f16c849a9adad7d86c8dd578dff6563643f90daffa90ebd1f0194a4a2832db6a96b59e76300b9ac7071758192daddb2b2b2' }, - { n: 201, s: '5c5c5c7b7b7bb8b8b8d7d7d78a0f0fb81414ec4848f176760f8a0f14b81448ec4876f1760f0f8a1414b84848ec7676f18a8a0fb8b814ecec48f1f1768a0f8ab814b8ec48ecf176f10f8a8a14b8b848ecec76f1f1' }, - { n: 390, s: 'ffffcdffff9acdcd9affff66cdcd669a9a66ffff33cdcd339a9a33666633ffff00cdcd009a9a00666600333300' }, - { n: 406, s: 'cdffcd9aff9a9acd9a66ff6666cd66669a6633ff3333cd33339a3333663300ff0000cd00009a00006600003300' }, - { n: 422, s: 'cdffff9affff9acdcd66ffff66cdcd669a9a33ffff33cdcd339a9a33666600ffff00cdcd009a9a006666003333' }, - { n: 590, s: 'cdcdff9a9aff9a9acd6666ff6666cd66669a3333ff3333cd33339a3333660000ff0000cd00009a000066000033' }, - { n: 606, s: 'ffcdffff9affcd9acdff66ffcd66cd9a669aff33ffcd33cd9a339a663366ff00ffcd00cd9a009a660066330033' }, - { n: 622, s: 'ffcdcdff9a9acd9a9aff6666cd66669a6666ff3333cd33339a3333663333ff0000cd00009a0000660000330000' }, - { n: 791, s: 'ffcd9acd9a669a66339a6600cd9a33ffcd66ff9a00ffcd33cd9a00ffcd00ff9a33cd66006633009a3300cd6633ff9a66ff6600ff6633cd3300ff33009aff3366cd00336600339a0066cd339aff6666ff0066ff3333cd0033ff00cdff9a9acd66669a33669a009acd33cdff669aff00cdff339acd00cdff009affcd66cd9a339a66009a6633cd9a66ffcd00ff6633ffcd00cd9a00ffcd33ff9a00cd66006633009a3333cd6666ff9a00ff9a33ff6600cd3300ff339acdff669acd33669a00339a3366cd669aff0066ff3366ff0033cd0033ff339aff0066cd00336600669a339acd66cdff009aff33cdff009acd00cdffcd9aff9a66cd66339a66009a9a33cdcd66ff9a00ffcd33ff9a00cdcd00ff9a33ff6600cd33006633009a6633cd9a66ff6600ff6633ff3300cd3300ffff339acd00666600339a0033cd3366ff669aff0066ff3366cd0033ff0033ff9acdcd669a9a33669a0066cd339aff66cdff009acd009aff33cdff009a' }, - { n: 920, s: 'cdcdcd9a9a9a666666333333' }]; - - moreCol.forEach(entry => { - const s = entry.s; - for (let n = 0; n < s.length; n += 6) { - const num = entry.n + n / 6; - colorMap[num] = conv(s.slice(n, n + 6)); - } - }); - - gbl_colors_list = colorMap; -} - -/** @summary Get list of colors - * @private */ -function getRootColors() { - return gbl_colors_list; -} - -/** @summary Produces rgb code for TColor object - * @private */ -function getRGBfromTColor(col) { - if (col?._typename !== clTColor) - return null; - - const rgb = toColor(col.fRed, col.fGreen, col.fBlue, col.fAlpha); - - switch (rgb) { - case 'rgb(255, 255, 255)': return 'white'; - case 'rgb(0, 0, 0)': return 'black'; - case 'rgb(255, 0, 0)': return 'red'; - case 'rgb(0, 255, 0)': return 'green'; - case 'rgb(0, 0, 255)': return 'blue'; - case 'rgb(255, 255, 0)': return 'yellow'; - case 'rgb(255, 0, 255)': return 'magenta'; - case 'rgb(0, 255, 255)': return 'cyan'; - } - return rgb; -} - -/** @summary Return list of grey colors for the original array - * @private */ -function getGrayColors(rgb_array) { - const gray_colors = []; - - if (!rgb_array) - rgb_array = getRootColors(); - - for (let n = 0; n < rgb_array.length; ++n) { - if (!rgb_array[n]) - continue; - const rgb = d3_color(rgb_array[n]), - gray = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; - rgb.r = rgb.g = rgb.b = gray; - gray_colors[n] = rgb.formatRgb(); - } - - return gray_colors; -} - -/** @summary Add new colors from object array - * @private */ -function extendRootColors(jsarr, objarr, grayscale) { - if (!jsarr) { - jsarr = []; - for (let n = 0; n < gbl_colors_list.length; ++n) - jsarr[n] = gbl_colors_list[n]; - } - - if (!objarr) - return jsarr; - - let rgb_array = objarr; - if (objarr._typename && objarr.arr) { - rgb_array = []; - for (let n = 0; n < objarr.arr.length; ++n) { - const col = objarr.arr[n]; - if ((col?._typename === clTLinearGradient) || (col?._typename === clTRadialGradient)) { - rgb_array[col.fNumber] = col; - col.toString = () => 'white'; - continue; - } - - if (col?._typename !== clTColor) - continue; - - if ((col.fNumber >= 0) && (col.fNumber <= 10000)) - rgb_array[col.fNumber] = getRGBfromTColor(col); - } - } - - for (let n = 0; n < rgb_array.length; ++n) { - if (rgb_array[n] && (jsarr[n] !== rgb_array[n])) - jsarr[n] = rgb_array[n]; - } - - return grayscale ? getGrayColors(jsarr) : jsarr; -} - -/** @summary Set global list of colors. - * @desc Either TObjArray of TColor instances or just plain array with rgb() code. - * List of colors typically stored together with TCanvas primitives - * @private */ -function adoptRootColors(objarr) { - extendRootColors(gbl_colors_list, objarr); -} - -/** @summary Return ROOT color by index - * @desc Color numbering corresponds typical ROOT colors - * @return {string} with RGB color code or existing color name like 'cyan' - * @private */ -function getColor(indx) { - return gbl_colors_list[indx]; -} - -/** @summary Search for specified color in the list of colors - * @return Color index or -1 if fails - * @private */ -function findColor(name) { - if (!name) - return -1; - for (let indx = 0; indx < gbl_colors_list.length; ++indx) { - if (gbl_colors_list[indx] === name) - return indx; - } - return -1; -} - -/** @summary Add new color - * @param {string} rgb - color name or just string with rgb value - * @param {Array} [lst] - optional colors list, to which add colors - * @param {number} [indx] - optional color index in provided colors list - * @return {number} index of new color - * @private */ -function addColor(rgb, lst, indx) { - if (!lst) - lst = gbl_colors_list; - - if ((rgb[0] === '#') && (isNodeJs() || (isBatchMode() && settings.ApproxTextSize))) - rgb = d3_color(rgb).formatRgb(); - - if (indx !== undefined) { - if (Number.isInteger(indx) && (indx > 0)) - lst[indx] = rgb; - return indx; - } - - indx = lst.indexOf(rgb); - if (indx >= 0) - return indx; - lst.push(rgb); - return lst.length - 1; -} - -/** - * @summary Color palette handle - * - * @private - */ - -class ColorPalette { - - /** @summary constructor */ - constructor(arr, grayscale) { - this.palette = grayscale ? getGrayColors(arr) : arr; - } - - /** @summary Returns color index which correspond to contour index of provided length */ - calcColorIndex(i, len) { - const plen = this.palette.length, theColor = Math.floor((i + 0.99) * plen / (len - 1)); - return (theColor > plen - 1) ? plen - 1 : theColor; - } - - /** @summary Returns color with provided index */ - getColor(indx) { return this.palette[indx]; } - - /** @summary Returns number of colors in the palette */ - getLength() { return this.palette.length; } - - /** @summary Calculate color for given i and len */ - calcColor(i, len) { return this.getColor(this.calcColorIndex(i, len)); } - -} // class ColorPalette - -function createDefaultPalette(grayscale) { - const hue2rgb = (p, q, t) => { - if (t < 0) - t += 1; - if (t > 1) - t -= 1; - if (t < 1 / 6) - return p + (q - p) * 6 * t; - if (t < 1 / 2) - return q; - if (t < 2 / 3) - return p + (q - p) * (2 / 3 - t) * 6; - return p; - }, HLStoRGB = (h, l, s) => { - const q = (l < 0.5) ? l * (1 + s) : l + s - l * s, - p = 2 * l - q, - r = hue2rgb(p, q, h + 1 / 3), - g = hue2rgb(p, q, h), - b = hue2rgb(p, q, h - 1 / 3); - return toColor(r, g, b); - }, minHue = 0, maxHue = 280, maxPretty = 50, palette = []; - for (let i = 0; i < maxPretty; ++i) { - const hue = (maxHue - (i + 1) * ((maxHue - minHue) / maxPretty)) / 360; - palette.push(HLStoRGB(hue, 0.5, 1)); - } - return new ColorPalette(palette, grayscale); -} - -function createGrayPalette() { - const palette = []; - for (let i = 0; i < 50; ++i) { - const code = toDec((i + 2) / 60); - palette.push(`rgb(${code}, ${code}, ${code})`); - } - return new ColorPalette(palette); -} - -/** @summary Set list of colors for specified color palette - * @desc One also can redefine existing palette - * Array should contain several colors in RGB format like `rgb(10,10,10)` or `#ff00ff` - * @private */ - -const customPalettes = {}; - -function setColorPalette(id, colors) { - if (!Number.isInteger(id) || (id < 0) || !colors?.length) - return false; - - customPalettes[id] = colors; - return true; -} - -/** @summary Create color palette - * @private */ -function getColorPalette(id, grayscale) { - id = id || settings.Palette; - if (customPalettes[id]) - return new ColorPalette(customPalettes[id], grayscale); - - if ((id > 0) && (id < 10)) - return createGrayPalette(); - if (id < 51) - return createDefaultPalette(grayscale); - if (id > 113) - id = 57; - const stops = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]; - let rgb; - /* eslint-disable @stylistic/js/comma-spacing */ - switch (id) { - // Deep Sea - case 51: rgb = [[0,9,13,17,24,32,27,25,29],[0,0,0,2,37,74,113,160,221],[28,42,59,78,98,129,154,184,221]]; break; - // Grey Scale - case 52: rgb = [[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255],[0,32,64,96,128,160,192,224,255]]; break; - // Dark Body Radiator - case 53: rgb = [[0,45,99,156,212,230,237,234,242],[0,0,0,45,101,168,238,238,243],[0,1,1,3,9,8,11,95,230]]; break; - // Two-color hue (dark blue through neutral gray to bright yellow) - case 54: rgb = [[0,22,44,68,93,124,160,192,237],[0,16,41,67,93,125,162,194,241],[97,100,99,99,93,68,44,26,74]]; break; - // Rain Bow - case 55: rgb = [[0,5,15,35,102,196,208,199,110],[0,48,124,192,206,226,97,16,0],[99,142,198,201,90,22,13,8,2]]; break; - // Inverted Dark Body Radiator - case 56: rgb = [[242,234,237,230,212,156,99,45,0],[243,238,238,168,101,45,0,0,0],[230,95,11,8,9,3,1,1,0]]; break; - // Bird (default, keep float for backward compatibility) - case 57: - rgb = [[53.091,15.096,19.89,5.916,45.951,135.1755,208.743,253.878,248.982], - [42.432,91.7745,128.5455,163.6845,183.039,191.046,186.864,200.481,250.716], - [134.9715,221.442,213.8175,201.807,163.8375,118.881,89.2245,50.184,13.7445]]; - break; - // Cubehelix - case 58: rgb = [[0,24,2,54,176,236,202,194,255],[0,29,92,129,117,120,176,236,255],[0,68,80,34,57,172,252,245,255]]; break; - // Green Red Violet - case 59: rgb = [[13,23,25,63,76,104,137,161,206],[95,67,37,21,0,12,35,52,79],[4,3,2,6,11,22,49,98,208]]; break; - // Blue Red Yellow - case 60: rgb = [[0,61,89,122,143,160,185,204,231],[0,0,0,0,14,37,72,132,235],[0,140,224,144,4,5,6,9,13]]; break; - // Ocean - case 61: rgb = [[14,7,2,0,5,11,55,131,229],[105,56,26,1,42,74,131,171,229],[2,21,35,60,92,113,160,185,229]]; break; - // Color Printable On Grey - case 62: rgb = [[0,0,0,70,148,231,235,237,244],[0,0,0,0,0,69,67,216,244],[0,102,228,231,177,124,137,20,244]]; break; - // Alpine - case 63: rgb = [[50,56,63,68,93,121,165,192,241],[66,81,91,96,111,128,155,189,241],[97,91,75,65,77,103,143,167,217]]; break; - // Aquamarine - case 64: rgb = [[145,166,167,156,131,114,101,112,132],[158,178,179,181,163,154,144,152,159],[190,199,201,192,176,169,160,166,190]]; break; - // Army - case 65: rgb = [[93,91,99,108,130,125,132,155,174],[126,124,128,129,131,121,119,153,173],[103,94,87,85,80,85,107,120,146]]; break; - // Atlantic - case 66: rgb = [[24,40,69,90,104,114,120,132,103],[29,52,94,127,150,162,159,151,101],[29,52,96,132,162,181,184,186,131]]; break; - // Aurora - case 67: rgb = [[46,38,61,92,113,121,132,150,191],[46,36,40,69,110,135,131,92,34],[46,80,74,70,81,105,165,211,225]]; break; - // Avocado - case 68: rgb = [[0,4,12,30,52,101,142,190,237],[0,40,86,121,140,172,187,213,240],[0,9,14,18,21,23,27,35,101]]; break; - // Beach - case 69: rgb = [[198,206,206,211,198,181,161,171,244],[103,133,150,172,178,174,163,175,244],[49,54,55,66,91,130,184,224,244]]; break; - // Black Body - case 70: rgb = [[243,243,240,240,241,239,186,151,129],[0,46,99,149,194,220,183,166,147],[6,8,36,91,169,235,246,240,233]]; break; - // Blue Green Yellow - case 71: rgb = [[22,19,19,25,35,53,88,139,210],[0,32,69,108,135,159,183,198,215],[77,96,110,116,110,100,90,78,70]]; break; - // Brown Cyan - case 72: rgb = [[68,116,165,182,189,180,145,111,71],[37,82,135,178,204,225,221,202,147],[16,55,105,147,196,226,232,224,178]]; break; - // CMYK - case 73: rgb = [[61,99,136,181,213,225,198,136,24],[149,140,96,83,132,178,190,135,22],[214,203,168,135,110,100,111,113,22]]; break; - // Candy - case 74: rgb = [[76,120,156,183,197,180,162,154,140],[34,35,42,69,102,137,164,188,197],[64,69,78,105,142,177,205,217,198]]; break; - // Cherry - case 75: rgb = [[37,102,157,188,196,214,223,235,251],[37,29,25,37,67,91,132,185,251],[37,32,33,45,66,98,137,187,251]]; break; - // Coffee - case 76: rgb = [[79,100,119,137,153,172,192,205,250],[63,79,93,103,115,135,167,196,250],[51,59,66,61,62,70,110,160,250]]; break; - // Dark Rain Bow - case 77: rgb = [[43,44,50,66,125,172,178,155,157],[63,63,85,101,138,163,122,51,39],[121,101,58,44,47,55,57,44,43]]; break; - // Dark Terrain - case 78: rgb = [[0,41,62,79,90,87,99,140,228],[0,57,81,93,85,70,71,125,228],[95,91,91,82,60,43,44,112,228]]; break; - // Fall - case 79: rgb = [[49,59,72,88,114,141,176,205,222],[78,72,66,57,59,75,106,142,173],[78,55,46,40,39,39,40,41,47]]; break; - // Fruit Punch - case 80: rgb = [[243,222,201,185,165,158,166,187,219],[94,108,132,135,125,96,68,51,61],[7,9,12,19,45,89,118,146,118]]; break; - // Fuchsia - case 81: rgb = [[19,44,74,105,137,166,194,206,220],[19,28,40,55,82,110,159,181,220],[19,42,68,96,129,157,188,203,220]]; break; - // Grey Yellow - case 82: rgb = [[33,44,70,99,140,165,199,211,216],[38,50,76,105,140,165,191,189,167],[55,67,97,124,140,166,163,129,52]]; break; - // Green Brown Terrain - case 83: rgb = [[0,33,73,124,136,152,159,171,223],[0,43,92,124,134,126,121,144,223],[0,43,68,76,73,64,72,114,223]]; break; - // Green Pink - case 84: rgb = [[5,18,45,124,193,223,205,128,49],[48,134,207,230,193,113,28,0,7],[6,15,41,121,193,226,208,130,49]]; break; - // Island - case 85: rgb = [[180,106,104,135,164,188,189,165,144],[72,126,154,184,198,207,205,190,179],[41,120,158,188,194,181,145,100,62]]; break; - // Lake - case 86: rgb = [[57,72,94,117,136,154,174,192,215],[0,33,68,109,140,171,192,196,209],[116,137,173,201,200,201,203,190,187]]; break; - // Light Temperature - case 87: rgb = [[31,71,123,160,210,222,214,199,183],[40,117,171,211,231,220,190,132,65],[234,214,228,222,210,160,105,60,34]]; break; - // Light Terrain - case 88: rgb = [[123,108,109,126,154,172,188,196,218],[184,138,130,133,154,175,188,196,218],[208,130,109,99,110,122,150,171,218]]; break; - // Mint - case 89: rgb = [[105,106,122,143,159,172,176,181,207],[252,197,194,187,174,162,153,136,125],[146,133,144,155,163,167,166,162,174]]; break; - // Neon - case 90: rgb = [[171,141,145,152,154,159,163,158,177],[236,143,100,63,53,55,44,31,6],[59,48,46,44,42,54,82,112,179]]; break; - // Pastel - case 91: rgb = [[180,190,209,223,204,228,205,152,91],[93,125,147,172,181,224,233,198,158],[236,218,160,133,114,132,162,220,218]]; break; - // Pearl - case 92: rgb = [[225,183,162,135,115,111,119,145,211],[205,177,166,135,124,117,117,132,172],[186,165,155,135,126,130,150,178,226]]; break; - // Pigeon - case 93: rgb = [[39,43,59,63,80,116,153,177,223],[39,43,59,74,91,114,139,165,223],[39,50,59,70,85,115,151,176,223]]; break; - // Plum - case 94: rgb = [[0,38,60,76,84,89,101,128,204],[0,10,15,23,35,57,83,123,199],[0,11,22,40,63,86,97,94,85]]; break; - // Red Blue - case 95: rgb = [[94,112,141,165,167,140,91,49,27],[27,46,88,135,166,161,135,97,58],[42,52,81,106,139,158,155,137,116]]; break; - // Rose - case 96: rgb = [[30,49,79,117,135,151,146,138,147],[63,60,72,90,94,94,68,46,16],[18,28,41,56,62,63,50,36,21]]; break; - // Rust - case 97: rgb = [[0,30,63,101,143,152,169,187,230],[0,14,28,42,58,61,67,74,91],[39,26,21,18,15,14,14,13,13]]; break; - // Sandy Terrain - case 98: rgb = [[149,140,164,179,182,181,131,87,61],[62,70,107,136,144,138,117,87,74],[40,38,45,49,49,49,38,32,34]]; break; - // Sienna - case 99: rgb = [[99,112,148,165,179,182,183,183,208],[39,40,57,79,104,127,148,161,198],[15,16,18,33,51,79,103,129,177]]; break; - // Solar - case 100: rgb = [[99,116,154,174,200,196,201,201,230],[0,0,8,32,58,83,119,136,173],[5,6,7,9,9,14,17,19,24]]; break; - // South West - case 101: rgb = [[82,106,126,141,155,163,142,107,66],[62,44,69,107,135,152,149,132,119],[39,25,31,60,73,68,49,72,188]]; break; - // Starry Night - case 102: rgb = [[18,29,44,72,116,158,184,208,221],[27,46,71,105,146,177,189,190,183],[39,55,80,108,130,133,124,100,76]]; break; - // Sunset - case 103: rgb = [[0,48,119,173,212,224,228,228,245],[0,13,30,47,79,127,167,205,245],[0,68,75,43,16,22,55,128,245]]; break; - // Temperature Map - case 104: rgb = [[34,70,129,187,225,226,216,193,179],[48,91,147,194,226,229,196,110,12],[234,212,216,224,206,110,53,40,29]]; break; - // Thermometer - case 105: rgb = [[30,55,103,147,174,203,188,151,105],[0,65,138,182,187,175,121,53,9],[191,202,212,208,171,140,97,57,30]]; break; - // Valentine - case 106: rgb = [[112,97,113,125,138,159,178,188,225],[16,17,24,37,56,81,110,136,189],[38,35,46,59,78,103,130,152,201]]; break; - // Visible Spectrum - case 107: rgb = [[18,72,5,23,29,201,200,98,29],[0,0,43,167,211,117,0,0,0],[51,203,177,26,10,9,8,3,0]]; break; - // Water Melon - case 108: rgb = [[19,42,64,88,118,147,175,187,205],[19,55,89,125,154,169,161,129,70],[19,32,47,70,100,128,145,130,75]]; break; - // Cool - case 109: rgb = [[33,31,42,68,86,111,141,172,227],[255,175,145,106,88,55,15,0,0],[255,205,202,203,208,205,203,206,231]]; break; - // Copper - case 110: rgb = [[0,25,50,79,110,145,181,201,254],[0,16,30,46,63,82,101,124,179],[0,12,21,29,39,49,61,74,103]]; break; - // Gist Earth - case 111: rgb = [[0,13,30,44,72,120,156,200,247],[0,36,84,117,141,153,151,158,247],[0,94,100,82,56,66,76,131,247]]; break; - // Viridis - case 112: rgb = [[26,51,43,33,28,35,74,144,246],[9,24,55,87,118,150,180,200,222],[30,96,112,114,112,101,72,35,0]]; break; - // Cividis - case 113: rgb = [[0,5,65,97,124,156,189,224,255],[32,54,77,100,123,148,175,203,234],[77,110,107,111,120,119,111,94,70]]; break; - default: return createDefaultPalette(); - } - /* eslint-enable @stylistic/js/comma-spacing */ - - const NColors = 255, Red = rgb[0], Green = rgb[1], Blue = rgb[2], palette = []; - - for (let g = 1; g < stops.length; g++) { - // create the colors... - const nColorsGradient = Math.round(Math.floor(NColors * stops[g]) - Math.floor(NColors * stops[g - 1])); - for (let c = 0; c < nColorsGradient; c++) { - const col = 'rgb(' + toDec(Red[g - 1] + c * (Red[g] - Red[g - 1]) / nColorsGradient, 1) + ', ' + - toDec(Green[g - 1] + c * (Green[g] - Green[g - 1]) / nColorsGradient, 1) + ', ' + - toDec(Blue[g - 1] + c * (Blue[g] - Blue[g - 1]) / nColorsGradient, 1) + ')'; - palette.push(col); - } - } - - return new ColorPalette(palette, grayscale); -} - - -/** @summary Decode list of ROOT colors coded by TWebCanvas - * @private */ -function decodeWebCanvasColors(oper) { - const colors = [], arr = oper.split(';'), - convert_rgb = isNodeJs() || (isBatchMode() && settings.ApproxTextSize); - for (let n = 0; n < arr.length; ++n) { - const name = arr[n]; - let p = name.indexOf(':'); - if (p > 0) { - const col = `rgb(${name.slice(p + 1)})`; - colors[parseInt(name.slice(0, p))] = convert_rgb ? d3_color(col).formatRgb() : col; - continue; - } - p = name.indexOf('='); - if (p > 0) { - let col = `rgba(${name.slice(p + 1)})`; - if (convert_rgb) { - col = d3_color(col); - col.opacity = (Math.round(col.opacity * 255) / 255).toFixed(2); - col = col.formatRgb(); - } - colors[parseInt(name.slice(0, p))] = col; - continue; - } - p = name.indexOf('#'); - if (p < 0) - continue; - - const colindx = parseInt(name.slice(0, p)), - data = JSON.parse(name.slice(p + 1)), - grad = { _typename: data[0] === 10 ? clTLinearGradient : clTRadialGradient, fNumber: colindx, fType: data[0] }; - - let cnt = 1; - - grad.fCoordinateMode = Math.round(data[cnt++]); - const nsteps = Math.round(data[cnt++]); - grad.fColorPositions = data.slice(cnt, cnt + nsteps); - cnt += nsteps; - grad.fColors = data.slice(cnt, cnt + 4 * nsteps); - cnt += 4 * nsteps; - grad.fStart = { fX: data[cnt++], fY: data[cnt++] }; - grad.fEnd = { fX: data[cnt++], fY: data[cnt++] }; - if (grad._typename === clTRadialGradient && cnt < data.length) { - grad.fR1 = data[cnt++]; - grad.fR2 = data[cnt]; - } - - colors[colindx] = grad; - } - - return colors; -} - - -createRootColors(); - -export { getColor, findColor, addColor, adoptRootColors, convertColor, - getRootColors, getGrayColors, - extendRootColors, getRGBfromTColor, createRootColors, toColor, - kWhite, kBlack, kRed, kGreen, kBlue, kYellow, kMagenta, kCyan, - ColorPalette, getColorPalette, setColorPalette, - clTLinearGradient, clTRadialGradient, decodeWebCanvasColors }; diff --git a/js/modules/base/func.mjs b/js/modules/base/func.mjs deleted file mode 100644 index 368ae03cda456..0000000000000 --- a/js/modules/base/func.mjs +++ /dev/null @@ -1,175 +0,0 @@ -import { isStr, clTF2, clTF3 } from '../core.mjs'; -import * as jsroot_math from './math.mjs'; - -/** @summary Assign `evalPar` function for TF1 object - * @private */ - -function provideEvalPar(obj, check_save) { - obj.$math = jsroot_math; - - let _func = obj.fTitle, isformula = false, pprefix = '['; - if (_func === 'gaus') - _func = 'gaus(0)'; - if (isStr(obj.fFormula?.fFormula)) { - if (obj.fFormula.fFormula.indexOf('[](double*x,double*p)') === 0) { - isformula = true; - pprefix = 'p['; - _func = obj.fFormula.fFormula.slice(21); - } else { - _func = obj.fFormula.fFormula; - pprefix = '[p'; - } - - if (obj.fFormula.fClingParameters && obj.fFormula.fParams) { - obj.fFormula.fParams.forEach(pair => { - const regex = new RegExp(`(\\[${pair.first}\\])`, 'g'), - parvalue = obj.fFormula.fClingParameters[pair.second]; - _func = _func.replace(regex, (parvalue < 0) ? `(${parvalue})` : parvalue); - }); - } - } - - if (!_func) - return !check_save || (obj.fSave?.length > 2); - - obj.formulas?.forEach(entry => { - _func = _func.replaceAll(entry.fName, entry.fTitle); - }); - - _func = _func.replace(/\b(TMath::SinH)\b/g, 'Math.sinh') - .replace(/\b(TMath::CosH)\b/g, 'Math.cosh') - .replace(/\b(TMath::TanH)\b/g, 'Math.tanh') - .replace(/\b(TMath::ASinH)\b/g, 'Math.asinh') - .replace(/\b(TMath::ACosH)\b/g, 'Math.acosh') - .replace(/\b(TMath::ATanH)\b/g, 'Math.atanh') - .replace(/\b(TMath::ASin)\b/g, 'Math.asin') - .replace(/\b(TMath::ACos)\b/g, 'Math.acos') - .replace(/\b(TMath::Atan)\b/g, 'Math.atan') - .replace(/\b(TMath::ATan2)\b/g, 'Math.atan2') - .replace(/\b(TMath::Sqrt)\b/g, 'Math.sqrt') - .replace(/\b(sin|SIN|TMath::Sin)\b/g, 'Math.sin') - .replace(/\b(cos|COS|TMath::Cos)\b/g, 'Math.cos') - .replace(/\b(tan|TAN|TMath::Tan)\b/g, 'Math.tan') - .replace(/\b(exp|EXP|TMath::Exp)\b/g, 'Math.exp') - .replace(/\b(log|LOG|TMath::Log)\b/g, 'Math.log') - .replace(/\b(TMath::Log2)\b/g, 'Math.log2') - .replace(/\b(log10|LOG10|TMath::Log10)\b/g, 'Math.log10') - .replace(/\b(pow|POW|TMath::Power)\b/g, 'Math.pow') - .replace(/\b(pi|PI)\b/g, 'Math.PI') - .replace(/\b(abs|ABS|TMath::Abs)\b/g, 'Math.abs') - .replace(/\bsqrt\(/g, 'Math.sqrt(') - .replace(/\bxygaus\(/g, 'this.$math.gausxy(this, x, y, ') - .replace(/\bgaus\(/g, 'this.$math.gaus(this, x, ') - .replace(/\bgausn\(/g, 'this.$math.gausn(this, x, ') - .replace(/\bexpo\(/g, 'this.$math.expo(this, x, ') - .replace(/\blandau\(/g, 'this.$math.landau(this, x, ') - .replace(/\blandaun\(/g, 'this.$math.landaun(this, x, ') - .replace(/\b(TMath::|ROOT::Math::)/g, 'this.$math.'); - - if (_func.match(/^pol[0-9]$/) && (parseInt(_func[3]) === obj.fNpar - 1)) { - _func = '[0]'; - for (let k = 1; k < obj.fNpar; ++k) - _func += ` + [${k}] * ` + ((k === 1) ? 'x' : `Math.pow(x,${k})`); - } - - if (_func.match(/^chebyshev[0-9]$/) && (parseInt(_func[9]) === obj.fNpar - 1)) { - _func = `this.$math.ChebyshevN(${obj.fNpar - 1}, x, `; - for (let k = 0; k < obj.fNpar; ++k) - _func += (k === 0 ? '[' : ', ') + `[${k}]`; - _func += '])'; - } - - for (let i = 0; i < obj.fNpar; ++i) - _func = _func.replaceAll(pprefix + i + ']', `(${obj.GetParValue(i)})`); - - for (let n = 2; n < 10; ++n) - _func = _func.replaceAll(`x^${n}`, `Math.pow(x,${n})`); - - if (isformula) { - _func = _func.replace(/x\[0\]/g, 'x'); - if (obj._typename === clTF3) { - _func = _func.replace(/x\[1\]/g, 'y'); - _func = _func.replace(/x\[2\]/g, 'z'); - obj.evalPar = new Function('x', 'y', 'z', _func).bind(obj); - } else if (obj._typename === clTF2) { - _func = _func.replace(/x\[1\]/g, 'y'); - obj.evalPar = new Function('x', 'y', _func).bind(obj); - } else - obj.evalPar = new Function('x', _func).bind(obj); - } else if (obj._typename === clTF3) - obj.evalPar = new Function('x', 'y', 'z', 'return ' + _func).bind(obj); - else if (obj._typename === clTF2) - obj.evalPar = new Function('x', 'y', 'return ' + _func).bind(obj); - else - obj.evalPar = new Function('x', 'return ' + _func).bind(obj); - - return true; -} - - -/** @summary Get interpolation in saved buffer - * @desc Several checks must be done before function can be used - * @private */ -function _getTF1Save(func, x) { - const np = func.fSave.length - 3, - xmin = func.fSave[np + 1], - xmax = func.fSave[np + 2], - dx = (xmax - xmin) / np; - if (x < xmin) - return func.fSave[0]; - if (x > xmax) - return func.fSave[np]; - - const bin = Math.min(np - 1, Math.floor((x - xmin) / dx)); - let xlow = xmin + bin * dx, - xup = xlow + dx, - ylow = func.fSave[bin], - yup = func.fSave[bin + 1]; - - if (!Number.isFinite(ylow) && (bin < np - 1)) { - xlow += dx; - xup += dx; - ylow = yup; - yup = func.fSave[bin + 2]; - } else if (!Number.isFinite(yup) && (bin > 0)) { - xup -= dx; - xlow -= dx; - yup = ylow; - ylow = func.fSave[bin - 1]; - } - - return ((xup * ylow - xlow * yup) + x * (yup - ylow)) / dx; -} - -/** @summary Provide TF1 value - * @desc First try evaluate, if not possible - check saved buffer - * @private */ -function getTF1Value(func, x, skip_eval = undefined) { - if (!func) - return 0; - - let iserr = false; - - if (!skip_eval && !func.evalPar) { - try { - if (!provideEvalPar(func)) - iserr = true; - } catch { - iserr = true; - } - } - - if (func.evalPar && !iserr) { - try { - const res = func.evalPar(x); - if (Number.isFinite(res)) - return res; - } catch { - } - } - - const np = (func.fSave?.length ?? 0) - 3; - return (np < 2) || (func.fSave[np + 1] === func.fSave[np + 2]) ? 0 : _getTF1Save(func, x); -} - -export { provideEvalPar, getTF1Value, _getTF1Save }; diff --git a/js/modules/base/jspdf.mjs b/js/modules/base/jspdf.mjs deleted file mode 100644 index aedc67048b7dd..0000000000000 --- a/js/modules/base/jspdf.mjs +++ /dev/null @@ -1,32164 +0,0 @@ -/** @license - * - * jsPDF - PDF Document creation from JavaScript - * Version 4.2.1 - * - * Copyright (c) 2010-2025 James Hall , https://github.com/MrRio/jsPDF - * 2015-2025 yWorks GmbH, http://www.yworks.com - * 2015-2025 Lukas Holländer , https://github.com/HackbrettXXX - * 2016-2018 Aras Abbasi - * 2010 Aaron Spike, https://github.com/acspike - * 2012 Willow Systems Corporation, https://github.com/willowsystems - * 2012 Pablo Hess, https://github.com/pablohess - * 2012 Florian Jenett, https://github.com/fjenett - * 2013 Warren Weckesser, https://github.com/warrenweckesser - * 2013 Youssef Beddad, https://github.com/lifof - * 2013 Lee Driscoll, https://github.com/lsdriscoll - * 2013 Stefan Slonevskiy, https://github.com/stefslon - * 2013 Jeremy Morel, https://github.com/jmorel - * 2013 Christoph Hartmann, https://github.com/chris-rock - * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria - * 2014 James Makes, https://github.com/dollaruw - * 2014 Diego Casorran, https://github.com/diegocr - * 2014 Steven Spungin, https://github.com/Flamenco - * 2014 Kenneth Glassey, https://github.com/Gavvers - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * Contributor(s): - * siefkenj, ahwolf, rickygu, Midnith, saintclair, eaparango, - * kim3er, mfo, alnorth, Flamenco - */ - -const globalObject = globalThis; - -/** - * A class to parse color values - * @author Stoyan Stefanov - * {@link http://www.phpied.com/rgb-color-parser-in-javascript/} - * @license Use it if you like it - */ - -function RGBColor(color_string) { - color_string = 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(); - - var channels; - - // 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" - }; - color_string = simple_colors[color_string] || color_string; - - // 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]), parseInt(bits[2]), parseInt(bits[3])]; - } - }, - { - 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; - }; -} - -let atob, btoa; - -if ((typeof process === 'object') && (typeof process.versions === 'object') && process.versions.node && process.versions.v8) { - atob = str => Buffer.from(str, 'base64').toString('latin1'); - btoa = str => Buffer.from(str, 'latin1').toString('base64'); -} else { - atob = globalThis.atob; - btoa = globalThis.btoa; -} - -function consoleLog() { - if (globalObject.console && typeof globalObject.console.log === "function") { - globalObject.console.log.apply(globalObject.console, arguments); - } -} - -function consoleWarn(str) { - if (globalObject.console) { - if (typeof globalObject.console.warn === "function") { - globalObject.console.warn.apply(globalObject.console, arguments); - } else { - consoleLog.call(null, arguments); - } - } -} - -function consoleError(str) { - if (globalObject.console) { - if (typeof globalObject.console.error === "function") { - globalObject.console.error.apply(globalObject.console, arguments); - } else { - consoleLog(str); - } - } -} -var console = { - log: consoleLog, - warn: consoleWarn, - error: consoleError -}; - -/** - * @license - * Joseph Myers does not specify a particular license for his work. - * - * Author: Joseph Myers - * Accessed from: http://www.myersdaily.org/joseph/javascript/md5.js - * - * Modified by: Owen Leong - */ - -function md5cycle(x, k) { - var a = x[0], - b = x[1], - c = x[2], - d = x[3]; - - a = ff(a, b, c, d, k[0], 7, -680876936); - d = ff(d, a, b, c, k[1], 12, -389564586); - c = ff(c, d, a, b, k[2], 17, 606105819); - b = ff(b, c, d, a, k[3], 22, -1044525330); - a = ff(a, b, c, d, k[4], 7, -176418897); - d = ff(d, a, b, c, k[5], 12, 1200080426); - c = ff(c, d, a, b, k[6], 17, -1473231341); - b = ff(b, c, d, a, k[7], 22, -45705983); - a = ff(a, b, c, d, k[8], 7, 1770035416); - d = ff(d, a, b, c, k[9], 12, -1958414417); - c = ff(c, d, a, b, k[10], 17, -42063); - b = ff(b, c, d, a, k[11], 22, -1990404162); - a = ff(a, b, c, d, k[12], 7, 1804603682); - d = ff(d, a, b, c, k[13], 12, -40341101); - c = ff(c, d, a, b, k[14], 17, -1502002290); - b = ff(b, c, d, a, k[15], 22, 1236535329); - - a = gg(a, b, c, d, k[1], 5, -165796510); - d = gg(d, a, b, c, k[6], 9, -1069501632); - c = gg(c, d, a, b, k[11], 14, 643717713); - b = gg(b, c, d, a, k[0], 20, -373897302); - a = gg(a, b, c, d, k[5], 5, -701558691); - d = gg(d, a, b, c, k[10], 9, 38016083); - c = gg(c, d, a, b, k[15], 14, -660478335); - b = gg(b, c, d, a, k[4], 20, -405537848); - a = gg(a, b, c, d, k[9], 5, 568446438); - d = gg(d, a, b, c, k[14], 9, -1019803690); - c = gg(c, d, a, b, k[3], 14, -187363961); - b = gg(b, c, d, a, k[8], 20, 1163531501); - a = gg(a, b, c, d, k[13], 5, -1444681467); - d = gg(d, a, b, c, k[2], 9, -51403784); - c = gg(c, d, a, b, k[7], 14, 1735328473); - b = gg(b, c, d, a, k[12], 20, -1926607734); - - a = hh(a, b, c, d, k[5], 4, -378558); - d = hh(d, a, b, c, k[8], 11, -2022574463); - c = hh(c, d, a, b, k[11], 16, 1839030562); - b = hh(b, c, d, a, k[14], 23, -35309556); - a = hh(a, b, c, d, k[1], 4, -1530992060); - d = hh(d, a, b, c, k[4], 11, 1272893353); - c = hh(c, d, a, b, k[7], 16, -155497632); - b = hh(b, c, d, a, k[10], 23, -1094730640); - a = hh(a, b, c, d, k[13], 4, 681279174); - d = hh(d, a, b, c, k[0], 11, -358537222); - c = hh(c, d, a, b, k[3], 16, -722521979); - b = hh(b, c, d, a, k[6], 23, 76029189); - a = hh(a, b, c, d, k[9], 4, -640364487); - d = hh(d, a, b, c, k[12], 11, -421815835); - c = hh(c, d, a, b, k[15], 16, 530742520); - b = hh(b, c, d, a, k[2], 23, -995338651); - - a = ii(a, b, c, d, k[0], 6, -198630844); - d = ii(d, a, b, c, k[7], 10, 1126891415); - c = ii(c, d, a, b, k[14], 15, -1416354905); - b = ii(b, c, d, a, k[5], 21, -57434055); - a = ii(a, b, c, d, k[12], 6, 1700485571); - d = ii(d, a, b, c, k[3], 10, -1894986606); - c = ii(c, d, a, b, k[10], 15, -1051523); - b = ii(b, c, d, a, k[1], 21, -2054922799); - a = ii(a, b, c, d, k[8], 6, 1873313359); - d = ii(d, a, b, c, k[15], 10, -30611744); - c = ii(c, d, a, b, k[6], 15, -1560198380); - b = ii(b, c, d, a, k[13], 21, 1309151649); - a = ii(a, b, c, d, k[4], 6, -145523070); - d = ii(d, a, b, c, k[11], 10, -1120210379); - c = ii(c, d, a, b, k[2], 15, 718787259); - b = ii(b, c, d, a, k[9], 21, -343485551); - - x[0] = add32(a, x[0]); - x[1] = add32(b, x[1]); - x[2] = add32(c, x[2]); - x[3] = add32(d, x[3]); -} - -function cmn(q, a, b, x, s, t) { - a = add32(add32(a, q), add32(x, t)); - return add32((a << s) | (a >>> (32 - s)), b); -} - -function ff(a, b, c, d, x, s, t) { - return cmn((b & c) | (~b & d), a, b, x, s, t); -} - -function gg(a, b, c, d, x, s, t) { - return cmn((b & d) | (c & ~d), a, b, x, s, t); -} - -function hh(a, b, c, d, x, s, t) { - return cmn(b ^ c ^ d, a, b, x, s, t); -} - -function ii(a, b, c, d, x, s, t) { - return cmn(c ^ (b | ~d), a, b, x, s, t); -} - -function md51(s) { - // txt = ''; - var n = s.length, - state = [1732584193, -271733879, -1732584194, 271733878], - i; - for (i = 64; i <= s.length; i += 64) { - md5cycle(state, md5blk(s.substring(i - 64, i))); - } - s = s.substring(i - 64); - var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - for (i = 0; i < s.length; i++) - tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); - tail[i >> 2] |= 0x80 << (i % 4 << 3); - if (i > 55) { - md5cycle(state, tail); - for (i = 0; i < 16; i++) tail[i] = 0; - } - tail[14] = n * 8; - md5cycle(state, tail); - return state; -} - -/* there needs to be support for Unicode here, - * unless we pretend that we can redefine the MD-5 - * algorithm for multi-byte characters (perhaps - * by adding every four 16-bit characters and - * shortening the sum to 32 bits). Otherwise - * I suggest performing MD-5 as if every character - * was two bytes--e.g., 0040 0025 = @%--but then - * how will an ordinary MD-5 sum be matched? - * There is no way to standardize text to something - * like UTF-8 before transformation; speed cost is - * utterly prohibitive. The JavaScript standard - * itself needs to look at this: it should start - * providing access to strings as preformed UTF-8 - * 8-bit unsigned value arrays. - */ -function md5blk(s) { - /* I figured global was faster. */ - var md5blks = [], - i; /* Andy King said do it this way. */ - for (i = 0; i < 64; i += 4) { - md5blks[i >> 2] = - s.charCodeAt(i) + - (s.charCodeAt(i + 1) << 8) + - (s.charCodeAt(i + 2) << 16) + - (s.charCodeAt(i + 3) << 24); - } - return md5blks; -} - -var hex_chr = "0123456789abcdef".split(""); - -function rhex(n) { - var s = "", - j = 0; - for (; j < 4; j++) - s += hex_chr[(n >> (j * 8 + 4)) & 0x0f] + hex_chr[(n >> (j * 8)) & 0x0f]; - return s; -} - -function hex(x) { - for (var i = 0; i < x.length; i++) x[i] = rhex(x[i]); - return x.join(""); -} - -// Converts a 4-byte number to byte string -function singleToByteString(n) { - return String.fromCharCode( - (n & 0xff) >> 0, - (n & 0xff00) >> 8, - (n & 0xff0000) >> 16, - (n & 0xff000000) >> 24 - ); -} - -// Converts an array of numbers to a byte string -function toByteString(x) { - return x.map(singleToByteString).join(""); -} - -// Returns the MD5 hash as a byte string -function md5Bin(s) { - return toByteString(md51(s)); -} - -// Returns MD5 hash as a hex string -function md5(s) { - return hex(md51(s)); -} - -var md5Check = md5("hello") != "5d41402abc4b2a76b9719d911017c592"; - -function add32(a, b) { - if (md5Check) { - /* if the md5Check does not match - the expected value, we're dealing - with an old browser and need - this function. */ - var lsw = (a & 0xffff) + (b & 0xffff), - msw = (a >> 16) + (b >> 16) + (lsw >> 16); - return (msw << 16) | (lsw & 0xffff); - } else { - /* this function is much faster, - so if possible we use it. Some IEs - are the only ones I know of that - need the idiotic second function, - generated by an if clause. */ - return (a + b) & 0xffffffff; - } -} - -/** - * @license - * FPDF is released under a permissive license: there is no usage restriction. - * You may embed it freely in your application (commercial or not), with or - * without modifications. - * - * Reference: http://www.fpdf.org/en/script/script37.php - */ - -function repeat(str, num) { - return new Array(num + 1).join(str); -} - -/** - * Converts a byte string to a hex string - * - * @name rc4 - * @function - * @param {string} key Byte string of encryption key - * @param {string} data Byte string of data to be encrypted - * @returns {string} Encrypted string - */ -function rc4(key, data) { - var lastKey, lastState; - if (key !== lastKey) { - var k = repeat(key, ((256 / key.length) >> 0) + 1); - var state = []; - for (var i = 0; i < 256; i++) { - state[i] = i; - } - var j = 0; - for (var i = 0; i < 256; i++) { - var t = state[i]; - j = (j + t + k.charCodeAt(i)) % 256; - state[i] = state[j]; - state[j] = t; - } - lastKey = key; - lastState = state; - } else { - state = lastState; - } - var length = data.length; - var a = 0; - var b = 0; - var out = ""; - for (var i = 0; i < length; i++) { - a = (a + 1) % 256; - t = state[a]; - b = (b + t) % 256; - state[a] = state[b]; - state[b] = t; - k = state[(state[a] + state[b]) % 256]; - out += String.fromCharCode(data.charCodeAt(i) ^ k); - } - return out; -} - -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - * Author: Owen Leong (@owenl131) - * Date: 15 Oct 2020 - * References: - * https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt - * https://github.com/foliojs/pdfkit/blob/master/lib/security.js - * http://www.fpdf.org/en/script/script37.php - */ - -var permissionOptions = { - print: 4, - modify: 8, - copy: 16, - "annot-forms": 32 -}; - -/** - * Initializes encryption settings - * - * @name constructor - * @function - * @param {Array} permissions Permissions allowed for user, "print", "modify", "copy" and "annot-forms". - * @param {String} userPassword Permissions apply to this user. Leaving this empty means the document - * is not password protected but viewer has the above permissions. - * @param {String} ownerPassword Owner has full functionalities to the file. - * @param {String} fileId As hex string, should be same as the file ID in the trailer. - * @example - * var security = new PDFSecurity(["print"]) - */ -function PDFSecurity(permissions, userPassword, ownerPassword, fileId) { - this.v = 1; // algorithm 1, future work can add in more recent encryption schemes - this.r = 2; // revision 2 - - // set flags for what functionalities the user can access - let protection = 192; - permissions.forEach(function(perm) { - if (typeof permissionOptions.perm !== "undefined") { - throw new Error("Invalid permission: " + perm); - } - protection += permissionOptions[perm]; - }); - - // padding is used to pad the passwords to 32 bytes, also is hashed and stored in the final PDF - this.padding = - "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" + - "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A"; - let paddedUserPassword = (userPassword + this.padding).substr(0, 32); - let paddedOwnerPassword = (ownerPassword + this.padding).substr(0, 32); - - this.O = this.processOwnerPassword(paddedUserPassword, paddedOwnerPassword); - this.P = -((protection ^ 255) + 1); - this.encryptionKey = md5Bin( - paddedUserPassword + - this.O + - this.lsbFirstWord(this.P) + - this.hexToBytes(fileId) - ).substr(0, 5); - this.U = rc4(this.encryptionKey, this.padding); -} - -/** - * Breaks down a 4-byte number into its individual bytes, with the least significant bit first - * - * @name lsbFirstWord - * @function - * @param {number} data 32-bit number - * @returns {Array} - */ -PDFSecurity.prototype.lsbFirstWord = function(data) { - return String.fromCharCode( - (data >> 0) & 0xff, - (data >> 8) & 0xff, - (data >> 16) & 0xff, - (data >> 24) & 0xff - ); -}; - -/** - * Converts a byte string to a hex string - * - * @name toHexString - * @function - * @param {String} byteString Byte string - * @returns {String} - */ -PDFSecurity.prototype.toHexString = function(byteString) { - return byteString - .split("") - .map(function(byte) { - return ("0" + (byte.charCodeAt(0) & 0xff).toString(16)).slice(-2); - }) - .join(""); -}; - -/** - * Converts a hex string to a byte string - * - * @name hexToBytes - * @function - * @param {String} hex Hex string - * @returns {String} - */ -PDFSecurity.prototype.hexToBytes = function(hex) { - for (var bytes = [], c = 0; c < hex.length; c += 2) - bytes.push(String.fromCharCode(parseInt(hex.substr(c, 2), 16))); - return bytes.join(""); -}; - -/** - * Computes the 'O' field in the encryption dictionary - * - * @name processOwnerPassword - * @function - * @param {String} paddedUserPassword Byte string of padded user password - * @param {String} paddedOwnerPassword Byte string of padded owner password - * @returns {String} - */ -PDFSecurity.prototype.processOwnerPassword = function( - paddedUserPassword, - paddedOwnerPassword -) { - let key = md5Bin(paddedOwnerPassword).substr(0, 5); - return rc4(key, paddedUserPassword); -}; - -/** - * Returns an encryptor function which can take in a byte string and returns the encrypted version - * - * @name encryptor - * @function - * @param {number} objectId - * @param {number} generation Not sure what this is for, you can set it to 0 - * @returns {Function} - * @example - * out("stream"); - * encryptor = security.encryptor(object.id, 0); - * out(encryptor(data)); - * out("endstream"); - */ -PDFSecurity.prototype.encryptor = function(objectId, generation) { - let key = md5Bin( - this.encryptionKey + - String.fromCharCode( - objectId & 0xff, - (objectId >> 8) & 0xff, - (objectId >> 16) & 0xff, - generation & 0xff, - (generation >> 8) & 0xff - ) - ).substr(0, 10); - return function(data) { - return rc4(key, data); - }; -}; - -/** - * Convert string to `PDF Name Object`. - * Detail: PDF Reference 1.3 - Chapter 3.2.4 Name Object - * @param str - */ -function toPDFName(str) { - // eslint-disable-next-line no-control-regex - if (/[^\u0000-\u00ff]/.test(str)) { - // non ascii string - throw new Error( - "Invalid PDF Name Object: " + str + ", Only accept ASCII characters." - ); - } - var result = "", - strLength = str.length; - for (var i = 0; i < strLength; i++) { - var charCode = str.charCodeAt(i); - if ( - charCode < 0x21 || - charCode === 0x23 /* # */ || - charCode === 0x25 /* % */ || - charCode === 0x28 /* ( */ || - charCode === 0x29 /* ) */ || - charCode === 0x2f /* / */ || - charCode === 0x3c /* < */ || - charCode === 0x3e /* > */ || - charCode === 0x5b /* [ */ || - charCode === 0x5d /* ] */ || - charCode === 0x7b /* { */ || - charCode === 0x7d /* } */ || - charCode > 0x7e - ) { - // Char CharCode hexStr paddingHexStr Result - // "\t" 9 9 09 #09 - // " " 32 20 20 #20 - // "©" 169 a9 a9 #a9 - var hexStr = charCode.toString(16), - paddingHexStr = ("0" + hexStr).slice(-2); - - result += "#" + paddingHexStr; - } else { - // Other ASCII printable characters between 0x21 <= X <= 0x7e - result += str[i]; - } - } - return result; -} - -/* eslint-disable no-console */ -/** - * jsPDF's Internal PubSub Implementation. - * Backward compatible rewritten on 2014 by - * Diego Casorran, https://github.com/diegocr - * - * @class - * @name PubSub - * @ignore - */ -function PubSub(context) { - if (typeof context !== "object") { - throw new Error( - "Invalid Context passed to initialize PubSub (jsPDF-module)" - ); - } - var topics = {}; - - this.subscribe = function(topic, callback, once) { - once = once || false; - if ( - typeof topic !== "string" || - typeof callback !== "function" || - typeof once !== "boolean" - ) { - throw new Error( - "Invalid arguments passed to PubSub.subscribe (jsPDF-module)" - ); - } - - if (!topics.hasOwnProperty(topic)) { - topics[topic] = {}; - } - - var token = Math.random().toString(35); - topics[topic][token] = [callback, !!once]; - - return token; - }; - - this.unsubscribe = function(token) { - for (var topic in topics) { - if (topics[topic][token]) { - delete topics[topic][token]; - if (Object.keys(topics[topic]).length === 0) { - delete topics[topic]; - } - return true; - } - } - return false; - }; - - this.publish = function(topic) { - if (topics.hasOwnProperty(topic)) { - var args = Array.prototype.slice.call(arguments, 1), - tokens = []; - - for (var token in topics[topic]) { - var sub = topics[topic][token]; - try { - sub[0].apply(context, args); - } catch (ex) { - if (globalObject.console) { - console.error("jsPDF PubSub Error", ex.message, ex); - } - } - if (sub[1]) tokens.push(token); - } - if (tokens.length) tokens.forEach(this.unsubscribe); - } - }; - - this.getTopics = function() { - return topics; - }; -} - -function GState(parameters) { - if (!(this instanceof GState)) { - return new GState(parameters); - } - - /** - * @name GState#opacity - * @type {any} - */ - /** - * @name GState#stroke-opacity - * @type {any} - */ - var supported = "opacity,stroke-opacity".split(","); - for (var p in parameters) { - if (parameters.hasOwnProperty(p) && supported.indexOf(p) >= 0) { - this[p] = parameters[p]; - } - } - /** - * @name GState#id - * @type {string} - */ - this.id = ""; // set by addGState() - /** - * @name GState#objectNumber - * @type {number} - */ - this.objectNumber = -1; // will be set by putGState() -} - -GState.prototype.equals = function equals(other) { - var ignore = "id,objectNumber,equals"; - var p; - if (!other || typeof other !== typeof this) return false; - var count = 0; - for (p in this) { - if (ignore.indexOf(p) >= 0) continue; - if (this.hasOwnProperty(p) && !other.hasOwnProperty(p)) return false; - if (this[p] !== other[p]) return false; - count++; - } - for (p in other) { - if (other.hasOwnProperty(p) && ignore.indexOf(p) < 0) count--; - } - return count === 0; -}; - -function Pattern(gState, matrix) { - this.gState = gState; - this.matrix = matrix; - - this.id = ""; // set by addPattern() - this.objectNumber = -1; // will be set by putPattern() -} - -function ShadingPattern(type, coords, colors, gState, matrix) { - if (!(this instanceof ShadingPattern)) { - return new ShadingPattern(type, coords, colors, gState, matrix); - } - - // see putPattern() for information how they are realized - this.type = type === "axial" ? 2 : 3; - this.coords = coords; - this.colors = colors; - - Pattern.call(this, gState, matrix); -} - -function TilingPattern(boundingBox, xStep, yStep, gState, matrix) { - if (!(this instanceof TilingPattern)) { - return new TilingPattern(boundingBox, xStep, yStep, gState, matrix); - } - - this.boundingBox = boundingBox; - this.xStep = xStep; - this.yStep = yStep; - - this.stream = ""; // set by endTilingPattern(); - - this.cloneIndex = 0; - - Pattern.call(this, gState, matrix); -} - -/** - * Creates new jsPDF document object instance. - * @name jsPDF - * @class - * @param {Object} [options] - Collection of settings initializing the jsPDF-instance - * @param {string} [options.orientation=portrait] - Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l").
- * @param {string} [options.unit=mm] Measurement unit (base unit) to be used when coordinates are specified.
- * Possible values are "pt" (points), "mm", "cm", "in", "px", "pc", "em" or "ex". Note that in order to get the correct scaling for "px" - * units, you need to enable the hotfix "px_scaling" by setting options.hotfixes = ["px_scaling"]. - * @param {string/Array} [options.format=a4] The format of the first page. Can be:
  • a0 - a10
  • b0 - b10
  • c0 - c10
  • dl
  • letter
  • government-letter
  • legal
  • junior-legal
  • ledger
  • tabloid
  • credit-card

- * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89] - * @param {boolean} [options.putOnlyUsedFonts=false] Only put fonts into the PDF, which were used. - * @param {boolean} [options.compress=false] Compress the generated PDF. - * @param {number} [options.precision=16] Precision of the element-positions. - * @param {number} [options.userUnit=1.0] Not to be confused with the base unit. Please inform yourself before you use it. - * @param {string[]} [options.hotfixes] An array of strings to enable hotfixes such as correct pixel scaling. - * @param {Object} [options.encryption] - * @param {string} [options.encryption.userPassword] Password for the user bound by the given permissions list. - * @param {string} [options.encryption.ownerPassword] Both userPassword and ownerPassword should be set for proper authentication. - * @param {string[]} [options.encryption.userPermissions] Array of permissions "print", "modify", "copy", "annot-forms", accessible by the user. - * @param {number|"smart"} [options.floatPrecision=16] - * @returns {jsPDF} jsPDF-instance - * @description - * ``` - * { - * orientation: 'p', - * unit: 'mm', - * format: 'a4', - * putOnlyUsedFonts:true, - * floatPrecision: 16 // or "smart", default is 16 - * } - * ``` - * - * @constructor - */ -function jsPDF(options) { - var orientation = typeof arguments[0] === "string" ? arguments[0] : "p"; - var unit = arguments[1]; - var format = arguments[2]; - var compressPdf = arguments[3]; - var filters = []; - var userUnit = 1.0; - var precision; - var floatPrecision = 16; - var defaultPathOperation = "S"; - var encryptionOptions = null; - - options = options || {}; - - if (typeof options === "object") { - orientation = options.orientation; - unit = options.unit || unit; - format = options.format || format; - compressPdf = options.compress || options.compressPdf || compressPdf; - encryptionOptions = options.encryption || null; - if (encryptionOptions !== null) { - encryptionOptions.userPassword = encryptionOptions.userPassword || ""; - encryptionOptions.ownerPassword = encryptionOptions.ownerPassword || ""; - encryptionOptions.userPermissions = - encryptionOptions.userPermissions || []; - } - userUnit = - typeof options.userUnit === "number" ? Math.abs(options.userUnit) : 1.0; - if (typeof options.precision !== "undefined") { - precision = options.precision; - } - if (typeof options.floatPrecision !== "undefined") { - floatPrecision = options.floatPrecision; - } - defaultPathOperation = options.defaultPathOperation || "S"; - } - - filters = - options.filters || (compressPdf === true ? ["FlateEncode"] : filters); - - unit = unit || "mm"; - orientation = ("" + (orientation || "P")).toLowerCase(); - var putOnlyUsedFonts = options.putOnlyUsedFonts || false; - var usedFonts = {}; - - var API = { - internal: {}, - __private__: {} - }; - - API.__private__.PubSub = PubSub; - - var pdfVersion = "1.3"; - var getPdfVersion = (API.__private__.getPdfVersion = function() { - return pdfVersion; - }); - - API.__private__.setPdfVersion = function(value) { - pdfVersion = value; - }; - - // Size in pt of various paper formats - var pageFormats = { - a0: [2383.94, 3370.39], - a1: [1683.78, 2383.94], - a2: [1190.55, 1683.78], - a3: [841.89, 1190.55], - a4: [595.28, 841.89], - a5: [419.53, 595.28], - a6: [297.64, 419.53], - a7: [209.76, 297.64], - a8: [147.4, 209.76], - a9: [104.88, 147.4], - a10: [73.7, 104.88], - b0: [2834.65, 4008.19], - b1: [2004.09, 2834.65], - b2: [1417.32, 2004.09], - b3: [1000.63, 1417.32], - b4: [708.66, 1000.63], - b5: [498.9, 708.66], - b6: [354.33, 498.9], - b7: [249.45, 354.33], - b8: [175.75, 249.45], - b9: [124.72, 175.75], - b10: [87.87, 124.72], - c0: [2599.37, 3676.54], - c1: [1836.85, 2599.37], - c2: [1298.27, 1836.85], - c3: [918.43, 1298.27], - c4: [649.13, 918.43], - c5: [459.21, 649.13], - c6: [323.15, 459.21], - c7: [229.61, 323.15], - c8: [161.57, 229.61], - c9: [113.39, 161.57], - c10: [79.37, 113.39], - dl: [311.81, 623.62], - letter: [612, 792], - "government-letter": [576, 756], - legal: [612, 1008], - "junior-legal": [576, 360], - ledger: [1224, 792], - tabloid: [792, 1224], - "credit-card": [153, 243] - }; - - API.__private__.getPageFormats = function() { - return pageFormats; - }; - - var getPageFormat = (API.__private__.getPageFormat = function(value) { - return pageFormats[value]; - }); - - format = format || "a4"; - - var ApiMode = { - COMPAT: "compat", - ADVANCED: "advanced" - }; - var apiMode = ApiMode.COMPAT; - - function advancedAPI() { - // prepend global change of basis matrix - // (Now, instead of converting every coordinate to the pdf coordinate system, we apply a matrix - // that does this job for us (however, texts, images and similar objects must be drawn bottom up)) - this.saveGraphicsState(); - out( - new Matrix( - scaleFactor, - 0, - 0, - -scaleFactor, - 0, - getPageHeight() * scaleFactor - ).toString() + " cm" - ); - this.setFontSize(this.getFontSize() / scaleFactor); - - // The default in MrRio's implementation is "S" (stroke), whereas the default in the yWorks implementation - // was "n" (none). Although this has nothing to do with transforms, we should use the API switch here. - defaultPathOperation = "n"; - - apiMode = ApiMode.ADVANCED; - } - - function compatAPI() { - this.restoreGraphicsState(); - defaultPathOperation = "S"; - apiMode = ApiMode.COMPAT; - } - - /** - * @function combineFontStyleAndFontWeight - * @param {string} fontStyle Fontstyle or variant. Example: "italic". - * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400 - * @returns {string} - * @private - */ - var combineFontStyleAndFontWeight = (API.__private__.combineFontStyleAndFontWeight = function( - fontStyle, - fontWeight - ) { - if ( - (fontStyle == "bold" && fontWeight == "normal") || - (fontStyle == "bold" && fontWeight == 400) || - (fontStyle == "normal" && fontWeight == "italic") || - (fontStyle == "bold" && fontWeight == "italic") - ) { - throw new Error("Invalid Combination of fontweight and fontstyle"); - } - if (fontWeight) { - fontStyle = - fontWeight == 400 || fontWeight === "normal" - ? fontStyle === "italic" - ? "italic" - : "normal" - : (fontWeight == 700 || fontWeight === "bold") && - fontStyle === "normal" - ? "bold" - : (fontWeight == 700 ? "bold" : fontWeight) + "" + fontStyle; - } - return fontStyle; - }); - - /** - * @callback ApiSwitchBody - * @param {jsPDF} pdf - */ - - /** - * For compatibility reasons jsPDF offers two API modes which differ in the way they convert between the the usual - * screen coordinates and the PDF coordinate system. - * - "compat": Offers full compatibility across all plugins but does not allow arbitrary transforms - * - "advanced": Allows arbitrary transforms and more advanced features like pattern fills. Some plugins might - * not support this mode, though. - * Initial mode is "compat". - * - * You can either provide a callback to the body argument, which means that jsPDF will automatically switch back to - * the original API mode afterwards; or you can omit the callback and switch back manually using {@link compatAPI}. - * - * Note, that the calls to {@link saveGraphicsState} and {@link restoreGraphicsState} need to be balanced within the - * callback or between calls of this method and its counterpart {@link compatAPI}. Calls to {@link beginFormObject} - * or {@link beginTilingPattern} need to be closed by their counterparts before switching back to "compat" API mode. - * - * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. - * The API mode will be switched back automatically afterwards. - * @returns {jsPDF} - * @memberof jsPDF# - * @name advancedAPI - */ - API.advancedAPI = function(body) { - var doSwitch = apiMode === ApiMode.COMPAT; - - if (doSwitch) { - advancedAPI.call(this); - } - - if (typeof body !== "function") { - return this; - } - - body(this); - - if (doSwitch) { - compatAPI.call(this); - } - - return this; - }; - - /** - * Switches to "compat" API mode. See {@link advancedAPI} for more details. - * - * @param {ApiSwitchBody=} body When provided, this callback will be called after the API mode has been switched. - * The API mode will be switched back automatically afterwards. - * @return {jsPDF} - * @memberof jsPDF# - * @name compatApi - */ - API.compatAPI = function(body) { - var doSwitch = apiMode === ApiMode.ADVANCED; - - if (doSwitch) { - compatAPI.call(this); - } - - if (typeof body !== "function") { - return this; - } - - body(this); - - if (doSwitch) { - advancedAPI.call(this); - } - - return this; - }; - - /** - * @return {boolean} True iff the current API mode is "advanced". See {@link advancedAPI}. - * @memberof jsPDF# - * @name isAdvancedAPI - */ - API.isAdvancedAPI = function() { - return apiMode === ApiMode.ADVANCED; - }; - - var advancedApiModeTrap = function(methodName) { - if (apiMode !== ApiMode.ADVANCED) { - throw new Error( - methodName + - " is only available in 'advanced' API mode. " + - "You need to call advancedAPI() first." - ); - } - }; - - var roundToPrecision = (API.roundToPrecision = API.__private__.roundToPrecision = function( - number, - parmPrecision - ) { - var tmpPrecision = precision || parmPrecision; - if (isNaN(number) || isNaN(tmpPrecision)) { - throw new Error("Invalid argument passed to jsPDF.roundToPrecision"); - } - return number.toFixed(tmpPrecision).replace(/0+$/, ""); - }); - - // high precision float - var hpf; - if (typeof floatPrecision === "number") { - hpf = API.hpf = API.__private__.hpf = function(number) { - if (isNaN(number)) { - throw new Error("Invalid argument passed to jsPDF.hpf"); - } - return roundToPrecision(number, floatPrecision); - }; - } else if (floatPrecision === "smart") { - hpf = API.hpf = API.__private__.hpf = function(number) { - if (isNaN(number)) { - throw new Error("Invalid argument passed to jsPDF.hpf"); - } - if (number > -1 && number < 1) { - return roundToPrecision(number, 16); - } else { - return roundToPrecision(number, 5); - } - }; - } else { - hpf = API.hpf = API.__private__.hpf = function(number) { - if (isNaN(number)) { - throw new Error("Invalid argument passed to jsPDF.hpf"); - } - return roundToPrecision(number, 16); - }; - } - var f2 = (API.f2 = API.__private__.f2 = function(number) { - if (isNaN(number)) { - throw new Error("Invalid argument passed to jsPDF.f2"); - } - return roundToPrecision(number, 2); - }); - - var f3 = (API.__private__.f3 = function(number) { - if (isNaN(number)) { - throw new Error("Invalid argument passed to jsPDF.f3"); - } - return roundToPrecision(number, 3); - }); - - var scale = (API.scale = API.__private__.scale = function(number) { - if (isNaN(number)) { - throw new Error("Invalid argument passed to jsPDF.scale"); - } - if (apiMode === ApiMode.COMPAT) { - return number * scaleFactor; - } else if (apiMode === ApiMode.ADVANCED) { - return number; - } - }); - - var transformY = function(y) { - if (apiMode === ApiMode.COMPAT) { - return getPageHeight() - y; - } else if (apiMode === ApiMode.ADVANCED) { - return y; - } - }; - - var transformScaleY = function(y) { - return scale(transformY(y)); - }; - - /** - * @name setPrecision - * @memberof jsPDF# - * @function - * @instance - * @param {string} precision - * @returns {jsPDF} - */ - API.__private__.setPrecision = API.setPrecision = function(value) { - if (typeof parseInt(value, 10) === "number") { - precision = parseInt(value, 10); - } - }; - - var fileId = "00000000000000000000000000000000"; - - var getFileId = (API.__private__.getFileId = function() { - return fileId; - }); - - var setFileId = (API.__private__.setFileId = function(value) { - if (typeof value !== "undefined" && /^[a-fA-F0-9]{32}$/.test(value)) { - fileId = value.toUpperCase(); - } else { - fileId = fileId - .split("") - .map(function() { - return "ABCDEF0123456789".charAt(Math.floor(Math.random() * 16)); - }) - .join(""); - } - - if (encryptionOptions !== null) { - encryption = new PDFSecurity( - encryptionOptions.userPermissions, - encryptionOptions.userPassword, - encryptionOptions.ownerPassword, - fileId - ); - } - return fileId; - }); - - /** - * @name setFileId - * @memberof jsPDF# - * @function - * @instance - * @param {string} value GUID. - * @returns {jsPDF} - */ - API.setFileId = function(value) { - setFileId(value); - return this; - }; - - /** - * @name getFileId - * @memberof jsPDF# - * @function - * @instance - * - * @returns {string} GUID. - */ - API.getFileId = function() { - return getFileId(); - }; - - var creationDate; - - var convertDateToPDFDate = (API.__private__.convertDateToPDFDate = function( - parmDate - ) { - var result = ""; - var tzoffset = parmDate.getTimezoneOffset(), - tzsign = tzoffset < 0 ? "+" : "-", - tzhour = Math.floor(Math.abs(tzoffset / 60)), - tzmin = Math.abs(tzoffset % 60), - timeZoneString = [tzsign, padd2(tzhour), "'", padd2(tzmin), "'"].join(""); - - result = [ - "D:", - parmDate.getFullYear(), - padd2(parmDate.getMonth() + 1), - padd2(parmDate.getDate()), - padd2(parmDate.getHours()), - padd2(parmDate.getMinutes()), - padd2(parmDate.getSeconds()), - timeZoneString - ].join(""); - return result; - }); - - var convertPDFDateToDate = (API.__private__.convertPDFDateToDate = function( - parmPDFDate - ) { - var year = parseInt(parmPDFDate.substr(2, 4), 10); - var month = parseInt(parmPDFDate.substr(6, 2), 10) - 1; - var date = parseInt(parmPDFDate.substr(8, 2), 10); - var hour = parseInt(parmPDFDate.substr(10, 2), 10); - var minutes = parseInt(parmPDFDate.substr(12, 2), 10); - var seconds = parseInt(parmPDFDate.substr(14, 2), 10); - // var timeZoneHour = parseInt(parmPDFDate.substr(16, 2), 10); - // var timeZoneMinutes = parseInt(parmPDFDate.substr(20, 2), 10); - - var resultingDate = new Date(year, month, date, hour, minutes, seconds, 0); - return resultingDate; - }); - - var setCreationDate = (API.__private__.setCreationDate = function(date) { - var tmpCreationDateString; - var regexPDFCreationDate = /^D:(20[0-2][0-9]|203[0-7]|19[7-9][0-9])(0[0-9]|1[0-2])([0-2][0-9]|3[0-1])(0[0-9]|1[0-9]|2[0-3])(0[0-9]|[1-5][0-9])(0[0-9]|[1-5][0-9])(\+0[0-9]|\+1[0-4]|-0[0-9]|-1[0-1])'(0[0-9]|[1-5][0-9])'?$/; - if (typeof date === "undefined") { - date = new Date(); - } - - if (date instanceof Date) { - tmpCreationDateString = convertDateToPDFDate(date); - } else if (regexPDFCreationDate.test(date)) { - tmpCreationDateString = date; - } else { - throw new Error("Invalid argument passed to jsPDF.setCreationDate"); - } - creationDate = tmpCreationDateString; - return creationDate; - }); - - var getCreationDate = (API.__private__.getCreationDate = function(type) { - var result = creationDate; - if (type === "jsDate") { - result = convertPDFDateToDate(creationDate); - } - return result; - }); - - /** - * @name setCreationDate - * @memberof jsPDF# - * @function - * @instance - * @param {Object} date - * @returns {jsPDF} - */ - API.setCreationDate = function(date) { - setCreationDate(date); - return this; - }; - - /** - * @name getCreationDate - * @memberof jsPDF# - * @function - * @instance - * @param {Object} type - * @returns {Object} - */ - API.getCreationDate = function(type) { - return getCreationDate(type); - }; - - var padd2 = (API.__private__.padd2 = function(number) { - return ("0" + parseInt(number)).slice(-2); - }); - - var padd2Hex = (API.__private__.padd2Hex = function(hexString) { - hexString = hexString.toString(); - return ("00" + hexString).substr(hexString.length); - }); - - var objectNumber = 0; // 'n' Current object number - var offsets = []; // List of offsets. Activated and reset by buildDocument(). Pupulated by various calls buildDocument makes. - var content = []; - var contentLength = 0; - var additionalObjects = []; - - var pages = []; - var currentPage; - var hasCustomDestination = false; - var outputDestination = content; - - var resetDocument = function() { - //reset fields relevant for objectNumber generation and xref. - objectNumber = 0; - contentLength = 0; - content = []; - offsets = []; - additionalObjects = []; - - rootDictionaryObjId = newObjectDeferred(); - resourceDictionaryObjId = newObjectDeferred(); - }; - - API.__private__.setCustomOutputDestination = function(destination) { - hasCustomDestination = true; - outputDestination = destination; - }; - var setOutputDestination = function(destination) { - if (!hasCustomDestination) { - outputDestination = destination; - } - }; - - API.__private__.resetCustomOutputDestination = function() { - hasCustomDestination = false; - outputDestination = content; - }; - - var out = (API.__private__.out = function(string) { - string = string.toString(); - contentLength += string.length + 1; - outputDestination.push(string); - - return outputDestination; - }); - - var write = (API.__private__.write = function(value) { - return out( - arguments.length === 1 - ? value.toString() - : Array.prototype.join.call(arguments, " ") - ); - }); - - var getArrayBuffer = (API.__private__.getArrayBuffer = function(data) { - var len = data.length, - ab = new ArrayBuffer(len), - u8 = new Uint8Array(ab); - - while (len--) u8[len] = data.charCodeAt(len); - return ab; - }); - - var standardFonts = [ - ["Helvetica", "helvetica", "normal", "WinAnsiEncoding"], - ["Helvetica-Bold", "helvetica", "bold", "WinAnsiEncoding"], - ["Helvetica-Oblique", "helvetica", "italic", "WinAnsiEncoding"], - ["Helvetica-BoldOblique", "helvetica", "bolditalic", "WinAnsiEncoding"], - ["Courier", "courier", "normal", "WinAnsiEncoding"], - ["Courier-Bold", "courier", "bold", "WinAnsiEncoding"], - ["Courier-Oblique", "courier", "italic", "WinAnsiEncoding"], - ["Courier-BoldOblique", "courier", "bolditalic", "WinAnsiEncoding"], - ["Times-Roman", "times", "normal", "WinAnsiEncoding"], - ["Times-Bold", "times", "bold", "WinAnsiEncoding"], - ["Times-Italic", "times", "italic", "WinAnsiEncoding"], - ["Times-BoldItalic", "times", "bolditalic", "WinAnsiEncoding"], - ["ZapfDingbats", "zapfdingbats", "normal", null], - ["Symbol", "symbol", "normal", null] - ]; - - API.__private__.getStandardFonts = function() { - return standardFonts; - }; - - var activeFontSize = options.fontSize || 16; - - /** - * Sets font size for upcoming text elements. - * - * @param {number} size Font size in points. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setFontSize - */ - API.__private__.setFontSize = API.setFontSize = function(size) { - if (apiMode === ApiMode.ADVANCED) { - activeFontSize = size / scaleFactor; - } else { - activeFontSize = size; - } - return this; - }; - - /** - * Gets the fontsize for upcoming text elements. - * - * @function - * @instance - * @returns {number} - * @memberof jsPDF# - * @name getFontSize - */ - var getFontSize = (API.__private__.getFontSize = API.getFontSize = function() { - if (apiMode === ApiMode.COMPAT) { - return activeFontSize; - } else { - return activeFontSize * scaleFactor; - } - }); - - var R2L = options.R2L || false; - - /** - * Set value of R2L functionality. - * - * @param {boolean} value - * @function - * @instance - * @returns {jsPDF} jsPDF-instance - * @memberof jsPDF# - * @name setR2L - */ - API.__private__.setR2L = API.setR2L = function(value) { - R2L = value; - return this; - }; - - /** - * Get value of R2L functionality. - * - * @function - * @instance - * @returns {boolean} jsPDF-instance - * @memberof jsPDF# - * @name getR2L - */ - API.__private__.getR2L = API.getR2L = function() { - return R2L; - }; - - var zoomMode; // default: 1; - - var setZoomMode = (API.__private__.setZoomMode = function(zoom) { - var validZoomModes = [ - undefined, - null, - "fullwidth", - "fullheight", - "fullpage", - "original" - ]; - - if (/^(?:\d+\.\d*|\d*\.\d+|\d+)%$/.test(zoom)) { - zoomMode = zoom; - } else if (!isNaN(zoom)) { - zoomMode = parseInt(zoom, 10); - } else if (validZoomModes.indexOf(zoom) !== -1) { - zoomMode = zoom; - } else { - throw new Error( - 'zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "' + - zoom + - '" is not recognized.' - ); - } - }); - - API.__private__.getZoomMode = function() { - return zoomMode; - }; - - var pageMode; // default: 'UseOutlines'; - var setPageMode = (API.__private__.setPageMode = function(pmode) { - var validPageModes = [ - undefined, - null, - "UseNone", - "UseOutlines", - "UseThumbs", - "FullScreen" - ]; - - if (validPageModes.indexOf(pmode) == -1) { - throw new Error( - 'Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "' + - pmode + - '" is not recognized.' - ); - } - pageMode = pmode; - }); - - API.__private__.getPageMode = function() { - return pageMode; - }; - - var layoutMode; // default: 'continuous'; - var setLayoutMode = (API.__private__.setLayoutMode = function(layout) { - var validLayoutModes = [ - undefined, - null, - "continuous", - "single", - "twoleft", - "tworight", - "two" - ]; - - if (validLayoutModes.indexOf(layout) == -1) { - throw new Error( - 'Layout mode must be one of continuous, single, twoleft, tworight. "' + - layout + - '" is not recognized.' - ); - } - layoutMode = layout; - }); - - API.__private__.getLayoutMode = function() { - return layoutMode; - }; - - /** - * Set the display mode options of the page like zoom and layout. - * - * @name setDisplayMode - * @memberof jsPDF# - * @function - * @instance - * @param {integer|String} zoom You can pass an integer or percentage as - * a string. 2 will scale the document up 2x, '200%' will scale up by the - * same amount. You can also set it to 'fullwidth', 'fullheight', - * 'fullpage', or 'original'. - * - * Only certain PDF readers support this, such as Adobe Acrobat. - * - * @param {string} layout Layout mode can be: 'continuous' - this is the - * default continuous scroll. 'single' - the single page mode only shows one - * page at a time. 'twoleft' - two column left mode, first page starts on - * the left, and 'tworight' - pages are laid out in two columns, with the - * first page on the right. This would be used for books. - * @param {string} pmode 'UseOutlines' - it shows the - * outline of the document on the left. 'UseThumbs' - shows thumbnails along - * the left. 'FullScreen' - prompts the user to enter fullscreen mode. - * - * @returns {jsPDF} - */ - API.__private__.setDisplayMode = API.setDisplayMode = function( - zoom, - layout, - pmode - ) { - setZoomMode(zoom); - setLayoutMode(layout); - setPageMode(pmode); - return this; - }; - - var documentProperties = { - title: "", - subject: "", - author: "", - keywords: "", - creator: "" - }; - - API.__private__.getDocumentProperty = function(key) { - if (Object.keys(documentProperties).indexOf(key) === -1) { - throw new Error("Invalid argument passed to jsPDF.getDocumentProperty"); - } - return documentProperties[key]; - }; - - API.__private__.getDocumentProperties = function() { - return documentProperties; - }; - - /** - * Adds a properties to the PDF document. - * - * @param {Object} A property_name-to-property_value object structure. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setDocumentProperties - */ - API.__private__.setDocumentProperties = API.setProperties = API.setDocumentProperties = function( - properties - ) { - // copying only those properties we can render. - for (var property in documentProperties) { - if (documentProperties.hasOwnProperty(property) && properties[property]) { - documentProperties[property] = properties[property]; - } - } - return this; - }; - - API.__private__.setDocumentProperty = function(key, value) { - if (Object.keys(documentProperties).indexOf(key) === -1) { - throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty"); - } - return (documentProperties[key] = value); - }; - - var fonts = {}; // collection of font objects, where key is fontKey - a dynamically created label for a given font. - var fontmap = {}; // mapping structure fontName > fontStyle > font key - performance layer. See addFont() - var activeFontKey; // will be string representing the KEY of the font as combination of fontName + fontStyle - var fontStateStack = []; // - var patterns = {}; // collection of pattern objects - var patternMap = {}; // see fonts - var gStates = {}; // collection of graphic state objects - var gStatesMap = {}; // see fonts - var activeGState = null; - var scaleFactor; // Scale factor - var page = 0; - var pagesContext = []; - var events = new PubSub(API); - var hotfixes = options.hotfixes || []; - - var renderTargets = {}; - var renderTargetMap = {}; - var renderTargetStack = []; - var pageX; - var pageY; - var pageMatrix; // only used for FormObjects - - /** - * A matrix object for 2D homogenous transformations:
- * | a b 0 |
- * | c d 0 |
- * | e f 1 |
- * pdf multiplies matrices righthand: v' = v x m1 x m2 x ... - * - * @class - * @name Matrix - * @param {number} sx - * @param {number} shy - * @param {number} shx - * @param {number} sy - * @param {number} tx - * @param {number} ty - * @constructor - */ - var Matrix = function(sx, shy, shx, sy, tx, ty) { - if (!(this instanceof Matrix)) { - return new Matrix(sx, shy, shx, sy, tx, ty); - } - - if (isNaN(sx)) sx = 1; - if (isNaN(shy)) shy = 0; - if (isNaN(shx)) shx = 0; - if (isNaN(sy)) sy = 1; - if (isNaN(tx)) tx = 0; - if (isNaN(ty)) ty = 0; - - this._matrix = [sx, shy, shx, sy, tx, ty]; - }; - - /** - * @name sx - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "sx", { - get: function() { - return this._matrix[0]; - }, - set: function(value) { - this._matrix[0] = value; - } - }); - - /** - * @name shy - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "shy", { - get: function() { - return this._matrix[1]; - }, - set: function(value) { - this._matrix[1] = value; - } - }); - - /** - * @name shx - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "shx", { - get: function() { - return this._matrix[2]; - }, - set: function(value) { - this._matrix[2] = value; - } - }); - - /** - * @name sy - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "sy", { - get: function() { - return this._matrix[3]; - }, - set: function(value) { - this._matrix[3] = value; - } - }); - - /** - * @name tx - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "tx", { - get: function() { - return this._matrix[4]; - }, - set: function(value) { - this._matrix[4] = value; - } - }); - - /** - * @name ty - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "ty", { - get: function() { - return this._matrix[5]; - }, - set: function(value) { - this._matrix[5] = value; - } - }); - - Object.defineProperty(Matrix.prototype, "a", { - get: function() { - return this._matrix[0]; - }, - set: function(value) { - this._matrix[0] = value; - } - }); - - Object.defineProperty(Matrix.prototype, "b", { - get: function() { - return this._matrix[1]; - }, - set: function(value) { - this._matrix[1] = value; - } - }); - - Object.defineProperty(Matrix.prototype, "c", { - get: function() { - return this._matrix[2]; - }, - set: function(value) { - this._matrix[2] = value; - } - }); - - Object.defineProperty(Matrix.prototype, "d", { - get: function() { - return this._matrix[3]; - }, - set: function(value) { - this._matrix[3] = value; - } - }); - - Object.defineProperty(Matrix.prototype, "e", { - get: function() { - return this._matrix[4]; - }, - set: function(value) { - this._matrix[4] = value; - } - }); - - Object.defineProperty(Matrix.prototype, "f", { - get: function() { - return this._matrix[5]; - }, - set: function(value) { - this._matrix[5] = value; - } - }); - - /** - * @name rotation - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "rotation", { - get: function() { - return Math.atan2(this.shx, this.sx); - } - }); - - /** - * @name scaleX - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "scaleX", { - get: function() { - return this.decompose().scale.sx; - } - }); - - /** - * @name scaleY - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "scaleY", { - get: function() { - return this.decompose().scale.sy; - } - }); - - /** - * @name isIdentity - * @memberof Matrix# - */ - Object.defineProperty(Matrix.prototype, "isIdentity", { - get: function() { - if (this.sx !== 1) { - return false; - } - if (this.shy !== 0) { - return false; - } - if (this.shx !== 0) { - return false; - } - if (this.sy !== 1) { - return false; - } - if (this.tx !== 0) { - return false; - } - if (this.ty !== 0) { - return false; - } - return true; - } - }); - - /** - * Join the Matrix Values to a String - * - * @function join - * @param {string} separator Specifies a string to separate each pair of adjacent elements of the array. The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If separator is an empty string, all elements are joined without any characters in between them. - * @returns {string} A string with all array elements joined. - * @memberof Matrix# - */ - Matrix.prototype.join = function(separator) { - return [this.sx, this.shy, this.shx, this.sy, this.tx, this.ty] - .map(hpf) - .join(separator); - }; - - /** - * Multiply the matrix with given Matrix - * - * @function multiply - * @param matrix - * @returns {Matrix} - * @memberof Matrix# - */ - Matrix.prototype.multiply = function(matrix) { - var sx = matrix.sx * this.sx + matrix.shy * this.shx; - var shy = matrix.sx * this.shy + matrix.shy * this.sy; - var shx = matrix.shx * this.sx + matrix.sy * this.shx; - var sy = matrix.shx * this.shy + matrix.sy * this.sy; - var tx = matrix.tx * this.sx + matrix.ty * this.shx + this.tx; - var ty = matrix.tx * this.shy + matrix.ty * this.sy + this.ty; - - return new Matrix(sx, shy, shx, sy, tx, ty); - }; - - /** - * @function decompose - * @memberof Matrix# - */ - Matrix.prototype.decompose = function() { - var a = this.sx; - var b = this.shy; - var c = this.shx; - var d = this.sy; - var e = this.tx; - var f = this.ty; - - var scaleX = Math.sqrt(a * a + b * b); - a /= scaleX; - b /= scaleX; - - var shear = a * c + b * d; - c -= a * shear; - d -= b * shear; - - var scaleY = Math.sqrt(c * c + d * d); - c /= scaleY; - d /= scaleY; - shear /= scaleY; - - if (a * d < b * c) { - a = -a; - b = -b; - shear = -shear; - scaleX = -scaleX; - } - - return { - scale: new Matrix(scaleX, 0, 0, scaleY, 0, 0), - translate: new Matrix(1, 0, 0, 1, e, f), - rotate: new Matrix(a, b, -b, a, 0, 0), - skew: new Matrix(1, 0, shear, 1, 0, 0) - }; - }; - - /** - * @function toString - * @memberof Matrix# - */ - Matrix.prototype.toString = function(parmPrecision) { - return this.join(" "); - }; - - /** - * @function inversed - * @memberof Matrix# - */ - Matrix.prototype.inversed = function() { - var a = this.sx, - b = this.shy, - c = this.shx, - d = this.sy, - e = this.tx, - f = this.ty; - - var quot = 1 / (a * d - b * c); - - var aInv = d * quot; - var bInv = -b * quot; - var cInv = -c * quot; - var dInv = a * quot; - var eInv = -aInv * e - cInv * f; - var fInv = -bInv * e - dInv * f; - - return new Matrix(aInv, bInv, cInv, dInv, eInv, fInv); - }; - - /** - * @function applyToPoint - * @memberof Matrix# - */ - Matrix.prototype.applyToPoint = function(pt) { - var x = pt.x * this.sx + pt.y * this.shx + this.tx; - var y = pt.x * this.shy + pt.y * this.sy + this.ty; - return new Point(x, y); - }; - - /** - * @function applyToRectangle - * @memberof Matrix# - */ - Matrix.prototype.applyToRectangle = function(rect) { - var pt1 = this.applyToPoint(rect); - var pt2 = this.applyToPoint(new Point(rect.x + rect.w, rect.y + rect.h)); - return new Rectangle(pt1.x, pt1.y, pt2.x - pt1.x, pt2.y - pt1.y); - }; - - /** - * Clone the Matrix - * - * @function clone - * @memberof Matrix# - * @name clone - * @instance - */ - Matrix.prototype.clone = function() { - var sx = this.sx; - var shy = this.shy; - var shx = this.shx; - var sy = this.sy; - var tx = this.tx; - var ty = this.ty; - - return new Matrix(sx, shy, shx, sy, tx, ty); - }; - - API.Matrix = Matrix; - - /** - * Multiplies two matrices. (see {@link Matrix}) - * @param {Matrix} m1 - * @param {Matrix} m2 - * @memberof jsPDF# - * @name matrixMult - */ - var matrixMult = (API.matrixMult = function(m1, m2) { - return m2.multiply(m1); - }); - - /** - * The identity matrix (equivalent to new Matrix(1, 0, 0, 1, 0, 0)). - * @type {Matrix} - * @memberof! jsPDF# - * @name identityMatrix - */ - var identityMatrix = new Matrix(1, 0, 0, 1, 0, 0); - API.unitMatrix = API.identityMatrix = identityMatrix; - - /** - * Adds a new pattern for later use. - * @param {String} key The key by it can be referenced later. The keys must be unique! - * @param {API.Pattern} pattern The pattern - */ - var addPattern = function(key, pattern) { - // only add it if it is not already present (the keys provided by the user must be unique!) - if (patternMap[key]) return; - - var prefix = pattern instanceof ShadingPattern ? "Sh" : "P"; - var patternKey = prefix + (Object.keys(patterns).length + 1).toString(10); - pattern.id = patternKey; - - patternMap[key] = patternKey; - patterns[patternKey] = pattern; - - events.publish("addPattern", pattern); - }; - - /** - * A pattern describing a shading pattern. - * - * Only available in "advanced" API mode. - * - * @param {String} type One of "axial" or "radial" - * @param {Array} coords Either [x1, y1, x2, y2] for "axial" type describing the two interpolation points - * or [x1, y1, r, x2, y2, r2] for "radial" describing inner and the outer circle. - * @param {Array} colors An array of objects with the fields "offset" and "color". "offset" describes - * the offset in parameter space [0, 1]. "color" is an array of length 3 describing RGB values in [0, 255]. - * @param {GState=} gState An additional graphics state that gets applied to the pattern (optional). - * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system - * and the use coordinate system (optional). - * @constructor - * @extends API.Pattern - */ - API.ShadingPattern = ShadingPattern; - - /** - * A PDF Tiling pattern. - * - * Only available in "advanced" API mode. - * - * @param {Array.} boundingBox The bounding box at which one pattern cell gets clipped. - * @param {Number} xStep Horizontal spacing between pattern cells. - * @param {Number} yStep Vertical spacing between pattern cells. - * @param {API.GState=} gState An additional graphics state that gets applied to the pattern (optional). - * @param {Matrix=} matrix A matrix that describes the transformation between the pattern coordinate system - * and the use coordinate system (optional). - * @constructor - * @extends API.Pattern - */ - API.TilingPattern = TilingPattern; - - /** - * Adds a new {@link API.ShadingPattern} for later use. Only available in "advanced" API mode. - * @param {String} key - * @param {Pattern} pattern - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name addPattern - */ - API.addShadingPattern = function(key, pattern) { - advancedApiModeTrap("addShadingPattern()"); - - addPattern(key, pattern); - return this; - }; - - /** - * Begins a new tiling pattern. All subsequent render calls are drawn to this pattern until {@link API.endTilingPattern} - * gets called. Only available in "advanced" API mode. - * @param {API.Pattern} pattern - * @memberof jsPDF# - * @name beginTilingPattern - */ - API.beginTilingPattern = function(pattern) { - advancedApiModeTrap("beginTilingPattern()"); - - beginNewRenderTarget( - pattern.boundingBox[0], - pattern.boundingBox[1], - pattern.boundingBox[2] - pattern.boundingBox[0], - pattern.boundingBox[3] - pattern.boundingBox[1], - pattern.matrix - ); - }; - - /** - * Ends a tiling pattern and sets the render target to the one active before {@link API.beginTilingPattern} has been called. - * - * Only available in "advanced" API mode. - * - * @param {string} key A unique key that is used to reference this pattern at later use. - * @param {API.Pattern} pattern The pattern to end. - * @memberof jsPDF# - * @name endTilingPattern - */ - API.endTilingPattern = function(key, pattern) { - advancedApiModeTrap("endTilingPattern()"); - - // retrieve the stream - pattern.stream = pages[currentPage].join("\n"); - - addPattern(key, pattern); - - events.publish("endTilingPattern", pattern); - - // restore state from stack - renderTargetStack.pop().restore(); - }; - - var newObject = (API.__private__.newObject = function() { - var oid = newObjectDeferred(); - newObjectDeferredBegin(oid, true); - return oid; - }); - - // Does not output the object. The caller must call newObjectDeferredBegin(oid) before outputing any data - var newObjectDeferred = (API.__private__.newObjectDeferred = function() { - objectNumber++; - offsets[objectNumber] = function() { - return contentLength; - }; - return objectNumber; - }); - - var newObjectDeferredBegin = function(oid, doOutput) { - doOutput = typeof doOutput === "boolean" ? doOutput : false; - offsets[oid] = contentLength; - if (doOutput) { - out(oid + " 0 obj"); - } - return oid; - }; - // Does not output the object until after the pages have been output. - // Returns an object containing the objectId and content. - // All pages have been added so the object ID can be estimated to start right after. - // This does not modify the current objectNumber; It must be updated after the newObjects are output. - var newAdditionalObject = (API.__private__.newAdditionalObject = function() { - var objId = newObjectDeferred(); - var obj = { - objId: objId, - content: "" - }; - additionalObjects.push(obj); - return obj; - }); - - var rootDictionaryObjId = newObjectDeferred(); - var resourceDictionaryObjId = newObjectDeferred(); - - ///////////////////// - // Private functions - ///////////////////// - - var decodeColorString = (API.__private__.decodeColorString = function(color) { - var colorEncoded = color.split(" "); - if ( - colorEncoded.length === 2 && - (colorEncoded[1] === "g" || colorEncoded[1] === "G") - ) { - // convert grayscale value to rgb so that it can be converted to hex for consistency - var floatVal = parseFloat(colorEncoded[0]); - colorEncoded = [floatVal, floatVal, floatVal, "r"]; - } else if ( - colorEncoded.length === 5 && - (colorEncoded[4] === "k" || colorEncoded[4] === "K") - ) { - // convert CMYK values to rbg so that it can be converted to hex for consistency - var red = (1.0 - colorEncoded[0]) * (1.0 - colorEncoded[3]); - var green = (1.0 - colorEncoded[1]) * (1.0 - colorEncoded[3]); - var blue = (1.0 - colorEncoded[2]) * (1.0 - colorEncoded[3]); - - colorEncoded = [red, green, blue, "r"]; - } - var colorAsRGB = "#"; - for (var i = 0; i < 3; i++) { - colorAsRGB += ( - "0" + Math.floor(parseFloat(colorEncoded[i]) * 255).toString(16) - ).slice(-2); - } - return colorAsRGB; - }); - - var encodeColorString = (API.__private__.encodeColorString = function( - options - ) { - var color; - - if (typeof options === "string") { - options = { - ch1: options - }; - } - var ch1 = options.ch1; - var ch2 = options.ch2; - var ch3 = options.ch3; - var ch4 = options.ch4; - var letterArray = - options.pdfColorType === "draw" ? ["G", "RG", "K"] : ["g", "rg", "k"]; - - if (typeof ch1 === "string" && ch1.charAt(0) !== "#") { - var rgbColor = new RGBColor(ch1); - if (rgbColor.ok) { - ch1 = rgbColor.toHex(); - } else if (!/^\d*\.?\d*$/.test(ch1)) { - throw new Error( - 'Invalid color "' + ch1 + '" passed to jsPDF.encodeColorString.' - ); - } - } - //convert short rgb to long form - if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{3}$/.test(ch1)) { - ch1 = "#" + ch1[1] + ch1[1] + ch1[2] + ch1[2] + ch1[3] + ch1[3]; - } - - if (typeof ch1 === "string" && /^#[0-9A-Fa-f]{6}$/.test(ch1)) { - var hex = parseInt(ch1.substr(1), 16); - ch1 = (hex >> 16) & 255; - ch2 = (hex >> 8) & 255; - ch3 = hex & 255; - } - - if ( - typeof ch2 === "undefined" || - (typeof ch4 === "undefined" && ch1 === ch2 && ch2 === ch3) - ) { - // Gray color space. - if (typeof ch1 === "string") { - color = ch1 + " " + letterArray[0]; - } else { - switch (options.precision) { - case 2: - color = f2(ch1 / 255) + " " + letterArray[0]; - break; - case 3: - default: - color = f3(ch1 / 255) + " " + letterArray[0]; - } - } - } else if (typeof ch4 === "undefined" || typeof ch4 === "object") { - // assume RGBA - if (ch4 && !isNaN(ch4.a)) { - //TODO Implement transparency. - //WORKAROUND use white for now, if transparent, otherwise handle as rgb - if (ch4.a === 0) { - color = ["1.", "1.", "1.", letterArray[1]].join(" "); - return color; - } - } - // assume RGB - if (typeof ch1 === "string") { - color = [ch1, ch2, ch3, letterArray[1]].join(" "); - } else { - switch (options.precision) { - case 2: - color = [ - f2(ch1 / 255), - f2(ch2 / 255), - f2(ch3 / 255), - letterArray[1] - ].join(" "); - break; - default: - case 3: - color = [ - f3(ch1 / 255), - f3(ch2 / 255), - f3(ch3 / 255), - letterArray[1] - ].join(" "); - } - } - } else { - // assume CMYK - if (typeof ch1 === "string") { - color = [ch1, ch2, ch3, ch4, letterArray[2]].join(" "); - } else { - switch (options.precision) { - case 2: - color = [f2(ch1), f2(ch2), f2(ch3), f2(ch4), letterArray[2]].join( - " " - ); - break; - case 3: - default: - color = [f3(ch1), f3(ch2), f3(ch3), f3(ch4), letterArray[2]].join( - " " - ); - } - } - } - return color; - }); - - var getFilters = (API.__private__.getFilters = function() { - return filters; - }); - - var putStream = (API.__private__.putStream = function(options) { - options = options || {}; - var data = options.data || ""; - var filters = options.filters || getFilters(); - var alreadyAppliedFilters = options.alreadyAppliedFilters || []; - var addLength1 = options.addLength1 || false; - var valueOfLength1 = data.length; - var objectId = options.objectId; - var encryptor = function(data) { - return data; - }; - if (encryptionOptions !== null && typeof objectId == "undefined") { - throw new Error( - "ObjectId must be passed to putStream for file encryption" - ); - } - if (encryptionOptions !== null) { - encryptor = encryption.encryptor(objectId, 0); - } - - var processedData = {}; - if (filters === true) { - filters = ["FlateEncode"]; - } - var keyValues = options.additionalKeyValues || []; - if (typeof jsPDF.API.processDataByFilters !== "undefined") { - processedData = jsPDF.API.processDataByFilters(data, filters); - } else { - processedData = { data: data, reverseChain: [] }; - } - var filterAsString = - processedData.reverseChain + - (Array.isArray(alreadyAppliedFilters) - ? alreadyAppliedFilters.join(" ") - : alreadyAppliedFilters.toString()); - - if (processedData.data.length !== 0) { - keyValues.push({ - key: "Length", - value: processedData.data.length - }); - if (addLength1 === true) { - keyValues.push({ - key: "Length1", - value: valueOfLength1 - }); - } - } - - if (filterAsString.length != 0) { - if (filterAsString.split("/").length - 1 === 1) { - keyValues.push({ - key: "Filter", - value: filterAsString - }); - } else { - keyValues.push({ - key: "Filter", - value: "[" + filterAsString + "]" - }); - - for (var j = 0; j < keyValues.length; j += 1) { - if (keyValues[j].key === "DecodeParms") { - var decodeParmsArray = []; - - for ( - var i = 0; - i < processedData.reverseChain.split("/").length - 1; - i += 1 - ) { - decodeParmsArray.push("null"); - } - - decodeParmsArray.push(keyValues[j].value); - keyValues[j].value = "[" + decodeParmsArray.join(" ") + "]"; - } - } - } - } - - out("<<"); - for (var k = 0; k < keyValues.length; k++) { - out("/" + keyValues[k].key + " " + keyValues[k].value); - } - out(">>"); - if (processedData.data.length !== 0) { - out("stream"); - out(encryptor(processedData.data)); - out("endstream"); - } - }); - - var putPage = (API.__private__.putPage = function(page) { - var pageNumber = page.number; - var data = page.data; - var pageObjectNumber = page.objId; - var pageContentsObjId = page.contentsObjId; - - newObjectDeferredBegin(pageObjectNumber, true); - out("<>"); - out("endobj"); - // Page content - var pageContent = data.join("\n"); - - if (apiMode === ApiMode.ADVANCED) { - // if the user forgot to switch back to COMPAT mode, we must balance the graphics stack again - pageContent += "\nQ"; - } - - newObjectDeferredBegin(pageContentsObjId, true); - putStream({ - data: pageContent, - filters: getFilters(), - objectId: pageContentsObjId - }); - out("endobj"); - return pageObjectNumber; - }); - - var putPages = (API.__private__.putPages = function() { - var n, - i, - pageObjectNumbers = []; - - for (n = 1; n <= page; n++) { - pagesContext[n].objId = newObjectDeferred(); - pagesContext[n].contentsObjId = newObjectDeferred(); - } - - for (n = 1; n <= page; n++) { - pageObjectNumbers.push( - putPage({ - number: n, - data: pages[n], - objId: pagesContext[n].objId, - contentsObjId: pagesContext[n].contentsObjId, - mediaBox: pagesContext[n].mediaBox, - cropBox: pagesContext[n].cropBox, - bleedBox: pagesContext[n].bleedBox, - trimBox: pagesContext[n].trimBox, - artBox: pagesContext[n].artBox, - userUnit: pagesContext[n].userUnit, - rootDictionaryObjId: rootDictionaryObjId, - resourceDictionaryObjId: resourceDictionaryObjId - }) - ); - } - newObjectDeferredBegin(rootDictionaryObjId, true); - out("<>"); - out("endobj"); - events.publish("postPutPages"); - }); - - var putFont = function(font) { - events.publish("putFont", { - font: font, - out: out, - newObject: newObject, - putStream: putStream - }); - - if (font.isAlreadyPutted !== true) { - font.objectNumber = newObject(); - out("<<"); - out("/Type /Font"); - out("/BaseFont /" + toPDFName(font.postScriptName)); - out("/Subtype /Type1"); - if (typeof font.encoding === "string") { - out("/Encoding /" + font.encoding); - } - out("/FirstChar 32"); - out("/LastChar 255"); - out(">>"); - out("endobj"); - } - }; - - var putFonts = function() { - for (var fontKey in fonts) { - if (fonts.hasOwnProperty(fontKey)) { - if ( - putOnlyUsedFonts === false || - (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey)) - ) { - putFont(fonts[fontKey]); - } - } - } - }; - - var putXObject = function(xObject) { - xObject.objectNumber = newObject(); - - var options = []; - options.push({ key: "Type", value: "/XObject" }); - options.push({ key: "Subtype", value: "/Form" }); - options.push({ - key: "BBox", - value: - "[" + - [ - hpf(xObject.x), - hpf(xObject.y), - hpf(xObject.x + xObject.width), - hpf(xObject.y + xObject.height) - ].join(" ") + - "]" - }); - options.push({ - key: "Matrix", - value: "[" + xObject.matrix.toString() + "]" - }); - // TODO: /Resources - - var stream = xObject.pages[1].join("\n"); - putStream({ - data: stream, - additionalKeyValues: options, - objectId: xObject.objectNumber - }); - out("endobj"); - }; - - var putXObjects = function() { - for (var xObjectKey in renderTargets) { - if (renderTargets.hasOwnProperty(xObjectKey)) { - putXObject(renderTargets[xObjectKey]); - } - } - }; - - var interpolateAndEncodeRGBStream = function(colors, numberSamples) { - var tValues = []; - var t; - var dT = 1.0 / (numberSamples - 1); - for (t = 0.0; t < 1.0; t += dT) { - tValues.push(t); - } - tValues.push(1.0); - // add first and last control point if not present - if (colors[0].offset != 0.0) { - var c0 = { - offset: 0.0, - color: colors[0].color - }; - colors.unshift(c0); - } - if (colors[colors.length - 1].offset != 1.0) { - var c1 = { - offset: 1.0, - color: colors[colors.length - 1].color - }; - colors.push(c1); - } - var out = ""; - var index = 0; - - for (var i = 0; i < tValues.length; i++) { - t = tValues[i]; - while (t > colors[index + 1].offset) index++; - var a = colors[index].offset; - var b = colors[index + 1].offset; - var d = (t - a) / (b - a); - - var aColor = colors[index].color; - var bColor = colors[index + 1].color; - - out += - padd2Hex(Math.round((1 - d) * aColor[0] + d * bColor[0]).toString(16)) + - padd2Hex(Math.round((1 - d) * aColor[1] + d * bColor[1]).toString(16)) + - padd2Hex(Math.round((1 - d) * aColor[2] + d * bColor[2]).toString(16)); - } - return out.trim(); - }; - - var putShadingPattern = function(pattern, numberSamples) { - /* - Axial patterns shade between the two points specified in coords, radial patterns between the inner - and outer circle. - The user can specify an array (colors) that maps t-Values in [0, 1] to RGB colors. These are now - interpolated to equidistant samples and written to pdf as a sample (type 0) function. - */ - // The number of color samples that should be used to describe the shading. - // The higher, the more accurate the gradient will be. - numberSamples || (numberSamples = 21); - var funcObjectNumber = newObject(); - var stream = interpolateAndEncodeRGBStream(pattern.colors, numberSamples); - - var options = []; - options.push({ key: "FunctionType", value: "0" }); - options.push({ key: "Domain", value: "[0.0 1.0]" }); - options.push({ key: "Size", value: "[" + numberSamples + "]" }); - options.push({ key: "BitsPerSample", value: "8" }); - options.push({ key: "Range", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" }); - options.push({ key: "Decode", value: "[0.0 1.0 0.0 1.0 0.0 1.0]" }); - - putStream({ - data: stream, - additionalKeyValues: options, - alreadyAppliedFilters: ["/ASCIIHexDecode"], - objectId: funcObjectNumber - }); - out("endobj"); - - pattern.objectNumber = newObject(); - out("<< /ShadingType " + pattern.type); - out("/ColorSpace /DeviceRGB"); - var coords = - "/Coords [" + - hpf(parseFloat(pattern.coords[0])) + - " " + // x1 - hpf(parseFloat(pattern.coords[1])) + - " "; // y1 - if (pattern.type === 2) { - // axial - coords += - hpf(parseFloat(pattern.coords[2])) + - " " + // x2 - hpf(parseFloat(pattern.coords[3])); // y2 - } else { - // radial - coords += - hpf(parseFloat(pattern.coords[2])) + - " " + // r1 - hpf(parseFloat(pattern.coords[3])) + - " " + // x2 - hpf(parseFloat(pattern.coords[4])) + - " " + // y2 - hpf(parseFloat(pattern.coords[5])); // r2 - } - coords += "]"; - out(coords); - - if (pattern.matrix) { - out("/Matrix [" + pattern.matrix.toString() + "]"); - } - out("/Function " + funcObjectNumber + " 0 R"); - out("/Extend [true true]"); - out(">>"); - out("endobj"); - }; - - var putTilingPattern = function(pattern, deferredResourceDictionaryIds) { - var resourcesObjectId = newObjectDeferred(); - var patternObjectId = newObject(); - - deferredResourceDictionaryIds.push({ - resourcesOid: resourcesObjectId, - objectOid: patternObjectId - }); - - pattern.objectNumber = patternObjectId; - var options = []; - options.push({ key: "Type", value: "/Pattern" }); - options.push({ key: "PatternType", value: "1" }); // tiling pattern - options.push({ key: "PaintType", value: "1" }); // colored tiling pattern - options.push({ key: "TilingType", value: "1" }); // constant spacing - options.push({ - key: "BBox", - value: "[" + pattern.boundingBox.map(hpf).join(" ") + "]" - }); - options.push({ key: "XStep", value: hpf(pattern.xStep) }); - options.push({ key: "YStep", value: hpf(pattern.yStep) }); - options.push({ key: "Resources", value: resourcesObjectId + " 0 R" }); - if (pattern.matrix) { - options.push({ - key: "Matrix", - value: "[" + pattern.matrix.toString() + "]" - }); - } - - putStream({ - data: pattern.stream, - additionalKeyValues: options, - objectId: pattern.objectNumber - }); - out("endobj"); - }; - - var putPatterns = function(deferredResourceDictionaryIds) { - var patternKey; - for (patternKey in patterns) { - if (patterns.hasOwnProperty(patternKey)) { - if (patterns[patternKey] instanceof ShadingPattern) { - putShadingPattern(patterns[patternKey]); - } else if (patterns[patternKey] instanceof TilingPattern) { - putTilingPattern(patterns[patternKey], deferredResourceDictionaryIds); - } - } - } - }; - - var putGState = function(gState) { - gState.objectNumber = newObject(); - out("<<"); - for (var p in gState) { - switch (p) { - case "opacity": - out("/ca " + f2(gState[p])); - break; - case "stroke-opacity": - out("/CA " + f2(gState[p])); - break; - } - } - out(">>"); - out("endobj"); - }; - - var putGStates = function() { - var gStateKey; - for (gStateKey in gStates) { - if (gStates.hasOwnProperty(gStateKey)) { - putGState(gStates[gStateKey]); - } - } - }; - - var putXobjectDict = function() { - out("/XObject <<"); - for (var xObjectKey in renderTargets) { - if ( - renderTargets.hasOwnProperty(xObjectKey) && - renderTargets[xObjectKey].objectNumber >= 0 - ) { - out( - "/" + - xObjectKey + - " " + - renderTargets[xObjectKey].objectNumber + - " 0 R" - ); - } - } - - // Loop through images, or other data objects - events.publish("putXobjectDict"); - out(">>"); - }; - - var putEncryptionDict = function() { - encryption.oid = newObject(); - out("<<"); - out("/Filter /Standard"); - out("/V " + encryption.v); - out("/R " + encryption.r); - out("/U <" + encryption.toHexString(encryption.U) + ">"); - out("/O <" + encryption.toHexString(encryption.O) + ">"); - out("/P " + encryption.P); - out(">>"); - out("endobj"); - }; - - var putFontDict = function() { - out("/Font <<"); - - for (var fontKey in fonts) { - if (fonts.hasOwnProperty(fontKey)) { - if ( - putOnlyUsedFonts === false || - (putOnlyUsedFonts === true && usedFonts.hasOwnProperty(fontKey)) - ) { - out("/" + fontKey + " " + fonts[fontKey].objectNumber + " 0 R"); - } - } - } - out(">>"); - }; - - var putShadingPatternDict = function() { - if (Object.keys(patterns).length > 0) { - out("/Shading <<"); - for (var patternKey in patterns) { - if ( - patterns.hasOwnProperty(patternKey) && - patterns[patternKey] instanceof ShadingPattern && - patterns[patternKey].objectNumber >= 0 - ) { - out( - "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R" - ); - } - } - - events.publish("putShadingPatternDict"); - out(">>"); - } - }; - - var putTilingPatternDict = function(objectOid) { - if (Object.keys(patterns).length > 0) { - out("/Pattern <<"); - for (var patternKey in patterns) { - if ( - patterns.hasOwnProperty(patternKey) && - patterns[patternKey] instanceof API.TilingPattern && - patterns[patternKey].objectNumber >= 0 && - patterns[patternKey].objectNumber < objectOid // prevent cyclic dependencies - ) { - out( - "/" + patternKey + " " + patterns[patternKey].objectNumber + " 0 R" - ); - } - } - events.publish("putTilingPatternDict"); - out(">>"); - } - }; - - var putGStatesDict = function() { - if (Object.keys(gStates).length > 0) { - var gStateKey; - out("/ExtGState <<"); - for (gStateKey in gStates) { - if ( - gStates.hasOwnProperty(gStateKey) && - gStates[gStateKey].objectNumber >= 0 - ) { - out("/" + gStateKey + " " + gStates[gStateKey].objectNumber + " 0 R"); - } - } - - events.publish("putGStateDict"); - out(">>"); - } - }; - - var putResourceDictionary = function(objectIds) { - newObjectDeferredBegin(objectIds.resourcesOid, true); - out("<<"); - out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"); - putFontDict(); - putShadingPatternDict(); - putTilingPatternDict(objectIds.objectOid); - putGStatesDict(); - putXobjectDict(); - out(">>"); - out("endobj"); - }; - - var putResources = function() { - // FormObjects, Patterns etc. might use other FormObjects/Patterns/Images - // which means their resource dictionaries must contain the already resolved - // object ids. For this reason we defer the serialization of the resource - // dicts until all objects have been serialized and have object ids. - // - // In order to prevent cyclic dependencies (which Adobe Reader doesn't like), - // we only put all oids that are smaller than the oid of the object the - // resource dict belongs to. This is correct behavior, since the streams - // may only use other objects that have already been defined and thus appear - // earlier in their respective collection. - // Currently, this only affects tiling patterns, but a (more) correct - // implementation of FormObjects would also define their own resource dicts. - var deferredResourceDictionaryIds = []; - - putFonts(); - putGStates(); - putXObjects(); - putPatterns(deferredResourceDictionaryIds); - - events.publish("putResources"); - deferredResourceDictionaryIds.forEach(putResourceDictionary); - putResourceDictionary({ - resourcesOid: resourceDictionaryObjId, - objectOid: Number.MAX_SAFE_INTEGER // output all objects - }); - events.publish("postPutResources"); - }; - - var putAdditionalObjects = function() { - events.publish("putAdditionalObjects"); - for (var i = 0; i < additionalObjects.length; i++) { - var obj = additionalObjects[i]; - newObjectDeferredBegin(obj.objId, true); - out(obj.content); - out("endobj"); - } - events.publish("postPutAdditionalObjects"); - }; - - var addFontToFontDictionary = function(font) { - fontmap[font.fontName] = fontmap[font.fontName] || {}; - fontmap[font.fontName][font.fontStyle] = font.id; - }; - - var addFont = function( - postScriptName, - fontName, - fontStyle, - encoding, - isStandardFont - ) { - var font = { - id: "F" + (Object.keys(fonts).length + 1).toString(10), - postScriptName: postScriptName, - fontName: fontName, - fontStyle: fontStyle, - encoding: encoding, - isStandardFont: isStandardFont || false, - metadata: {} - }; - - events.publish("addFont", { - font: font, - instance: this - }); - - fonts[font.id] = font; - addFontToFontDictionary(font); - return font.id; - }; - - var addFonts = function(arrayOfFonts) { - for (var i = 0, l = standardFonts.length; i < l; i++) { - var fontKey = addFont.call( - this, - arrayOfFonts[i][0], - arrayOfFonts[i][1], - arrayOfFonts[i][2], - standardFonts[i][3], - true - ); - - if (putOnlyUsedFonts === false) { - usedFonts[fontKey] = true; - } - // adding aliases for standard fonts, this time matching the capitalization - var parts = arrayOfFonts[i][0].split("-"); - addFontToFontDictionary({ - id: fontKey, - fontName: parts[0], - fontStyle: parts[1] || "" - }); - } - events.publish("addFonts", { - fonts: fonts, - dictionary: fontmap - }); - }; - - var SAFE = function __safeCall(fn) { - fn.foo = function __safeCallWrapper() { - try { - return fn.apply(this, arguments); - } catch (e) { - var stack = e.stack || ""; - if (~stack.indexOf(" at ")) stack = stack.split(" at ")[1]; - var m = - "Error in function " + - stack.split("\n")[0].split("<")[0] + - ": " + - e.message; - if (globalObject.console) { - globalObject.console.error(m, e); - if (globalObject.alert) alert(m); - } else { - throw new Error(m); - } - } - }; - fn.foo.bar = fn; - return fn.foo; - }; - - var to8bitStream = function(text, flags) { - /** - * PDF 1.3 spec: - * "For text strings encoded in Unicode, the first two bytes must be 254 followed by - * 255, representing the Unicode byte order marker, U+FEFF. (This sequence conflicts - * with the PDFDocEncoding character sequence thorn ydieresis, which is unlikely - * to be a meaningful beginning of a word or phrase.) The remainder of the - * string consists of Unicode character codes, according to the UTF-16 encoding - * specified in the Unicode standard, version 2.0. Commonly used Unicode values - * are represented as 2 bytes per character, with the high-order byte appearing first - * in the string." - * - * In other words, if there are chars in a string with char code above 255, we - * recode the string to UCS2 BE - string doubles in length and BOM is prepended. - * - * HOWEVER! - * Actual *content* (body) text (as opposed to strings used in document properties etc) - * does NOT expect BOM. There, it is treated as a literal GID (Glyph ID) - * - * Because of Adobe's focus on "you subset your fonts!" you are not supposed to have - * a font that maps directly Unicode (UCS2 / UTF16BE) code to font GID, but you could - * fudge it with "Identity-H" encoding and custom CIDtoGID map that mimics Unicode - * code page. There, however, all characters in the stream are treated as GIDs, - * including BOM, which is the reason we need to skip BOM in content text (i.e. that - * that is tied to a font). - * - * To signal this "special" PDFEscape / to8bitStream handling mode, - * API.text() function sets (unless you overwrite it with manual values - * given to API.text(.., flags) ) - * flags.autoencode = true - * flags.noBOM = true - * - * =================================================================================== - * `flags` properties relied upon: - * .sourceEncoding = string with encoding label. - * "Unicode" by default. = encoding of the incoming text. - * pass some non-existing encoding name - * (ex: 'Do not touch my strings! I know what I am doing.') - * to make encoding code skip the encoding step. - * .outputEncoding = Either valid PDF encoding name - * (must be supported by jsPDF font metrics, otherwise no encoding) - * or a JS object, where key = sourceCharCode, value = outputCharCode - * missing keys will be treated as: sourceCharCode === outputCharCode - * .noBOM - * See comment higher above for explanation for why this is important - * .autoencode - * See comment higher above for explanation for why this is important - */ - - var i, - l, - sourceEncoding, - encodingBlock, - outputEncoding, - newtext, - isUnicode, - ch, - bch; - - flags = flags || {}; - sourceEncoding = flags.sourceEncoding || "Unicode"; - outputEncoding = flags.outputEncoding; - - // This 'encoding' section relies on font metrics format - // attached to font objects by, among others, - // "Willow Systems' standard_font_metrics plugin" - // see jspdf.plugin.standard_font_metrics.js for format - // of the font.metadata.encoding Object. - // It should be something like - // .encoding = {'codePages':['WinANSI....'], 'WinANSI...':{code:code, ...}} - // .widths = {0:width, code:width, ..., 'fof':divisor} - // .kerning = {code:{previous_char_code:shift, ..., 'fof':-divisor},...} - if ( - (flags.autoencode || outputEncoding) && - fonts[activeFontKey].metadata && - fonts[activeFontKey].metadata[sourceEncoding] && - fonts[activeFontKey].metadata[sourceEncoding].encoding - ) { - encodingBlock = fonts[activeFontKey].metadata[sourceEncoding].encoding; - - // each font has default encoding. Some have it clearly defined. - if (!outputEncoding && fonts[activeFontKey].encoding) { - outputEncoding = fonts[activeFontKey].encoding; - } - - // Hmmm, the above did not work? Let's try again, in different place. - if (!outputEncoding && encodingBlock.codePages) { - outputEncoding = encodingBlock.codePages[0]; // let's say, first one is the default - } - - if (typeof outputEncoding === "string") { - outputEncoding = encodingBlock[outputEncoding]; - } - // we want output encoding to be a JS Object, where - // key = sourceEncoding's character code and - // value = outputEncoding's character code. - if (outputEncoding) { - isUnicode = false; - newtext = []; - for (i = 0, l = text.length; i < l; i++) { - ch = outputEncoding[text.charCodeAt(i)]; - if (ch) { - newtext.push(String.fromCharCode(ch)); - } else { - newtext.push(text[i]); - } - - // since we are looping over chars anyway, might as well - // check for residual unicodeness - if (newtext[i].charCodeAt(0) >> 8) { - /* more than 255 */ - isUnicode = true; - } - } - text = newtext.join(""); - } - } - - i = text.length; - // isUnicode may be set to false above. Hence the triple-equal to undefined - while (isUnicode === undefined && i !== 0) { - if (text.charCodeAt(i - 1) >> 8) { - /* more than 255 */ - isUnicode = true; - } - i--; - } - if (!isUnicode) { - return text; - } - - newtext = flags.noBOM ? [] : [254, 255]; - for (i = 0, l = text.length; i < l; i++) { - ch = text.charCodeAt(i); - bch = ch >> 8; // divide by 256 - if (bch >> 8) { - /* something left after dividing by 256 second time */ - throw new Error( - "Character at position " + - i + - " of string '" + - text + - "' exceeds 16bits. Cannot be encoded into UCS-2 BE" - ); - } - newtext.push(bch); - newtext.push(ch - (bch << 8)); - } - return String.fromCharCode.apply(undefined, newtext); - }; - - var pdfEscape = (API.__private__.pdfEscape = API.pdfEscape = function( - text, - flags - ) { - /** - * Replace '/', '(', and ')' with pdf-safe versions - * - * Doing to8bitStream does NOT make this PDF display unicode text. For that - * we also need to reference a unicode font and embed it - royal pain in the rear. - * - * There is still a benefit to to8bitStream - PDF simply cannot handle 16bit chars, - * which JavaScript Strings are happy to provide. So, while we still cannot display - * 2-byte characters property, at least CONDITIONALLY converting (entire string containing) - * 16bit chars to (USC-2-BE) 2-bytes per char + BOM streams we ensure that entire PDF - * is still parseable. - * This will allow immediate support for unicode in document properties strings. - */ - return to8bitStream(text, flags) - .replace(/\\/g, "\\\\") - .replace(/\(/g, "\\(") - .replace(/\)/g, "\\)"); - }); - - var beginPage = (API.__private__.beginPage = function(format) { - pages[++page] = []; - pagesContext[page] = { - objId: 0, - contentsObjId: 0, - userUnit: Number(userUnit), - artBox: null, - bleedBox: null, - cropBox: null, - trimBox: null, - mediaBox: { - bottomLeftX: 0, - bottomLeftY: 0, - topRightX: Number(format[0]), - topRightY: Number(format[1]) - } - }; - _setPage(page); - setOutputDestination(pages[currentPage]); - }); - - var _addPage = function(parmFormat, parmOrientation) { - var dimensions, width, height; - - orientation = parmOrientation || orientation; - - if (typeof parmFormat === "string") { - dimensions = getPageFormat(parmFormat.toLowerCase()); - if (Array.isArray(dimensions)) { - width = dimensions[0]; - height = dimensions[1]; - } - } - - if (Array.isArray(parmFormat)) { - width = parmFormat[0] * scaleFactor; - height = parmFormat[1] * scaleFactor; - } - - if (isNaN(width)) { - width = format[0]; - height = format[1]; - } - - if (width > 14400 || height > 14400) { - console.warn( - "A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400" - ); - width = Math.min(14400, width); - height = Math.min(14400, height); - } - - format = [width, height]; - - switch (orientation.substr(0, 1)) { - case "l": - if (height > width) { - format = [height, width]; - } - break; - case "p": - if (width > height) { - format = [height, width]; - } - break; - } - - beginPage(format); - - // Set line width - setLineWidth(lineWidth); - // Set draw color - out(strokeColor); - // resurrecting non-default line caps, joins - if (lineCapID !== 0) { - out(lineCapID + " J"); - } - if (lineJoinID !== 0) { - out(lineJoinID + " j"); - } - events.publish("addPage", { - pageNumber: page - }); - }; - - var _deletePage = function(n) { - if (n > 0 && n <= page) { - pages.splice(n, 1); - pagesContext.splice(n, 1); - page--; - if (currentPage > page) { - currentPage = page; - } - this.setPage(currentPage); - } - }; - - var _setPage = function(n) { - if (n > 0 && n <= page) { - currentPage = n; - } - }; - - var getNumberOfPages = (API.__private__.getNumberOfPages = API.getNumberOfPages = function() { - return pages.length - 1; - }); - - /** - * Returns a document-specific font key - a label assigned to a - * font name + font type combination at the time the font was added - * to the font inventory. - * - * Font key is used as label for the desired font for a block of text - * to be added to the PDF document stream. - * @private - * @function - * @param fontName {string} can be undefined on "falthy" to indicate "use current" - * @param fontStyle {string} can be undefined on "falthy" to indicate "use current" - * @returns {string} Font key. - * @ignore - */ - var getFont = function(fontName, fontStyle, options) { - var key = undefined, - fontNameLowerCase; - options = options || {}; - - fontName = - fontName !== undefined ? fontName : fonts[activeFontKey].fontName; - fontStyle = - fontStyle !== undefined ? fontStyle : fonts[activeFontKey].fontStyle; - fontNameLowerCase = fontName.toLowerCase(); - - if ( - fontmap[fontNameLowerCase] !== undefined && - fontmap[fontNameLowerCase][fontStyle] !== undefined - ) { - key = fontmap[fontNameLowerCase][fontStyle]; - } else if ( - fontmap[fontName] !== undefined && - fontmap[fontName][fontStyle] !== undefined - ) { - key = fontmap[fontName][fontStyle]; - } else { - if (options.disableWarning === false) { - console.warn( - "Unable to look up font label for font '" + - fontName + - "', '" + - fontStyle + - "'. Refer to getFontList() for available fonts." - ); - } - } - - if (!key && !options.noFallback) { - key = fontmap["times"][fontStyle]; - if (key == null) { - key = fontmap["times"]["normal"]; - } - } - return key; - }; - - var putInfo = (API.__private__.putInfo = function() { - var objectId = newObject(); - var encryptor = function(data) { - return data; - }; - if (encryptionOptions !== null) { - encryptor = encryption.encryptor(objectId, 0); - } - out("<<"); - out("/Producer (" + pdfEscape(encryptor("jsPDF " + jsPDF.version)) + ")"); - for (var key in documentProperties) { - if (documentProperties.hasOwnProperty(key) && documentProperties[key]) { - out( - "/" + - key.substr(0, 1).toUpperCase() + - key.substr(1) + - " (" + - pdfEscape(encryptor(documentProperties[key])) + - ")" - ); - } - } - out("/CreationDate (" + pdfEscape(encryptor(creationDate)) + ")"); - out(">>"); - out("endobj"); - }); - - var putCatalog = (API.__private__.putCatalog = function(options) { - options = options || {}; - var tmpRootDictionaryObjId = - options.rootDictionaryObjId || rootDictionaryObjId; - newObject(); - out("<<"); - out("/Type /Catalog"); - out("/Pages " + tmpRootDictionaryObjId + " 0 R"); - // PDF13ref Section 7.2.1 - if (!zoomMode) zoomMode = "fullwidth"; - switch (zoomMode) { - case "fullwidth": - out("/OpenAction [3 0 R /FitH null]"); - break; - case "fullheight": - out("/OpenAction [3 0 R /FitV null]"); - break; - case "fullpage": - out("/OpenAction [3 0 R /Fit]"); - break; - case "original": - out("/OpenAction [3 0 R /XYZ null null 1]"); - break; - default: - var pcn = "" + zoomMode; - if (pcn.substr(pcn.length - 1) === "%") - zoomMode = parseInt(zoomMode) / 100; - if (typeof zoomMode === "number") { - out("/OpenAction [3 0 R /XYZ null null " + f2(zoomMode) + "]"); - } - } - if (!layoutMode) layoutMode = "continuous"; - switch (layoutMode) { - case "continuous": - out("/PageLayout /OneColumn"); - break; - case "single": - out("/PageLayout /SinglePage"); - break; - case "two": - case "twoleft": - out("/PageLayout /TwoColumnLeft"); - break; - case "tworight": - out("/PageLayout /TwoColumnRight"); - break; - } - if (pageMode) { - /** - * A name object specifying how the document should be displayed when opened: - * UseNone : Neither document outline nor thumbnail images visible -- DEFAULT - * UseOutlines : Document outline visible - * UseThumbs : Thumbnail images visible - * FullScreen : Full-screen mode, with no menu bar, window controls, or any other window visible - */ - out("/PageMode /" + pageMode); - } - events.publish("putCatalog"); - out(">>"); - out("endobj"); - }); - - var putTrailer = (API.__private__.putTrailer = function() { - out("trailer"); - out("<<"); - out("/Size " + (objectNumber + 1)); - // Root and Info must be the last and second last objects written respectively - out("/Root " + objectNumber + " 0 R"); - out("/Info " + (objectNumber - 1) + " 0 R"); - if (encryptionOptions !== null) { - out("/Encrypt " + encryption.oid + " 0 R"); - } - out("/ID [ <" + fileId + "> <" + fileId + "> ]"); - out(">>"); - }); - - var putHeader = (API.__private__.putHeader = function() { - out("%PDF-" + pdfVersion); - out("%\xBA\xDF\xAC\xE0"); - }); - - var putXRef = (API.__private__.putXRef = function() { - var p = "0000000000"; - - out("xref"); - out("0 " + (objectNumber + 1)); - out("0000000000 65535 f "); - for (var i = 1; i <= objectNumber; i++) { - var offset = offsets[i]; - if (typeof offset === "function") { - out((p + offsets[i]()).slice(-10) + " 00000 n "); - } else { - if (typeof offsets[i] !== "undefined") { - out((p + offsets[i]).slice(-10) + " 00000 n "); - } else { - out("0000000000 00000 n "); - } - } - } - }); - - var buildDocument = (API.__private__.buildDocument = function() { - resetDocument(); - setOutputDestination(content); - - events.publish("buildDocument"); - - putHeader(); - putPages(); - putAdditionalObjects(); - putResources(); - if (encryptionOptions !== null) putEncryptionDict(); - putInfo(); - putCatalog(); - - var offsetOfXRef = contentLength; - putXRef(); - putTrailer(); - out("startxref"); - out("" + offsetOfXRef); - out("%%EOF"); - - setOutputDestination(pages[currentPage]); - - return content.join("\n"); - }); - - var getBlob = (API.__private__.getBlob = function(data) { - return new Blob([getArrayBuffer(data)], { - type: "application/pdf" - }); - }); - - /** - * Generates the PDF document. - * - * If `type` argument is undefined, output is raw body of resulting PDF returned as a string. - * - * @param {string} type A string identifying one of the possible output types.
- * Possible values are:
- * 'arraybuffer' -> (ArrayBuffer)
- * 'blob' -> (Blob)
- * 'bloburi'/'bloburl' -> (string)
- * 'datauristring'/'dataurlstring' -> (string)
- * 'datauri'/'dataurl' -> (undefined) -> change location to generated datauristring/dataurlstring
- * @param {Object|string} options An object providing some additional signalling to PDF generator.
- * Possible options are 'filename'.
- * A string can be passed instead of {filename:string} and defaults to 'generated.pdf' - * @function - * @instance - * @returns {string|window|ArrayBuffer|Blob|jsPDF|null|undefined} - * @memberof jsPDF# - * @name output - */ - var output = (API.output = API.__private__.output = SAFE(function output( - type, - options - ) { - options = options || {}; - - if (typeof options === "string") { - options = { - filename: options - }; - } else { - options.filename = options.filename || "generated.pdf"; - } - - switch (type) { - case undefined: - return buildDocument(); - case "save": - API.save(options.filename); - break; - case "arraybuffer": - return getArrayBuffer(buildDocument()); - case "blob": - return getBlob(buildDocument()); - case "bloburi": - case "bloburl": - // Developer is responsible of calling revokeObjectURL - if ( - typeof globalObject.URL !== "undefined" && - typeof globalObject.URL.createObjectURL === "function" - ) { - return ( - (globalObject.URL && - globalObject.URL.createObjectURL(getBlob(buildDocument()))) || - void 0 - ); - } else { - console.warn( - "bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser." - ); - } - break; - case "datauristring": - case "dataurlstring": - var dataURI = ""; - var pdfDocument = buildDocument(); - try { - dataURI = btoa(pdfDocument); - } catch (e) { - dataURI = btoa(unescape(encodeURIComponent(pdfDocument))); - } - return ( - "data:application/pdf;filename=" + - encodeURIComponent(options.filename) + - ";base64," + - dataURI - ); - case "datauri": - case "dataurl": - return (globalObject.document.location.href = this.output( - "datauristring", - options - )); - default: - return null; - } - })); - - /** - * Used to see if a supplied hotfix was requested when the pdf instance was created. - * @param {string} hotfixName - The name of the hotfix to check. - * @returns {boolean} - */ - var hasHotfix = function(hotfixName) { - return ( - Array.isArray(hotfixes) === true && hotfixes.indexOf(hotfixName) > -1 - ); - }; - - switch (unit) { - case "pt": - scaleFactor = 1; - break; - case "mm": - scaleFactor = 72 / 25.4; - break; - case "cm": - scaleFactor = 72 / 2.54; - break; - case "in": - scaleFactor = 72; - break; - case "px": - if (hasHotfix("px_scaling") == true) { - scaleFactor = 72 / 96; - } else { - scaleFactor = 96 / 72; - } - break; - case "pc": - scaleFactor = 12; - break; - case "em": - scaleFactor = 12; - break; - case "ex": - scaleFactor = 6; - break; - default: - if (typeof unit === "number") { - scaleFactor = unit; - } else { - throw new Error("Invalid unit: " + unit); - } - } - - var encryption = null; - setCreationDate(); - setFileId(); - - var getEncryptor = function(objectId) { - if (encryptionOptions !== null) { - return encryption.encryptor(objectId, 0); - } - return function(data) { - return data; - }; - }; - - //--------------------------------------- - // Public API - - var getPageInfo = (API.__private__.getPageInfo = API.getPageInfo = function( - pageNumberOneBased - ) { - if (isNaN(pageNumberOneBased) || pageNumberOneBased % 1 !== 0) { - throw new Error("Invalid argument passed to jsPDF.getPageInfo"); - } - var objId = pagesContext[pageNumberOneBased].objId; - return { - objId: objId, - pageNumber: pageNumberOneBased, - pageContext: pagesContext[pageNumberOneBased] - }; - }); - - var getPageInfoByObjId = (API.__private__.getPageInfoByObjId = function( - objId - ) { - if (isNaN(objId) || objId % 1 !== 0) { - throw new Error("Invalid argument passed to jsPDF.getPageInfoByObjId"); - } - for (var pageNumber in pagesContext) { - if (pagesContext[pageNumber].objId === objId) { - break; - } - } - return getPageInfo(pageNumber); - }); - - var getCurrentPageInfo = (API.__private__.getCurrentPageInfo = API.getCurrentPageInfo = function() { - return { - objId: pagesContext[currentPage].objId, - pageNumber: currentPage, - pageContext: pagesContext[currentPage] - }; - }); - - /** - * Adds (and transfers the focus to) new page to the PDF document. - * @param format {String/Array} The format of the new page. Can be:
  • a0 - a10
  • b0 - b10
  • c0 - c10
  • dl
  • letter
  • government-letter
  • legal
  • junior-legal
  • ledger
  • tabloid
  • credit-card

- * Default is "a4". If you want to use your own format just pass instead of one of the above predefined formats the size as an number-array, e.g. [595.28, 841.89] - * @param orientation {string} Orientation of the new page. Possible values are "portrait" or "landscape" (or shortcuts "p" (Default), "l"). - * @function - * @instance - * @returns {jsPDF} - * - * @memberof jsPDF# - * @name addPage - */ - API.addPage = function() { - _addPage.apply(this, arguments); - return this; - }; - /** - * Adds (and transfers the focus to) new page to the PDF document. - * @function - * @instance - * @returns {jsPDF} - * - * @memberof jsPDF# - * @name setPage - * @param {number} page Switch the active page to the page number specified (indexed starting at 1). - * @example - * doc = jsPDF() - * doc.addPage() - * doc.addPage() - * doc.text('I am on page 3', 10, 10) - * doc.setPage(1) - * doc.text('I am on page 1', 10, 10) - */ - API.setPage = function() { - _setPage.apply(this, arguments); - setOutputDestination.call(this, pages[currentPage]); - return this; - }; - - /** - * @name insertPage - * @memberof jsPDF# - * - * @function - * @instance - * @param {Object} beforePage - * @returns {jsPDF} - */ - API.insertPage = function(beforePage) { - this.addPage(); - this.movePage(currentPage, beforePage); - return this; - }; - - /** - * @name movePage - * @memberof jsPDF# - * @function - * @instance - * @param {number} targetPage - * @param {number} beforePage - * @returns {jsPDF} - */ - API.movePage = function(targetPage, beforePage) { - var tmpPages, tmpPagesContext; - if (targetPage > beforePage) { - tmpPages = pages[targetPage]; - tmpPagesContext = pagesContext[targetPage]; - for (var i = targetPage; i > beforePage; i--) { - pages[i] = pages[i - 1]; - pagesContext[i] = pagesContext[i - 1]; - } - pages[beforePage] = tmpPages; - pagesContext[beforePage] = tmpPagesContext; - this.setPage(beforePage); - } else if (targetPage < beforePage) { - tmpPages = pages[targetPage]; - tmpPagesContext = pagesContext[targetPage]; - for (var j = targetPage; j < beforePage; j++) { - pages[j] = pages[j + 1]; - pagesContext[j] = pagesContext[j + 1]; - } - pages[beforePage] = tmpPages; - pagesContext[beforePage] = tmpPagesContext; - this.setPage(beforePage); - } - return this; - }; - - /** - * Deletes a page from the PDF. - * @name deletePage - * @memberof jsPDF# - * @function - * @param {number} targetPage - * @instance - * @returns {jsPDF} - */ - API.deletePage = function() { - _deletePage.apply(this, arguments); - return this; - }; - - /** - * Adds text to page. Supports adding multiline text when 'text' argument is an Array of Strings. - * - * @function - * @instance - * @param {String|Array} text String or array of strings to be added to the page. Each line is shifted one line down per font, spacing settings declared before this call. - * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page. - * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page. - * @param {Object} [options] - Collection of settings signaling how the text must be encoded. - * @param {string} [options.align=left] - The alignment of the text, possible values: left, center, right, justify. - * @param {string} [options.baseline=alphabetic] - Sets text baseline used when drawing the text, possible values: alphabetic, ideographic, bottom, top, middle, hanging - * @param {number|Matrix} [options.angle=0] - Rotate the text clockwise or counterclockwise. Expects the angle in degree. - * @param {number} [options.rotationDirection=1] - Direction of the rotation. 0 = clockwise, 1 = counterclockwise. - * @param {number} [options.charSpace=0] - The space between each letter. - * @param {number} [options.horizontalScale=1] - Horizontal scale of the text as a factor of the regular size. - * @param {number} [options.lineHeightFactor=1.15] - The lineheight of each line. - * @param {Object} [options.flags] - Flags for to8bitStream. - * @param {boolean} [options.flags.noBOM=true] - Don't add BOM to Unicode-text. - * @param {boolean} [options.flags.autoencode=true] - Autoencode the Text. - * @param {number} [options.maxWidth=0] - Split the text by given width, 0 = no split. - * @param {string} [options.renderingMode=fill] - Set how the text should be rendered, possible values: fill, stroke, fillThenStroke, invisible, fillAndAddForClipping, strokeAndAddPathForClipping, fillThenStrokeAndAddToPathForClipping, addToPathForClipping. - * @param {boolean} [options.isInputVisual] - Option for the BidiEngine - * @param {boolean} [options.isOutputVisual] - Option for the BidiEngine - * @param {boolean} [options.isInputRtl] - Option for the BidiEngine - * @param {boolean} [options.isOutputRtl] - Option for the BidiEngine - * @param {boolean} [options.isSymmetricSwapping] - Option for the BidiEngine - * @param {number|Matrix} transform If transform is a number the text will be rotated by this value around the anchor set by x and y. - * - * If it is a Matrix, this matrix gets directly applied to the text, which allows shearing - * effects etc.; the x and y offsets are then applied AFTER the coordinate system has been established by this - * matrix. This means passing a rotation matrix that is equivalent to some rotation angle will in general yield a - * DIFFERENT result. A matrix is only allowed in "advanced" API mode. - * @returns {jsPDF} - * @memberof jsPDF# - * @name text - */ - API.__private__.text = API.text = function(text, x, y, options, transform) { - /* - * Inserts something like this into PDF - * BT - * /F1 16 Tf % Font name + size - * 16 TL % How many units down for next line in multiline text - * 0 g % color - * 28.35 813.54 Td % position - * (line one) Tj - * T* (line two) Tj - * T* (line three) Tj - * ET - */ - options = options || {}; - var scope = options.scope || this; - var payload, da, angle, align, charSpace, maxWidth, flags, horizontalScale; - - // Pre-August-2012 the order of arguments was function(x, y, text, flags) - // in effort to make all calls have similar signature like - // function(data, coordinates... , miscellaneous) - // this method had its args flipped. - // code below allows backward compatibility with old arg order. - if ( - typeof text === "number" && - typeof x === "number" && - (typeof y === "string" || Array.isArray(y)) - ) { - var tmp = y; - y = x; - x = text; - text = tmp; - } - - var transformationMatrix; - - if (arguments[3] instanceof Matrix === false) { - flags = arguments[3]; - angle = arguments[4]; - align = arguments[5]; - - if (typeof flags !== "object" || flags === null) { - if (typeof angle === "string") { - align = angle; - angle = null; - } - if (typeof flags === "string") { - align = flags; - flags = null; - } - if (typeof flags === "number") { - angle = flags; - flags = null; - } - options = { - flags: flags, - angle: angle, - align: align - }; - } - } else { - advancedApiModeTrap( - "The transform parameter of text() with a Matrix value" - ); - transformationMatrix = transform; - } - - if (isNaN(x) || isNaN(y) || typeof text === "undefined" || text === null) { - throw new Error("Invalid arguments passed to jsPDF.text"); - } - - if (text.length === 0) { - return scope; - } - - var xtra = ""; - var isHex = false; - var lineHeight = - typeof options.lineHeightFactor === "number" - ? options.lineHeightFactor - : lineHeightFactor; - var scaleFactor = scope.internal.scaleFactor; - - function ESC(s) { - s = s.split("\t").join(Array(options.TabLen || 9).join(" ")); - return pdfEscape(s, flags); - } - - function transformTextToSpecialArray(text) { - //we don't want to destroy original text array, so cloning it - var sa = text.concat(); - var da = []; - var len = sa.length; - var curDa; - //we do array.join('text that must not be PDFescaped") - //thus, pdfEscape each component separately - while (len--) { - curDa = sa.shift(); - if (typeof curDa === "string") { - da.push(curDa); - } else { - if ( - Array.isArray(text) && - (curDa.length === 1 || - (curDa[1] === undefined && curDa[2] === undefined)) - ) { - da.push(curDa[0]); - } else { - da.push([curDa[0], curDa[1], curDa[2]]); - } - } - } - return da; - } - - function processTextByFunction(text, processingFunction) { - var result; - if (typeof text === "string") { - result = processingFunction(text)[0]; - } else if (Array.isArray(text)) { - //we don't want to destroy original text array, so cloning it - var sa = text.concat(); - var da = []; - var len = sa.length; - var curDa; - var tmpResult; - //we do array.join('text that must not be PDFescaped") - //thus, pdfEscape each component separately - while (len--) { - curDa = sa.shift(); - if (typeof curDa === "string") { - da.push(processingFunction(curDa)[0]); - } else if (Array.isArray(curDa) && typeof curDa[0] === "string") { - tmpResult = processingFunction(curDa[0], curDa[1], curDa[2]); - da.push([tmpResult[0], tmpResult[1], tmpResult[2]]); - } - } - result = da; - } - return result; - } - - //Check if text is of type String - var textIsOfTypeString = false; - var tmpTextIsOfTypeString = true; - - if (typeof text === "string") { - textIsOfTypeString = true; - } else if (Array.isArray(text)) { - //we don't want to destroy original text array, so cloning it - var sa = text.concat(); - da = []; - var len = sa.length; - var curDa; - //we do array.join('text that must not be PDFescaped") - //thus, pdfEscape each component separately - while (len--) { - curDa = sa.shift(); - if ( - typeof curDa !== "string" || - (Array.isArray(curDa) && typeof curDa[0] !== "string") - ) { - tmpTextIsOfTypeString = false; - } - } - textIsOfTypeString = tmpTextIsOfTypeString; - } - if (textIsOfTypeString === false) { - throw new Error( - 'Type of text must be string or Array. "' + - text + - '" is not recognized.' - ); - } - - //If there are any newlines in text, we assume - //the user wanted to print multiple lines, so break the - //text up into an array. If the text is already an array, - //we assume the user knows what they are doing. - //Convert text into an array anyway to simplify - //later code. - - if (typeof text === "string") { - if (text.match(/[\r?\n]/)) { - text = text.split(/\r\n|\r|\n/g); - } else { - text = [text]; - } - } - - //baseline - var height = activeFontSize / scope.internal.scaleFactor; - var descent = height * (lineHeight - 1); - - switch (options.baseline) { - case "bottom": - y -= descent; - break; - case "top": - y += height - descent; - break; - case "hanging": - y += height - 2 * descent; - break; - case "middle": - y += height / 2 - descent; - break; - } - - //multiline - maxWidth = options.maxWidth || 0; - - if (maxWidth > 0) { - if (typeof text === "string") { - text = scope.splitTextToSize(text, maxWidth); - } else if (Object.prototype.toString.call(text) === "[object Array]") { - text = text.reduce(function(acc, textLine) { - return acc.concat(scope.splitTextToSize(textLine, maxWidth)); - }, []); - } - } - - //creating Payload-Object to make text byRef - payload = { - text: text, - x: x, - y: y, - options: options, - mutex: { - pdfEscape: pdfEscape, - activeFontKey: activeFontKey, - fonts: fonts, - activeFontSize: activeFontSize - } - }; - events.publish("preProcessText", payload); - - text = payload.text; - options = payload.options; - - //angle - angle = options.angle; - - if ( - transformationMatrix instanceof Matrix === false && - angle && - typeof angle === "number" - ) { - angle *= Math.PI / 180; - - if (options.rotationDirection === 0) { - angle = -angle; - } - - if (apiMode === ApiMode.ADVANCED) { - angle = -angle; - } - - var c = Math.cos(angle); - var s = Math.sin(angle); - transformationMatrix = new Matrix(c, s, -s, c, 0, 0); - } else if (angle && angle instanceof Matrix) { - transformationMatrix = angle; - } - - if (apiMode === ApiMode.ADVANCED && !transformationMatrix) { - transformationMatrix = identityMatrix; - } - - //charSpace - - charSpace = options.charSpace || activeCharSpace; - - if (typeof charSpace !== "undefined") { - xtra += hpf(scale(charSpace)) + " Tc\n"; - this.setCharSpace(this.getCharSpace() || 0); - } - - horizontalScale = options.horizontalScale; - if (typeof horizontalScale !== "undefined") { - xtra += hpf(horizontalScale * 100) + " Tz\n"; - } - - //lang - - options.lang; - - //renderingMode - var renderingMode = -1; - var parmRenderingMode = - typeof options.renderingMode !== "undefined" - ? options.renderingMode - : options.stroke; - var pageContext = scope.internal.getCurrentPageInfo().pageContext; - - switch (parmRenderingMode) { - case 0: - case false: - case "fill": - renderingMode = 0; - break; - case 1: - case true: - case "stroke": - renderingMode = 1; - break; - case 2: - case "fillThenStroke": - renderingMode = 2; - break; - case 3: - case "invisible": - renderingMode = 3; - break; - case 4: - case "fillAndAddForClipping": - renderingMode = 4; - break; - case 5: - case "strokeAndAddPathForClipping": - renderingMode = 5; - break; - case 6: - case "fillThenStrokeAndAddToPathForClipping": - renderingMode = 6; - break; - case 7: - case "addToPathForClipping": - renderingMode = 7; - break; - } - - var usedRenderingMode = - typeof pageContext.usedRenderingMode !== "undefined" - ? pageContext.usedRenderingMode - : -1; - - //if the coder wrote it explicitly to use a specific - //renderingMode, then use it - if (renderingMode !== -1) { - xtra += renderingMode + " Tr\n"; - //otherwise check if we used the rendering Mode already - //if so then set the rendering Mode... - } else if (usedRenderingMode !== -1) { - xtra += "0 Tr\n"; - } - - if (renderingMode !== -1) { - pageContext.usedRenderingMode = renderingMode; - } - - //align - align = options.align || "left"; - var leading = activeFontSize * lineHeight; - var pageWidth = scope.internal.pageSize.getWidth(); - var activeFont = fonts[activeFontKey]; - charSpace = options.charSpace || activeCharSpace; - maxWidth = options.maxWidth || 0; - - var lineWidths; - flags = Object.assign({ autoencode: true, noBOM: true }, options.flags); - - var wordSpacingPerLine = []; - var findWidth = function(v) { - return ( - (scope.getStringUnitWidth(v, { - font: activeFont, - charSpace: charSpace, - fontSize: activeFontSize, - doKerning: false - }) * - activeFontSize) / - scaleFactor - ); - }; - if (Object.prototype.toString.call(text) === "[object Array]") { - da = transformTextToSpecialArray(text); - var newY; - if (align !== "left") { - lineWidths = da.map(findWidth); - } - //The first line uses the "main" Td setting, - //and the subsequent lines are offset by the - //previous line's x coordinate. - var prevWidth = 0; - var newX; - if (align === "right") { - //The passed in x coordinate defines the - //rightmost point of the text. - x -= lineWidths[0]; - text = []; - len = da.length; - for (var i = 0; i < len; i++) { - if (i === 0) { - newX = getHorizontalCoordinate(x); - newY = getVerticalCoordinate(y); - } else { - newX = scale(prevWidth - lineWidths[i]); - newY = -leading; - } - text.push([da[i], newX, newY]); - prevWidth = lineWidths[i]; - } - } else if (align === "center") { - //The passed in x coordinate defines - //the center point. - x -= lineWidths[0] / 2; - text = []; - len = da.length; - for (var j = 0; j < len; j++) { - if (j === 0) { - newX = getHorizontalCoordinate(x); - newY = getVerticalCoordinate(y); - } else { - newX = scale((prevWidth - lineWidths[j]) / 2); - newY = -leading; - } - text.push([da[j], newX, newY]); - prevWidth = lineWidths[j]; - } - } else if (align === "left") { - text = []; - len = da.length; - for (var h = 0; h < len; h++) { - text.push(da[h]); - } - } else if (align === "justify" && activeFont.encoding === "Identity-H") { - // when using unicode fonts, wordSpacePerLine does not apply - text = []; - len = da.length; - maxWidth = maxWidth !== 0 ? maxWidth : pageWidth; - let backToStartX = 0; - for (var l = 0; l < len; l++) { - newY = l === 0 ? getVerticalCoordinate(y) : -leading; - newX = l === 0 ? getHorizontalCoordinate(x) : backToStartX; - if (l < len - 1) { - let spacing = scale( - (maxWidth - lineWidths[l]) / (da[l].split(" ").length - 1) - ); - let words = da[l].split(" "); - text.push([words[0] + " ", newX, newY]); - backToStartX = 0; // distance to reset back to the left - for (let i = 1; i < words.length; i++) { - let shiftAmount = - (findWidth(words[i - 1] + " " + words[i]) - - findWidth(words[i])) * - scaleFactor + - spacing; - if (i == words.length - 1) text.push([words[i], shiftAmount, 0]); - else text.push([words[i] + " ", shiftAmount, 0]); - backToStartX -= shiftAmount; - } - } else { - text.push([da[l], newX, newY]); - } - } - text.push(["", backToStartX, 0]); - } else if (align === "justify") { - text = []; - len = da.length; - maxWidth = maxWidth !== 0 ? maxWidth : pageWidth; - for (var l = 0; l < len; l++) { - newY = l === 0 ? getVerticalCoordinate(y) : -leading; - newX = l === 0 ? getHorizontalCoordinate(x) : 0; - - const numSpaces = da[l].split(" ").length - 1; - const spacing = - numSpaces > 0 ? (maxWidth - lineWidths[l]) / numSpaces : 0; - - if (l < len - 1) { - wordSpacingPerLine.push(hpf(scale(spacing))); - } else { - wordSpacingPerLine.push(0); - } - text.push([da[l], newX, newY]); - } - } else { - throw new Error( - 'Unrecognized alignment option, use "left", "center", "right" or "justify".' - ); - } - } - - //R2L - var doReversing = typeof options.R2L === "boolean" ? options.R2L : R2L; - if (doReversing === true) { - text = processTextByFunction(text, function(text, posX, posY) { - return [ - text - .split("") - .reverse() - .join(""), - posX, - posY - ]; - }); - } - - //creating Payload-Object to make text byRef - payload = { - text: text, - x: x, - y: y, - options: options, - mutex: { - pdfEscape: pdfEscape, - activeFontKey: activeFontKey, - fonts: fonts, - activeFontSize: activeFontSize - } - }; - events.publish("postProcessText", payload); - - text = payload.text; - isHex = payload.mutex.isHex || false; - - //Escaping - var activeFontEncoding = fonts[activeFontKey].encoding; - - if ( - activeFontEncoding === "WinAnsiEncoding" || - activeFontEncoding === "StandardEncoding" - ) { - text = processTextByFunction(text, function(text, posX, posY) { - return [ESC(text), posX, posY]; - }); - } - - da = transformTextToSpecialArray(text); - - text = []; - var STRING = 0; - var ARRAY = 1; - var variant = Array.isArray(da[0]) ? ARRAY : STRING; - var posX; - var posY; - var content; - var wordSpacing = ""; - - var generatePosition = function( - parmPosX, - parmPosY, - parmTransformationMatrix - ) { - var position = ""; - if (parmTransformationMatrix instanceof Matrix) { - // It is kind of more intuitive to apply a plain rotation around the text anchor set by x and y - // but when the user supplies an arbitrary transformation matrix, the x and y offsets should be applied - // in the coordinate system established by this matrix - if (typeof options.angle === "number") { - parmTransformationMatrix = matrixMult( - parmTransformationMatrix, - new Matrix(1, 0, 0, 1, parmPosX, parmPosY) - ); - } else { - parmTransformationMatrix = matrixMult( - new Matrix(1, 0, 0, 1, parmPosX, parmPosY), - parmTransformationMatrix - ); - } - - if (apiMode === ApiMode.ADVANCED) { - parmTransformationMatrix = matrixMult( - new Matrix(1, 0, 0, -1, 0, 0), - parmTransformationMatrix - ); - } - - position = parmTransformationMatrix.join(" ") + " Tm\n"; - } else { - position = hpf(parmPosX) + " " + hpf(parmPosY) + " Td\n"; - } - return position; - }; - - for (var lineIndex = 0; lineIndex < da.length; lineIndex++) { - wordSpacing = ""; - - switch (variant) { - case ARRAY: - content = - (isHex ? "<" : "(") + da[lineIndex][0] + (isHex ? ">" : ")"); - posX = parseFloat(da[lineIndex][1]); - posY = parseFloat(da[lineIndex][2]); - break; - case STRING: - content = (isHex ? "<" : "(") + da[lineIndex] + (isHex ? ">" : ")"); - posX = getHorizontalCoordinate(x); - posY = getVerticalCoordinate(y); - break; - } - - if ( - typeof wordSpacingPerLine !== "undefined" && - typeof wordSpacingPerLine[lineIndex] !== "undefined" - ) { - wordSpacing = wordSpacingPerLine[lineIndex] + " Tw\n"; - } - - if (lineIndex === 0) { - text.push( - wordSpacing + - generatePosition(posX, posY, transformationMatrix) + - content - ); - } else if (variant === STRING) { - text.push(wordSpacing + content); - } else if (variant === ARRAY) { - text.push( - wordSpacing + - generatePosition(posX, posY, transformationMatrix) + - content - ); - } - } - - text = variant === STRING ? text.join(" Tj\nT* ") : text.join(" Tj\n"); - text += " Tj\n"; - - var result = "BT\n/"; - result += activeFontKey + " " + activeFontSize + " Tf\n"; // font face, style, size - result += hpf(activeFontSize * lineHeight) + " TL\n"; // line spacing - result += textColor + "\n"; - result += xtra; - result += text; - result += "ET"; - - out(result); - usedFonts[activeFontKey] = true; - return scope; - }; - - // PDF supports these path painting and clip path operators: - // - // S - stroke - // s - close/stroke - // f (F) - fill non-zero - // f* - fill evenodd - // B - fill stroke nonzero - // B* - fill stroke evenodd - // b - close fill stroke nonzero - // b* - close fill stroke evenodd - // n - nothing (consume path) - // W - clip nonzero - // W* - clip evenodd - // - // In order to keep the API small, we omit the close-and-fill/stroke operators and provide a separate close() - // method. - /** - * - * @name clip - * @function - * @instance - * @param {string} rule Only possible value is 'evenodd' - * @returns {jsPDF} - * @memberof jsPDF# - * @description All .clip() after calling drawing ops with a style argument of null. - */ - var clip = (API.__private__.clip = API.clip = function(rule) { - // Call .clip() after calling drawing ops with a style argument of null - // W is the PDF clipping op - if ("evenodd" === rule) { - out("W*"); - } else { - out("W"); - } - return this; - }); - - /** - * @name clipEvenOdd - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @description Modify the current clip path by intersecting it with the current path using the even-odd rule. Note - * that this will NOT consume the current path. In order to only use this path for clipping call - * {@link API.discardPath} afterwards. - */ - API.clipEvenOdd = function() { - return clip("evenodd"); - }; - - /** - * Consumes the current path without any effect. Mainly used in combination with {@link clip} or - * {@link clipEvenOdd}. The PDF "n" operator. - * @name discardPath - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - */ - API.__private__.discardPath = API.discardPath = function() { - out("n"); - return this; - }; - - var isValidStyle = (API.__private__.isValidStyle = function(style) { - var validStyleVariants = [ - undefined, - null, - "S", - "D", - "F", - "DF", - "FD", - "f", - "f*", - "B", - "B*", - "n" - ]; - var result = false; - if (validStyleVariants.indexOf(style) !== -1) { - result = true; - } - return result; - }); - - API.__private__.setDefaultPathOperation = API.setDefaultPathOperation = function( - operator - ) { - if (isValidStyle(operator)) { - defaultPathOperation = operator; - } - return this; - }; - - var getStyle = (API.__private__.getStyle = API.getStyle = function(style) { - // see path-painting operators in PDF spec - var op = defaultPathOperation; // stroke - - switch (style) { - case "D": - case "S": - op = "S"; // stroke - break; - case "F": - op = "f"; // fill - break; - case "FD": - case "DF": - op = "B"; - break; - case "f": - case "f*": - case "B": - case "B*": - /* - Allow direct use of these PDF path-painting operators: - - f fill using nonzero winding number rule - - f* fill using even-odd rule - - B fill then stroke with fill using non-zero winding number rule - - B* fill then stroke with fill using even-odd rule - */ - op = style; - break; - } - return op; - }); - - /** - * Close the current path. The PDF "h" operator. - * @name close - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - */ - var close = (API.close = function() { - out("h"); - return this; - }); - - /** - * Stroke the path. The PDF "S" operator. - * @name stroke - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - */ - API.stroke = function() { - out("S"); - return this; - }; - - /** - * Fill the current path using the nonzero winding number rule. If a pattern is provided, the path will be filled - * with this pattern, otherwise with the current fill color. Equivalent to the PDF "f" operator. - * @name fill - * @function - * @instance - * @param {PatternData=} pattern If provided the path will be filled with this pattern - * @returns {jsPDF} - * @memberof jsPDF# - */ - API.fill = function(pattern) { - fillWithOptionalPattern("f", pattern); - return this; - }; - - /** - * Fill the current path using the even-odd rule. The PDF f* operator. - * @see API.fill - * @name fillEvenOdd - * @function - * @instance - * @param {PatternData=} pattern If provided the path will be filled with this pattern - * @returns {jsPDF} - * @memberof jsPDF# - */ - API.fillEvenOdd = function(pattern) { - fillWithOptionalPattern("f*", pattern); - return this; - }; - - /** - * Fill using the nonzero winding number rule and then stroke the current Path. The PDF "B" operator. - * @see API.fill - * @name fillStroke - * @function - * @instance - * @param {PatternData=} pattern If provided the path will be stroked with this pattern - * @returns {jsPDF} - * @memberof jsPDF# - */ - API.fillStroke = function(pattern) { - fillWithOptionalPattern("B", pattern); - return this; - }; - - /** - * Fill using the even-odd rule and then stroke the current Path. The PDF "B" operator. - * @see API.fill - * @name fillStrokeEvenOdd - * @function - * @instance - * @param {PatternData=} pattern If provided the path will be fill-stroked with this pattern - * @returns {jsPDF} - * @memberof jsPDF# - */ - API.fillStrokeEvenOdd = function(pattern) { - fillWithOptionalPattern("B*", pattern); - return this; - }; - - var fillWithOptionalPattern = function(style, pattern) { - if (typeof pattern === "object") { - fillWithPattern(pattern, style); - } else { - out(style); - } - }; - - var putStyle = function(style) { - if ( - style === null || - (apiMode === ApiMode.ADVANCED && style === undefined) - ) { - return; - } - - style = getStyle(style); - - // stroking / filling / both the path - out(style); - }; - - function cloneTilingPattern(patternKey, boundingBox, xStep, yStep, matrix) { - var clone = new TilingPattern( - boundingBox || this.boundingBox, - xStep || this.xStep, - yStep || this.yStep, - this.gState, - matrix || this.matrix - ); - clone.stream = this.stream; - var key = patternKey + "$$" + this.cloneIndex++ + "$$"; - addPattern(key, clone); - return clone; - } - - var fillWithPattern = function(patternData, style) { - var patternId = patternMap[patternData.key]; - var pattern = patterns[patternId]; - - if (pattern instanceof ShadingPattern) { - out("q"); - - out(clipRuleFromStyle(style)); - - if (pattern.gState) { - API.setGState(pattern.gState); - } - out(patternData.matrix.toString() + " cm"); - out("/" + patternId + " sh"); - out("Q"); - } else if (pattern instanceof TilingPattern) { - // pdf draws patterns starting at the bottom left corner and they are not affected by the global transformation, - // so we must flip them - var matrix = new Matrix(1, 0, 0, -1, 0, getPageHeight()); - - if (patternData.matrix) { - matrix = matrix.multiply(patternData.matrix || identityMatrix); - // we cannot apply a matrix to the pattern on use so we must abuse the pattern matrix and create new instances - // for each use - patternId = cloneTilingPattern.call( - pattern, - patternData.key, - patternData.boundingBox, - patternData.xStep, - patternData.yStep, - matrix - ).id; - } - - out("q"); - out("/Pattern cs"); - out("/" + patternId + " scn"); - - if (pattern.gState) { - API.setGState(pattern.gState); - } - - out(style); - out("Q"); - } - }; - - var clipRuleFromStyle = function(style) { - switch (style) { - case "f": - case "F": - return "W n"; - case "f*": - return "W* n"; - case "B": - return "W S"; - case "B*": - return "W* S"; - - // these two are for compatibility reasons (in the past, calling any primitive method with a shading pattern - // and "n"/"S" as style would still fill/fill and stroke the path) - case "S": - return "W S"; - case "n": - return "W n"; - } - }; - - /** - * Begin a new subpath by moving the current point to coordinates (x, y). The PDF "m" operator. - * @param {number} x - * @param {number} y - * @name moveTo - * @function - * @instance - * @memberof jsPDF# - * @returns {jsPDF} - */ - var moveTo = (API.moveTo = function(x, y) { - out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " m"); - return this; - }); - - /** - * Append a straight line segment from the current point to the point (x, y). The PDF "l" operator. - * @param {number} x - * @param {number} y - * @memberof jsPDF# - * @name lineTo - * @function - * @instance - * @memberof jsPDF# - * @returns {jsPDF} - */ - var lineTo = (API.lineTo = function(x, y) { - out(hpf(scale(x)) + " " + hpf(transformScaleY(y)) + " l"); - return this; - }); - - /** - * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point - * (x3, y3), using (x1, y1) and (x2, y2) as Bézier control points. The new current point shall be (x3, x3). - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @param {number} x3 - * @param {number} y3 - * @memberof jsPDF# - * @name curveTo - * @function - * @instance - * @memberof jsPDF# - * @returns {jsPDF} - */ - var curveTo = (API.curveTo = function(x1, y1, x2, y2, x3, y3) { - out( - [ - hpf(scale(x1)), - hpf(transformScaleY(y1)), - hpf(scale(x2)), - hpf(transformScaleY(y2)), - hpf(scale(x3)), - hpf(transformScaleY(y3)), - "c" - ].join(" ") - ); - return this; - }); - - /** - * Draw a line on the current page. - * - * @name line - * @function - * @instance - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @param {string} style A string specifying the painting style or null. Valid styles include: 'S' [default] - stroke, 'F' - fill, and 'DF' (or 'FD') - fill then stroke. A null value postpones setting the style so that a shape may be composed using multiple method calls. The last drawing method call used to define the shape should not have a null style argument. default: 'S' - * @returns {jsPDF} - * @memberof jsPDF# - */ - API.__private__.line = API.line = function(x1, y1, x2, y2, style) { - if ( - isNaN(x1) || - isNaN(y1) || - isNaN(x2) || - isNaN(y2) || - !isValidStyle(style) - ) { - throw new Error("Invalid arguments passed to jsPDF.line"); - } - if (apiMode === ApiMode.COMPAT) { - return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1], style || "S"); - } else { - return this.lines([[x2 - x1, y2 - y1]], x1, y1, [1, 1]).stroke(); - } - }; - - /** - * @typedef {Object} PatternData - * {Matrix|undefined} matrix - * {Number|undefined} xStep - * {Number|undefined} yStep - * {Array.|undefined} boundingBox - */ - - /** - * Adds series of curves (straight lines or cubic bezier curves) to canvas, starting at `x`, `y` coordinates. - * All data points in `lines` are relative to last line origin. - * `x`, `y` become x1,y1 for first line / curve in the set. - * For lines you only need to specify [x2, y2] - (ending point) vector against x1, y1 starting point. - * For bezier curves you need to specify [x2,y2,x3,y3,x4,y4] - vectors to control points 1, 2, ending point. All vectors are against the start of the curve - x1,y1. - * - * @example .lines([[2,2],[-2,2],[1,1,2,2,3,3],[2,1]], 212,110, [1,1], 'F', false) // line, line, bezier curve, line - * @param {Array} lines Array of *vector* shifts as pairs (lines) or sextets (cubic bezier curves). - * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} scale (Defaults to [1.0,1.0]) x,y Scaling factor for all vectors. Elements can be any floating number Sub-one makes drawing smaller. Over-one grows the drawing. Negative flips the direction. - * @param {string=} style A string specifying the painting style or null. Valid styles include: - * 'S' [default] - stroke, - * 'F' - fill, - * and 'DF' (or 'FD') - fill then stroke. - * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple - * method calls. The last drawing method call used to define the shape should not have a null style argument. - * - * In "advanced" API mode this parameter is deprecated. - * @param {Boolean=} closed If true, the path is closed with a straight line from the end of the last curve to the starting point. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name lines - */ - API.__private__.lines = API.lines = function( - lines, - x, - y, - scale, - style, - closed - ) { - var scalex, scaley, i, l, leg, x2, y2, x3, y3, x4, y4, tmp; - - // Pre-August-2012 the order of arguments was function(x, y, lines, scale, style) - // in effort to make all calls have similar signature like - // function(content, coordinateX, coordinateY , miscellaneous) - // this method had its args flipped. - // code below allows backward compatibility with old arg order. - if (typeof lines === "number") { - tmp = y; - y = x; - x = lines; - lines = tmp; - } - - scale = scale || [1, 1]; - closed = closed || false; - - if ( - isNaN(x) || - isNaN(y) || - !Array.isArray(lines) || - !Array.isArray(scale) || - !isValidStyle(style) || - typeof closed !== "boolean" - ) { - throw new Error("Invalid arguments passed to jsPDF.lines"); - } - - // starting point - moveTo(x, y); - - scalex = scale[0]; - scaley = scale[1]; - l = lines.length; - //, x2, y2 // bezier only. In page default measurement "units", *after* scaling - //, x3, y3 // bezier only. In page default measurement "units", *after* scaling - // ending point for all, lines and bezier. . In page default measurement "units", *after* scaling - x4 = x; // last / ending point = starting point for first item. - y4 = y; // last / ending point = starting point for first item. - - for (i = 0; i < l; i++) { - leg = lines[i]; - if (leg.length === 2) { - // simple line - x4 = leg[0] * scalex + x4; // here last x4 was prior ending point - y4 = leg[1] * scaley + y4; // here last y4 was prior ending point - lineTo(x4, y4); - } else { - // bezier curve - x2 = leg[0] * scalex + x4; // here last x4 is prior ending point - y2 = leg[1] * scaley + y4; // here last y4 is prior ending point - x3 = leg[2] * scalex + x4; // here last x4 is prior ending point - y3 = leg[3] * scaley + y4; // here last y4 is prior ending point - x4 = leg[4] * scalex + x4; // here last x4 was prior ending point - y4 = leg[5] * scaley + y4; // here last y4 was prior ending point - curveTo(x2, y2, x3, y3, x4, y4); - } - } - - if (closed) { - close(); - } - - putStyle(style); - return this; - }; - - /** - * Similar to {@link API.lines} but all coordinates are interpreted as absolute coordinates instead of relative. - * @param {Array} lines An array of {op: operator, c: coordinates} object, where op is one of "m" (move to), "l" (line to) - * "c" (cubic bezier curve) and "h" (close (sub)path)). c is an array of coordinates. "m" and "l" expect two, "c" - * six and "h" an empty array (or undefined). - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name path - */ - API.path = function(lines) { - for (var i = 0; i < lines.length; i++) { - var leg = lines[i]; - var coords = leg.c; - switch (leg.op) { - case "m": - moveTo(coords[0], coords[1]); - break; - case "l": - lineTo(coords[0], coords[1]); - break; - case "c": - curveTo.apply(this, coords); - break; - case "h": - close(); - break; - } - } - - return this; - }; - - /** - * Adds a rectangle to PDF. - * - * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} w Width (in units declared at inception of PDF document) - * @param {number} h Height (in units declared at inception of PDF document) - * @param {string=} style A string specifying the painting style or null. Valid styles include: - * 'S' [default] - stroke, - * 'F' - fill, - * and 'DF' (or 'FD') - fill then stroke. - * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple - * method calls. The last drawing method call used to define the shape should not have a null style argument. - * - * In "advanced" API mode this parameter is deprecated. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name rect - */ - API.__private__.rect = API.rect = function(x, y, w, h, style) { - if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h) || !isValidStyle(style)) { - throw new Error("Invalid arguments passed to jsPDF.rect"); - } - if (apiMode === ApiMode.COMPAT) { - h = -h; - } - - out( - [ - hpf(scale(x)), - hpf(transformScaleY(y)), - hpf(scale(w)), - hpf(scale(h)), - "re" - ].join(" ") - ); - - putStyle(style); - return this; - }; - - /** - * Adds a triangle to PDF. - * - * @param {number} x1 Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y1 Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} x2 Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y2 Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} x3 Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y3 Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {string=} style A string specifying the painting style or null. Valid styles include: - * 'S' [default] - stroke, - * 'F' - fill, - * and 'DF' (or 'FD') - fill then stroke. - * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple - * method calls. The last drawing method call used to define the shape should not have a null style argument. - * - * In "advanced" API mode this parameter is deprecated. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name triangle - */ - API.__private__.triangle = API.triangle = function( - x1, - y1, - x2, - y2, - x3, - y3, - style - ) { - if ( - isNaN(x1) || - isNaN(y1) || - isNaN(x2) || - isNaN(y2) || - isNaN(x3) || - isNaN(y3) || - !isValidStyle(style) - ) { - throw new Error("Invalid arguments passed to jsPDF.triangle"); - } - this.lines( - [ - [x2 - x1, y2 - y1], // vector to point 2 - [x3 - x2, y3 - y2], // vector to point 3 - [x1 - x3, y1 - y3] // closing vector back to point 1 - ], - x1, - y1, // start of path - [1, 1], - style, - true - ); - return this; - }; - - /** - * Adds a rectangle with rounded corners to PDF. - * - * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} w Width (in units declared at inception of PDF document) - * @param {number} h Height (in units declared at inception of PDF document) - * @param {number} rx Radius along x axis (in units declared at inception of PDF document) - * @param {number} ry Radius along y axis (in units declared at inception of PDF document) - * @param {string=} style A string specifying the painting style or null. Valid styles include: - * 'S' [default] - stroke, - * 'F' - fill, - * and 'DF' (or 'FD') - fill then stroke. - * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple - * method calls. The last drawing method call used to define the shape should not have a null style argument. - * - * In "advanced" API mode this parameter is deprecated. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name roundedRect - */ - API.__private__.roundedRect = API.roundedRect = function( - x, - y, - w, - h, - rx, - ry, - style - ) { - if ( - isNaN(x) || - isNaN(y) || - isNaN(w) || - isNaN(h) || - isNaN(rx) || - isNaN(ry) || - !isValidStyle(style) - ) { - throw new Error("Invalid arguments passed to jsPDF.roundedRect"); - } - var MyArc = (4 / 3) * (Math.SQRT2 - 1); - - rx = Math.min(rx, w * 0.5); - ry = Math.min(ry, h * 0.5); - - this.lines( - [ - [w - 2 * rx, 0], - [rx * MyArc, 0, rx, ry - ry * MyArc, rx, ry], - [0, h - 2 * ry], - [0, ry * MyArc, -(rx * MyArc), ry, -rx, ry], - [-w + 2 * rx, 0], - [-(rx * MyArc), 0, -rx, -(ry * MyArc), -rx, -ry], - [0, -h + 2 * ry], - [0, -(ry * MyArc), rx * MyArc, -ry, rx, -ry] - ], - x + rx, - y, // start of path - [1, 1], - style, - true - ); - return this; - }; - - /** - * Adds an ellipse to PDF. - * - * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} rx Radius along x axis (in units declared at inception of PDF document) - * @param {number} ry Radius along y axis (in units declared at inception of PDF document) - * @param {string=} style A string specifying the painting style or null. Valid styles include: - * 'S' [default] - stroke, - * 'F' - fill, - * and 'DF' (or 'FD') - fill then stroke. - * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple - * method calls. The last drawing method call used to define the shape should not have a null style argument. - * - * In "advanced" API mode this parameter is deprecated. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name ellipse - */ - API.__private__.ellipse = API.ellipse = function(x, y, rx, ry, style) { - if ( - isNaN(x) || - isNaN(y) || - isNaN(rx) || - isNaN(ry) || - !isValidStyle(style) - ) { - throw new Error("Invalid arguments passed to jsPDF.ellipse"); - } - var lx = (4 / 3) * (Math.SQRT2 - 1) * rx, - ly = (4 / 3) * (Math.SQRT2 - 1) * ry; - - moveTo(x + rx, y); - curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry); - curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y); - curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry); - curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y); - - putStyle(style); - return this; - }; - - /** - * Adds an circle to PDF. - * - * @param {number} x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} r Radius (in units declared at inception of PDF document) - * @param {string=} style A string specifying the painting style or null. Valid styles include: - * 'S' [default] - stroke, - * 'F' - fill, - * and 'DF' (or 'FD') - fill then stroke. - * In "compat" API mode, a null value postpones setting the style so that a shape may be composed using multiple - * method calls. The last drawing method call used to define the shape should not have a null style argument. - * - * In "advanced" API mode this parameter is deprecated. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name circle - */ - API.__private__.circle = API.circle = function(x, y, r, style) { - if (isNaN(x) || isNaN(y) || isNaN(r) || !isValidStyle(style)) { - throw new Error("Invalid arguments passed to jsPDF.circle"); - } - return this.ellipse(x, y, r, r, style); - }; - - /** - * Sets text font face, variant for upcoming text elements. - * See output of jsPDF.getFontList() for possible font names, styles. - * - * @param {string} fontName Font name or family. Example: "times". - * @param {string} fontStyle Font style or variant. Example: "italic". - * @param {number | string} fontWeight Weight of the Font. Example: "normal" | 400 - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setFont - */ - API.setFont = function(fontName, fontStyle, fontWeight) { - if (fontWeight) { - fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight); - } - activeFontKey = getFont(fontName, fontStyle, { - disableWarning: false - }); - return this; - }; - - /** - * Gets text font face, variant for upcoming text elements. - * - * @function - * @instance - * @returns {Object} - * @memberof jsPDF# - * @name getFont - */ - var getFontEntry = (API.__private__.getFont = API.getFont = function() { - return fonts[getFont.apply(API, arguments)]; - }); - - /** - * Returns an object - a tree of fontName to fontStyle relationships available to - * active PDF document. - * - * @public - * @function - * @instance - * @returns {Object} Like {'times':['normal', 'italic', ... ], 'arial':['normal', 'bold', ... ], ... } - * @memberof jsPDF# - * @name getFontList - */ - API.__private__.getFontList = API.getFontList = function() { - var list = {}, - fontName, - fontStyle; - - for (fontName in fontmap) { - if (fontmap.hasOwnProperty(fontName)) { - list[fontName] = []; - for (fontStyle in fontmap[fontName]) { - if (fontmap[fontName].hasOwnProperty(fontStyle)) { - list[fontName].push(fontStyle); - } - } - } - } - return list; - }; - - /** - * Add a custom font to the current instance. - * - * @param {string} postScriptName PDF specification full name for the font. - * @param {string} id PDF-document-instance-specific label assinged to the font. - * @param {string} fontStyle Style of the Font. - * @param {number | string} fontWeight Weight of the Font. - * @param {Object} encoding Encoding_name-to-Font_metrics_object mapping. - * @function - * @instance - * @memberof jsPDF# - * @name addFont - * @returns {string} fontId - */ - API.addFont = function( - postScriptName, - fontName, - fontStyle, - fontWeight, - encoding - ) { - var encodingOptions = [ - "StandardEncoding", - "MacRomanEncoding", - "Identity-H", - "WinAnsiEncoding" - ]; - if (arguments[3] && encodingOptions.indexOf(arguments[3]) !== -1) { - //IE 11 fix - encoding = arguments[3]; - } else if (arguments[3] && encodingOptions.indexOf(arguments[3]) == -1) { - fontStyle = combineFontStyleAndFontWeight(fontStyle, fontWeight); - } - encoding = encoding || "Identity-H"; - return addFont.call(this, postScriptName, fontName, fontStyle, encoding); - }; - - var lineWidth = options.lineWidth || 0.200025; // 2mm - /** - * Gets the line width, default: 0.200025. - * - * @function - * @instance - * @returns {number} lineWidth - * @memberof jsPDF# - * @name getLineWidth - */ - var getLineWidth = (API.__private__.getLineWidth = API.getLineWidth = function() { - return lineWidth; - }); - - /** - * Sets line width for upcoming lines. - * - * @param {number} width Line width (in units declared at inception of PDF document). - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setLineWidth - */ - var setLineWidth = (API.__private__.setLineWidth = API.setLineWidth = function( - width - ) { - lineWidth = width; - out(hpf(scale(width)) + " w"); - return this; - }); - - /** - * Sets the dash pattern for upcoming lines. - * - * To reset the settings simply call the method without any parameters. - * @param {Array} dashArray An array containing 0-2 numbers. The first number sets the length of the - * dashes, the second number the length of the gaps. If the second number is missing, the gaps are considered - * to be as long as the dashes. An empty array means solid, unbroken lines. - * @param {number} dashPhase The phase lines start with. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setLineDashPattern - */ - API.__private__.setLineDash = jsPDF.API.setLineDash = jsPDF.API.setLineDashPattern = function( - dashArray, - dashPhase - ) { - dashArray = dashArray || []; - dashPhase = dashPhase || 0; - - if (isNaN(dashPhase) || !Array.isArray(dashArray)) { - throw new Error("Invalid arguments passed to jsPDF.setLineDash"); - } - - dashArray = dashArray - .map(function(x) { - return hpf(scale(x)); - }) - .join(" "); - dashPhase = hpf(scale(dashPhase)); - - out("[" + dashArray + "] " + dashPhase + " d"); - return this; - }; - - var lineHeightFactor; - - var getLineHeight = (API.__private__.getLineHeight = API.getLineHeight = function() { - return activeFontSize * lineHeightFactor; - }); - - API.__private__.getLineHeight = API.getLineHeight = function() { - return activeFontSize * lineHeightFactor; - }; - - /** - * Sets the LineHeightFactor of proportion. - * - * @param {number} value LineHeightFactor value. Default: 1.15. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setLineHeightFactor - */ - var setLineHeightFactor = (API.__private__.setLineHeightFactor = API.setLineHeightFactor = function( - value - ) { - value = value || 1.15; - if (typeof value === "number") { - lineHeightFactor = value; - } - return this; - }); - - /** - * Gets the LineHeightFactor, default: 1.15. - * - * @function - * @instance - * @returns {number} lineHeightFactor - * @memberof jsPDF# - * @name getLineHeightFactor - */ - var getLineHeightFactor = (API.__private__.getLineHeightFactor = API.getLineHeightFactor = function() { - return lineHeightFactor; - }); - - setLineHeightFactor(options.lineHeight); - - var getHorizontalCoordinate = (API.__private__.getHorizontalCoordinate = function( - value - ) { - return scale(value); - }); - - var getVerticalCoordinate = (API.__private__.getVerticalCoordinate = function( - value - ) { - if (apiMode === ApiMode.ADVANCED) { - return value; - } else { - var pageHeight = - pagesContext[currentPage].mediaBox.topRightY - - pagesContext[currentPage].mediaBox.bottomLeftY; - return pageHeight - scale(value); - } - }); - - var getHorizontalCoordinateString = (API.__private__.getHorizontalCoordinateString = API.getHorizontalCoordinateString = function( - value - ) { - return hpf(getHorizontalCoordinate(value)); - }); - - var getVerticalCoordinateString = (API.__private__.getVerticalCoordinateString = API.getVerticalCoordinateString = function( - value - ) { - return hpf(getVerticalCoordinate(value)); - }); - - var strokeColor = options.strokeColor || "0 G"; - - /** - * Gets the stroke color for upcoming elements. - * - * @function - * @instance - * @returns {string} colorAsHex - * @memberof jsPDF# - * @name getDrawColor - */ - API.__private__.getStrokeColor = API.getDrawColor = function() { - return decodeColorString(strokeColor); - }; - - /** - * Sets the stroke color for upcoming elements. - * - * Depending on the number of arguments given, Gray, RGB, or CMYK - * color space is implied. - * - * When only ch1 is given, "Gray" color space is implied and it - * must be a value in the range from 0.00 (solid black) to to 1.00 (white) - * if values are communicated as String types, or in range from 0 (black) - * to 255 (white) if communicated as Number type. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each - * value must be in the range from 0.00 (minimum intensity) to to 1.00 - * (max intensity) if values are communicated as String types, or - * from 0 (min intensity) to to 255 (max intensity) if values are communicated - * as Number types. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each - * value must be a in the range from 0.00 (0% concentration) to to - * 1.00 (100% concentration) - * - * Because JavaScript treats fixed point numbers badly (rounds to - * floating point nearest to binary representation) it is highly advised to - * communicate the fractional numbers as String types, not JavaScript Number type. - * - * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. - * @param {Number} ch2 Color channel value. - * @param {Number} ch3 Color channel value. - * @param {Number} ch4 Color channel value. - * - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setDrawColor - */ - API.__private__.setStrokeColor = API.setDrawColor = function( - ch1, - ch2, - ch3, - ch4 - ) { - var options = { - ch1: ch1, - ch2: ch2, - ch3: ch3, - ch4: ch4, - pdfColorType: "draw", - precision: 2 - }; - - strokeColor = encodeColorString(options); - out(strokeColor); - return this; - }; - - var fillColor = options.fillColor || "0 g"; - - /** - * Gets the fill color for upcoming elements. - * - * @function - * @instance - * @returns {string} colorAsHex - * @memberof jsPDF# - * @name getFillColor - */ - API.__private__.getFillColor = API.getFillColor = function() { - return decodeColorString(fillColor); - }; - - /** - * Sets the fill color for upcoming elements. - * - * Depending on the number of arguments given, Gray, RGB, or CMYK - * color space is implied. - * - * When only ch1 is given, "Gray" color space is implied and it - * must be a value in the range from 0.00 (solid black) to to 1.00 (white) - * if values are communicated as String types, or in range from 0 (black) - * to 255 (white) if communicated as Number type. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each - * value must be in the range from 0.00 (minimum intensity) to to 1.00 - * (max intensity) if values are communicated as String types, or - * from 0 (min intensity) to to 255 (max intensity) if values are communicated - * as Number types. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each - * value must be a in the range from 0.00 (0% concentration) to to - * 1.00 (100% concentration) - * - * Because JavaScript treats fixed point numbers badly (rounds to - * floating point nearest to binary representation) it is highly advised to - * communicate the fractional numbers as String types, not JavaScript Number type. - * - * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. - * @param {Number} ch2 Color channel value. - * @param {Number} ch3 Color channel value. - * @param {Number} ch4 Color channel value. - * - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setFillColor - */ - API.__private__.setFillColor = API.setFillColor = function( - ch1, - ch2, - ch3, - ch4 - ) { - var options = { - ch1: ch1, - ch2: ch2, - ch3: ch3, - ch4: ch4, - pdfColorType: "fill", - precision: 2 - }; - - fillColor = encodeColorString(options); - out(fillColor); - return this; - }; - - var textColor = options.textColor || "0 g"; - /** - * Gets the text color for upcoming elements. - * - * @function - * @instance - * @returns {string} colorAsHex - * @memberof jsPDF# - * @name getTextColor - */ - var getTextColor = (API.__private__.getTextColor = API.getTextColor = function() { - return decodeColorString(textColor); - }); - /** - * Sets the text color for upcoming elements. - * - * Depending on the number of arguments given, Gray, RGB, or CMYK - * color space is implied. - * - * When only ch1 is given, "Gray" color space is implied and it - * must be a value in the range from 0.00 (solid black) to to 1.00 (white) - * if values are communicated as String types, or in range from 0 (black) - * to 255 (white) if communicated as Number type. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When only ch1,ch2,ch3 are given, "RGB" color space is implied and each - * value must be in the range from 0.00 (minimum intensity) to to 1.00 - * (max intensity) if values are communicated as String types, or - * from 0 (min intensity) to to 255 (max intensity) if values are communicated - * as Number types. - * The RGB-like 0-255 range is provided for backward compatibility. - * - * When ch1,ch2,ch3,ch4 are given, "CMYK" color space is implied and each - * value must be a in the range from 0.00 (0% concentration) to to - * 1.00 (100% concentration) - * - * Because JavaScript treats fixed point numbers badly (rounds to - * floating point nearest to binary representation) it is highly advised to - * communicate the fractional numbers as String types, not JavaScript Number type. - * - * @param {Number|String} ch1 Color channel value or {string} ch1 color value in hexadecimal, example: '#FFFFFF'. - * @param {Number} ch2 Color channel value. - * @param {Number} ch3 Color channel value. - * @param {Number} ch4 Color channel value. - * - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setTextColor - */ - API.__private__.setTextColor = API.setTextColor = function( - ch1, - ch2, - ch3, - ch4 - ) { - var options = { - ch1: ch1, - ch2: ch2, - ch3: ch3, - ch4: ch4, - pdfColorType: "text", - precision: 3 - }; - textColor = encodeColorString(options); - - return this; - }; - - var activeCharSpace = options.charSpace; - - /** - * Get global value of CharSpace. - * - * @function - * @instance - * @returns {number} charSpace - * @memberof jsPDF# - * @name getCharSpace - */ - var getCharSpace = (API.__private__.getCharSpace = API.getCharSpace = function() { - return parseFloat(activeCharSpace || 0); - }); - - /** - * Set global value of CharSpace. - * - * @param {number} charSpace - * @function - * @instance - * @returns {jsPDF} jsPDF-instance - * @memberof jsPDF# - * @name setCharSpace - */ - API.__private__.setCharSpace = API.setCharSpace = function(charSpace) { - if (isNaN(charSpace)) { - throw new Error("Invalid argument passed to jsPDF.setCharSpace"); - } - activeCharSpace = charSpace; - return this; - }; - - var lineCapID = 0; - /** - * Is an Object providing a mapping from human-readable to - * integer flag values designating the varieties of line cap - * and join styles. - * - * @memberof jsPDF# - * @name CapJoinStyles - */ - API.CapJoinStyles = { - 0: 0, - butt: 0, - but: 0, - miter: 0, - 1: 1, - round: 1, - rounded: 1, - circle: 1, - 2: 2, - projecting: 2, - project: 2, - square: 2, - bevel: 2 - }; - - /** - * Sets the line cap styles. - * See {jsPDF.CapJoinStyles} for variants. - * - * @param {String|Number} style A string or number identifying the type of line cap. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setLineCap - */ - API.__private__.setLineCap = API.setLineCap = function(style) { - var id = API.CapJoinStyles[style]; - if (id === undefined) { - throw new Error( - "Line cap style of '" + - style + - "' is not recognized. See or extend .CapJoinStyles property for valid styles" - ); - } - lineCapID = id; - out(id + " J"); - - return this; - }; - - var lineJoinID = 0; - /** - * Sets the line join styles. - * See {jsPDF.CapJoinStyles} for variants. - * - * @param {String|Number} style A string or number identifying the type of line join. - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setLineJoin - */ - API.__private__.setLineJoin = API.setLineJoin = function(style) { - var id = API.CapJoinStyles[style]; - if (id === undefined) { - throw new Error( - "Line join style of '" + - style + - "' is not recognized. See or extend .CapJoinStyles property for valid styles" - ); - } - lineJoinID = id; - out(id + " j"); - - return this; - }; - /** - * Sets the miterLimit property, which effects the maximum miter length. - * - * @param {number} length The length of the miter - * @function - * @instance - * @returns {jsPDF} - * @memberof jsPDF# - * @name setLineMiterLimit - */ - API.__private__.setLineMiterLimit = API.__private__.setMiterLimit = API.setLineMiterLimit = API.setMiterLimit = function( - length - ) { - length = length || 0; - if (isNaN(length)) { - throw new Error("Invalid argument passed to jsPDF.setLineMiterLimit"); - } - out(hpf(scale(length)) + " M"); - - return this; - }; - - /** - * An object representing a pdf graphics state. - * @class GState - */ - - /** - * - * @param parameters A parameter object that contains all properties this graphics state wants to set. - * Supported are: opacity, stroke-opacity - * @constructor - */ - API.GState = GState; - - /** - * Sets a either previously added {@link GState} (via {@link addGState}) or a new {@link GState}. - * @param {String|GState} gState If type is string, a previously added GState is used, if type is GState - * it will be added before use. - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name setGState - */ - API.setGState = function(gState) { - if (typeof gState === "string") { - gState = gStates[gStatesMap[gState]]; - } else { - gState = addGState(null, gState); - } - - if (!gState.equals(activeGState)) { - out("/" + gState.id + " gs"); - activeGState = gState; - } - }; - - /** - * Adds a new Graphics State. Duplicates are automatically eliminated. - * @param {String} key Might also be null, if no later reference to this gState is needed - * @param {Object} gState The gState object - */ - var addGState = function(key, gState) { - // only add it if it is not already present (the keys provided by the user must be unique!) - if (key && gStatesMap[key]) return; - var duplicate = false; - for (var s in gStates) { - if (gStates.hasOwnProperty(s)) { - if (gStates[s].equals(gState)) { - duplicate = true; - break; - } - } - } - - if (duplicate) { - gState = gStates[s]; - } else { - var gStateKey = "GS" + (Object.keys(gStates).length + 1).toString(10); - gStates[gStateKey] = gState; - gState.id = gStateKey; - } - - // several user keys may point to the same GState object - key && (gStatesMap[key] = gState.id); - - events.publish("addGState", gState); - - return gState; - }; - - /** - * Adds a new {@link GState} for later use. See {@link setGState}. - * @param {String} key - * @param {GState} gState - * @function - * @instance - * @returns {jsPDF} - * - * @memberof jsPDF# - * @name addGState - */ - API.addGState = function(key, gState) { - addGState(key, gState); - return this; - }; - - /** - * Saves the current graphics state ("pushes it on the stack"). It can be restored by {@link restoreGraphicsState} - * later. Here, the general pdf graphics state is meant, also including the current transformation matrix, - * fill and stroke colors etc. - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name saveGraphicsState - */ - API.saveGraphicsState = function() { - out("q"); - // as we cannot set font key and size independently we must keep track of both - fontStateStack.push({ - key: activeFontKey, - size: activeFontSize, - color: textColor - }); - return this; - }; - - /** - * Restores a previously saved graphics state saved by {@link saveGraphicsState} ("pops the stack"). - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name restoreGraphicsState - */ - API.restoreGraphicsState = function() { - out("Q"); - - // restore previous font state - var fontState = fontStateStack.pop(); - activeFontKey = fontState.key; - activeFontSize = fontState.size; - textColor = fontState.color; - - activeGState = null; - - return this; - }; - - /** - * Appends this matrix to the left of all previously applied matrices. - * - * @param {Matrix} matrix - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name setCurrentTransformationMatrix - */ - API.setCurrentTransformationMatrix = function(matrix) { - out(matrix.toString() + " cm"); - return this; - }; - - /** - * Inserts a debug comment into the generated pdf. - * @function - * @instance - * @param {String} text - * @returns {jsPDF} - * @memberof jsPDF# - * @name comment - */ - API.comment = function(text) { - out("#" + text); - return this; - }; - - /** - * Point - */ - var Point = function(x, y) { - var _x = x || 0; - Object.defineProperty(this, "x", { - enumerable: true, - get: function() { - return _x; - }, - set: function(value) { - if (!isNaN(value)) { - _x = parseFloat(value); - } - } - }); - - var _y = y || 0; - Object.defineProperty(this, "y", { - enumerable: true, - get: function() { - return _y; - }, - set: function(value) { - if (!isNaN(value)) { - _y = parseFloat(value); - } - } - }); - - var _type = "pt"; - Object.defineProperty(this, "type", { - enumerable: true, - get: function() { - return _type; - }, - set: function(value) { - _type = value.toString(); - } - }); - return this; - }; - - /** - * Rectangle - */ - var Rectangle = function(x, y, w, h) { - Point.call(this, x, y); - this.type = "rect"; - - var _w = w || 0; - Object.defineProperty(this, "w", { - enumerable: true, - get: function() { - return _w; - }, - set: function(value) { - if (!isNaN(value)) { - _w = parseFloat(value); - } - } - }); - - var _h = h || 0; - Object.defineProperty(this, "h", { - enumerable: true, - get: function() { - return _h; - }, - set: function(value) { - if (!isNaN(value)) { - _h = parseFloat(value); - } - } - }); - - return this; - }; - - /** - * FormObject/RenderTarget - */ - - var RenderTarget = function() { - this.page = page; - this.currentPage = currentPage; - this.pages = pages.slice(0); - this.pagesContext = pagesContext.slice(0); - this.x = pageX; - this.y = pageY; - this.matrix = pageMatrix; - this.width = getUnscaledPageWidth(currentPage); - this.height = getUnscaledPageHeight(currentPage); - this.outputDestination = outputDestination; - - this.id = ""; // set by endFormObject() - this.objectNumber = -1; // will be set by putXObject() - }; - - RenderTarget.prototype.restore = function() { - page = this.page; - currentPage = this.currentPage; - pagesContext = this.pagesContext; - pages = this.pages; - pageX = this.x; - pageY = this.y; - pageMatrix = this.matrix; - setPageWidthWithoutScaling(currentPage, this.width); - setPageHeightWithoutScaling(currentPage, this.height); - outputDestination = this.outputDestination; - }; - - var beginNewRenderTarget = function(x, y, width, height, matrix) { - // save current state - renderTargetStack.push(new RenderTarget()); - - // clear pages - page = currentPage = 0; - pages = []; - pageX = x; - pageY = y; - - pageMatrix = matrix; - - beginPage([width, height]); - }; - - var endFormObject = function(key) { - // only add it if it is not already present (the keys provided by the user must be unique!) - if (renderTargetMap[key]) { - renderTargetStack.pop().restore(); - return; - } - - // save the created xObject - var newXObject = new RenderTarget(); - - var xObjectId = "Xo" + (Object.keys(renderTargets).length + 1).toString(10); - newXObject.id = xObjectId; - - renderTargetMap[key] = xObjectId; - renderTargets[xObjectId] = newXObject; - - events.publish("addFormObject", newXObject); - - // restore state from stack - renderTargetStack.pop().restore(); - }; - - /** - * Starts a new pdf form object, which means that all consequent draw calls target a new independent object - * until {@link endFormObject} is called. The created object can be referenced and drawn later using - * {@link doFormObject}. Nested form objects are possible. - * x, y, width, height set the bounding box that is used to clip the content. - * - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - * @param {Matrix} matrix The matrix that will be applied to convert the form objects coordinate system to - * the parent's. - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name beginFormObject - */ - API.beginFormObject = function(x, y, width, height, matrix) { - // The user can set the output target to a new form object. Nested form objects are possible. - // Currently, they use the resource dictionary of the surrounding stream. This should be changed, as - // the PDF-Spec states: - // "In PDF 1.2 and later versions, form XObjects may be independent of the content streams in which - // they appear, and this is strongly recommended although not requiredIn PDF 1.2 and later versions, - // form XObjects may be independent of the content streams in which they appear, and this is strongly - // recommended although not required" - beginNewRenderTarget(x, y, width, height, matrix); - return this; - }; - - /** - * Completes and saves the form object. - * @param {String} key The key by which this form object can be referenced. - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name endFormObject - */ - API.endFormObject = function(key) { - endFormObject(key); - return this; - }; - - /** - * Draws the specified form object by referencing to the respective pdf XObject created with - * {@link API.beginFormObject} and {@link endFormObject}. - * The location is determined by matrix. - * - * @param {String} key The key to the form object. - * @param {Matrix} matrix The matrix applied before drawing the form object. - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name doFormObject - */ - API.doFormObject = function(key, matrix) { - var xObject = renderTargets[renderTargetMap[key]]; - out("q"); - out(matrix.toString() + " cm"); - out("/" + xObject.id + " Do"); - out("Q"); - return this; - }; - - /** - * Returns the form object specified by key. - * @param key {String} - * @returns {{x: number, y: number, width: number, height: number, matrix: Matrix}} - * @function - * @returns {jsPDF} - * @memberof jsPDF# - * @name getFormObject - */ - API.getFormObject = function(key) { - var xObject = renderTargets[renderTargetMap[key]]; - return { - x: xObject.x, - y: xObject.y, - width: xObject.width, - height: xObject.height, - matrix: xObject.matrix - }; - }; - - /** - * Saves as PDF document. An alias of jsPDF.output('save', 'filename.pdf'). - * Uses FileSaver.js-method saveAs. - * - * @memberof jsPDF# - * @name save - * @function - * @instance - * @param {string} filename The filename including extension. - * @param {Object} options An Object with additional options, possible options: 'returnPromise'. - * @returns {jsPDF|Promise} jsPDF-instance */ - API.save = function(filename, options) { - - options = options || {}; - options.returnPromise = options.returnPromise || false; - - return Promise.reject(Error('save function removed')); - }; - - // applying plugins (more methods) ON TOP of built-in API. - // this is intentional as we allow plugins to override - // built-ins - for (var plugin in jsPDF.API) { - if (jsPDF.API.hasOwnProperty(plugin)) { - if (plugin === "events" && jsPDF.API.events.length) { - (function(events, newEvents) { - // jsPDF.API.events is a JS Array of Arrays - // where each Array is a pair of event name, handler - // Events were added by plugins to the jsPDF instantiator. - // These are always added to the new instance and some ran - // during instantiation. - var eventname, handler_and_args, i; - - for (i = newEvents.length - 1; i !== -1; i--) { - // subscribe takes 3 args: 'topic', function, runonce_flag - // if undefined, runonce is false. - // users can attach callback directly, - // or they can attach an array with [callback, runonce_flag] - // that's what the "apply" magic is for below. - eventname = newEvents[i][0]; - handler_and_args = newEvents[i][1]; - events.subscribe.apply( - events, - [eventname].concat( - typeof handler_and_args === "function" - ? [handler_and_args] - : handler_and_args - ) - ); - } - })(events, jsPDF.API.events); - } else { - API[plugin] = jsPDF.API[plugin]; - } - } - } - - function getUnscaledPageWidth(pageNumber) { - return ( - pagesContext[pageNumber].mediaBox.topRightX - - pagesContext[pageNumber].mediaBox.bottomLeftX - ); - } - - function setPageWidthWithoutScaling(pageNumber, value) { - pagesContext[pageNumber].mediaBox.topRightX = - value + pagesContext[pageNumber].mediaBox.bottomLeftX; - } - - function getUnscaledPageHeight(pageNumber) { - return ( - pagesContext[pageNumber].mediaBox.topRightY - - pagesContext[pageNumber].mediaBox.bottomLeftY - ); - } - - function setPageHeightWithoutScaling(pageNumber, value) { - pagesContext[pageNumber].mediaBox.topRightY = - value + pagesContext[pageNumber].mediaBox.bottomLeftY; - } - - var getPageWidth = (API.getPageWidth = function(pageNumber) { - pageNumber = pageNumber || currentPage; - return getUnscaledPageWidth(pageNumber) / scaleFactor; - }); - - var setPageWidth = (API.setPageWidth = function(pageNumber, value) { - setPageWidthWithoutScaling(pageNumber, value * scaleFactor); - }); - - var getPageHeight = (API.getPageHeight = function(pageNumber) { - pageNumber = pageNumber || currentPage; - return getUnscaledPageHeight(pageNumber) / scaleFactor; - }); - - var setPageHeight = (API.setPageHeight = function(pageNumber, value) { - setPageHeightWithoutScaling(pageNumber, value * scaleFactor); - }); - - /** - * Object exposing internal API to plugins - * @public - * @ignore - */ - API.internal = { - pdfEscape: pdfEscape, - getStyle: getStyle, - getFont: getFontEntry, - getFontSize: getFontSize, - getCharSpace: getCharSpace, - getTextColor: getTextColor, - getLineHeight: getLineHeight, - getLineHeightFactor: getLineHeightFactor, - getLineWidth: getLineWidth, - write: write, - getHorizontalCoordinate: getHorizontalCoordinate, - getVerticalCoordinate: getVerticalCoordinate, - getCoordinateString: getHorizontalCoordinateString, - getVerticalCoordinateString: getVerticalCoordinateString, - collections: {}, - newObject: newObject, - newAdditionalObject: newAdditionalObject, - newObjectDeferred: newObjectDeferred, - newObjectDeferredBegin: newObjectDeferredBegin, - getFilters: getFilters, - putStream: putStream, - events: events, - scaleFactor: scaleFactor, - pageSize: { - getWidth: function() { - return getPageWidth(currentPage); - }, - setWidth: function(value) { - setPageWidth(currentPage, value); - }, - getHeight: function() { - return getPageHeight(currentPage); - }, - setHeight: function(value) { - setPageHeight(currentPage, value); - } - }, - encryptionOptions: encryptionOptions, - encryption: encryption, - getEncryptor: getEncryptor, - output: output, - getNumberOfPages: getNumberOfPages, - get pages() { - return pages; - }, - out: out, - f2: f2, - f3: f3, - getPageInfo: getPageInfo, - getPageInfoByObjId: getPageInfoByObjId, - getCurrentPageInfo: getCurrentPageInfo, - getPDFVersion: getPdfVersion, - Point: Point, - Rectangle: Rectangle, - Matrix: Matrix, - hasHotfix: hasHotfix //Expose the hasHotfix check so plugins can also check them. - }; - - Object.defineProperty(API.internal.pageSize, "width", { - get: function() { - return getPageWidth(currentPage); - }, - set: function(value) { - setPageWidth(currentPage, value); - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(API.internal.pageSize, "height", { - get: function() { - return getPageHeight(currentPage); - }, - set: function(value) { - setPageHeight(currentPage, value); - }, - enumerable: true, - configurable: true - }); - - ////////////////////////////////////////////////////// - // continuing initialization of jsPDF Document object - ////////////////////////////////////////////////////// - // Add the first page automatically - addFonts.call(API, standardFonts); - activeFontKey = "F1"; - _addPage(format, orientation); - - events.publish("initialized"); - return API; -} - -/** - * jsPDF.API is a STATIC property of jsPDF class. - * jsPDF.API is an object you can add methods and properties to. - * The methods / properties you add will show up in new jsPDF objects. - * - * One property is prepopulated. It is the 'events' Object. Plugin authors can add topics, - * callbacks to this object. These will be reassigned to all new instances of jsPDF. - * - * @static - * @public - * @memberof jsPDF# - * @name API - * - * @example - * jsPDF.API.mymethod = function(){ - * // 'this' will be ref to internal API object. see jsPDF source - * // , so you can refer to built-in methods like so: - * // this.line(....) - * // this.text(....) - * } - * var pdfdoc = new jsPDF() - * pdfdoc.mymethod() // <- !!!!!! - */ -jsPDF.API = { - events: [] -}; -/** - * The version of jsPDF. - * @name version - * @type {string} - * @memberof jsPDF# - */ -jsPDF.version = "4.2.1"; - -/* global jsPDF */ - -var jsPDFAPI = jsPDF.API; -var scaleFactor = 1; - -var pdfEscape = function(value) { - return value - .replace(/\\/g, "\\\\") - .replace(/\(/g, "\\(") - .replace(/\)/g, "\\)"); -}; -var pdfUnescape = function(value) { - return value - .replace(/\\\\/g, "\\") - .replace(/\\\(/g, "(") - .replace(/\\\)/g, ")"); -}; - -/** - * Escapes a PDF Name Object. - * Replaces special characters (delimiter, whitespace, #) with their hex representation. - */ -var pdfEscapeName = function(value) { - return value - .toString() - .replace(/#/g, "#23") - .replace(/[\s\n\r()<>[\]{}\/%]/g, c => { - const hex = c - .charCodeAt(0) - .toString(16) - .toUpperCase(); - return "#" + (hex.length === 1 ? "0" + hex : hex); - }); -}; - -var f2 = function(number) { - return number.toFixed(2); // Ie, %.2f -}; - -var f5 = function(number) { - return number.toFixed(5); // Ie, %.2f -}; - -jsPDFAPI.__acroform__ = {}; -var inherit = function(child, parent) { - child.prototype = Object.create(parent.prototype); - child.prototype.constructor = child; -}; - -var scale = function(x) { - return x * scaleFactor; -}; - -var createFormXObject = function(formObject) { - var xobj = new AcroFormXObject(); - var height = AcroFormAppearance.internal.getHeight(formObject) || 0; - var width = AcroFormAppearance.internal.getWidth(formObject) || 0; - xobj.BBox = [0, 0, Number(f2(width)), Number(f2(height))]; - return xobj; -}; - -/** - * Bit-Operations - */ -var setBit = (jsPDFAPI.__acroform__.setBit = function(number, bitPosition) { - number = number || 0; - bitPosition = bitPosition || 0; - - if (isNaN(number) || isNaN(bitPosition)) { - throw new Error( - "Invalid arguments passed to jsPDF.API.__acroform__.setBit" - ); - } - var bitMask = 1 << bitPosition; - - number |= bitMask; - - return number; -}); - -var clearBit = (jsPDFAPI.__acroform__.clearBit = function(number, bitPosition) { - number = number || 0; - bitPosition = bitPosition || 0; - - if (isNaN(number) || isNaN(bitPosition)) { - throw new Error( - "Invalid arguments passed to jsPDF.API.__acroform__.clearBit" - ); - } - var bitMask = 1 << bitPosition; - - number &= ~bitMask; - - return number; -}); - -var getBit = (jsPDFAPI.__acroform__.getBit = function(number, bitPosition) { - if (isNaN(number) || isNaN(bitPosition)) { - throw new Error( - "Invalid arguments passed to jsPDF.API.__acroform__.getBit" - ); - } - return (number & (1 << bitPosition)) === 0 ? 0 : 1; -}); - -/* - * Ff starts counting the bit position at 1 and not like javascript at 0 - */ -var getBitForPdf = (jsPDFAPI.__acroform__.getBitForPdf = function( - number, - bitPosition -) { - if (isNaN(number) || isNaN(bitPosition)) { - throw new Error( - "Invalid arguments passed to jsPDF.API.__acroform__.getBitForPdf" - ); - } - return getBit(number, bitPosition - 1); -}); - -var setBitForPdf = (jsPDFAPI.__acroform__.setBitForPdf = function( - number, - bitPosition -) { - if (isNaN(number) || isNaN(bitPosition)) { - throw new Error( - "Invalid arguments passed to jsPDF.API.__acroform__.setBitForPdf" - ); - } - return setBit(number, bitPosition - 1); -}); - -var clearBitForPdf = (jsPDFAPI.__acroform__.clearBitForPdf = function( - number, - bitPosition -) { - if (isNaN(number) || isNaN(bitPosition)) { - throw new Error( - "Invalid arguments passed to jsPDF.API.__acroform__.clearBitForPdf" - ); - } - return clearBit(number, bitPosition - 1); -}); - -var calculateCoordinates = (jsPDFAPI.__acroform__.calculateCoordinates = function( - args, - scope -) { - var getHorizontalCoordinate = scope.internal.getHorizontalCoordinate; - var getVerticalCoordinate = scope.internal.getVerticalCoordinate; - var x = args[0]; - var y = args[1]; - var w = args[2]; - var h = args[3]; - - var coordinates = {}; - - coordinates.lowerLeft_X = getHorizontalCoordinate(x) || 0; - coordinates.lowerLeft_Y = getVerticalCoordinate(y + h) || 0; - coordinates.upperRight_X = getHorizontalCoordinate(x + w) || 0; - coordinates.upperRight_Y = getVerticalCoordinate(y) || 0; - - return [ - Number(f2(coordinates.lowerLeft_X)), - Number(f2(coordinates.lowerLeft_Y)), - Number(f2(coordinates.upperRight_X)), - Number(f2(coordinates.upperRight_Y)) - ]; -}); - -var calculateAppearanceStream = function(formObject) { - if (formObject.appearanceStreamContent) { - return formObject.appearanceStreamContent; - } - - if (!formObject.V && !formObject.DV) { - return; - } - - // else calculate it - - var stream = []; - var text = formObject._V || formObject.DV; - var calcRes = calculateX(formObject, text); - var fontKey = formObject.scope.internal.getFont( - formObject.fontName, - formObject.fontStyle - ).id; - - //PDF 32000-1:2008, page 444 - stream.push("/Tx BMC"); - stream.push("q"); - stream.push("BT"); // Begin Text - stream.push(formObject.scope.__private__.encodeColorString(formObject.color)); - stream.push("/" + fontKey + " " + f2(calcRes.fontSize) + " Tf"); - stream.push("1 0 0 1 0 0 Tm"); // Transformation Matrix - stream.push(calcRes.text); - stream.push("ET"); // End Text - stream.push("Q"); - stream.push("EMC"); - - var appearanceStreamContent = createFormXObject(formObject); - appearanceStreamContent.scope = formObject.scope; - appearanceStreamContent.stream = stream.join("\n"); - return appearanceStreamContent; -}; - -var calculateX = function(formObject, text) { - var maxFontSize = - formObject.fontSize === 0 ? formObject.maxFontSize : formObject.fontSize; - var returnValue = { - text: "", - fontSize: "" - }; - // Remove Brackets - text = text.substr(0, 1) == "(" ? text.substr(1) : text; - text = - text.substr(text.length - 1) == ")" - ? text.substr(0, text.length - 1) - : text; - // split into array of words - var textSplit = text.split(" "); - if (formObject.multiline) { - textSplit = textSplit.map(word => word.split("\n")); - } else { - textSplit = textSplit.map(word => [word]); - } - - var fontSize = maxFontSize; // The Starting fontSize (The Maximum) - var lineSpacing = 2; - var borderPadding = 2; - - var height = AcroFormAppearance.internal.getHeight(formObject) || 0; - height = height < 0 ? -height : height; - var width = AcroFormAppearance.internal.getWidth(formObject) || 0; - width = width < 0 ? -width : width; - - var isSmallerThanWidth = function(i, lastLine, fontSize) { - if (i + 1 < textSplit.length) { - var tmp = lastLine + " " + textSplit[i + 1][0]; - var TextWidth = calculateFontSpace(tmp, formObject, fontSize).width; - var FieldWidth = width - 2 * borderPadding; - return TextWidth <= FieldWidth; - } else { - return false; - } - }; - - fontSize++; - FontSize: while (fontSize > 0) { - text = ""; - fontSize--; - var textHeight = calculateFontSpace("3", formObject, fontSize).height; - var startY = formObject.multiline - ? height - fontSize - : (height - textHeight) / 2; - startY += lineSpacing; - var startX; - - var lastY = startY; - var firstWordInLine = 0, - lastWordInLine = 0; - var lastLength; - var currWord = 0; - - if (fontSize <= 0) { - // In case, the Text doesn't fit at all - fontSize = 12; - text = "(...) Tj\n"; - text += - "% Width of Text: " + - calculateFontSpace(text, formObject, fontSize).width + - ", FieldWidth:" + - width + - "\n"; - break; - } - - var lastLine = ""; - var lineCount = 0; - Line: for (var i = 0; i < textSplit.length; i++) { - if (textSplit.hasOwnProperty(i)) { - let isWithNewLine = false; - if (textSplit[i].length !== 1 && currWord !== textSplit[i].length - 1) { - if ( - (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > - height - ) { - continue FontSize; - } - - lastLine += textSplit[i][currWord]; - isWithNewLine = true; - lastWordInLine = i; - i--; - } else { - lastLine += textSplit[i][currWord] + " "; - lastLine = - lastLine.substr(lastLine.length - 1) == " " - ? lastLine.substr(0, lastLine.length - 1) - : lastLine; - var key = parseInt(i); - var nextLineIsSmaller = isSmallerThanWidth(key, lastLine, fontSize); - var isLastWord = i >= textSplit.length - 1; - - if (nextLineIsSmaller && !isLastWord) { - lastLine += " "; - currWord = 0; - continue; // Line - } else if (!nextLineIsSmaller && !isLastWord) { - if (!formObject.multiline) { - continue FontSize; - } else { - if ( - (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > - height - ) { - // If the Text is higher than the - // FieldObject - continue FontSize; - } - lastWordInLine = key; - // go on - } - } else if (isLastWord) { - lastWordInLine = key; - } else { - if ( - formObject.multiline && - (textHeight + lineSpacing) * (lineCount + 2) + lineSpacing > - height - ) { - // If the Text is higher than the FieldObject - continue FontSize; - } - } - } - // Remove last blank - - var line = ""; - - for (var x = firstWordInLine; x <= lastWordInLine; x++) { - var currLine = textSplit[x]; - if (formObject.multiline) { - if (x === lastWordInLine) { - line += currLine[currWord] + " "; - currWord = (currWord + 1) % currLine.length; - continue; - } - if (x === firstWordInLine) { - line += currLine[currLine.length - 1] + " "; - continue; - } - } - line += currLine[0] + " "; - } - - // Remove last blank - line = - line.substr(line.length - 1) == " " - ? line.substr(0, line.length - 1) - : line; - // lastLength -= blankSpace.width; - lastLength = calculateFontSpace(line, formObject, fontSize).width; - - // Calculate startX - switch (formObject.textAlign) { - case "right": - startX = width - lastLength - borderPadding; - break; - case "center": - startX = (width - lastLength) / 2; - break; - case "left": - default: - startX = borderPadding; - break; - } - text += f2(startX) + " " + f2(lastY) + " Td\n"; - text += "(" + pdfEscape(line) + ") Tj\n"; - // reset X in PDF - text += -f2(startX) + " 0 Td\n"; - - // After a Line, adjust y position - lastY = -(fontSize + lineSpacing); - - // Reset for next iteration step - lastLength = 0; - firstWordInLine = isWithNewLine ? lastWordInLine : lastWordInLine + 1; - lineCount++; - - lastLine = ""; - continue Line; - } - } - break; - } - - returnValue.text = text; - returnValue.fontSize = fontSize; - - return returnValue; -}; - -/** - * Small workaround for calculating the TextMetric approximately. - * - * @param text - * @param fontsize - * @returns {TextMetrics} (Has Height and Width) - */ -var calculateFontSpace = function(text, formObject, fontSize) { - var font = formObject.scope.internal.getFont( - formObject.fontName, - formObject.fontStyle - ); - var width = - formObject.scope.getStringUnitWidth(text, { - font: font, - fontSize: parseFloat(fontSize), - charSpace: 0 - }) * parseFloat(fontSize); - var height = - formObject.scope.getStringUnitWidth("3", { - font: font, - fontSize: parseFloat(fontSize), - charSpace: 0 - }) * - parseFloat(fontSize) * - 1.5; - return { height: height, width: width }; -}; - -var acroformPluginTemplate = { - fields: [], - xForms: [], - /** - * acroFormDictionaryRoot contains information about the AcroForm - * Dictionary 0: The Event-Token, the AcroFormDictionaryCallback has - * 1: The Object ID of the Root - */ - acroFormDictionaryRoot: null, - /** - * After the PDF gets evaluated, the reference to the root has to be - * reset, this indicates, whether the root has already been printed - * out - */ - printedOut: false, - internal: null, - isInitialized: false -}; - -var annotReferenceCallback = function(scope) { - //set objId to undefined and force it to get a new objId on buildDocument - scope.internal.acroformPlugin.acroFormDictionaryRoot.objId = undefined; - var fields = scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields; - for (var i in fields) { - if (fields.hasOwnProperty(i)) { - var formObject = fields[i]; - //set objId to undefined and force it to get a new objId on buildDocument - formObject.objId = undefined; - // add Annot Reference! - if (formObject.hasAnnotation) { - // If theres an Annotation Widget in the Form Object, put the - // Reference in the /Annot array - createAnnotationReference(formObject, scope); - } - } - } -}; - -var putForm = function(formObject) { - if (formObject.scope.internal.acroformPlugin.printedOut) { - formObject.scope.internal.acroformPlugin.printedOut = false; - formObject.scope.internal.acroformPlugin.acroFormDictionaryRoot = null; - } - formObject.scope.internal.acroformPlugin.acroFormDictionaryRoot.Fields.push( - formObject - ); -}; -/** - * Create the Reference to the widgetAnnotation, so that it gets referenced - * in the Annot[] int the+ (Requires the Annotation Plugin) - */ -var createAnnotationReference = function(object, scope) { - var options = { - type: "reference", - object: object - }; - var findEntry = function(entry) { - return entry.type === options.type && entry.object === options.object; - }; - if ( - scope.internal - .getPageInfo(object.page) - .pageContext.annotations.find(findEntry) === undefined - ) { - scope.internal - .getPageInfo(object.page) - .pageContext.annotations.push(options); - } -}; - -// Callbacks - -var putCatalogCallback = function(scope) { - // Put reference to AcroForm to DocumentCatalog - if ( - typeof scope.internal.acroformPlugin.acroFormDictionaryRoot !== "undefined" - ) { - // for safety, shouldn't normally be the case - scope.internal.write( - "/AcroForm " + - scope.internal.acroformPlugin.acroFormDictionaryRoot.objId + - " " + - 0 + - " R" - ); - } else { - throw new Error("putCatalogCallback: Root missing."); - } -}; - -/** - * Adds /Acroform X 0 R to Document Catalog, and creates the AcroForm - * Dictionary - */ -var AcroFormDictionaryCallback = function(scope) { - // Remove event - scope.internal.events.unsubscribe( - scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID - ); - delete scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID; - scope.internal.acroformPlugin.printedOut = true; -}; - -/** - * Creates the single Fields and writes them into the Document - * - * If fieldArray is set, use the fields that are inside it instead of the - * fields from the AcroRoot (for the FormXObjects...) - */ -var createFieldCallback = function(fieldArray, scope) { - var standardFields = !fieldArray; - - if (!fieldArray) { - // in case there is no fieldArray specified, we want to print out - // the Fields of the AcroForm - // Print out Root - scope.internal.newObjectDeferredBegin( - scope.internal.acroformPlugin.acroFormDictionaryRoot.objId, - true - ); - scope.internal.acroformPlugin.acroFormDictionaryRoot.putStream(); - } - - fieldArray = - fieldArray || scope.internal.acroformPlugin.acroFormDictionaryRoot.Kids; - - for (var i in fieldArray) { - if (fieldArray.hasOwnProperty(i)) { - var fieldObject = fieldArray[i]; - var keyValueList = []; - var oldRect = fieldObject.Rect; - - if (fieldObject.Rect) { - fieldObject.Rect = calculateCoordinates(fieldObject.Rect, scope); - } - - // Start Writing the Object - scope.internal.newObjectDeferredBegin(fieldObject.objId, true); - - fieldObject.DA = AcroFormAppearance.createDefaultAppearanceStream( - fieldObject - ); - - if ( - typeof fieldObject === "object" && - typeof fieldObject.getKeyValueListForStream === "function" - ) { - keyValueList = fieldObject.getKeyValueListForStream(); - } - - fieldObject.Rect = oldRect; - - if ( - fieldObject.hasAppearanceStream && - !fieldObject.appearanceStreamContent - ) { - // Calculate Appearance - var appearance = calculateAppearanceStream(fieldObject); - keyValueList.push({ key: "AP", value: "<>" }); - - scope.internal.acroformPlugin.xForms.push(appearance); - } - - // Assume AppearanceStreamContent is a Array with N,R,D (at least - // one of them!) - if (fieldObject.appearanceStreamContent) { - var appearanceStreamString = ""; - // Iterate over N,R and D - for (var k in fieldObject.appearanceStreamContent) { - if (fieldObject.appearanceStreamContent.hasOwnProperty(k)) { - var value = fieldObject.appearanceStreamContent[k]; - appearanceStreamString += "/" + k + " "; - appearanceStreamString += "<<"; - if (Object.keys(value).length >= 1 || Array.isArray(value)) { - // appearanceStream is an Array or Object! - for (var i in value) { - if (value.hasOwnProperty(i)) { - var obj = value[i]; - if (typeof obj === "function") { - // if Function is referenced, call it in order - // to get the FormXObject - obj = obj.call(scope, fieldObject); - } - appearanceStreamString += "/" + i + " " + obj + " "; - - // In case the XForm is already used, e.g. OffState - // of CheckBoxes, don't add it - if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) - scope.internal.acroformPlugin.xForms.push(obj); - } - } - } else { - obj = value; - if (typeof obj === "function") { - // if Function is referenced, call it in order to - // get the FormXObject - obj = obj.call(scope, fieldObject); - } - appearanceStreamString += "/" + i + " " + obj; - if (!(scope.internal.acroformPlugin.xForms.indexOf(obj) >= 0)) - scope.internal.acroformPlugin.xForms.push(obj); - } - appearanceStreamString += ">>"; - } - } - - // appearance stream is a normal Object.. - keyValueList.push({ - key: "AP", - value: "<<\n" + appearanceStreamString + ">>" - }); - } - - scope.internal.putStream({ - additionalKeyValues: keyValueList, - objectId: fieldObject.objId - }); - - scope.internal.out("endobj"); - } - } - if (standardFields) { - createXFormObjectCallback(scope.internal.acroformPlugin.xForms, scope); - } -}; - -var createXFormObjectCallback = function(fieldArray, scope) { - for (var i in fieldArray) { - if (fieldArray.hasOwnProperty(i)) { - var key = i; - var fieldObject = fieldArray[i]; - // Start Writing the Object - scope.internal.newObjectDeferredBegin(fieldObject.objId, true); - - if ( - typeof fieldObject === "object" && - typeof fieldObject.putStream === "function" - ) { - fieldObject.putStream(); - } - delete fieldArray[key]; - } - } -}; - -var initializeAcroForm = function(scope, formObject) { - formObject.scope = scope; - if ( - scope.internal !== undefined && - (scope.internal.acroformPlugin === undefined || - scope.internal.acroformPlugin.isInitialized === false) - ) { - AcroFormField.FieldNum = 0; - scope.internal.acroformPlugin = JSON.parse( - JSON.stringify(acroformPluginTemplate) - ); - if (scope.internal.acroformPlugin.acroFormDictionaryRoot) { - throw new Error("Exception while creating AcroformDictionary"); - } - scaleFactor = scope.internal.scaleFactor; - // The Object Number of the AcroForm Dictionary - scope.internal.acroformPlugin.acroFormDictionaryRoot = new AcroFormDictionary(); - scope.internal.acroformPlugin.acroFormDictionaryRoot.scope = scope; - - // add Callback for creating the AcroForm Dictionary - scope.internal.acroformPlugin.acroFormDictionaryRoot._eventID = scope.internal.events.subscribe( - "postPutResources", - function() { - AcroFormDictionaryCallback(scope); - } - ); - - scope.internal.events.subscribe("buildDocument", function() { - annotReferenceCallback(scope); - }); // buildDocument - - // Register event, that is triggered when the DocumentCatalog is - // written, in order to add /AcroForm - - scope.internal.events.subscribe("putCatalog", function() { - putCatalogCallback(scope); - }); - - // Register event, that creates all Fields - scope.internal.events.subscribe("postPutPages", function(fieldArray) { - createFieldCallback(fieldArray, scope); - }); - - scope.internal.acroformPlugin.isInitialized = true; - } -}; - -//PDF 32000-1:2008, page 26, 7.3.6 -var arrayToPdfArray = (jsPDFAPI.__acroform__.arrayToPdfArray = function( - array, - objId, - scope -) { - var encryptor = function(data) { - return data; - }; - if (Array.isArray(array)) { - var content = "["; - for (var i = 0; i < array.length; i++) { - if (i !== 0) { - content += " "; - } - switch (typeof array[i]) { - case "boolean": - case "number": - case "object": - content += array[i].toString(); - break; - case "string": - if (array[i].substr(0, 1) === "/") { - content += "/" + pdfEscapeName(array[i].substr(1)); - } else { - if (typeof objId !== "undefined" && scope) - encryptor = scope.internal.getEncryptor(objId); - content += "(" + pdfEscape(encryptor(array[i].toString())) + ")"; - } - break; - } - } - content += "]"; - return content; - } - throw new Error( - "Invalid argument passed to jsPDF.__acroform__.arrayToPdfArray" - ); -}); - -function getMatches(string, regex, index) { - index || (index = 1); // default to the first capturing group - var matches = []; - var match; - while ((match = regex.exec(string))) { - matches.push(match[index]); - } - return matches; -} -var pdfArrayToStringArray = function(array) { - var result = []; - if (typeof array === "string") { - result = getMatches(array, /\((.*?)\)/g); - } - return result; -}; - -var toPdfString = function(string, objId, scope) { - var encryptor = function(data) { - return data; - }; - if (typeof objId !== "undefined" && scope) - encryptor = scope.internal.getEncryptor(objId); - string = string || ""; - string.toString(); - string = "(" + pdfEscape(encryptor(string)) + ")"; - return string; -}; - -// ########################## -// Classes -// ########################## - -/** - * @class AcroFormPDFObject - * @classdesc A AcroFormPDFObject - */ -var AcroFormPDFObject = function() { - this._objId = undefined; - this._scope = undefined; - - /** - * @name AcroFormPDFObject#objId - * @type {any} - */ - Object.defineProperty(this, "objId", { - get: function() { - if (typeof this._objId === "undefined") { - if (typeof this.scope === "undefined") { - return undefined; - } - this._objId = this.scope.internal.newObjectDeferred(); - } - return this._objId; - }, - set: function(value) { - this._objId = value; - } - }); - Object.defineProperty(this, "scope", { - value: this._scope, - writable: true - }); -}; - -/** - * @function AcroFormPDFObject.toString - */ -AcroFormPDFObject.prototype.toString = function() { - return this.objId + " 0 R"; -}; - -AcroFormPDFObject.prototype.putStream = function() { - var keyValueList = this.getKeyValueListForStream(); - this.scope.internal.putStream({ - data: this.stream, - additionalKeyValues: keyValueList, - objectId: this.objId - }); - this.scope.internal.out("endobj"); -}; - -/** - * Returns an key-value-List of all non-configurable Variables from the Object - * - * @name getKeyValueListForStream - * @returns {string} - */ -AcroFormPDFObject.prototype.getKeyValueListForStream = function() { - var keyValueList = []; - var keys = Object.getOwnPropertyNames(this).filter(function(key) { - return ( - key != "content" && - key != "appearanceStreamContent" && - key != "scope" && - key != "objId" && - key.substring(0, 1) != "_" - ); - }); - - for (var i in keys) { - if (Object.getOwnPropertyDescriptor(this, keys[i]).configurable === false) { - var key = keys[i]; - var value = this[key]; - - if (value) { - if (Array.isArray(value)) { - keyValueList.push({ - key: key, - value: arrayToPdfArray(value, this.objId, this.scope) - }); - } else if (value instanceof AcroFormPDFObject) { - // In case it is a reference to another PDFObject, - // take the reference number - value.scope = this.scope; - keyValueList.push({ key: key, value: value.objId + " 0 R" }); - } else if (typeof value !== "function") { - keyValueList.push({ key: key, value: value }); - } - } - } - } - return keyValueList; -}; - -var AcroFormXObject = function() { - AcroFormPDFObject.call(this); - - Object.defineProperty(this, "Type", { - value: "/XObject", - configurable: false, - writable: true - }); - - Object.defineProperty(this, "Subtype", { - value: "/Form", - configurable: false, - writable: true - }); - - Object.defineProperty(this, "FormType", { - value: 1, - configurable: false, - writable: true - }); - - var _BBox = []; - Object.defineProperty(this, "BBox", { - configurable: false, - get: function() { - return _BBox; - }, - set: function(value) { - _BBox = value; - } - }); - - Object.defineProperty(this, "Resources", { - value: "2 0 R", - configurable: false, - writable: true - }); - - var _stream; - Object.defineProperty(this, "stream", { - enumerable: false, - configurable: true, - set: function(value) { - _stream = value.trim(); - }, - get: function() { - if (_stream) { - return _stream; - } else { - return null; - } - } - }); -}; - -inherit(AcroFormXObject, AcroFormPDFObject); - -var AcroFormDictionary = function() { - AcroFormPDFObject.call(this); - - var _Kids = []; - - Object.defineProperty(this, "Kids", { - enumerable: false, - configurable: true, - get: function() { - if (_Kids.length > 0) { - return _Kids; - } else { - return undefined; - } - } - }); - Object.defineProperty(this, "Fields", { - enumerable: false, - configurable: false, - get: function() { - return _Kids; - } - }); - - // Default Appearance - var _DA; - Object.defineProperty(this, "DA", { - enumerable: false, - configurable: false, - get: function() { - if (!_DA) { - return undefined; - } - var encryptor = function(data) { - return data; - }; - if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); - return "(" + pdfEscape(encryptor(_DA)) + ")"; - }, - set: function(value) { - _DA = value; - } - }); -}; - -inherit(AcroFormDictionary, AcroFormPDFObject); - -/** - * The Field Object contains the Variables, that every Field needs - * - * @class AcroFormField - * @classdesc An AcroForm FieldObject - */ -var AcroFormField = function() { - AcroFormPDFObject.call(this); - - //Annotation-Flag See Table 165 - var _F = 4; - Object.defineProperty(this, "F", { - enumerable: false, - configurable: false, - get: function() { - return _F; - }, - set: function(value) { - if (!isNaN(value)) { - _F = value; - } else { - throw new Error( - 'Invalid value "' + value + '" for attribute F supplied.' - ); - } - } - }); - - /** - * (PDF 1.2) If set, print the annotation when the page is printed. If clear, never print the annotation, regardless of wether is is displayed on the screen. - * NOTE 2 This can be useful for annotations representing interactive pushbuttons, which would serve no meaningful purpose on the printed page. - * - * @name AcroFormField#showWhenPrinted - * @default true - * @type {boolean} - */ - Object.defineProperty(this, "showWhenPrinted", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(_F, 3)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.F = setBitForPdf(_F, 3); - } else { - this.F = clearBitForPdf(_F, 3); - } - } - }); - - var _Ff = 0; - Object.defineProperty(this, "Ff", { - enumerable: false, - configurable: false, - get: function() { - return _Ff; - }, - set: function(value) { - if (!isNaN(value)) { - _Ff = value; - } else { - throw new Error( - 'Invalid value "' + value + '" for attribute Ff supplied.' - ); - } - } - }); - - var _Rect = []; - Object.defineProperty(this, "Rect", { - enumerable: false, - configurable: false, - get: function() { - if (_Rect.length === 0) { - return undefined; - } - return _Rect; - }, - set: function(value) { - if (typeof value !== "undefined") { - _Rect = value; - } else { - _Rect = []; - } - } - }); - - /** - * The x-position of the field. - * - * @name AcroFormField#x - * @default null - * @type {number} - */ - Object.defineProperty(this, "x", { - enumerable: true, - configurable: true, - get: function() { - if (!_Rect || isNaN(_Rect[0])) { - return 0; - } - return _Rect[0]; - }, - set: function(value) { - _Rect[0] = value; - } - }); - - /** - * The y-position of the field. - * - * @name AcroFormField#y - * @default null - * @type {number} - */ - Object.defineProperty(this, "y", { - enumerable: true, - configurable: true, - get: function() { - if (!_Rect || isNaN(_Rect[1])) { - return 0; - } - return _Rect[1]; - }, - set: function(value) { - _Rect[1] = value; - } - }); - - /** - * The width of the field. - * - * @name AcroFormField#width - * @default null - * @type {number} - */ - Object.defineProperty(this, "width", { - enumerable: true, - configurable: true, - get: function() { - if (!_Rect || isNaN(_Rect[2])) { - return 0; - } - return _Rect[2]; - }, - set: function(value) { - _Rect[2] = value; - } - }); - - /** - * The height of the field. - * - * @name AcroFormField#height - * @default null - * @type {number} - */ - Object.defineProperty(this, "height", { - enumerable: true, - configurable: true, - get: function() { - if (!_Rect || isNaN(_Rect[3])) { - return 0; - } - return _Rect[3]; - }, - set: function(value) { - _Rect[3] = value; - } - }); - - var _FT = ""; - Object.defineProperty(this, "FT", { - enumerable: true, - configurable: false, - get: function() { - return _FT; - }, - set: function(value) { - switch (value) { - case "/Btn": - case "/Tx": - case "/Ch": - case "/Sig": - _FT = value; - break; - default: - throw new Error( - 'Invalid value "' + value + '" for attribute FT supplied.' - ); - } - } - }); - - var _T = null; - - Object.defineProperty(this, "T", { - enumerable: true, - configurable: false, - get: function() { - if (!_T || _T.length < 1) { - // In case of a Child from a Radio´Group, you don't need a FieldName - if (this instanceof AcroFormChildClass) { - return undefined; - } - _T = "FieldObject" + AcroFormField.FieldNum++; - } - var encryptor = function(data) { - return data; - }; - if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); - return "(" + pdfEscape(encryptor(_T)) + ")"; - }, - set: function(value) { - _T = value.toString(); - } - }); - - /** - * (Optional) The partial field name (see 12.7.3.2, “Field Names”). - * - * @name AcroFormField#fieldName - * @default null - * @type {string} - */ - Object.defineProperty(this, "fieldName", { - configurable: true, - enumerable: true, - get: function() { - return _T; - }, - set: function(value) { - _T = value; - } - }); - - var _fontName = "helvetica"; - /** - * The fontName of the font to be used. - * - * @name AcroFormField#fontName - * @default 'helvetica' - * @type {string} - */ - Object.defineProperty(this, "fontName", { - enumerable: true, - configurable: true, - get: function() { - return _fontName; - }, - set: function(value) { - _fontName = value; - } - }); - - var _fontStyle = "normal"; - /** - * The fontStyle of the font to be used. - * - * @name AcroFormField#fontStyle - * @default 'normal' - * @type {string} - */ - Object.defineProperty(this, "fontStyle", { - enumerable: true, - configurable: true, - get: function() { - return _fontStyle; - }, - set: function(value) { - _fontStyle = value; - } - }); - - var _fontSize = 0; - /** - * The fontSize of the font to be used. - * - * @name AcroFormField#fontSize - * @default 0 (for auto) - * @type {number} - */ - Object.defineProperty(this, "fontSize", { - enumerable: true, - configurable: true, - get: function() { - return _fontSize; - }, - set: function(value) { - _fontSize = value; - } - }); - - var _maxFontSize = undefined; - /** - * The maximum fontSize of the font to be used. - * - * @name AcroFormField#maxFontSize - * @default 0 (for auto) - * @type {number} - */ - Object.defineProperty(this, "maxFontSize", { - enumerable: true, - configurable: true, - get: function() { - if (_maxFontSize === undefined) { - // use the old default value here - the value is some kind of random as it depends on the scaleFactor (user unit) - // ("50" is transformed to the "user space" but then used in "pdf space") - return 50 / scaleFactor; - } else { - return _maxFontSize; - } - }, - set: function(value) { - _maxFontSize = value; - } - }); - - var _color = "black"; - /** - * The color of the text - * - * @name AcroFormField#color - * @default 'black' - * @type {string|rgba} - */ - Object.defineProperty(this, "color", { - enumerable: true, - configurable: true, - get: function() { - return _color; - }, - set: function(value) { - _color = value; - } - }); - - var _DA = "/F1 0 Tf 0 g"; - // Defines the default appearance (Needed for variable Text) - Object.defineProperty(this, "DA", { - enumerable: true, - configurable: false, - get: function() { - if ( - !_DA || - this instanceof AcroFormChildClass || - this instanceof AcroFormTextField - ) { - return undefined; - } - return toPdfString(_DA, this.objId, this.scope); - }, - set: function(value) { - value = value.toString(); - _DA = value; - } - }); - - var _DV = null; - Object.defineProperty(this, "DV", { - enumerable: false, - configurable: false, - get: function() { - if (!_DV) { - return undefined; - } - if (this instanceof AcroFormButton === false) { - return toPdfString(_DV, this.objId, this.scope); - } - return _DV; - }, - set: function(value) { - value = value.toString(); - if (this instanceof AcroFormButton === false) { - if (value.substr(0, 1) === "(") { - _DV = pdfUnescape(value.substr(1, value.length - 2)); - } else { - _DV = pdfUnescape(value); - } - } else { - _DV = value; - } - } - }); - - /** - * (Optional; inheritable) The default value to which the field reverts when a reset-form action is executed (see 12.7.5.3, “Reset-Form Action”). The format of this value is the same as that of value. - * - * @name AcroFormField#defaultValue - * @default null - * @type {any} - */ - Object.defineProperty(this, "defaultValue", { - enumerable: true, - configurable: true, - get: function() { - if (this instanceof AcroFormButton === true) { - return pdfUnescape(_DV.substr(1, _DV.length - 1)); - } else { - return _DV; - } - }, - set: function(value) { - value = value.toString(); - if (this instanceof AcroFormButton === true) { - _DV = "/" + pdfEscapeName(value); - } else { - _DV = value; - } - } - }); - - var _V = null; - Object.defineProperty(this, "_V", { - enumerable: false, - configurable: false, - get: function() { - if (!_V) { - return undefined; - } - return _V; - }, - set: function(value) { - this.V = value; - } - }); - Object.defineProperty(this, "V", { - enumerable: false, - configurable: false, - get: function() { - if (!_V) { - return undefined; - } - if (this instanceof AcroFormButton === false) { - return toPdfString(_V, this.objId, this.scope); - } - return _V; - }, - set: function(value) { - value = value.toString(); - if (this instanceof AcroFormButton === false) { - if (value.substr(0, 1) === "(") { - _V = pdfUnescape(value.substr(1, value.length - 2)); - } else { - _V = pdfUnescape(value); - } - } else { - _V = value; - } - } - }); - - /** - * (Optional; inheritable) The field’s value, whose format varies depending on the field type. See the descriptions of individual field types for further information. - * - * @name AcroFormField#value - * @default null - * @type {any} - */ - Object.defineProperty(this, "value", { - enumerable: true, - configurable: true, - get: function() { - if (this instanceof AcroFormButton === true) { - return pdfUnescape(_V.substr(1, _V.length - 1)); - } else { - return _V; - } - }, - set: function(value) { - value = value.toString(); - if (this instanceof AcroFormButton === true) { - _V = "/" + pdfEscapeName(value); - } else { - _V = value; - } - } - }); - - /** - * Check if field has annotations - * - * @name AcroFormField#hasAnnotation - * @readonly - * @type {boolean} - */ - Object.defineProperty(this, "hasAnnotation", { - enumerable: true, - configurable: true, - get: function() { - return this.Rect; - } - }); - - Object.defineProperty(this, "Type", { - enumerable: true, - configurable: false, - get: function() { - return this.hasAnnotation ? "/Annot" : null; - } - }); - - Object.defineProperty(this, "Subtype", { - enumerable: true, - configurable: false, - get: function() { - return this.hasAnnotation ? "/Widget" : null; - } - }); - - var _hasAppearanceStream = false; - /** - * true if field has an appearanceStream - * - * @name AcroFormField#hasAppearanceStream - * @readonly - * @type {boolean} - */ - Object.defineProperty(this, "hasAppearanceStream", { - enumerable: true, - configurable: true, - get: function() { - return _hasAppearanceStream; - }, - set: function(value) { - value = Boolean(value); - _hasAppearanceStream = value; - } - }); - - /** - * The page on which the AcroFormField is placed - * - * @name AcroFormField#page - * @type {number} - */ - var _page; - Object.defineProperty(this, "page", { - enumerable: true, - configurable: true, - get: function() { - if (!_page) { - return undefined; - } - return _page; - }, - set: function(value) { - _page = value; - } - }); - - /** - * If set, the user may not change the value of the field. Any associated widget annotations will not interact with the user; that is, they will not respond to mouse clicks or change their appearance in response to mouse motions. This flag is useful for fields whose values are computed or imported from a database. - * - * @name AcroFormField#readOnly - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "readOnly", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 1)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 1); - } else { - this.Ff = clearBitForPdf(this.Ff, 1); - } - } - }); - - /** - * If set, the field shall have a value at the time it is exported by a submitform action (see 12.7.5.2, “Submit-Form Action”). - * - * @name AcroFormField#required - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "required", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 2)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 2); - } else { - this.Ff = clearBitForPdf(this.Ff, 2); - } - } - }); - - /** - * If set, the field shall not be exported by a submit-form action (see 12.7.5.2, “Submit-Form Action”) - * - * @name AcroFormField#noExport - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "noExport", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 3)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 3); - } else { - this.Ff = clearBitForPdf(this.Ff, 3); - } - } - }); - - var _Q = null; - Object.defineProperty(this, "Q", { - enumerable: true, - configurable: false, - get: function() { - if (_Q === null) { - return undefined; - } - return _Q; - }, - set: function(value) { - if ([0, 1, 2].indexOf(value) !== -1) { - _Q = value; - } else { - throw new Error( - 'Invalid value "' + value + '" for attribute Q supplied.' - ); - } - } - }); - - /** - * (Optional; inheritable) A code specifying the form of quadding (justification) that shall be used in displaying the text: - * 'left', 'center', 'right' - * - * @name AcroFormField#textAlign - * @default 'left' - * @type {string} - */ - Object.defineProperty(this, "textAlign", { - get: function() { - var result; - switch (_Q) { - case 0: - default: - result = "left"; - break; - case 1: - result = "center"; - break; - case 2: - result = "right"; - break; - } - return result; - }, - configurable: true, - enumerable: true, - set: function(value) { - switch (value) { - case "right": - case 2: - _Q = 2; - break; - case "center": - case 1: - _Q = 1; - break; - case "left": - case 0: - default: - _Q = 0; - } - } - }); -}; - -inherit(AcroFormField, AcroFormPDFObject); - -/** - * @class AcroFormChoiceField - * @extends AcroFormField - */ -var AcroFormChoiceField = function() { - AcroFormField.call(this); - // Field Type = Choice Field - this.FT = "/Ch"; - // options - this.V = "()"; - - this.fontName = "zapfdingbats"; - // Top Index - var _TI = 0; - - Object.defineProperty(this, "TI", { - enumerable: true, - configurable: false, - get: function() { - return _TI; - }, - set: function(value) { - _TI = value; - } - }); - - /** - * (Optional) For scrollable list boxes, the top index (the index in the Opt array of the first option visible in the list). Default value: 0. - * - * @name AcroFormChoiceField#topIndex - * @default 0 - * @type {number} - */ - Object.defineProperty(this, "topIndex", { - enumerable: true, - configurable: true, - get: function() { - return _TI; - }, - set: function(value) { - _TI = value; - } - }); - - var _Opt = []; - Object.defineProperty(this, "Opt", { - enumerable: true, - configurable: false, - get: function() { - return arrayToPdfArray(_Opt, this.objId, this.scope); - }, - set: function(value) { - _Opt = pdfArrayToStringArray(value); - } - }); - - /** - * @memberof AcroFormChoiceField - * @name getOptions - * @function - * @instance - * @returns {array} array of Options - */ - this.getOptions = function() { - return _Opt; - }; - - /** - * @memberof AcroFormChoiceField - * @name setOptions - * @function - * @instance - * @param {array} value - */ - this.setOptions = function(value) { - _Opt = value; - if (this.sort) { - _Opt.sort(); - } - }; - - /** - * @memberof AcroFormChoiceField - * @name addOption - * @function - * @instance - * @param {string} value - */ - this.addOption = function(value) { - value = value || ""; - value = value.toString(); - _Opt.push(value); - if (this.sort) { - _Opt.sort(); - } - }; - - /** - * @memberof AcroFormChoiceField - * @name removeOption - * @function - * @instance - * @param {string} value - * @param {boolean} allEntries (default: false) - */ - this.removeOption = function(value, allEntries) { - allEntries = allEntries || false; - value = value || ""; - value = value.toString(); - - while (_Opt.indexOf(value) !== -1) { - _Opt.splice(_Opt.indexOf(value), 1); - if (allEntries === false) { - break; - } - } - }; - - /** - * If set, the field is a combo box; if clear, the field is a list box. - * - * @name AcroFormChoiceField#combo - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "combo", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 18)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 18); - } else { - this.Ff = clearBitForPdf(this.Ff, 18); - } - } - }); - - /** - * If set, the combo box shall include an editable text box as well as a drop-down list; if clear, it shall include only a drop-down list. This flag shall be used only if the Combo flag is set. - * - * @name AcroFormChoiceField#edit - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "edit", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 19)); - }, - set: function(value) { - //PDF 32000-1:2008, page 444 - if (this.combo === true) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 19); - } else { - this.Ff = clearBitForPdf(this.Ff, 19); - } - } - } - }); - - /** - * If set, the field’s option items shall be sorted alphabetically. This flag is intended for use by writers, not by readers. Conforming readers shall display the options in the order in which they occur in the Opt array (see Table 231). - * - * @name AcroFormChoiceField#sort - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "sort", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 20)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 20); - _Opt.sort(); - } else { - this.Ff = clearBitForPdf(this.Ff, 20); - } - } - }); - - /** - * (PDF 1.4) If set, more than one of the field’s option items may be selected simultaneously; if clear, at most one item shall be selected - * - * @name AcroFormChoiceField#multiSelect - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "multiSelect", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 22)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 22); - } else { - this.Ff = clearBitForPdf(this.Ff, 22); - } - } - }); - - /** - * (PDF 1.4) If set, text entered in the field shall not be spellchecked. This flag shall not be used unless the Combo and Edit flags are both set. - * - * @name AcroFormChoiceField#doNotSpellCheck - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "doNotSpellCheck", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 23)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 23); - } else { - this.Ff = clearBitForPdf(this.Ff, 23); - } - } - }); - - /** - * (PDF 1.5) If set, the new value shall be committed as soon as a selection is made (commonly with the pointing device). In this case, supplying a value for a field involves three actions: selecting the field for fill-in, selecting a choice for the fill-in value, and leaving that field, which finalizes or “commits” the data choice and triggers any actions associated with the entry or changing of this data. If this flag is on, then processing does not wait for leaving the field action to occur, but immediately proceeds to the third step. - * This option enables applications to perform an action once a selection is made, without requiring the user to exit the field. If clear, the new value is not committed until the user exits the field. - * - * @name AcroFormChoiceField#commitOnSelChange - * @default false - * @type {boolean} - */ - Object.defineProperty(this, "commitOnSelChange", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 27)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 27); - } else { - this.Ff = clearBitForPdf(this.Ff, 27); - } - } - }); - - this.hasAppearanceStream = false; -}; -inherit(AcroFormChoiceField, AcroFormField); - -/** - * @class AcroFormListBox - * @extends AcroFormChoiceField - * @extends AcroFormField - */ -var AcroFormListBox = function() { - AcroFormChoiceField.call(this); - this.fontName = "helvetica"; - - //PDF 32000-1:2008, page 444 - this.combo = false; -}; -inherit(AcroFormListBox, AcroFormChoiceField); - -/** - * @class AcroFormComboBox - * @extends AcroFormListBox - * @extends AcroFormChoiceField - * @extends AcroFormField - */ -var AcroFormComboBox = function() { - AcroFormListBox.call(this); - this.combo = true; -}; -inherit(AcroFormComboBox, AcroFormListBox); - -/** - * @class AcroFormEditBox - * @extends AcroFormComboBox - * @extends AcroFormListBox - * @extends AcroFormChoiceField - * @extends AcroFormField - */ -var AcroFormEditBox = function() { - AcroFormComboBox.call(this); - this.edit = true; -}; -inherit(AcroFormEditBox, AcroFormComboBox); - -/** - * @class AcroFormButton - * @extends AcroFormField - */ -var AcroFormButton = function() { - AcroFormField.call(this); - this.FT = "/Btn"; - - /** - * (Radio buttons only) If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect. If clear, clicking the selected button deselects it, leaving no button selected. - * - * @name AcroFormButton#noToggleToOff - * @type {boolean} - */ - Object.defineProperty(this, "noToggleToOff", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 15)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 15); - } else { - this.Ff = clearBitForPdf(this.Ff, 15); - } - } - }); - - /** - * If set, the field is a set of radio buttons; if clear, the field is a checkbox. This flag may be set only if the Pushbutton flag is clear. - * - * @name AcroFormButton#radio - * @type {boolean} - */ - Object.defineProperty(this, "radio", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 16)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 16); - } else { - this.Ff = clearBitForPdf(this.Ff, 16); - } - } - }); - - /** - * If set, the field is a pushbutton that does not retain a permanent value. - * - * @name AcroFormButton#pushButton - * @type {boolean} - */ - Object.defineProperty(this, "pushButton", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 17)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 17); - } else { - this.Ff = clearBitForPdf(this.Ff, 17); - } - } - }); - - /** - * (PDF 1.5) If set, a group of radio buttons within a radio button field that use the same value for the on state will turn on and off in unison; that is if one is checked, they are all checked. If clear, the buttons are mutually exclusive (the same behavior as HTML radio buttons). - * - * @name AcroFormButton#radioIsUnison - * @type {boolean} - */ - Object.defineProperty(this, "radioIsUnison", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 26)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 26); - } else { - this.Ff = clearBitForPdf(this.Ff, 26); - } - } - }); - - var _MK = {}; - Object.defineProperty(this, "MK", { - enumerable: false, - configurable: false, - get: function() { - var encryptor = function(data) { - return data; - }; - if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); - if (Object.keys(_MK).length !== 0) { - var result = []; - result.push("<<"); - var key; - for (key in _MK) { - result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); - } - result.push(">>"); - return result.join("\n"); - } - return undefined; - }, - set: function(value) { - if (typeof value === "object") { - _MK = value; - } - } - }); - - /** - * From the PDF reference: - * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. - * Unlike the remaining entries listed in this Table which apply only to widget annotations associated with pushbutton fields (see Pushbuttons in 12.7.4.2, "Button Fields"), the CA entry may be used with any type of button field, including check boxes (see Check Boxes in 12.7.4.2, "Button Fields") and radio buttons (Radio Buttons in 12.7.4.2, "Button Fields"). - * - * - '8' = Cross, - * - 'l' = Circle, - * - '' = nothing - * @name AcroFormButton#caption - * @type {string} - */ - Object.defineProperty(this, "caption", { - enumerable: true, - configurable: true, - get: function() { - return _MK.CA || ""; - }, - set: function(value) { - if (typeof value === "string") { - _MK.CA = value; - } - } - }); - - var _AS; - Object.defineProperty(this, "AS", { - enumerable: false, - configurable: false, - get: function() { - return _AS; - }, - set: function(value) { - var name = value === undefined || value === null ? "" : value.toString(); - if (name.substr(0, 1) === "/") { - name = name.substr(1); - } - _AS = "/" + pdfEscapeName(name); - } - }); - - /** - * (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) The annotation's appearance state, which selects the applicable appearance stream from an appearance subdictionary (see Section 12.5.5, "Appearance Streams") - * - * @name AcroFormButton#appearanceState - * @type {any} - */ - Object.defineProperty(this, "appearanceState", { - enumerable: true, - configurable: true, - get: function() { - return _AS.substr(1, _AS.length - 1); - }, - set: function(value) { - _AS = "/" + pdfEscapeName(value); - } - }); -}; -inherit(AcroFormButton, AcroFormField); - -/** - * @class AcroFormPushButton - * @extends AcroFormButton - * @extends AcroFormField - */ -var AcroFormPushButton = function() { - AcroFormButton.call(this); - this.pushButton = true; -}; -inherit(AcroFormPushButton, AcroFormButton); - -/** - * @class AcroFormRadioButton - * @extends AcroFormButton - * @extends AcroFormField - */ -var AcroFormRadioButton = function() { - AcroFormButton.call(this); - this.radio = true; - this.pushButton = false; - - var _Kids = []; - Object.defineProperty(this, "Kids", { - enumerable: true, - configurable: false, - get: function() { - return _Kids; - }, - set: function(value) { - if (typeof value !== "undefined") { - _Kids = value; - } else { - _Kids = []; - } - } - }); -}; -inherit(AcroFormRadioButton, AcroFormButton); - -/** - * The Child class of a RadioButton (the radioGroup) -> The single Buttons - * - * @class AcroFormChildClass - * @extends AcroFormField - * @ignore - */ -var AcroFormChildClass = function() { - AcroFormField.call(this); - - var _parent; - Object.defineProperty(this, "Parent", { - enumerable: false, - configurable: false, - get: function() { - return _parent; - }, - set: function(value) { - _parent = value; - } - }); - - var _optionName; - Object.defineProperty(this, "optionName", { - enumerable: false, - configurable: true, - get: function() { - return _optionName; - }, - set: function(value) { - _optionName = value; - } - }); - - var _MK = {}; - Object.defineProperty(this, "MK", { - enumerable: false, - configurable: false, - get: function() { - var encryptor = function(data) { - return data; - }; - if (this.scope) encryptor = this.scope.internal.getEncryptor(this.objId); - var result = []; - result.push("<<"); - var key; - for (key in _MK) { - result.push("/" + key + " (" + pdfEscape(encryptor(_MK[key])) + ")"); - } - result.push(">>"); - return result.join("\n"); - }, - set: function(value) { - if (typeof value === "object") { - _MK = value; - } - } - }); - - /** - * From the PDF reference: - * (Optional, button fields only) The widget annotation's normal caption which shall be displayed when it is not interacting with the user. - * Unlike the remaining entries listed in this Table which apply only to widget annotations associated with pushbutton fields (see Pushbuttons in 12.7.4.2, "Button Fields"), the CA entry may be used with any type of button field, including check boxes (see Check Boxes in 12.7.4.2, "Button Fields") and radio buttons (Radio Buttons in 12.7.4.2, "Button Fields"). - * - * - '8' = Cross, - * - 'l' = Circle, - * - '' = nothing - * @name AcroFormButton#caption - * @type {string} - */ - Object.defineProperty(this, "caption", { - enumerable: true, - configurable: true, - get: function() { - return _MK.CA || ""; - }, - set: function(value) { - if (typeof value === "string") { - _MK.CA = value; - } - } - }); - - var _AS; - Object.defineProperty(this, "AS", { - enumerable: false, - configurable: false, - get: function() { - return _AS; - }, - set: function(value) { - var name = value === undefined || value === null ? "" : value.toString(); - if (name.substr(0, 1) === "/") { - name = name.substr(1); - } - _AS = "/" + pdfEscapeName(name); - } - }); - - /** - * (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) The annotation's appearance state, which selects the applicable appearance stream from an appearance subdictionary (see Section 12.5.5, "Appearance Streams") - * - * @name AcroFormButton#appearanceState - * @type {any} - */ - Object.defineProperty(this, "appearanceState", { - enumerable: true, - configurable: true, - get: function() { - return _AS.substr(1, _AS.length - 1); - }, - set: function(value) { - var name = value === undefined || value === null ? "" : value.toString(); - if (name.substr(0, 1) === "/") { - name = name.substr(1); - } - _AS = "/" + pdfEscapeName(name); - } - }); - this.caption = "l"; - this.appearanceState = "Off"; - // todo: set AppearanceType as variable that can be set from the - // outside... - this._AppearanceType = AcroFormAppearance.RadioButton.Circle; - // The Default appearanceType is the Circle - this.appearanceStreamContent = this._AppearanceType.createAppearanceStream( - this.optionName - ); -}; -inherit(AcroFormChildClass, AcroFormField); - -AcroFormRadioButton.prototype.setAppearance = function(appearance) { - if (!("createAppearanceStream" in appearance && "getCA" in appearance)) { - throw new Error( - "Couldn't assign Appearance to RadioButton. Appearance was Invalid!" - ); - } - for (var objId in this.Kids) { - if (this.Kids.hasOwnProperty(objId)) { - var child = this.Kids[objId]; - child.appearanceStreamContent = appearance.createAppearanceStream( - child.optionName - ); - child.caption = appearance.getCA(); - } - } -}; - -AcroFormRadioButton.prototype.createOption = function(name) { - // Create new Child for RadioGroup - var child = new AcroFormChildClass(); - child.Parent = this; - child.optionName = name; - // Add to Parent - this.Kids.push(child); - - addField.call(this.scope, child); - - return child; -}; - -/** - * @class AcroFormCheckBox - * @extends AcroFormButton - * @extends AcroFormField - */ -var AcroFormCheckBox = function() { - AcroFormButton.call(this); - - this.fontName = "zapfdingbats"; - this.caption = "3"; - this.appearanceState = "On"; - this.value = "On"; - this.textAlign = "center"; - this.appearanceStreamContent = AcroFormAppearance.CheckBox.createAppearanceStream(); -}; -inherit(AcroFormCheckBox, AcroFormButton); - -/** - * @class AcroFormTextField - * @extends AcroFormField - */ -var AcroFormTextField = function() { - AcroFormField.call(this); - this.FT = "/Tx"; - - /** - * If set, the field may contain multiple lines of text; if clear, the field’s text shall be restricted to a single line. - * - * @name AcroFormTextField#multiline - * @type {boolean} - */ - Object.defineProperty(this, "multiline", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 13)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 13); - } else { - this.Ff = clearBitForPdf(this.Ff, 13); - } - } - }); - - /** - * (PDF 1.4) If set, the text entered in the field represents the pathname of a file whose contents shall be submitted as the value of the field. - * - * @name AcroFormTextField#fileSelect - * @type {boolean} - */ - Object.defineProperty(this, "fileSelect", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 21)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 21); - } else { - this.Ff = clearBitForPdf(this.Ff, 21); - } - } - }); - - /** - * (PDF 1.4) If set, text entered in the field shall not be spell-checked. - * - * @name AcroFormTextField#doNotSpellCheck - * @type {boolean} - */ - Object.defineProperty(this, "doNotSpellCheck", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 23)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 23); - } else { - this.Ff = clearBitForPdf(this.Ff, 23); - } - } - }); - - /** - * (PDF 1.4) If set, the field shall not scroll (horizontally for single-line fields, vertically for multiple-line fields) to accommodate more text than fits within its annotation rectangle. Once the field is full, no further text shall be accepted for interactive form filling; for noninteractive form filling, the filler should take care not to add more character than will visibly fit in the defined area. - * - * @name AcroFormTextField#doNotScroll - * @type {boolean} - */ - Object.defineProperty(this, "doNotScroll", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 24)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 24); - } else { - this.Ff = clearBitForPdf(this.Ff, 24); - } - } - }); - - /** - * (PDF 1.5) May be set only if the MaxLen entry is present in the text field dictionary (see Table 229) and if the Multiline, Password, and FileSelect flags are clear. If set, the field shall be automatically divided into as many equally spaced positions, or combs, as the value of MaxLen, and the text is laid out into those combs. - * - * @name AcroFormTextField#comb - * @type {boolean} - */ - Object.defineProperty(this, "comb", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 25)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 25); - } else { - this.Ff = clearBitForPdf(this.Ff, 25); - } - } - }); - - /** - * (PDF 1.5) If set, the value of this field shall be a rich text string (see 12.7.3.4, “Rich Text Strings”). If the field has a value, the RV entry of the field dictionary (Table 222) shall specify the rich text string. - * - * @name AcroFormTextField#richText - * @type {boolean} - */ - Object.defineProperty(this, "richText", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 26)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 26); - } else { - this.Ff = clearBitForPdf(this.Ff, 26); - } - } - }); - - var _MaxLen = null; - Object.defineProperty(this, "MaxLen", { - enumerable: true, - configurable: false, - get: function() { - return _MaxLen; - }, - set: function(value) { - _MaxLen = value; - } - }); - - /** - * (Optional; inheritable) The maximum length of the field’s text, in characters. - * - * @name AcroFormTextField#maxLength - * @type {number} - */ - Object.defineProperty(this, "maxLength", { - enumerable: true, - configurable: true, - get: function() { - return _MaxLen; - }, - set: function(value) { - if (Number.isInteger(value)) { - _MaxLen = value; - } - } - }); - - Object.defineProperty(this, "hasAppearanceStream", { - enumerable: true, - configurable: true, - get: function() { - return this.V || this.DV; - } - }); -}; -inherit(AcroFormTextField, AcroFormField); - -/** - * @class AcroFormPasswordField - * @extends AcroFormTextField - * @extends AcroFormField - */ -var AcroFormPasswordField = function() { - AcroFormTextField.call(this); - - /** - * If set, the field is intended for entering a secure password that should not be echoed visibly to the screen. Characters typed from the keyboard shall instead be echoed in some unreadable form, such as asterisks or bullet characters. - * NOTE To protect password confidentiality, readers should never store the value of the text field in the PDF file if this flag is set. - * - * @name AcroFormTextField#password - * @type {boolean} - */ - Object.defineProperty(this, "password", { - enumerable: true, - configurable: true, - get: function() { - return Boolean(getBitForPdf(this.Ff, 14)); - }, - set: function(value) { - if (Boolean(value) === true) { - this.Ff = setBitForPdf(this.Ff, 14); - } else { - this.Ff = clearBitForPdf(this.Ff, 14); - } - } - }); - this.password = true; -}; -inherit(AcroFormPasswordField, AcroFormTextField); - -// Contains Methods for creating standard appearances -var AcroFormAppearance = { - CheckBox: { - createAppearanceStream: function() { - var appearance = { - N: { - On: AcroFormAppearance.CheckBox.YesNormal - }, - D: { - On: AcroFormAppearance.CheckBox.YesPushDown, - Off: AcroFormAppearance.CheckBox.OffPushDown - } - }; - - return appearance; - }, - /** - * Returns the standard On Appearance for a CheckBox - * - * @returns {AcroFormXObject} - */ - YesPushDown: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - var fontKey = formObject.scope.internal.getFont( - formObject.fontName, - formObject.fontStyle - ).id; - var encodedColor = formObject.scope.__private__.encodeColorString( - formObject.color - ); - var calcRes = calculateX(formObject, formObject.caption); - stream.push("0.749023 g"); - stream.push( - "0 0 " + - f2(AcroFormAppearance.internal.getWidth(formObject)) + - " " + - f2(AcroFormAppearance.internal.getHeight(formObject)) + - " re" - ); - stream.push("f"); - stream.push("BMC"); - stream.push("q"); - stream.push("0 0 1 rg"); - stream.push( - "/" + fontKey + " " + f2(calcRes.fontSize) + " Tf " + encodedColor - ); - stream.push("BT"); - stream.push(calcRes.text); - stream.push("ET"); - stream.push("Q"); - stream.push("EMC"); - xobj.stream = stream.join("\n"); - return xobj; - }, - - YesNormal: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var fontKey = formObject.scope.internal.getFont( - formObject.fontName, - formObject.fontStyle - ).id; - var encodedColor = formObject.scope.__private__.encodeColorString( - formObject.color - ); - var stream = []; - var height = AcroFormAppearance.internal.getHeight(formObject); - var width = AcroFormAppearance.internal.getWidth(formObject); - var calcRes = calculateX(formObject, formObject.caption); - stream.push("1 g"); - stream.push("0 0 " + f2(width) + " " + f2(height) + " re"); - stream.push("f"); - stream.push("q"); - stream.push("0 0 1 rg"); - stream.push("0 0 " + f2(width - 1) + " " + f2(height - 1) + " re"); - stream.push("W"); - stream.push("n"); - stream.push("0 g"); - stream.push("BT"); - stream.push( - "/" + fontKey + " " + f2(calcRes.fontSize) + " Tf " + encodedColor - ); - stream.push(calcRes.text); - stream.push("ET"); - stream.push("Q"); - xobj.stream = stream.join("\n"); - return xobj; - }, - - /** - * Returns the standard Off Appearance for a CheckBox - * - * @returns {AcroFormXObject} - */ - OffPushDown: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - stream.push("0.749023 g"); - stream.push( - "0 0 " + - f2(AcroFormAppearance.internal.getWidth(formObject)) + - " " + - f2(AcroFormAppearance.internal.getHeight(formObject)) + - " re" - ); - stream.push("f"); - xobj.stream = stream.join("\n"); - return xobj; - } - }, - - RadioButton: { - Circle: { - createAppearanceStream: function(name) { - var appearanceStreamContent = { - D: { - Off: AcroFormAppearance.RadioButton.Circle.OffPushDown - }, - N: {} - }; - appearanceStreamContent.N[name] = - AcroFormAppearance.RadioButton.Circle.YesNormal; - appearanceStreamContent.D[name] = - AcroFormAppearance.RadioButton.Circle.YesPushDown; - return appearanceStreamContent; - }, - getCA: function() { - return "l"; - }, - - YesNormal: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - // Make the Radius of the Circle relative to min(height, width) of formObject - var DotRadius = - AcroFormAppearance.internal.getWidth(formObject) <= - AcroFormAppearance.internal.getHeight(formObject) - ? AcroFormAppearance.internal.getWidth(formObject) / 4 - : AcroFormAppearance.internal.getHeight(formObject) / 4; - // The Borderpadding... - DotRadius = Number((DotRadius * 0.9).toFixed(5)); - var c = AcroFormAppearance.internal.Bezier_C; - var DotRadiusBezier = Number((DotRadius * c).toFixed(5)); - /* - * The Following is a Circle created with Bezier-Curves. - */ - stream.push("q"); - stream.push( - "1 0 0 1 " + - f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + - " " + - f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + - " cm" - ); - stream.push(DotRadius + " 0 m"); - stream.push( - DotRadius + - " " + - DotRadiusBezier + - " " + - DotRadiusBezier + - " " + - DotRadius + - " 0 " + - DotRadius + - " c" - ); - stream.push( - "-" + - DotRadiusBezier + - " " + - DotRadius + - " -" + - DotRadius + - " " + - DotRadiusBezier + - " -" + - DotRadius + - " 0 c" - ); - stream.push( - "-" + - DotRadius + - " -" + - DotRadiusBezier + - " -" + - DotRadiusBezier + - " -" + - DotRadius + - " 0 -" + - DotRadius + - " c" - ); - stream.push( - DotRadiusBezier + - " -" + - DotRadius + - " " + - DotRadius + - " -" + - DotRadiusBezier + - " " + - DotRadius + - " 0 c" - ); - stream.push("f"); - stream.push("Q"); - xobj.stream = stream.join("\n"); - return xobj; - }, - YesPushDown: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - var DotRadius = - AcroFormAppearance.internal.getWidth(formObject) <= - AcroFormAppearance.internal.getHeight(formObject) - ? AcroFormAppearance.internal.getWidth(formObject) / 4 - : AcroFormAppearance.internal.getHeight(formObject) / 4; - // The Borderpadding... - DotRadius = Number((DotRadius * 0.9).toFixed(5)); - // Save results for later use; no need to waste - // processor ticks on doing math - var k = Number((DotRadius * 2).toFixed(5)); - var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); - var dc = Number( - (DotRadius * AcroFormAppearance.internal.Bezier_C).toFixed(5) - ); - - stream.push("0.749023 g"); - stream.push("q"); - stream.push( - "1 0 0 1 " + - f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + - " " + - f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + - " cm" - ); - stream.push(k + " 0 m"); - stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); - stream.push( - "-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c" - ); - stream.push( - "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" - ); - stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); - stream.push("f"); - stream.push("Q"); - stream.push("0 g"); - stream.push("q"); - stream.push( - "1 0 0 1 " + - f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + - " " + - f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + - " cm" - ); - stream.push(DotRadius + " 0 m"); - stream.push( - "" + - DotRadius + - " " + - dc + - " " + - dc + - " " + - DotRadius + - " 0 " + - DotRadius + - " c" - ); - stream.push( - "-" + - dc + - " " + - DotRadius + - " -" + - DotRadius + - " " + - dc + - " -" + - DotRadius + - " 0 c" - ); - stream.push( - "-" + - DotRadius + - " -" + - dc + - " -" + - dc + - " -" + - DotRadius + - " 0 -" + - DotRadius + - " c" - ); - stream.push( - dc + - " -" + - DotRadius + - " " + - DotRadius + - " -" + - dc + - " " + - DotRadius + - " 0 c" - ); - stream.push("f"); - stream.push("Q"); - xobj.stream = stream.join("\n"); - return xobj; - }, - OffPushDown: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - var DotRadius = - AcroFormAppearance.internal.getWidth(formObject) <= - AcroFormAppearance.internal.getHeight(formObject) - ? AcroFormAppearance.internal.getWidth(formObject) / 4 - : AcroFormAppearance.internal.getHeight(formObject) / 4; - // The Borderpadding... - DotRadius = Number((DotRadius * 0.9).toFixed(5)); - // Save results for later use; no need to waste - // processor ticks on doing math - var k = Number((DotRadius * 2).toFixed(5)); - var kc = Number((k * AcroFormAppearance.internal.Bezier_C).toFixed(5)); - - stream.push("0.749023 g"); - stream.push("q"); - stream.push( - "1 0 0 1 " + - f5(AcroFormAppearance.internal.getWidth(formObject) / 2) + - " " + - f5(AcroFormAppearance.internal.getHeight(formObject) / 2) + - " cm" - ); - stream.push(k + " 0 m"); - stream.push(k + " " + kc + " " + kc + " " + k + " 0 " + k + " c"); - stream.push( - "-" + kc + " " + k + " -" + k + " " + kc + " -" + k + " 0 c" - ); - stream.push( - "-" + k + " -" + kc + " -" + kc + " -" + k + " 0 -" + k + " c" - ); - stream.push(kc + " -" + k + " " + k + " -" + kc + " " + k + " 0 c"); - stream.push("f"); - stream.push("Q"); - xobj.stream = stream.join("\n"); - return xobj; - } - }, - - Cross: { - /** - * Creates the Actual AppearanceDictionary-References - * - * @param {string} name - * @returns {Object} - * @ignore - */ - createAppearanceStream: function(name) { - var appearanceStreamContent = { - D: { - Off: AcroFormAppearance.RadioButton.Cross.OffPushDown - }, - N: {} - }; - appearanceStreamContent.N[name] = - AcroFormAppearance.RadioButton.Cross.YesNormal; - appearanceStreamContent.D[name] = - AcroFormAppearance.RadioButton.Cross.YesPushDown; - return appearanceStreamContent; - }, - getCA: function() { - return "8"; - }, - - YesNormal: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - var cross = AcroFormAppearance.internal.calculateCross(formObject); - stream.push("q"); - stream.push( - "1 1 " + - f2(AcroFormAppearance.internal.getWidth(formObject) - 2) + - " " + - f2(AcroFormAppearance.internal.getHeight(formObject) - 2) + - " re" - ); - stream.push("W"); - stream.push("n"); - stream.push(f2(cross.x1.x) + " " + f2(cross.x1.y) + " m"); - stream.push(f2(cross.x2.x) + " " + f2(cross.x2.y) + " l"); - stream.push(f2(cross.x4.x) + " " + f2(cross.x4.y) + " m"); - stream.push(f2(cross.x3.x) + " " + f2(cross.x3.y) + " l"); - stream.push("s"); - stream.push("Q"); - xobj.stream = stream.join("\n"); - return xobj; - }, - YesPushDown: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var cross = AcroFormAppearance.internal.calculateCross(formObject); - var stream = []; - stream.push("0.749023 g"); - stream.push( - "0 0 " + - f2(AcroFormAppearance.internal.getWidth(formObject)) + - " " + - f2(AcroFormAppearance.internal.getHeight(formObject)) + - " re" - ); - stream.push("f"); - stream.push("q"); - stream.push( - "1 1 " + - f2(AcroFormAppearance.internal.getWidth(formObject) - 2) + - " " + - f2(AcroFormAppearance.internal.getHeight(formObject) - 2) + - " re" - ); - stream.push("W"); - stream.push("n"); - stream.push(f2(cross.x1.x) + " " + f2(cross.x1.y) + " m"); - stream.push(f2(cross.x2.x) + " " + f2(cross.x2.y) + " l"); - stream.push(f2(cross.x4.x) + " " + f2(cross.x4.y) + " m"); - stream.push(f2(cross.x3.x) + " " + f2(cross.x3.y) + " l"); - stream.push("s"); - stream.push("Q"); - xobj.stream = stream.join("\n"); - return xobj; - }, - OffPushDown: function(formObject) { - var xobj = createFormXObject(formObject); - xobj.scope = formObject.scope; - var stream = []; - stream.push("0.749023 g"); - stream.push( - "0 0 " + - f2(AcroFormAppearance.internal.getWidth(formObject)) + - " " + - f2(AcroFormAppearance.internal.getHeight(formObject)) + - " re" - ); - stream.push("f"); - xobj.stream = stream.join("\n"); - return xobj; - } - } - }, - - /** - * Returns the standard Appearance - * - * @returns {AcroFormXObject} - */ - createDefaultAppearanceStream: function(formObject) { - // Set Helvetica to Standard Font (size: auto) - // Color: Black - var fontKey = formObject.scope.internal.getFont( - formObject.fontName, - formObject.fontStyle - ).id; - var encodedColor = formObject.scope.__private__.encodeColorString( - formObject.color - ); - var fontSize = formObject.fontSize; - var result = "/" + fontKey + " " + fontSize + " Tf " + encodedColor; - return result; - } -}; - -AcroFormAppearance.internal = { - Bezier_C: 0.551915024494, - - calculateCross: function(formObject) { - var width = AcroFormAppearance.internal.getWidth(formObject); - var height = AcroFormAppearance.internal.getHeight(formObject); - var a = Math.min(width, height); - - var cross = { - x1: { - // upperLeft - x: (width - a) / 2, - y: (height - a) / 2 + a // height - borderPadding - }, - x2: { - // lowerRight - x: (width - a) / 2 + a, - y: (height - a) / 2 // borderPadding - }, - x3: { - // lowerLeft - x: (width - a) / 2, - y: (height - a) / 2 // borderPadding - }, - x4: { - // upperRight - x: (width - a) / 2 + a, - y: (height - a) / 2 + a // height - borderPadding - } - }; - - return cross; - } -}; -AcroFormAppearance.internal.getWidth = function(formObject) { - var result = 0; - if (typeof formObject === "object") { - result = scale(formObject.Rect[2]); - } - return result; -}; -AcroFormAppearance.internal.getHeight = function(formObject) { - var result = 0; - if (typeof formObject === "object") { - result = scale(formObject.Rect[3]); - } - return result; -}; - -// Public: - -/** - * Add an AcroForm-Field to the jsPDF-instance - * - * @name addField - * @function - * @instance - * @param {Object} fieldObject - * @returns {jsPDF} - */ -var addField = (jsPDFAPI.addField = function(fieldObject) { - initializeAcroForm(this, fieldObject); - - if (fieldObject instanceof AcroFormField) { - putForm(fieldObject); - } else { - throw new Error("Invalid argument passed to jsPDF.addField."); - } - fieldObject.page = fieldObject.scope.internal.getCurrentPageInfo().pageNumber; - return this; -}); - -jsPDFAPI.AcroFormChoiceField = AcroFormChoiceField; -jsPDFAPI.AcroFormListBox = AcroFormListBox; -jsPDFAPI.AcroFormComboBox = AcroFormComboBox; -jsPDFAPI.AcroFormEditBox = AcroFormEditBox; -jsPDFAPI.AcroFormButton = AcroFormButton; -jsPDFAPI.AcroFormPushButton = AcroFormPushButton; -jsPDFAPI.AcroFormRadioButton = AcroFormRadioButton; -jsPDFAPI.AcroFormCheckBox = AcroFormCheckBox; -jsPDFAPI.AcroFormTextField = AcroFormTextField; -jsPDFAPI.AcroFormPasswordField = AcroFormPasswordField; -jsPDFAPI.AcroFormAppearance = AcroFormAppearance; - -jsPDFAPI.AcroForm = { - ChoiceField: AcroFormChoiceField, - ListBox: AcroFormListBox, - ComboBox: AcroFormComboBox, - EditBox: AcroFormEditBox, - Button: AcroFormButton, - PushButton: AcroFormPushButton, - RadioButton: AcroFormRadioButton, - CheckBox: AcroFormCheckBox, - TextField: AcroFormTextField, - PasswordField: AcroFormPasswordField, - Appearance: AcroFormAppearance -}; - -jsPDF.AcroForm = { - ChoiceField: AcroFormChoiceField, - ListBox: AcroFormListBox, - ComboBox: AcroFormComboBox, - EditBox: AcroFormEditBox, - Button: AcroFormButton, - PushButton: AcroFormPushButton, - RadioButton: AcroFormRadioButton, - CheckBox: AcroFormCheckBox, - TextField: AcroFormTextField, - PasswordField: AcroFormPasswordField, - Appearance: AcroFormAppearance -}; - -var AcroForm = jsPDF.AcroForm; - -/** @license - * jsPDF addImage plugin - * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/ - * 2013 Chris Dowling, https://github.com/gingerchris - * 2013 Trinh Ho, https://github.com/ineedfat - * 2013 Edwin Alejandro Perez, https://github.com/eaparango - * 2013 Norah Smith, https://github.com/burnburnrocket - * 2014 Diego Casorran, https://github.com/diegocr - * 2014 James Robb, https://github.com/jamesbrobb - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -(function(jsPDFAPI) { - - var namespace = "addImage_"; - jsPDFAPI.__addimage__ = {}; - - var UNKNOWN = "UNKNOWN"; - - // Heuristic selection of a good batch for large array .apply. Not limiting make the call overflow. - // With too small batch iteration will be slow as more calls are made, - // higher values cause larger and slower garbage collection. - var ARRAY_APPLY_BATCH = 8192; - - var imageFileTypeHeaders = { - PNG: [[0x89, 0x50, 0x4e, 0x47]], - TIFF: [ - [0x4d, 0x4d, 0x00, 0x2a], //Motorola - [0x49, 0x49, 0x2a, 0x00] //Intel - ], - JPEG: [ - [ - 0xff, - 0xd8, - 0xff, - 0xe0, - undefined, - undefined, - 0x4a, - 0x46, - 0x49, - 0x46, - 0x00 - ], //JFIF - [ - 0xff, - 0xd8, - 0xff, - 0xe1, - undefined, - undefined, - 0x45, - 0x78, - 0x69, - 0x66, - 0x00, - 0x00 - ], //Exif - [0xff, 0xd8, 0xff, 0xdb], //JPEG RAW - [0xff, 0xd8, 0xff, 0xee] //EXIF RAW - ], - JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]], - GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]], - GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]], - WEBP: [ - [ - 0x52, - 0x49, - 0x46, - 0x46, - undefined, - undefined, - undefined, - undefined, - 0x57, - 0x45, - 0x42, - 0x50 - ] - ], - BMP: [ - [0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc. - [0x42, 0x41], //BA - OS/2 struct bitmap array - [0x43, 0x49], //CI - OS/2 struct color icon - [0x43, 0x50], //CP - OS/2 const color pointer - [0x49, 0x43], //IC - OS/2 struct icon - [0x50, 0x54] //PT - OS/2 pointer - ] - }; - - /** - * Recognize filetype of Image by magic-bytes - * - * https://en.wikipedia.org/wiki/List_of_file_signatures - * - * @name getImageFileTypeByImageData - * @public - * @function - * @param {string|arraybuffer} imageData imageData as binary String or arraybuffer - * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG' - * - * @returns {string} filetype of Image - */ - var getImageFileTypeByImageData = (jsPDFAPI.__addimage__.getImageFileTypeByImageData = function( - imageData, - fallbackFormat - ) { - fallbackFormat = fallbackFormat || UNKNOWN; - var i; - var j; - var result = UNKNOWN; - var headerSchemata; - var compareResult; - var fileType; - - if ( - fallbackFormat === "RGBA" || - (imageData.data !== undefined && - imageData.data instanceof Uint8ClampedArray && - "height" in imageData && - "width" in imageData) - ) { - return "RGBA"; - } - - if (isArrayBufferView(imageData)) { - for (fileType in imageFileTypeHeaders) { - headerSchemata = imageFileTypeHeaders[fileType]; - for (i = 0; i < headerSchemata.length; i += 1) { - compareResult = true; - for (j = 0; j < headerSchemata[i].length; j += 1) { - if (headerSchemata[i][j] === undefined) { - continue; - } - if (headerSchemata[i][j] !== imageData[j]) { - compareResult = false; - break; - } - } - if (compareResult === true) { - result = fileType; - break; - } - } - } - } else { - for (fileType in imageFileTypeHeaders) { - headerSchemata = imageFileTypeHeaders[fileType]; - for (i = 0; i < headerSchemata.length; i += 1) { - compareResult = true; - for (j = 0; j < headerSchemata[i].length; j += 1) { - if (headerSchemata[i][j] === undefined) { - continue; - } - if (headerSchemata[i][j] !== imageData.charCodeAt(j)) { - compareResult = false; - break; - } - } - if (compareResult === true) { - result = fileType; - break; - } - } - } - } - - if (result === UNKNOWN && fallbackFormat !== UNKNOWN) { - result = fallbackFormat; - } - return result; - }); - - // Image functionality ported from pdf.js - var putImage = function(image) { - var out = this.internal.write; - var putStream = this.internal.putStream; - var getFilters = this.internal.getFilters; - - var filter = getFilters(); - while (filter.indexOf("FlateEncode") !== -1) { - filter.splice(filter.indexOf("FlateEncode"), 1); - } - - image.objectId = this.internal.newObject(); - - var additionalKeyValues = []; - additionalKeyValues.push({ key: "Type", value: "/XObject" }); - additionalKeyValues.push({ key: "Subtype", value: "/Image" }); - additionalKeyValues.push({ key: "Width", value: image.width }); - additionalKeyValues.push({ key: "Height", value: image.height }); - - if (image.colorSpace === color_spaces.INDEXED) { - additionalKeyValues.push({ - key: "ColorSpace", - value: - "[/Indexed /DeviceRGB " + - // if an indexed png defines more than one colour with transparency, we've created a sMask - (image.palette.length / 3 - 1) + - " " + - ("sMask" in image && typeof image.sMask !== "undefined" - ? image.objectId + 2 - : image.objectId + 1) + - " 0 R]" - }); - } else { - additionalKeyValues.push({ - key: "ColorSpace", - value: "/" + image.colorSpace - }); - if (image.colorSpace === color_spaces.DEVICE_CMYK) { - additionalKeyValues.push({ key: "Decode", value: "[1 0 1 0 1 0 1 0]" }); - } - } - additionalKeyValues.push({ - key: "BitsPerComponent", - value: image.bitsPerComponent - }); - if ( - "decodeParameters" in image && - typeof image.decodeParameters !== "undefined" - ) { - additionalKeyValues.push({ - key: "DecodeParms", - value: "<<" + image.decodeParameters + ">>" - }); - } - if ( - "transparency" in image && - Array.isArray(image.transparency) && - image.transparency.length > 0 - ) { - var transparency = "", - i = 0, - len = image.transparency.length; - for (; i < len; i++) - transparency += - image.transparency[i] + " " + image.transparency[i] + " "; - - additionalKeyValues.push({ - key: "Mask", - value: "[" + transparency + "]" - }); - } - if (typeof image.sMask !== "undefined") { - additionalKeyValues.push({ - key: "SMask", - value: image.objectId + 1 + " 0 R" - }); - } - - var alreadyAppliedFilters = - typeof image.filter !== "undefined" ? ["/" + image.filter] : undefined; - - putStream({ - data: image.data, - additionalKeyValues: additionalKeyValues, - alreadyAppliedFilters: alreadyAppliedFilters, - objectId: image.objectId - }); - - out("endobj"); - - // Soft mask - if ("sMask" in image && typeof image.sMask !== "undefined") { - const sMaskBitsPerComponent = - image.sMaskBitsPerComponent ?? image.bitsPerComponent; - const sMask = { - width: image.width, - height: image.height, - colorSpace: "DeviceGray", - bitsPerComponent: sMaskBitsPerComponent, - data: image.sMask - }; - if ("filter" in image) { - sMask.decodeParameters = `/Predictor ${image.predictor} /Colors 1 /BitsPerComponent ${sMaskBitsPerComponent} /Columns ${image.width}`; - sMask.filter = image.filter; - } - putImage.call(this, sMask); - } - - //Palette - if (image.colorSpace === color_spaces.INDEXED) { - var objId = this.internal.newObject(); - //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>'); - //putStream(zlib.compress(img['pal'])); - putStream({ - data: arrayBufferToBinaryString(new Uint8Array(image.palette)), - objectId: objId - }); - out("endobj"); - } - }; - var putResourcesCallback = function() { - var images = this.internal.collections[namespace + "images"]; - for (var i in images) { - putImage.call(this, images[i]); - } - }; - var putXObjectsDictCallback = function() { - var images = this.internal.collections[namespace + "images"], - out = this.internal.write, - image; - for (var i in images) { - image = images[i]; - out("/I" + image.index, image.objectId, "0", "R"); - } - }; - - var checkCompressValue = function(value) { - if (value && typeof value === "string") value = value.toUpperCase(); - return value in jsPDFAPI.image_compression ? value : image_compression.NONE; - }; - - var initialize = function() { - if (!this.internal.collections[namespace + "images"]) { - this.internal.collections[namespace + "images"] = {}; - this.internal.events.subscribe("putResources", putResourcesCallback); - this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback); - } - }; - - var getImages = function() { - var images = this.internal.collections[namespace + "images"]; - initialize.call(this); - return images; - }; - var getImageIndex = function() { - return Object.keys(this.internal.collections[namespace + "images"]).length; - }; - var notDefined = function(value) { - return typeof value === "undefined" || value === null || value.length === 0; - }; - var generateAliasFromImageData = function(imageData) { - if (typeof imageData === "string" || isArrayBufferView(imageData)) { - return sHashCode(imageData); - } else if (isArrayBufferView(imageData.data)) { - return sHashCode(imageData.data); - } - - return null; - }; - - var isImageTypeSupported = function(type) { - return typeof jsPDFAPI["process" + type.toUpperCase()] === "function"; - }; - - var isDOMElement = function(object) { - return typeof object === "object" && object.nodeType === 1; - }; - - var getImageDataFromElement = function(element, format) { - //if element is an image which uses data url definition, just return the dataurl - if (element.nodeName === "IMG" && element.hasAttribute("src")) { - var src = "" + element.getAttribute("src"); - - //is base64 encoded dataUrl, directly process it - if (src.indexOf("data:image/") === 0) { - return atob( - unescape(src) - .split("base64,") - .pop() - ); - } - - //it is probably an url, try to load it - var tmpImageData = jsPDFAPI.loadFile(src, true); - if (tmpImageData !== undefined) { - return tmpImageData; - } - } - - if (element.nodeName === "CANVAS") { - if (element.width === 0 || element.height === 0) { - throw new Error( - "Given canvas must have data. Canvas width: " + - element.width + - ", height: " + - element.height - ); - } - var mimeType; - switch (format) { - case "PNG": - mimeType = "image/png"; - break; - case "WEBP": - mimeType = "image/webp"; - break; - case "JPEG": - case "JPG": - default: - mimeType = "image/jpeg"; - break; - } - return atob( - element - .toDataURL(mimeType, 1.0) - .split("base64,") - .pop() - ); - } - }; - - var checkImagesForAlias = function(alias) { - var images = this.internal.collections[namespace + "images"]; - if (images) { - for (var e in images) { - if (alias === images[e].alias) { - return images[e]; - } - } - } - }; - - var determineWidthAndHeight = function(width, height, image) { - if (!width && !height) { - width = -96; - height = -96; - } - if (width < 0) { - width = (-1 * image.width * 72) / width / this.internal.scaleFactor; - } - if (height < 0) { - height = (-1 * image.height * 72) / height / this.internal.scaleFactor; - } - if (width === 0) { - width = (height * image.width) / image.height; - } - if (height === 0) { - height = (width * image.height) / image.width; - } - - return [width, height]; - }; - - var writeImageToPDF = function(x, y, width, height, image, rotation) { - var dims = determineWidthAndHeight.call(this, width, height, image), - coord = this.internal.getCoordinateString, - vcoord = this.internal.getVerticalCoordinateString; - - var images = getImages.call(this); - - width = dims[0]; - height = dims[1]; - images[image.index] = image; - - if (rotation) { - rotation *= Math.PI / 180; - var c = Math.cos(rotation); - var s = Math.sin(rotation); - //like in pdf Reference do it 4 digits instead of 2 - var f4 = function(number) { - return number.toFixed(4); - }; - var rotationTransformationMatrix = [ - f4(c), - f4(s), - f4(s * -1), - f4(c), - 0, - 0, - "cm" - ]; - } - this.internal.write("q"); //Save graphics state - if (rotation) { - this.internal.write( - [1, "0", "0", 1, coord(x), vcoord(y + height), "cm"].join(" ") - ); //Translate - this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate - this.internal.write( - [coord(width), "0", "0", coord(height), "0", "0", "cm"].join(" ") - ); //Scale - } else { - this.internal.write( - [ - coord(width), - "0", - "0", - coord(height), - coord(x), - vcoord(y + height), - "cm" - ].join(" ") - ); //Translate and Scale - } - - if (this.isAdvancedAPI()) { - // draw image bottom up when in "advanced" API mode - this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" ")); - } - - this.internal.write("/I" + image.index + " Do"); //Paint Image - this.internal.write("Q"); //Restore graphics state - }; - - /** - * COLOR SPACES - */ - var color_spaces = (jsPDFAPI.color_spaces = { - DEVICE_RGB: "DeviceRGB", - DEVICE_GRAY: "DeviceGray", - DEVICE_CMYK: "DeviceCMYK", - CAL_GREY: "CalGray", - CAL_RGB: "CalRGB", - LAB: "Lab", - ICC_BASED: "ICCBased", - INDEXED: "Indexed", - PATTERN: "Pattern", - SEPARATION: "Separation", - DEVICE_N: "DeviceN" - }); - - /** - * DECODE METHODS - */ - jsPDFAPI.decode = { - DCT_DECODE: "DCTDecode", - FLATE_DECODE: "FlateDecode", - LZW_DECODE: "LZWDecode", - JPX_DECODE: "JPXDecode", - JBIG2_DECODE: "JBIG2Decode", - ASCII85_DECODE: "ASCII85Decode", - ASCII_HEX_DECODE: "ASCIIHexDecode", - RUN_LENGTH_DECODE: "RunLengthDecode", - CCITT_FAX_DECODE: "CCITTFaxDecode" - }; - - /** - * IMAGE COMPRESSION TYPES - */ - var image_compression = (jsPDFAPI.image_compression = { - NONE: "NONE", - FAST: "FAST", - MEDIUM: "MEDIUM", - SLOW: "SLOW" - }); - - /** - * @name sHashCode - * @function - * @param {string} data - * @returns {string} - */ - var sHashCode = (jsPDFAPI.__addimage__.sHashCode = function(data) { - var hash = 0, - i, - len; - - if (typeof data === "string") { - len = data.length; - for (i = 0; i < len; i++) { - hash = (hash << 5) - hash + data.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - } else if (isArrayBufferView(data)) { - len = data.byteLength / 2; - for (i = 0; i < len; i++) { - hash = (hash << 5) - hash + data[i]; - hash |= 0; // Convert to 32bit integer - } - } - return hash; - }); - - /** - * Validates if given String is a valid Base64-String - * - * @name validateStringAsBase64 - * @public - * @function - * @param {String} possible Base64-String - * - * @returns {boolean} - */ - var validateStringAsBase64 = (jsPDFAPI.__addimage__.validateStringAsBase64 = function( - possibleBase64String - ) { - possibleBase64String = possibleBase64String || ""; - possibleBase64String.toString().trim(); - - var result = true; - - if (possibleBase64String.length === 0) { - result = false; - } - - if (possibleBase64String.length % 4 !== 0) { - result = false; - } - - if ( - /^[A-Za-z0-9+/]+$/.test( - possibleBase64String.substr(0, possibleBase64String.length - 2) - ) === false - ) { - result = false; - } - - if ( - /^[A-Za-z0-9/][A-Za-z0-9+/]|[A-Za-z0-9+/]=|==$/.test( - possibleBase64String.substr(-2) - ) === false - ) { - result = false; - } - return result; - }); - - /** - * Strips out and returns info from a valid base64 data URI - * - * @name extractImageFromDataUrl - * @function - * @param {string} dataUrl a valid data URI of format 'data:[][;base64],' - * @returns {string} The raw Base64-encoded data. - */ - var extractImageFromDataUrl = (jsPDFAPI.__addimage__.extractImageFromDataUrl = function( - dataUrl - ) { - if (dataUrl == null) { - return null; - } - - // avoid using a regexp for parsing because it might be vulnerable against ReDoS attacks - - dataUrl = dataUrl.trim(); - - if (!dataUrl.startsWith("data:")) { - return null; - } - - const commaIndex = dataUrl.indexOf(","); - if (commaIndex < 0) { - return null; - } - - const dataScheme = dataUrl.substring(0, commaIndex).trim(); - if (!dataScheme.endsWith("base64")) { - return null; - } - - return dataUrl.substring(commaIndex + 1); - }); - - /** - * Tests supplied object to determine if ArrayBuffer - * - * @name isArrayBuffer - * @function - * @param {Object} object an Object - * - * @returns {boolean} - */ - jsPDFAPI.__addimage__.isArrayBuffer = function(object) { - return object instanceof ArrayBuffer; - }; - - /** - * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface - * - * @name isArrayBufferView - * @function - * @param {Object} object an Object - * @returns {boolean} - */ - var isArrayBufferView = (jsPDFAPI.__addimage__.isArrayBufferView = function( - object - ) { - return ( - object instanceof Int8Array || - object instanceof Uint8Array || - object instanceof Uint8ClampedArray || - object instanceof Int16Array || - object instanceof Uint16Array || - object instanceof Int32Array || - object instanceof Uint32Array || - object instanceof Float32Array || - object instanceof Float64Array - ); - }); - - /** - * Convert Binary String to ArrayBuffer - * - * @name binaryStringToUint8Array - * @public - * @function - * @param {string} BinaryString with ImageData - * @returns {Uint8Array} - */ - var binaryStringToUint8Array = (jsPDFAPI.__addimage__.binaryStringToUint8Array = function( - binary_string - ) { - var len = binary_string.length; - var bytes = new Uint8Array(len); - for (var i = 0; i < len; i++) { - bytes[i] = binary_string.charCodeAt(i); - } - return bytes; - }); - - /** - * Convert the Buffer to a Binary String - * - * @name arrayBufferToBinaryString - * @public - * @function - * @param {ArrayBuffer|ArrayBufferView} ArrayBuffer buffer or bufferView with ImageData - * - * @returns {String} - */ - var arrayBufferToBinaryString = (jsPDFAPI.__addimage__.arrayBufferToBinaryString = function( - buffer - ) { - var out = ""; - // There are calls with both ArrayBuffer and already converted Uint8Array or other BufferView. - // Do not copy the array if input is already an array. - var buf = isArrayBufferView(buffer) ? buffer : new Uint8Array(buffer); - for (var i = 0; i < buf.length; i += ARRAY_APPLY_BATCH) { - // Limit the amount of characters being parsed to prevent overflow. - // Note that while TextDecoder would be faster, it does not have the same - // functionality as fromCharCode with any provided encodings as of 3/2021. - out += String.fromCharCode.apply( - null, - buf.subarray(i, i + ARRAY_APPLY_BATCH) - ); - } - return out; - }); - - /** - * Possible parameter for addImage, an RGBA buffer with size. - * - * @typedef {Object} RGBAData - * @property {Uint8ClampedArray} data - Single dimensional array of RGBA values. For example from canvas getImageData. - * @property {number} width - Image width as the data does not carry this information in itself. - * @property {number} height - Image height as the data does not carry this information in itself. - */ - - /** - * Adds an Image to the PDF. - * - * @name addImage - * @public - * @function - * @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array|RGBAData} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement or object containing RGBA array (like output from canvas.getImageData). - * @param {string} format format of file if filetype-recognition fails or in case of a Canvas-Element needs to be specified (default for Canvas is JPEG), e.g. 'JPEG', 'PNG', 'WEBP' - * @param {number} x x Coordinate (in units declared at inception of PDF document) against left edge of the page - * @param {number} y y Coordinate (in units declared at inception of PDF document) against upper edge of the page - * @param {number} width width of the image (in units declared at inception of PDF document) - * @param {number} height height of the Image (in units declared at inception of PDF document) - * @param {string} alias alias of the image (if used multiple times) - * @param {string} compression compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW' - * @param {number} rotation rotation of the image in degrees (0-359) - * - * @throws {Error} if the input is invalid, such as invalid image data. - * - * @returns jsPDF - */ - jsPDFAPI.addImage = function() { - var imageData, format, x, y, w, h, alias, compression, rotation; - - imageData = arguments[0]; - if (typeof arguments[1] === "number") { - format = UNKNOWN; - x = arguments[1]; - y = arguments[2]; - w = arguments[3]; - h = arguments[4]; - alias = arguments[5]; - compression = arguments[6]; - rotation = arguments[7]; - } else { - format = arguments[1]; - x = arguments[2]; - y = arguments[3]; - w = arguments[4]; - h = arguments[5]; - alias = arguments[6]; - compression = arguments[7]; - rotation = arguments[8]; - } - - if ( - typeof imageData === "object" && - !isDOMElement(imageData) && - "imageData" in imageData - ) { - var options = imageData; - - imageData = options.imageData; - format = options.format || format || UNKNOWN; - x = options.x || x || 0; - y = options.y || y || 0; - w = options.w || options.width || w; - h = options.h || options.height || h; - alias = options.alias || alias; - compression = options.compression || compression; - rotation = options.rotation || options.angle || rotation; - } - - //If compression is not explicitly set, determine if we should use compression - var filter = this.internal.getFilters(); - if (compression === undefined && filter.indexOf("FlateEncode") !== -1) { - compression = "SLOW"; - } - - if (isNaN(x) || isNaN(y)) { - throw new Error("Invalid coordinates passed to jsPDF.addImage"); - } - - initialize.call(this); - - var image = processImageData.call( - this, - imageData, - format, - alias, - compression - ); - - writeImageToPDF.call(this, x, y, w, h, image, rotation); - - return this; - }; - - var processImageData = function(imageData, format, alias, compression) { - var result, dataAsBinaryString; - - if ( - typeof imageData === "string" && - getImageFileTypeByImageData(imageData) === UNKNOWN - ) { - imageData = unescape(imageData); - var tmpImageData = convertBase64ToBinaryString(imageData, false); - - if (tmpImageData !== "") { - imageData = tmpImageData; - } else { - tmpImageData = jsPDFAPI.loadFile(imageData, true); - if (tmpImageData !== undefined) { - imageData = tmpImageData; - } - } - } - - if (isDOMElement(imageData)) { - imageData = getImageDataFromElement(imageData, format); - } - - format = getImageFileTypeByImageData(imageData, format); - if (!isImageTypeSupported(format)) { - throw new Error( - "addImage does not support files of type '" + - format + - "', please ensure that a plugin for '" + - format + - "' support is added." - ); - } - - // now do the heavy lifting - - if (notDefined(alias)) { - alias = generateAliasFromImageData(imageData); - } - result = checkImagesForAlias.call(this, alias); - - if (!result) { - // no need to convert if imageData is already uint8array - if (!(imageData instanceof Uint8Array) && format !== "RGBA") { - dataAsBinaryString = imageData; - imageData = binaryStringToUint8Array(imageData); - } - - result = this["process" + format.toUpperCase()]( - imageData, - getImageIndex.call(this), - alias, - checkCompressValue(compression), - dataAsBinaryString - ); - } - - if (!result) { - throw new Error("An unknown error occurred whilst processing the image."); - } - return result; - }; - - /** - * @name convertBase64ToBinaryString - * @function - * @param {string} stringData - * @returns {string} binary string - */ - var convertBase64ToBinaryString = (jsPDFAPI.__addimage__.convertBase64ToBinaryString = function( - stringData, - throwError - ) { - throwError = typeof throwError === "boolean" ? throwError : true; - var imageData = ""; - var rawData; - - if (typeof stringData === "string") { - rawData = extractImageFromDataUrl(stringData) ?? stringData; - - try { - imageData = atob(rawData); - } catch (e) { - if (throwError) { - if (!validateStringAsBase64(rawData)) { - throw new Error( - "Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString " - ); - } else { - throw new Error( - "atob-Error in jsPDF.convertBase64ToBinaryString " + e.message - ); - } - } - } - } - return imageData; - }); - - /** - * @name getImageProperties - * @function - * @param {Object} imageData - * @returns {Object} - */ - jsPDFAPI.getImageProperties = function(imageData) { - var image; - var tmpImageData = ""; - var format; - - if (isDOMElement(imageData)) { - imageData = getImageDataFromElement(imageData); - } - - if ( - typeof imageData === "string" && - getImageFileTypeByImageData(imageData) === UNKNOWN - ) { - tmpImageData = convertBase64ToBinaryString(imageData, false); - - if (tmpImageData === "") { - tmpImageData = jsPDFAPI.loadFile(imageData) || ""; - } - imageData = tmpImageData; - } - - format = getImageFileTypeByImageData(imageData); - if (!isImageTypeSupported(format)) { - throw new Error( - "addImage does not support files of type '" + - format + - "', please ensure that a plugin for '" + - format + - "' support is added." - ); - } - - if (!(imageData instanceof Uint8Array)) { - imageData = binaryStringToUint8Array(imageData); - } - - image = this["process" + format.toUpperCase()](imageData); - - if (!image) { - throw new Error("An unknown error occurred whilst processing the image"); - } - - image.fileType = format; - - return image; - }; -})(jsPDF.API); - -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -(function(jsPDFAPI) { - - var notEmpty = function(obj) { - if (typeof obj != "undefined") { - if (obj != "") { - return true; - } - } - }; - - jsPDF.API.events.push([ - "addPage", - function(addPageData) { - var pageInfo = this.internal.getPageInfo(addPageData.pageNumber); - pageInfo.pageContext.annotations = []; - } - ]); - - jsPDFAPI.events.push([ - "putPage", - function(putPageData) { - var getHorizontalCoordinateString = this.internal.getCoordinateString; - var getVerticalCoordinateString = this.internal - .getVerticalCoordinateString; - var pageInfo = this.internal.getPageInfoByObjId(putPageData.objId); - var pageAnnos = putPageData.pageContext.annotations; - - var anno, rect, line; - var found = false; - for (var a = 0; a < pageAnnos.length && !found; a++) { - anno = pageAnnos[a]; - switch (anno.type) { - case "link": - if ( - notEmpty(anno.options.url) || - notEmpty(anno.options.pageNumber) - ) { - found = true; - } - break; - case "reference": - case "text": - case "freetext": - found = true; - break; - } - } - if (found == false) { - return; - } - - this.internal.write("/Annots ["); - for (var i = 0; i < pageAnnos.length; i++) { - anno = pageAnnos[i]; - var escape = this.internal.pdfEscape; - var encryptor = this.internal.getEncryptor(putPageData.objId); - - switch (anno.type) { - case "reference": - // References to Widget Annotations (for AcroForm Fields) - this.internal.write(" " + anno.object.objId + " 0 R "); - break; - case "text": - // Create a an object for both the text and the popup - var objText = this.internal.newAdditionalObject(); - var objPopup = this.internal.newAdditionalObject(); - var encryptorText = this.internal.getEncryptor(objText.objId); - - var title = anno.title || "Note"; - rect = - "/Rect [" + - getHorizontalCoordinateString(anno.bounds.x) + - " " + - getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + - " " + - getHorizontalCoordinateString(anno.bounds.x + anno.bounds.w) + - " " + - getVerticalCoordinateString(anno.bounds.y) + - "] "; - - line = - "<>"; - objText.content = line; - - var parent = objText.objId + " 0 R"; - var popoff = 30; - rect = - "/Rect [" + - getHorizontalCoordinateString(anno.bounds.x + popoff) + - " " + - getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) + - " " + - getHorizontalCoordinateString( - anno.bounds.x + anno.bounds.w + popoff - ) + - " " + - getVerticalCoordinateString(anno.bounds.y) + - "] "; - line = - "<>"; - } else if (anno.options.pageNumber) { - // first page is 0 - var info = this.internal.getPageInfo(anno.options.pageNumber); - line = - "< pageNumber or url [required] - *

If pageNumber is specified, top and zoom may also be specified

- * @name link - * @function - * @param {number} x - * @param {number} y - * @param {number} w - * @param {number} h - * @param {Object} options - */ - jsPDFAPI.link = function(x, y, w, h, options) { - var pageInfo = this.internal.getCurrentPageInfo(); - var getHorizontalCoordinateString = this.internal.getCoordinateString; - var getVerticalCoordinateString = this.internal.getVerticalCoordinateString; - - pageInfo.pageContext.annotations.push({ - finalBounds: { - x: getHorizontalCoordinateString(x), - y: getVerticalCoordinateString(y), - w: getHorizontalCoordinateString(x + w), - h: getVerticalCoordinateString(y + h) - }, - options: options, - type: "link" - }); - }; - - /** - * Currently only supports single line text. - * Returns the width of the text/link - * - * @name textWithLink - * @function - * @param {string} text - * @param {number} x - * @param {number} y - * @param {Object} options - * @returns {number} width the width of the text/link - */ - jsPDFAPI.textWithLink = function(text, x, y, options) { - var totalLineWidth = this.getTextWidth(text); - var lineHeight = this.internal.getLineHeight() / this.internal.scaleFactor; - var linkHeight, linkWidth; - - // Checking if maxWidth option is passed to determine lineWidth and number of lines for each line - if (options.maxWidth !== undefined) { - var { maxWidth } = options; - linkWidth = maxWidth; - var numOfLines = this.splitTextToSize(text, linkWidth).length; - linkHeight = Math.ceil(lineHeight * numOfLines); - } else { - linkWidth = totalLineWidth; - linkHeight = lineHeight; - } - - this.text(text, x, y, options); - - //TODO We really need the text baseline height to do this correctly. - // Or ability to draw text on top, bottom, center, or baseline. - y += lineHeight * 0.2; - //handle x position based on the align option - if (options.align === "center") { - x = x - totalLineWidth / 2; //since starting from center move the x position by half of text width - } - if (options.align === "right") { - x = x - totalLineWidth; - } - this.link(x, y - lineHeight, linkWidth, linkHeight, options); - return totalLineWidth; - }; - - //TODO move into external library - /** - * @name getTextWidth - * @function - * @param {string} text - * @returns {number} txtWidth - */ - jsPDFAPI.getTextWidth = function(text) { - var fontSize = this.internal.getFontSize(); - var txtWidth = - (this.getStringUnitWidth(text) * fontSize) / this.internal.scaleFactor; - return txtWidth; - }; - - return this; -})(jsPDF.API); - -/** - * @license - * Copyright (c) 2017 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * jsPDF arabic parser PlugIn - * - * @name arabic - * @module - */ -(function(jsPDFAPI) { - - /** - * Arabic shape substitutions: char code => (isolated, final, initial, medial). - * Arabic Substition A - */ - var arabicSubstitionA = { - 0x0621: [0xfe80], // ARABIC LETTER HAMZA - 0x0622: [0xfe81, 0xfe82], // ARABIC LETTER ALEF WITH MADDA ABOVE - 0x0623: [0xfe83, 0xfe84], // ARABIC LETTER ALEF WITH HAMZA ABOVE - 0x0624: [0xfe85, 0xfe86], // ARABIC LETTER WAW WITH HAMZA ABOVE - 0x0625: [0xfe87, 0xfe88], // ARABIC LETTER ALEF WITH HAMZA BELOW - 0x0626: [0xfe89, 0xfe8a, 0xfe8b, 0xfe8c], // ARABIC LETTER YEH WITH HAMZA ABOVE - 0x0627: [0xfe8d, 0xfe8e], // ARABIC LETTER ALEF - 0x0628: [0xfe8f, 0xfe90, 0xfe91, 0xfe92], // ARABIC LETTER BEH - 0x0629: [0xfe93, 0xfe94], // ARABIC LETTER TEH MARBUTA - 0x062a: [0xfe95, 0xfe96, 0xfe97, 0xfe98], // ARABIC LETTER TEH - 0x062b: [0xfe99, 0xfe9a, 0xfe9b, 0xfe9c], // ARABIC LETTER THEH - 0x062c: [0xfe9d, 0xfe9e, 0xfe9f, 0xfea0], // ARABIC LETTER JEEM - 0x062d: [0xfea1, 0xfea2, 0xfea3, 0xfea4], // ARABIC LETTER HAH - 0x062e: [0xfea5, 0xfea6, 0xfea7, 0xfea8], // ARABIC LETTER KHAH - 0x062f: [0xfea9, 0xfeaa], // ARABIC LETTER DAL - 0x0630: [0xfeab, 0xfeac], // ARABIC LETTER THAL - 0x0631: [0xfead, 0xfeae], // ARABIC LETTER REH - 0x0632: [0xfeaf, 0xfeb0], // ARABIC LETTER ZAIN - 0x0633: [0xfeb1, 0xfeb2, 0xfeb3, 0xfeb4], // ARABIC LETTER SEEN - 0x0634: [0xfeb5, 0xfeb6, 0xfeb7, 0xfeb8], // ARABIC LETTER SHEEN - 0x0635: [0xfeb9, 0xfeba, 0xfebb, 0xfebc], // ARABIC LETTER SAD - 0x0636: [0xfebd, 0xfebe, 0xfebf, 0xfec0], // ARABIC LETTER DAD - 0x0637: [0xfec1, 0xfec2, 0xfec3, 0xfec4], // ARABIC LETTER TAH - 0x0638: [0xfec5, 0xfec6, 0xfec7, 0xfec8], // ARABIC LETTER ZAH - 0x0639: [0xfec9, 0xfeca, 0xfecb, 0xfecc], // ARABIC LETTER AIN - 0x063a: [0xfecd, 0xfece, 0xfecf, 0xfed0], // ARABIC LETTER GHAIN - 0x0641: [0xfed1, 0xfed2, 0xfed3, 0xfed4], // ARABIC LETTER FEH - 0x0642: [0xfed5, 0xfed6, 0xfed7, 0xfed8], // ARABIC LETTER QAF - 0x0643: [0xfed9, 0xfeda, 0xfedb, 0xfedc], // ARABIC LETTER KAF - 0x0644: [0xfedd, 0xfede, 0xfedf, 0xfee0], // ARABIC LETTER LAM - 0x0645: [0xfee1, 0xfee2, 0xfee3, 0xfee4], // ARABIC LETTER MEEM - 0x0646: [0xfee5, 0xfee6, 0xfee7, 0xfee8], // ARABIC LETTER NOON - 0x0647: [0xfee9, 0xfeea, 0xfeeb, 0xfeec], // ARABIC LETTER HEH - 0x0648: [0xfeed, 0xfeee], // ARABIC LETTER WAW - 0x0649: [0xfeef, 0xfef0, 64488, 64489], // ARABIC LETTER ALEF MAKSURA - 0x064a: [0xfef1, 0xfef2, 0xfef3, 0xfef4], // ARABIC LETTER YEH - 0x0671: [0xfb50, 0xfb51], // ARABIC LETTER ALEF WASLA - 0x0677: [0xfbdd], // ARABIC LETTER U WITH HAMZA ABOVE - 0x0679: [0xfb66, 0xfb67, 0xfb68, 0xfb69], // ARABIC LETTER TTEH - 0x067a: [0xfb5e, 0xfb5f, 0xfb60, 0xfb61], // ARABIC LETTER TTEHEH - 0x067b: [0xfb52, 0xfb53, 0xfb54, 0xfb55], // ARABIC LETTER BEEH - 0x067e: [0xfb56, 0xfb57, 0xfb58, 0xfb59], // ARABIC LETTER PEH - 0x067f: [0xfb62, 0xfb63, 0xfb64, 0xfb65], // ARABIC LETTER TEHEH - 0x0680: [0xfb5a, 0xfb5b, 0xfb5c, 0xfb5d], // ARABIC LETTER BEHEH - 0x0683: [0xfb76, 0xfb77, 0xfb78, 0xfb79], // ARABIC LETTER NYEH - 0x0684: [0xfb72, 0xfb73, 0xfb74, 0xfb75], // ARABIC LETTER DYEH - 0x0686: [0xfb7a, 0xfb7b, 0xfb7c, 0xfb7d], // ARABIC LETTER TCHEH - 0x0687: [0xfb7e, 0xfb7f, 0xfb80, 0xfb81], // ARABIC LETTER TCHEHEH - 0x0688: [0xfb88, 0xfb89], // ARABIC LETTER DDAL - 0x068c: [0xfb84, 0xfb85], // ARABIC LETTER DAHAL - 0x068d: [0xfb82, 0xfb83], // ARABIC LETTER DDAHAL - 0x068e: [0xfb86, 0xfb87], // ARABIC LETTER DUL - 0x0691: [0xfb8c, 0xfb8d], // ARABIC LETTER RREH - 0x0698: [0xfb8a, 0xfb8b], // ARABIC LETTER JEH - 0x06a4: [0xfb6a, 0xfb6b, 0xfb6c, 0xfb6d], // ARABIC LETTER VEH - 0x06a6: [0xfb6e, 0xfb6f, 0xfb70, 0xfb71], // ARABIC LETTER PEHEH - 0x06a9: [0xfb8e, 0xfb8f, 0xfb90, 0xfb91], // ARABIC LETTER KEHEH - 0x06ad: [0xfbd3, 0xfbd4, 0xfbd5, 0xfbd6], // ARABIC LETTER NG - 0x06af: [0xfb92, 0xfb93, 0xfb94, 0xfb95], // ARABIC LETTER GAF - 0x06b1: [0xfb9a, 0xfb9b, 0xfb9c, 0xfb9d], // ARABIC LETTER NGOEH - 0x06b3: [0xfb96, 0xfb97, 0xfb98, 0xfb99], // ARABIC LETTER GUEH - 0x06ba: [0xfb9e, 0xfb9f], // ARABIC LETTER NOON GHUNNA - 0x06bb: [0xfba0, 0xfba1, 0xfba2, 0xfba3], // ARABIC LETTER RNOON - 0x06be: [0xfbaa, 0xfbab, 0xfbac, 0xfbad], // ARABIC LETTER HEH DOACHASHMEE - 0x06c0: [0xfba4, 0xfba5], // ARABIC LETTER HEH WITH YEH ABOVE - 0x06c1: [0xfba6, 0xfba7, 0xfba8, 0xfba9], // ARABIC LETTER HEH GOAL - 0x06c5: [0xfbe0, 0xfbe1], // ARABIC LETTER KIRGHIZ OE - 0x06c6: [0xfbd9, 0xfbda], // ARABIC LETTER OE - 0x06c7: [0xfbd7, 0xfbd8], // ARABIC LETTER U - 0x06c8: [0xfbdb, 0xfbdc], // ARABIC LETTER YU - 0x06c9: [0xfbe2, 0xfbe3], // ARABIC LETTER KIRGHIZ YU - 0x06cb: [0xfbde, 0xfbdf], // ARABIC LETTER VE - 0x06cc: [0xfbfc, 0xfbfd, 0xfbfe, 0xfbff], // ARABIC LETTER FARSI YEH - 0x06d0: [0xfbe4, 0xfbe5, 0xfbe6, 0xfbe7], //ARABIC LETTER E - 0x06d2: [0xfbae, 0xfbaf], // ARABIC LETTER YEH BARREE - 0x06d3: [0xfbb0, 0xfbb1] // ARABIC LETTER YEH BARREE WITH HAMZA ABOVE - }; - - /* - var ligaturesSubstitutionA = { - 0xFBEA: []// ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF ISOLATED FORM - }; - */ - - var ligatures = { - 0xfedf: { - 0xfe82: 0xfef5, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM - 0xfe84: 0xfef7, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM - 0xfe88: 0xfef9, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM - 0xfe8e: 0xfefb // ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM - }, - 0xfee0: { - 0xfe82: 0xfef6, // ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM - 0xfe84: 0xfef8, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM - 0xfe88: 0xfefa, // ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM - 0xfe8e: 0xfefc // ARABIC LIGATURE LAM WITH ALEF FINAL FORM - }, - 0xfe8d: { 0xfedf: { 0xfee0: { 0xfeea: 0xfdf2 } } }, // ALLAH - 0x0651: { - 0x064c: 0xfc5e, // Shadda + Dammatan - 0x064d: 0xfc5f, // Shadda + Kasratan - 0x064e: 0xfc60, // Shadda + Fatha - 0x064f: 0xfc61, // Shadda + Damma - 0x0650: 0xfc62 // Shadda + Kasra - } - }; - - var arabic_diacritics = { - 1612: 64606, // Shadda + Dammatan - 1613: 64607, // Shadda + Kasratan - 1614: 64608, // Shadda + Fatha - 1615: 64609, // Shadda + Damma - 1616: 64610 // Shadda + Kasra - }; - - var alfletter = [1570, 1571, 1573, 1575]; - - var noChangeInForm = -1; - var isolatedForm = 0; - var finalForm = 1; - var initialForm = 2; - var medialForm = 3; - - jsPDFAPI.__arabicParser__ = {}; - - //private - var isInArabicSubstitutionA = (jsPDFAPI.__arabicParser__.isInArabicSubstitutionA = function( - letter - ) { - return typeof arabicSubstitionA[letter.charCodeAt(0)] !== "undefined"; - }); - - var isArabicLetter = (jsPDFAPI.__arabicParser__.isArabicLetter = function( - letter - ) { - return ( - typeof letter === "string" && - /^[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]+$/.test( - letter - ) - ); - }); - - var isArabicEndLetter = (jsPDFAPI.__arabicParser__.isArabicEndLetter = function( - letter - ) { - return ( - isArabicLetter(letter) && - isInArabicSubstitutionA(letter) && - arabicSubstitionA[letter.charCodeAt(0)].length <= 2 - ); - }); - - var isArabicAlfLetter = (jsPDFAPI.__arabicParser__.isArabicAlfLetter = function( - letter - ) { - return ( - isArabicLetter(letter) && alfletter.indexOf(letter.charCodeAt(0)) >= 0 - ); - }); - - jsPDFAPI.__arabicParser__.arabicLetterHasIsolatedForm = function(letter) { - return ( - isArabicLetter(letter) && - isInArabicSubstitutionA(letter) && - arabicSubstitionA[letter.charCodeAt(0)].length >= 1 - ); - }; - - var arabicLetterHasFinalForm = (jsPDFAPI.__arabicParser__.arabicLetterHasFinalForm = function( - letter - ) { - return ( - isArabicLetter(letter) && - isInArabicSubstitutionA(letter) && - arabicSubstitionA[letter.charCodeAt(0)].length >= 2 - ); - }); - - jsPDFAPI.__arabicParser__.arabicLetterHasInitialForm = function(letter) { - return ( - isArabicLetter(letter) && - isInArabicSubstitutionA(letter) && - arabicSubstitionA[letter.charCodeAt(0)].length >= 3 - ); - }; - - var arabicLetterHasMedialForm = (jsPDFAPI.__arabicParser__.arabicLetterHasMedialForm = function( - letter - ) { - return ( - isArabicLetter(letter) && - isInArabicSubstitutionA(letter) && - arabicSubstitionA[letter.charCodeAt(0)].length == 4 - ); - }); - - var resolveLigatures = (jsPDFAPI.__arabicParser__.resolveLigatures = function( - letters - ) { - var i = 0; - var tmpLigatures = ligatures; - var result = ""; - var effectedLetters = 0; - - for (i = 0; i < letters.length; i += 1) { - if (typeof tmpLigatures[letters.charCodeAt(i)] !== "undefined") { - effectedLetters++; - tmpLigatures = tmpLigatures[letters.charCodeAt(i)]; - - if (typeof tmpLigatures === "number") { - result += String.fromCharCode(tmpLigatures); - tmpLigatures = ligatures; - effectedLetters = 0; - } - if (i === letters.length - 1) { - tmpLigatures = ligatures; - result += letters.charAt(i - (effectedLetters - 1)); - i = i - (effectedLetters - 1); - effectedLetters = 0; - } - } else { - tmpLigatures = ligatures; - result += letters.charAt(i - effectedLetters); - i = i - effectedLetters; - effectedLetters = 0; - } - } - - return result; - }); - - jsPDFAPI.__arabicParser__.isArabicDiacritic = function(letter) { - return ( - letter !== undefined && - arabic_diacritics[letter.charCodeAt(0)] !== undefined - ); - }; - - var getCorrectForm = (jsPDFAPI.__arabicParser__.getCorrectForm = function( - currentChar, - beforeChar, - nextChar - ) { - if (!isArabicLetter(currentChar)) { - return -1; - } - - if (isInArabicSubstitutionA(currentChar) === false) { - return noChangeInForm; - } - if ( - !arabicLetterHasFinalForm(currentChar) || - (!isArabicLetter(beforeChar) && !isArabicLetter(nextChar)) || - (!isArabicLetter(nextChar) && isArabicEndLetter(beforeChar)) || - (isArabicEndLetter(currentChar) && !isArabicLetter(beforeChar)) || - (isArabicEndLetter(currentChar) && isArabicAlfLetter(beforeChar)) || - (isArabicEndLetter(currentChar) && isArabicEndLetter(beforeChar)) - ) { - return isolatedForm; - } - - if ( - arabicLetterHasMedialForm(currentChar) && - isArabicLetter(beforeChar) && - !isArabicEndLetter(beforeChar) && - isArabicLetter(nextChar) && - arabicLetterHasFinalForm(nextChar) - ) { - return medialForm; - } - - if (isArabicEndLetter(currentChar) || !isArabicLetter(nextChar)) { - return finalForm; - } - return initialForm; - }); - - /** - * @name processArabic - * @function - * @param {string} text - * @returns {string} - */ - var parseArabic = function(text) { - text = text || ""; - - var result = ""; - var i = 0; - var j = 0; - var position = 0; - var currentLetter = ""; - var prevLetter = ""; - var nextLetter = ""; - - var words = text.split("\\s+"); - var newWords = []; - for (i = 0; i < words.length; i += 1) { - newWords.push(""); - for (j = 0; j < words[i].length; j += 1) { - currentLetter = words[i][j]; - prevLetter = words[i][j - 1]; - nextLetter = words[i][j + 1]; - if (isArabicLetter(currentLetter)) { - position = getCorrectForm(currentLetter, prevLetter, nextLetter); - if (position !== -1) { - newWords[i] += String.fromCharCode( - arabicSubstitionA[currentLetter.charCodeAt(0)][position] - ); - } else { - newWords[i] += currentLetter; - } - } else { - newWords[i] += currentLetter; - } - } - - newWords[i] = resolveLigatures(newWords[i]); - } - result = newWords.join(" "); - - return result; - }; - - var processArabic = (jsPDFAPI.__arabicParser__.processArabic = jsPDFAPI.processArabic = function() { - var text = - typeof arguments[0] === "string" ? arguments[0] : arguments[0].text; - var tmpText = []; - var result; - - if (Array.isArray(text)) { - var i = 0; - tmpText = []; - for (i = 0; i < text.length; i += 1) { - if (Array.isArray(text[i])) { - tmpText.push([parseArabic(text[i][0]), text[i][1], text[i][2]]); - } else { - tmpText.push([parseArabic(text[i])]); - } - } - result = tmpText; - } else { - result = parseArabic(text); - } - if (typeof arguments[0] === "string") { - return result; - } else { - arguments[0].text = result; - return arguments[0]; - } - }); - - jsPDFAPI.events.push(["preProcessText", processArabic]); -})(jsPDF.API); - -/** @license - * jsPDF Autoprint Plugin - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * @name autoprint - * @module - */ -(function(jsPDFAPI) { - - /** - * Makes the PDF automatically open the print-Dialog when opened in a PDF-viewer. - * - * @name autoPrint - * @function - * @param {Object} options (optional) Set the attribute variant to 'non-conform' (default) or 'javascript' to activate different methods of automatic printing when opening in a PDF-viewer . - * @returns {jsPDF} - * @example - * var doc = new jsPDF(); - * doc.text(10, 10, 'This is a test'); - * doc.autoPrint({variant: 'non-conform'}); - * doc.save('autoprint.pdf'); - */ - jsPDFAPI.autoPrint = function(options) { - var refAutoPrintTag; - options = options || {}; - options.variant = options.variant || "non-conform"; - - switch (options.variant) { - case "javascript": - //https://github.com/Rob--W/pdf.js/commit/c676ecb5a0f54677b9f3340c3ef2cf42225453bb - this.addJS("print({});"); - break; - case "non-conform": - default: - this.internal.events.subscribe("postPutResources", function() { - refAutoPrintTag = this.internal.newObject(); - this.internal.out("<<"); - this.internal.out("/S /Named"); - this.internal.out("/Type /Action"); - this.internal.out("/N /Print"); - this.internal.out(">>"); - this.internal.out("endobj"); - }); - - this.internal.events.subscribe("putCatalog", function() { - this.internal.out("/OpenAction " + refAutoPrintTag + " 0 R"); - }); - break; - } - return this; - }; -})(jsPDF.API); - -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * jsPDF Canvas PlugIn - * This plugin mimics the HTML5 Canvas - * - * The goal is to provide a way for current canvas users to print directly to a PDF. - * @name canvas - * @module - */ -(function(jsPDFAPI) { - - /** - * @class Canvas - * @classdesc A Canvas Wrapper for jsPDF - */ - var Canvas = function() { - var jsPdfInstance = undefined; - Object.defineProperty(this, "pdf", { - get: function() { - return jsPdfInstance; - }, - set: function(value) { - jsPdfInstance = value; - } - }); - - var _width = 150; - /** - * The height property is a positive integer reflecting the height HTML attribute of the element interpreted in CSS pixels. When the attribute is not specified, or if it is set to an invalid value, like a negative, the default value of 150 is used. - * This is one of the two properties, the other being width, that controls the size of the canvas. - * - * @name width - */ - Object.defineProperty(this, "width", { - get: function() { - return _width; - }, - set: function(value) { - if (isNaN(value) || Number.isInteger(value) === false || value < 0) { - _width = 150; - } else { - _width = value; - } - if (this.getContext("2d").pageWrapXEnabled) { - this.getContext("2d").pageWrapX = _width + 1; - } - } - }); - - var _height = 300; - /** - * The width property is a positive integer reflecting the width HTML attribute of the element interpreted in CSS pixels. When the attribute is not specified, or if it is set to an invalid value, like a negative, the default value of 300 is used. - * This is one of the two properties, the other being height, that controls the size of the canvas. - * - * @name height - */ - Object.defineProperty(this, "height", { - get: function() { - return _height; - }, - set: function(value) { - if (isNaN(value) || Number.isInteger(value) === false || value < 0) { - _height = 300; - } else { - _height = value; - } - if (this.getContext("2d").pageWrapYEnabled) { - this.getContext("2d").pageWrapY = _height + 1; - } - } - }); - - var _childNodes = []; - Object.defineProperty(this, "childNodes", { - get: function() { - return _childNodes; - }, - set: function(value) { - _childNodes = value; - } - }); - - var _style = {}; - Object.defineProperty(this, "style", { - get: function() { - return _style; - }, - set: function(value) { - _style = value; - } - }); - - Object.defineProperty(this, "parentNode", {}); - }; - - /** - * The getContext() method returns a drawing context on the canvas, or null if the context identifier is not supported. - * - * @name getContext - * @function - * @param {string} contextType Is a String containing the context identifier defining the drawing context associated to the canvas. Possible value is "2d", leading to the creation of a Context2D object representing a two-dimensional rendering context. - * @param {object} contextAttributes - */ - Canvas.prototype.getContext = function(contextType, contextAttributes) { - contextType = contextType || "2d"; - var key; - - if (contextType !== "2d") { - return null; - } - for (key in contextAttributes) { - if (this.pdf.context2d.hasOwnProperty(key)) { - this.pdf.context2d[key] = contextAttributes[key]; - } - } - this.pdf.context2d._canvas = this; - return this.pdf.context2d; - }; - - /** - * The toDataURL() method is just a stub to throw an error if accidently called. - * - * @name toDataURL - * @function - */ - Canvas.prototype.toDataURL = function() { - throw new Error("toDataURL is not implemented."); - }; - - jsPDFAPI.events.push([ - "initialized", - function() { - this.canvas = new Canvas(); - this.canvas.pdf = this; - } - ]); - - return this; -})(jsPDF.API); - -/** - * @license - * ==================================================================== - * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com - * 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br - * 2013 Lee Driscoll, https://github.com/lsdriscoll - * 2014 Juan Pablo Gaviria, https://github.com/juanpgaviria - * 2014 James Hall, james@parall.ax - * 2014 Diego Casorran, https://github.com/diegocr - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -/** - * @name cell - * @module - */ -(function(jsPDFAPI) { - - var NO_MARGINS = { left: 0, top: 0, bottom: 0, right: 0 }; - - var px2pt = (0.264583 * 72) / 25.4; - var printingHeaderRow = false; - - var _initialize = function() { - if (typeof this.internal.__cell__ === "undefined") { - this.internal.__cell__ = {}; - this.internal.__cell__.padding = 3; - this.internal.__cell__.headerFunction = undefined; - this.internal.__cell__.margins = Object.assign({}, NO_MARGINS); - this.internal.__cell__.margins.width = this.getPageWidth(); - _reset.call(this); - } - }; - - var _reset = function() { - this.internal.__cell__.lastCell = new Cell(); - this.internal.__cell__.pages = 1; - }; - - var Cell = function() { - var _x = arguments[0]; - Object.defineProperty(this, "x", { - enumerable: true, - get: function() { - return _x; - }, - set: function(value) { - _x = value; - } - }); - var _y = arguments[1]; - Object.defineProperty(this, "y", { - enumerable: true, - get: function() { - return _y; - }, - set: function(value) { - _y = value; - } - }); - var _width = arguments[2]; - Object.defineProperty(this, "width", { - enumerable: true, - get: function() { - return _width; - }, - set: function(value) { - _width = value; - } - }); - var _height = arguments[3]; - Object.defineProperty(this, "height", { - enumerable: true, - get: function() { - return _height; - }, - set: function(value) { - _height = value; - } - }); - var _text = arguments[4]; - Object.defineProperty(this, "text", { - enumerable: true, - get: function() { - return _text; - }, - set: function(value) { - _text = value; - } - }); - var _lineNumber = arguments[5]; - Object.defineProperty(this, "lineNumber", { - enumerable: true, - get: function() { - return _lineNumber; - }, - set: function(value) { - _lineNumber = value; - } - }); - var _align = arguments[6]; - Object.defineProperty(this, "align", { - enumerable: true, - get: function() { - return _align; - }, - set: function(value) { - _align = value; - } - }); - - return this; - }; - - Cell.prototype.clone = function() { - return new Cell( - this.x, - this.y, - this.width, - this.height, - this.text, - this.lineNumber, - this.align - ); - }; - - Cell.prototype.toArray = function() { - return [ - this.x, - this.y, - this.width, - this.height, - this.text, - this.lineNumber, - this.align - ]; - }; - - /** - * @name setHeaderFunction - * @function - * @param {function} func - */ - jsPDFAPI.setHeaderFunction = function(func) { - _initialize.call(this); - this.internal.__cell__.headerFunction = - typeof func === "function" ? func : undefined; - return this; - }; - - /** - * @name getTextDimensions - * @function - * @param {string} txt - * @returns {Object} dimensions - */ - jsPDFAPI.getTextDimensions = function(text, options) { - _initialize.call(this); - options = options || {}; - var fontSize = options.fontSize || this.getFontSize(); - var font = options.font || this.getFont(); - var scaleFactor = options.scaleFactor || this.internal.scaleFactor; - var width = 0; - var amountOfLines = 0; - var height = 0; - var tempWidth = 0; - var scope = this; - - if (!Array.isArray(text) && typeof text !== "string") { - if (typeof text === "number") { - text = String(text); - } else { - throw new Error( - "getTextDimensions expects text-parameter to be of type String or type Number or an Array of Strings." - ); - } - } - - const maxWidth = options.maxWidth; - if (maxWidth > 0) { - if (typeof text === "string") { - text = this.splitTextToSize(text, maxWidth); - } else if (Object.prototype.toString.call(text) === "[object Array]") { - text = text.reduce(function(acc, textLine) { - return acc.concat(scope.splitTextToSize(textLine, maxWidth)); - }, []); - } - } else { - // Without the else clause, it will not work if you do not pass along maxWidth - text = Array.isArray(text) ? text : [text]; - } - - for (var i = 0; i < text.length; i++) { - tempWidth = this.getStringUnitWidth(text[i], { font: font }) * fontSize; - if (width < tempWidth) { - width = tempWidth; - } - } - - if (width !== 0) { - amountOfLines = text.length; - } - - width = width / scaleFactor; - height = Math.max( - (amountOfLines * fontSize * this.getLineHeightFactor() - - fontSize * (this.getLineHeightFactor() - 1)) / - scaleFactor, - 0 - ); - return { w: width, h: height }; - }; - - /** - * @name cellAddPage - * @function - */ - jsPDFAPI.cellAddPage = function() { - _initialize.call(this); - - this.addPage(); - - var margins = this.internal.__cell__.margins || NO_MARGINS; - this.internal.__cell__.lastCell = new Cell( - margins.left, - margins.top, - undefined, - undefined - ); - this.internal.__cell__.pages += 1; - - return this; - }; - - /** - * @name cell - * @function - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - * @param {string} text - * @param {number} lineNumber lineNumber - * @param {string} align - * @return {jsPDF} jsPDF-instance - */ - var cell = (jsPDFAPI.cell = function() { - var currentCell; - - if (arguments[0] instanceof Cell) { - currentCell = arguments[0]; - } else { - currentCell = new Cell( - arguments[0], - arguments[1], - arguments[2], - arguments[3], - arguments[4], - arguments[5], - arguments[6] - ); - } - _initialize.call(this); - var lastCell = this.internal.__cell__.lastCell; - var padding = this.internal.__cell__.padding; - var margins = this.internal.__cell__.margins || NO_MARGINS; - var tableHeaderRow = this.internal.__cell__.tableHeaderRow; - var printHeaders = this.internal.__cell__.printHeaders; - // If this is not the first cell, we must change its position - if (typeof lastCell.lineNumber !== "undefined") { - if (lastCell.lineNumber === currentCell.lineNumber) { - //Same line - currentCell.x = (lastCell.x || 0) + (lastCell.width || 0); - currentCell.y = lastCell.y || 0; - } else { - //New line - if ( - lastCell.y + lastCell.height + currentCell.height + margins.bottom > - this.getPageHeight() - ) { - this.cellAddPage(); - currentCell.y = margins.top; - if (printHeaders && tableHeaderRow) { - this.printHeaderRow(currentCell.lineNumber, true); - currentCell.y += tableHeaderRow[0].height; - } - } else { - currentCell.y = lastCell.y + lastCell.height || currentCell.y; - } - } - } - - if (typeof currentCell.text[0] !== "undefined") { - this.rect( - currentCell.x, - currentCell.y, - currentCell.width, - currentCell.height, - printingHeaderRow === true ? "FD" : undefined - ); - if (currentCell.align === "right") { - this.text( - currentCell.text, - currentCell.x + currentCell.width - padding, - currentCell.y + padding, - { align: "right", baseline: "top" } - ); - } else if (currentCell.align === "center") { - this.text( - currentCell.text, - currentCell.x + currentCell.width / 2, - currentCell.y + padding, - { - align: "center", - baseline: "top", - maxWidth: currentCell.width - padding - padding - } - ); - } else { - this.text( - currentCell.text, - currentCell.x + padding, - currentCell.y + padding, - { - align: "left", - baseline: "top", - maxWidth: currentCell.width - padding - padding - } - ); - } - } - this.internal.__cell__.lastCell = currentCell; - return this; - }); - - /** - * Create a table from a set of data. - * @name table - * @function - * @param {Integer} [x] : left-position for top-left corner of table - * @param {Integer} [y] top-position for top-left corner of table - * @param {Object[]} [data] An array of objects containing key-value pairs corresponding to a row of data. - * @param {String[]} [headers] Omit or null to auto-generate headers at a performance cost - - * @param {Object} [config.printHeaders] True to print column headers at the top of every page - * @param {Object} [config.autoSize] True to dynamically set the column widths to match the widest cell value - * @param {Object} [config.margins] margin values for left, top, bottom, and width - * @param {Object} [config.fontSize] Integer fontSize to use (optional) - * @param {Object} [config.padding] cell-padding in pt to use (optional) - * @param {Object} [config.headerBackgroundColor] default is #c8c8c8 (optional) - * @param {Object} [config.headerTextColor] default is #000 (optional) - * @param {Object} [config.rowStart] callback to handle before print each row (optional) - * @param {Object} [config.cellStart] callback to handle before print each cell (optional) - * @returns {jsPDF} jsPDF-instance - */ - - jsPDFAPI.table = function(x, y, data, headers, config) { - _initialize.call(this); - if (!data) { - throw new Error("No data for PDF table."); - } - - config = config || {}; - - var headerNames = [], - headerLabels = [], - headerAligns = [], - i, - columnMatrix = {}, - columnWidths = {}, - column, - columnMinWidths = [], - j, - tableHeaderConfigs = [], - //set up defaults. If a value is provided in config, defaults will be overwritten: - autoSize = config.autoSize || false, - printHeaders = config.printHeaders === false ? false : true, - fontSize = - config.css && typeof config.css["font-size"] !== "undefined" - ? config.css["font-size"] * 16 - : config.fontSize || 12, - margins = - config.margins || - Object.assign({ width: this.getPageWidth() }, NO_MARGINS), - padding = typeof config.padding === "number" ? config.padding : 3, - headerBackgroundColor = config.headerBackgroundColor || "#c8c8c8", - headerTextColor = config.headerTextColor || "#000"; - - _reset.call(this); - - this.internal.__cell__.printHeaders = printHeaders; - this.internal.__cell__.margins = margins; - this.internal.__cell__.table_font_size = fontSize; - this.internal.__cell__.padding = padding; - this.internal.__cell__.headerBackgroundColor = headerBackgroundColor; - this.internal.__cell__.headerTextColor = headerTextColor; - this.setFontSize(fontSize); - - // Set header values - if (headers === undefined || headers === null) { - // No headers defined so we derive from data - headerNames = Object.keys(data[0]); - headerLabels = headerNames; - headerAligns = headerNames.map(function() { - return "left"; - }); - } else if (Array.isArray(headers) && typeof headers[0] === "object") { - headerNames = headers.map(function(header) { - return header.name; - }); - headerLabels = headers.map(function(header) { - return header.prompt || header.name || ""; - }); - headerAligns = headers.map(function(header) { - return header.align || "left"; - }); - // Split header configs into names and prompts - for (i = 0; i < headers.length; i += 1) { - columnWidths[headers[i].name] = headers[i].width * px2pt; - } - } else if (Array.isArray(headers) && typeof headers[0] === "string") { - headerNames = headers; - headerLabels = headerNames; - headerAligns = headerNames.map(function() { - return "left"; - }); - } - - if ( - autoSize || - (Array.isArray(headers) && typeof headers[0] === "string") - ) { - var headerName; - for (i = 0; i < headerNames.length; i += 1) { - headerName = headerNames[i]; - - // Create a matrix of columns e.g., {column_title: [row1_Record, row2_Record]} - - columnMatrix[headerName] = data.map(function(rec) { - return rec[headerName]; - }); - - // get header width - this.setFont(undefined, "bold"); - columnMinWidths.push( - this.getTextDimensions(headerLabels[i], { - fontSize: this.internal.__cell__.table_font_size, - scaleFactor: this.internal.scaleFactor - }).w - ); - column = columnMatrix[headerName]; - - // get cell widths - this.setFont(undefined, "normal"); - for (j = 0; j < column.length; j += 1) { - columnMinWidths.push( - this.getTextDimensions(column[j], { - fontSize: this.internal.__cell__.table_font_size, - scaleFactor: this.internal.scaleFactor - }).w - ); - } - - // get final column width - columnWidths[headerName] = - Math.max.apply(null, columnMinWidths) + padding + padding; - - //have to reset - columnMinWidths = []; - } - } - - // -- Construct the table - - if (printHeaders) { - var row = {}; - for (i = 0; i < headerNames.length; i += 1) { - row[headerNames[i]] = {}; - row[headerNames[i]].text = headerLabels[i]; - row[headerNames[i]].align = headerAligns[i]; - } - - var rowHeight = calculateLineHeight.call(this, row, columnWidths); - - // Construct the header row - tableHeaderConfigs = headerNames.map(function(value) { - return new Cell( - x, - y, - columnWidths[value], - rowHeight, - row[value].text, - undefined, - row[value].align - ); - }); - - // Store the table header config - this.setTableHeaderRow(tableHeaderConfigs); - - // Print the header for the start of the table - this.printHeaderRow(1, false); - } - - // Construct the data rows - - var align = headers.reduce(function(pv, cv) { - pv[cv.name] = cv.align; - return pv; - }, {}); - for (i = 0; i < data.length; i += 1) { - if ("rowStart" in config && config.rowStart instanceof Function) { - config.rowStart( - { - row: i, - data: data[i] - }, - this - ); - } - var lineHeight = calculateLineHeight.call(this, data[i], columnWidths); - - for (j = 0; j < headerNames.length; j += 1) { - var cellData = data[i][headerNames[j]]; - if ("cellStart" in config && config.cellStart instanceof Function) { - config.cellStart( - { - row: i, - col: j, - data: cellData - }, - this - ); - } - cell.call( - this, - new Cell( - x, - y, - columnWidths[headerNames[j]], - lineHeight, - cellData, - i + 2, - align[headerNames[j]] - ) - ); - } - } - this.internal.__cell__.table_x = x; - this.internal.__cell__.table_y = y; - return this; - }; - - /** - * Calculate the height for containing the highest column - * - * @name calculateLineHeight - * @function - * @param {Object[]} model is the line of data we want to calculate the height of - * @param {Integer[]} columnWidths is size of each column - * @returns {number} lineHeight - * @private - */ - var calculateLineHeight = function calculateLineHeight(model, columnWidths) { - var padding = this.internal.__cell__.padding; - var fontSize = this.internal.__cell__.table_font_size; - var scaleFactor = this.internal.scaleFactor; - - return Object.keys(model) - .map(function(key) { - var value = model[key]; - return this.splitTextToSize( - value.hasOwnProperty("text") ? value.text : value, - columnWidths[key] - padding - padding - ); - }, this) - .map(function(value) { - return ( - (this.getLineHeightFactor() * value.length * fontSize) / scaleFactor + - padding + - padding - ); - }, this) - .reduce(function(pv, cv) { - return Math.max(pv, cv); - }, 0); - }; - - /** - * Store the config for outputting a table header - * - * @name setTableHeaderRow - * @function - * @param {Object[]} config - * An array of cell configs that would define a header row: Each config matches the config used by jsPDFAPI.cell - * except the lineNumber parameter is excluded - */ - jsPDFAPI.setTableHeaderRow = function(config) { - _initialize.call(this); - this.internal.__cell__.tableHeaderRow = config; - }; - - /** - * Output the store header row - * - * @name printHeaderRow - * @function - * @param {number} lineNumber The line number to output the header at - * @param {boolean} new_page - */ - jsPDFAPI.printHeaderRow = function(lineNumber, new_page) { - _initialize.call(this); - if (!this.internal.__cell__.tableHeaderRow) { - throw new Error("Property tableHeaderRow does not exist."); - } - - var tableHeaderCell; - - printingHeaderRow = true; - if (typeof this.internal.__cell__.headerFunction === "function") { - var position = this.internal.__cell__.headerFunction( - this, - this.internal.__cell__.pages - ); - this.internal.__cell__.lastCell = new Cell( - position[0], - position[1], - position[2], - position[3], - undefined, - -1 - ); - } - this.setFont(undefined, "bold"); - - var tempHeaderConf = []; - for (var i = 0; i < this.internal.__cell__.tableHeaderRow.length; i += 1) { - tableHeaderCell = this.internal.__cell__.tableHeaderRow[i].clone(); - if (new_page) { - tableHeaderCell.y = this.internal.__cell__.margins.top || 0; - tempHeaderConf.push(tableHeaderCell); - } - tableHeaderCell.lineNumber = lineNumber; - var currentTextColor = this.getTextColor(); - this.setTextColor(this.internal.__cell__.headerTextColor); - this.setFillColor(this.internal.__cell__.headerBackgroundColor); - cell.call(this, tableHeaderCell); - this.setTextColor(currentTextColor); - } - if (tempHeaderConf.length > 0) { - this.setTableHeaderRow(tempHeaderConf); - } - this.setFont(undefined, "normal"); - printingHeaderRow = false; - }; -})(jsPDF.API); - -function toLookup(arr) { - return arr.reduce(function(lookup, name, index) { - lookup[name] = index; - - return lookup; - }, {}); -} - -var fontStyleOrder = { - italic: ["italic", "oblique", "normal"], - oblique: ["oblique", "italic", "normal"], - normal: ["normal", "oblique", "italic"] -}; - -var fontStretchOrder = [ - "ultra-condensed", - "extra-condensed", - "condensed", - "semi-condensed", - "normal", - "semi-expanded", - "expanded", - "extra-expanded", - "ultra-expanded" -]; - -// For a given font-stretch value, we need to know where to start our search -// from in the fontStretchOrder list. -var fontStretchLookup = toLookup(fontStretchOrder); - -var fontWeights = [100, 200, 300, 400, 500, 600, 700, 800, 900]; -var fontWeightsLookup = toLookup(fontWeights); - -function normalizeFontStretch(stretch) { - stretch = stretch || "normal"; - - return typeof fontStretchLookup[stretch] === "number" ? stretch : "normal"; -} - -function normalizeFontStyle(style) { - style = style || "normal"; - - return fontStyleOrder[style] ? style : "normal"; -} - -function normalizeFontWeight(weight) { - if (!weight) { - return 400; - } - - if (typeof weight === "number") { - // Ignore values which aren't valid font-weights. - return weight >= 100 && weight <= 900 && weight % 100 === 0 ? weight : 400; - } - - if (/^\d00$/.test(weight)) { - return parseInt(weight); - } - - switch (weight) { - case "bold": - return 700; - - case "normal": - default: - return 400; - } -} - -function normalizeFontFace(fontFace) { - var family = fontFace.family.replace(/"|'/g, "").toLowerCase(); - - var style = normalizeFontStyle(fontFace.style); - var weight = normalizeFontWeight(fontFace.weight); - var stretch = normalizeFontStretch(fontFace.stretch); - - return { - family: family, - style: style, - weight: weight, - stretch: stretch, - src: fontFace.src || [], - - // The ref property maps this font-face to the font - // added by the .addFont() method. - ref: fontFace.ref || { - name: family, - style: [stretch, style, weight].join(" ") - } - }; -} - -/** - * Turns a list of font-faces into a map, for easier lookup when resolving - * fonts. - * @private - */ -function buildFontFaceMap(fontFaces) { - var map = {}; - - for (var i = 0; i < fontFaces.length; ++i) { - var normalized = normalizeFontFace(fontFaces[i]); - - var name = normalized.family; - var stretch = normalized.stretch; - var style = normalized.style; - var weight = normalized.weight; - - map[name] = map[name] || {}; - - map[name][stretch] = map[name][stretch] || {}; - map[name][stretch][style] = map[name][stretch][style] || {}; - map[name][stretch][style][weight] = normalized; - } - - return map; -} - -/** - * Searches a map of stretches, weights, etc. in the given direction and - * then, if no match has been found, in the opposite directions. - * - * @param {Object.} matchingSet A map of the various font variations. - * @param {any[]} order The order of the different variations - * @param {number} pivot The starting point of the search in the order list. - * @param {number} dir The initial direction of the search (desc = -1, asc = 1) - * @private - */ - -function searchFromPivot(matchingSet, order, pivot, dir) { - var i; - - for (i = pivot; i >= 0 && i < order.length; i += dir) { - if (matchingSet[order[i]]) { - return matchingSet[order[i]]; - } - } - - for (i = pivot; i >= 0 && i < order.length; i -= dir) { - if (matchingSet[order[i]]) { - return matchingSet[order[i]]; - } - } -} - -function resolveFontStretch(stretch, matchingSet) { - if (matchingSet[stretch]) { - return matchingSet[stretch]; - } - - var pivot = fontStretchLookup[stretch]; - - // If the font-stretch value is normal or more condensed, we want to - // start with a descending search, otherwise we should do ascending. - var dir = pivot <= fontStretchLookup["normal"] ? -1 : 1; - var match = searchFromPivot(matchingSet, fontStretchOrder, pivot, dir); - - if (!match) { - // Since a font-family cannot exist without having at least one stretch value - // we should never reach this point. - throw new Error( - "Could not find a matching font-stretch value for " + stretch - ); - } - - return match; -} - -function resolveFontStyle(fontStyle, matchingSet) { - if (matchingSet[fontStyle]) { - return matchingSet[fontStyle]; - } - - var ordering = fontStyleOrder[fontStyle]; - - for (var i = 0; i < ordering.length; ++i) { - if (matchingSet[ordering[i]]) { - return matchingSet[ordering[i]]; - } - } - - // Since a font-family cannot exist without having at least one style value - // we should never reach this point. - throw new Error("Could not find a matching font-style for " + fontStyle); -} - -function resolveFontWeight(weight, matchingSet) { - if (matchingSet[weight]) { - return matchingSet[weight]; - } - - if (weight === 400 && matchingSet[500]) { - return matchingSet[500]; - } - - if (weight === 500 && matchingSet[400]) { - return matchingSet[400]; - } - - var pivot = fontWeightsLookup[weight]; - - // If the font-stretch value is normal or more condensed, we want to - // start with a descending search, otherwise we should do ascending. - var dir = weight < 400 ? -1 : 1; - var match = searchFromPivot(matchingSet, fontWeights, pivot, dir); - - if (!match) { - // Since a font-family cannot exist without having at least one stretch value - // we should never reach this point. - throw new Error( - "Could not find a matching font-weight for value " + weight - ); - } - - return match; -} - -var defaultGenericFontFamilies = { - "sans-serif": "helvetica", - fixed: "courier", - monospace: "courier", - terminal: "courier", - cursive: "times", - fantasy: "times", - serif: "times" -}; - -var systemFonts = { - caption: "times", - icon: "times", - menu: "times", - "message-box": "times", - "small-caption": "times", - "status-bar": "times" -}; - -function ruleToString(rule) { - return [rule.stretch, rule.style, rule.weight, rule.family].join(" "); -} - -function resolveFontFace(fontFaceMap, rules, opts) { - opts = opts || {}; - - var defaultFontFamily = opts.defaultFontFamily || "times"; - var genericFontFamilies = Object.assign( - {}, - defaultGenericFontFamilies, - opts.genericFontFamilies || {} - ); - - var rule = null; - var matches = null; - - for (var i = 0; i < rules.length; ++i) { - rule = normalizeFontFace(rules[i]); - - if (genericFontFamilies[rule.family]) { - rule.family = genericFontFamilies[rule.family]; - } - - if (fontFaceMap.hasOwnProperty(rule.family)) { - matches = fontFaceMap[rule.family]; - - break; - } - } - - // Always fallback to a known font family. - matches = matches || fontFaceMap[defaultFontFamily]; - - if (!matches) { - // At this point we should definitiely have a font family, but if we - // don't there is something wrong with our configuration - throw new Error( - "Could not find a font-family for the rule '" + - ruleToString(rule) + - "' and default family '" + - defaultFontFamily + - "'." - ); - } - - matches = resolveFontStretch(rule.stretch, matches); - matches = resolveFontStyle(rule.style, matches); - matches = resolveFontWeight(rule.weight, matches); - - if (!matches) { - // We should've fount - throw new Error( - "Failed to resolve a font for the rule '" + ruleToString(rule) + "'." - ); - } - - return matches; -} - -function eatWhiteSpace(input) { - return input.trimLeft(); -} - -function parseQuotedFontFamily(input, quote) { - var index = 0; - - while (index < input.length) { - var current = input.charAt(index); - - if (current === quote) { - return [input.substring(0, index), input.substring(index + 1)]; - } - - index += 1; - } - - // Unexpected end of input - return null; -} - -function parseNonQuotedFontFamily(input) { - // It implements part of the identifier parser here: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - // - // NOTE: This parser pretty much ignores escaped identifiers and that there is a thing called unicode. - // - // Breakdown of regexp: - // -[a-z_] - when identifier starts with a hyphen, you're not allowed to have another hyphen or a digit - // [a-z_] - allow a-z and underscore at beginning of input - // [a-z0-9_-]* - after that, anything goes - var match = input.match(/^(-[a-z_]|[a-z_])[a-z0-9_-]*/i); - - // non quoted value contains illegal characters - if (match === null) { - return null; - } - - return [match[0], input.substring(match[0].length)]; -} - -var defaultFont = ["times"]; - -function parseFontFamily(input) { - var result = []; - var ch, parsed; - var remaining = input.trim(); - - if (remaining === "") { - return defaultFont; - } - - if (remaining in systemFonts) { - return [systemFonts[remaining]]; - } - - while (remaining !== "") { - parsed = null; - remaining = eatWhiteSpace(remaining); - ch = remaining.charAt(0); - - switch (ch) { - case '"': - case "'": - parsed = parseQuotedFontFamily(remaining.substring(1), ch); - break; - - default: - parsed = parseNonQuotedFontFamily(remaining); - break; - } - - if (parsed === null) { - return defaultFont; - } - - result.push(parsed[0]); - - remaining = eatWhiteSpace(parsed[1]); - - // We expect end of input or a comma separator here - if (remaining !== "" && remaining.charAt(0) !== ",") { - return defaultFont; - } - - remaining = remaining.replace(/^,/, ""); - } - - return result; -} - -/* eslint-disable no-fallthrough */ - -/** - * This plugin mimics the HTML5 CanvasRenderingContext2D. - * - * The goal is to provide a way for current canvas implementations to print directly to a PDF. - * - * @name context2d - * @module - */ -(function(jsPDFAPI) { - var ContextLayer = function(ctx) { - ctx = ctx || {}; - this.isStrokeTransparent = ctx.isStrokeTransparent || false; - this.strokeOpacity = ctx.strokeOpacity || 1; - this.strokeStyle = ctx.strokeStyle || "#000000"; - this.fillStyle = ctx.fillStyle || "#000000"; - this.isFillTransparent = ctx.isFillTransparent || false; - this.fillOpacity = ctx.fillOpacity || 1; - this.font = ctx.font || "10px sans-serif"; - this.textBaseline = ctx.textBaseline || "alphabetic"; - this.textAlign = ctx.textAlign || "left"; - this.lineWidth = ctx.lineWidth || 1; - this.lineJoin = ctx.lineJoin || "miter"; - this.lineCap = ctx.lineCap || "butt"; - this.path = ctx.path || []; - this.transform = - typeof ctx.transform !== "undefined" - ? ctx.transform.clone() - : new Matrix(); - this.globalCompositeOperation = ctx.globalCompositeOperation || "normal"; - this.globalAlpha = ctx.globalAlpha || 1.0; - this.clip_path = ctx.clip_path || []; - this.currentPoint = ctx.currentPoint || new Point(); - this.miterLimit = ctx.miterLimit || 10.0; - this.lastPoint = ctx.lastPoint || new Point(); - this.lineDashOffset = ctx.lineDashOffset || 0.0; - this.lineDash = ctx.lineDash || []; - this.margin = ctx.margin || [0, 0, 0, 0]; - this.prevPageLastElemOffset = ctx.prevPageLastElemOffset || 0; - - this.ignoreClearRect = - typeof ctx.ignoreClearRect === "boolean" ? ctx.ignoreClearRect : true; - return this; - }; - - //stub - var f2, - getHorizontalCoordinateString, - getVerticalCoordinateString, - getHorizontalCoordinate, - getVerticalCoordinate, - Point, - Rectangle, - Matrix, - _ctx; - jsPDFAPI.events.push([ - "initialized", - function() { - this.context2d = new Context2D(this); - - f2 = this.internal.f2; - getHorizontalCoordinateString = this.internal.getCoordinateString; - getVerticalCoordinateString = this.internal.getVerticalCoordinateString; - getHorizontalCoordinate = this.internal.getHorizontalCoordinate; - getVerticalCoordinate = this.internal.getVerticalCoordinate; - Point = this.internal.Point; - Rectangle = this.internal.Rectangle; - Matrix = this.internal.Matrix; - _ctx = new ContextLayer(); - } - ]); - - var Context2D = function(pdf) { - Object.defineProperty(this, "canvas", { - get: function() { - return { parentNode: false, style: false }; - } - }); - - var _pdf = pdf; - Object.defineProperty(this, "pdf", { - get: function() { - return _pdf; - } - }); - - var _pageWrapXEnabled = false; - /** - * @name pageWrapXEnabled - * @type {boolean} - * @default false - */ - Object.defineProperty(this, "pageWrapXEnabled", { - get: function() { - return _pageWrapXEnabled; - }, - set: function(value) { - _pageWrapXEnabled = Boolean(value); - } - }); - - var _pageWrapYEnabled = false; - /** - * @name pageWrapYEnabled - * @type {boolean} - * @default true - */ - Object.defineProperty(this, "pageWrapYEnabled", { - get: function() { - return _pageWrapYEnabled; - }, - set: function(value) { - _pageWrapYEnabled = Boolean(value); - } - }); - - var _posX = 0; - /** - * @name posX - * @type {number} - * @default 0 - */ - Object.defineProperty(this, "posX", { - get: function() { - return _posX; - }, - set: function(value) { - if (!isNaN(value)) { - _posX = value; - } - } - }); - - var _posY = 0; - /** - * @name posY - * @type {number} - * @default 0 - */ - Object.defineProperty(this, "posY", { - get: function() { - return _posY; - }, - set: function(value) { - if (!isNaN(value)) { - _posY = value; - } - } - }); - - /** - * Gets or sets the page margin when using auto paging. Has no effect when {@link autoPaging} is off. - * @name margin - * @type {number|number[]} - * @default [0, 0, 0, 0] - */ - Object.defineProperty(this, "margin", { - get: function() { - return _ctx.margin; - }, - set: function(value) { - var margin; - if (typeof value === "number") { - margin = [value, value, value, value]; - } else { - margin = new Array(4); - margin[0] = value[0]; - margin[1] = value.length >= 2 ? value[1] : margin[0]; - margin[2] = value.length >= 3 ? value[2] : margin[0]; - margin[3] = value.length >= 4 ? value[3] : margin[1]; - } - _ctx.margin = margin; - } - }); - - var _autoPaging = false; - /** - * Gets or sets the auto paging mode. When auto paging is enabled, the context2d will automatically draw on the - * next page if a shape or text chunk doesn't fit entirely on the current page. The context2d will create new - * pages if required. - * - * Context2d supports different modes: - *
    - *
  • - * false: Auto paging is disabled. - *
  • - *
  • - * true or 'slice': Will cut shapes or text chunks across page breaks. Will possibly - * slice text in half, making it difficult to read. - *
  • - *
  • - * 'text': Trys not to cut text in half across page breaks. Works best for documents consisting - * mostly of a single column of text. - *
  • - *
- * @name Context2D#autoPaging - * @type {boolean|"slice"|"text"} - * @default false - */ - Object.defineProperty(this, "autoPaging", { - get: function() { - return _autoPaging; - }, - set: function(value) { - _autoPaging = value; - } - }); - - var lastBreak = 0; - /** - * @name lastBreak - * @type {number} - * @default 0 - */ - Object.defineProperty(this, "lastBreak", { - get: function() { - return lastBreak; - }, - set: function(value) { - lastBreak = value; - } - }); - - var pageBreaks = []; - /** - * Y Position of page breaks. - * @name pageBreaks - * @type {number} - * @default 0 - */ - Object.defineProperty(this, "pageBreaks", { - get: function() { - return pageBreaks; - }, - set: function(value) { - pageBreaks = value; - } - }); - - /** - * @name ctx - * @type {object} - * @default {} - */ - Object.defineProperty(this, "ctx", { - get: function() { - return _ctx; - }, - set: function(value) { - if (value instanceof ContextLayer) { - _ctx = value; - } - } - }); - - /** - * @name path - * @type {array} - * @default [] - */ - Object.defineProperty(this, "path", { - get: function() { - return _ctx.path; - }, - set: function(value) { - _ctx.path = value; - } - }); - - /** - * @name ctxStack - * @type {array} - * @default [] - */ - var _ctxStack = []; - Object.defineProperty(this, "ctxStack", { - get: function() { - return _ctxStack; - }, - set: function(value) { - _ctxStack = value; - } - }); - - /** - * Sets or returns the color, gradient, or pattern used to fill the drawing - * - * @name fillStyle - * @default #000000 - * @property {(color|gradient|pattern)} value The color of the drawing. Default value is #000000
- * A gradient object (linear or radial) used to fill the drawing (not supported by context2d)
- * A pattern object to use to fill the drawing (not supported by context2d) - */ - Object.defineProperty(this, "fillStyle", { - get: function() { - return this.ctx.fillStyle; - }, - set: function(value) { - var rgba; - rgba = getRGBA(value); - - this.ctx.fillStyle = rgba.style; - this.ctx.isFillTransparent = rgba.a === 0; - this.ctx.fillOpacity = rgba.a; - - this.pdf.setFillColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); - this.pdf.setTextColor(rgba.r, rgba.g, rgba.b, { a: rgba.a }); - } - }); - - /** - * Sets or returns the color, gradient, or pattern used for strokes - * - * @name strokeStyle - * @default #000000 - * @property {color} color A CSS color value that indicates the stroke color of the drawing. Default value is #000000 (not supported by context2d) - * @property {gradient} gradient A gradient object (linear or radial) used to create a gradient stroke (not supported by context2d) - * @property {pattern} pattern A pattern object used to create a pattern stroke (not supported by context2d) - */ - Object.defineProperty(this, "strokeStyle", { - get: function() { - return this.ctx.strokeStyle; - }, - set: function(value) { - var rgba = getRGBA(value); - - this.ctx.strokeStyle = rgba.style; - this.ctx.isStrokeTransparent = rgba.a === 0; - this.ctx.strokeOpacity = rgba.a; - - if (rgba.a === 0) { - this.pdf.setDrawColor(255, 255, 255); - } else if (rgba.a === 1) { - this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); - } else { - this.pdf.setDrawColor(rgba.r, rgba.g, rgba.b); - } - } - }); - - /** - * Sets or returns the style of the end caps for a line - * - * @name lineCap - * @default butt - * @property {(butt|round|square)} lineCap butt A flat edge is added to each end of the line
- * round A rounded end cap is added to each end of the line
- * square A square end cap is added to each end of the line
- */ - Object.defineProperty(this, "lineCap", { - get: function() { - return this.ctx.lineCap; - }, - set: function(value) { - if (["butt", "round", "square"].indexOf(value) !== -1) { - this.ctx.lineCap = value; - this.pdf.setLineCap(value); - } - } - }); - - /** - * Sets or returns the current line width - * - * @name lineWidth - * @default 1 - * @property {number} lineWidth The current line width, in pixels - */ - Object.defineProperty(this, "lineWidth", { - get: function() { - return this.ctx.lineWidth; - }, - set: function(value) { - if (!isNaN(value)) { - this.ctx.lineWidth = value; - this.pdf.setLineWidth(value); - } - } - }); - - /** - * Sets or returns the type of corner created, when two lines meet - */ - Object.defineProperty(this, "lineJoin", { - get: function() { - return this.ctx.lineJoin; - }, - set: function(value) { - if (["bevel", "round", "miter"].indexOf(value) !== -1) { - this.ctx.lineJoin = value; - this.pdf.setLineJoin(value); - } - } - }); - - /** - * A number specifying the miter limit ratio in coordinate space units. Zero, negative, Infinity, and NaN values are ignored. The default value is 10.0. - * - * @name miterLimit - * @default 10 - */ - Object.defineProperty(this, "miterLimit", { - get: function() { - return this.ctx.miterLimit; - }, - set: function(value) { - if (!isNaN(value)) { - this.ctx.miterLimit = value; - this.pdf.setMiterLimit(value); - } - } - }); - - Object.defineProperty(this, "textBaseline", { - get: function() { - return this.ctx.textBaseline; - }, - set: function(value) { - this.ctx.textBaseline = value; - } - }); - - Object.defineProperty(this, "textAlign", { - get: function() { - return this.ctx.textAlign; - }, - set: function(value) { - if (["right", "end", "center", "left", "start"].indexOf(value) !== -1) { - this.ctx.textAlign = value; - } - } - }); - - var _fontFaceMap = null; - var _cachedFontList = null; - - function getFontFaceMap(pdf, fontFaces) { - var currentFontMap = pdf.getFontList(); - - // Check if the font list has changed by comparing the JSON representation - var currentFontMapString = JSON.stringify(currentFontMap); - - if (_fontFaceMap === null || _cachedFontList !== currentFontMapString) { - var convertedFontFaces = convertToFontFaces(currentFontMap); - - _fontFaceMap = buildFontFaceMap(convertedFontFaces.concat(fontFaces)); - _cachedFontList = currentFontMapString; - } - - return _fontFaceMap; - } - - function convertToFontFaces(fontMap) { - var fontFaces = []; - - Object.keys(fontMap).forEach(function(family) { - var styles = fontMap[family]; - - styles.forEach(function(style) { - var fontFace = null; - - switch (style) { - case "bold": - fontFace = { - family: family, - weight: "bold" - }; - break; - - case "italic": - fontFace = { - family: family, - style: "italic" - }; - break; - - case "bolditalic": - fontFace = { - family: family, - weight: "bold", - style: "italic" - }; - break; - - case "": - case "normal": - fontFace = { - family: family - }; - break; - } - - // If font-face is still null here, it is a font with some styling we don't recognize and - // cannot map or it is a font added via the fontFaces option of .html(). - if (fontFace !== null) { - fontFace.ref = { - name: family, - style: style - }; - - fontFaces.push(fontFace); - } - }); - }); - - return fontFaces; - } - - var _fontFaces = null; - /** - * A map of available font-faces, as passed in the options of - * .html(). If set a limited implementation of the font style matching - * algorithm defined by https://www.w3.org/TR/css-fonts-3/#font-matching-algorithm - * will be used. If not set it will fallback to previous behavior. - */ - - Object.defineProperty(this, "fontFaces", { - get: function() { - return _fontFaces; - }, - set: function(value) { - _fontFaceMap = null; - _cachedFontList = null; - _fontFaces = value; - } - }); - - Object.defineProperty(this, "font", { - get: function() { - return this.ctx.font; - }, - set: function(value) { - this.ctx.font = value; - var rx, matches; - - //source: https://stackoverflow.com/a/10136041 - // eslint-disable-next-line no-useless-escape - rx = /^\s*(?=(?:(?:[-a-z]+\s*){0,2}(italic|oblique))?)(?=(?:(?:[-a-z]+\s*){0,2}(small-caps))?)(?=(?:(?:[-a-z]+\s*){0,2}(bold(?:er)?|lighter|[1-9]00))?)(?:(?:normal|\1|\2|\3)\s*){0,3}((?:xx?-)?(?:small|large)|medium|smaller|larger|[.\d]+(?:\%|in|[cem]m|ex|p[ctx]))(?:\s*\/\s*(normal|[.\d]+(?:\%|in|[cem]m|ex|p[ctx])))?\s*([-_,\"\'\sa-z0-9]+?)\s*$/i; - matches = rx.exec(value); - if (matches !== null) { - var fontStyle = matches[1]; - matches[2]; - var fontWeight = matches[3]; - var fontSize = matches[4]; - matches[5]; - var fontFamily = matches[6]; - } else { - return; - } - var rxFontSize = /^([.\d]+)((?:%|in|[cem]m|ex|p[ctx]))$/i; - var fontSizeUnit = rxFontSize.exec(fontSize)[2]; - - if ("px" === fontSizeUnit) { - fontSize = Math.floor( - parseFloat(fontSize) * this.pdf.internal.scaleFactor - ); - } else if ("em" === fontSizeUnit) { - fontSize = Math.floor(parseFloat(fontSize) * this.pdf.getFontSize()); - } else { - fontSize = Math.floor( - parseFloat(fontSize) * this.pdf.internal.scaleFactor - ); - } - - this.pdf.setFontSize(fontSize); - var parts = parseFontFamily(fontFamily); - - if (this.fontFaces) { - var fontFaceMap = getFontFaceMap(this.pdf, this.fontFaces); - - var rules = parts.map(function(ff) { - return { - family: ff, - stretch: "normal", // TODO: Extract font-stretch from font rule (perhaps write proper parser for it?) - weight: fontWeight, - style: fontStyle - }; - }); - - var font = resolveFontFace(fontFaceMap, rules); - this.pdf.setFont(font.ref.name, font.ref.style); - return; - } - - var style = ""; - if ( - fontWeight === "bold" || - parseInt(fontWeight, 10) >= 700 || - fontStyle === "bold" - ) { - style = "bold"; - } - - if (fontStyle === "italic") { - style += "italic"; - } - - if (style.length === 0) { - style = "normal"; - } - var jsPdfFontName = ""; - - var fallbackFonts = { - arial: "Helvetica", - Arial: "Helvetica", - verdana: "Helvetica", - Verdana: "Helvetica", - helvetica: "Helvetica", - Helvetica: "Helvetica", - "sans-serif": "Helvetica", - fixed: "Courier", - monospace: "Courier", - terminal: "Courier", - cursive: "Times", - fantasy: "Times", - serif: "Times" - }; - - for (var i = 0; i < parts.length; i++) { - if ( - this.pdf.internal.getFont(parts[i], style, { - noFallback: true, - disableWarning: true - }) !== undefined - ) { - jsPdfFontName = parts[i]; - break; - } else if ( - style === "bolditalic" && - this.pdf.internal.getFont(parts[i], "bold", { - noFallback: true, - disableWarning: true - }) !== undefined - ) { - jsPdfFontName = parts[i]; - style = "bold"; - } else if ( - this.pdf.internal.getFont(parts[i], "normal", { - noFallback: true, - disableWarning: true - }) !== undefined - ) { - jsPdfFontName = parts[i]; - style = "normal"; - break; - } - } - if (jsPdfFontName === "") { - for (var j = 0; j < parts.length; j++) { - if (fallbackFonts[parts[j]]) { - jsPdfFontName = fallbackFonts[parts[j]]; - break; - } - } - } - jsPdfFontName = jsPdfFontName === "" ? "Times" : jsPdfFontName; - this.pdf.setFont(jsPdfFontName, style); - } - }); - - Object.defineProperty(this, "globalCompositeOperation", { - get: function() { - return this.ctx.globalCompositeOperation; - }, - set: function(value) { - this.ctx.globalCompositeOperation = value; - } - }); - - Object.defineProperty(this, "globalAlpha", { - get: function() { - return this.ctx.globalAlpha; - }, - set: function(value) { - this.ctx.globalAlpha = value; - } - }); - - /** - * A float specifying the amount of the line dash offset. The default value is 0.0. - * - * @name lineDashOffset - * @default 0.0 - */ - Object.defineProperty(this, "lineDashOffset", { - get: function() { - return this.ctx.lineDashOffset; - }, - set: function(value) { - this.ctx.lineDashOffset = value; - setLineDash.call(this); - } - }); - - // Not HTML API - Object.defineProperty(this, "lineDash", { - get: function() { - return this.ctx.lineDash; - }, - set: function(value) { - this.ctx.lineDash = value; - setLineDash.call(this); - } - }); - - // Not HTML API - Object.defineProperty(this, "ignoreClearRect", { - get: function() { - return this.ctx.ignoreClearRect; - }, - set: function(value) { - this.ctx.ignoreClearRect = Boolean(value); - } - }); - }; - - /** - * Sets the line dash pattern used when stroking lines. - * @name setLineDash - * @function - * @description It uses an array of values that specify alternating lengths of lines and gaps which describe the pattern. - */ - Context2D.prototype.setLineDash = function(dashArray) { - this.lineDash = dashArray; - }; - - /** - * gets the current line dash pattern. - * @name getLineDash - * @function - * @returns {Array} An Array of numbers that specify distances to alternately draw a line and a gap (in coordinate space units). If the number, when setting the elements, is odd, the elements of the array get copied and concatenated. For example, setting the line dash to [5, 15, 25] will result in getting back [5, 15, 25, 5, 15, 25]. - */ - Context2D.prototype.getLineDash = function() { - if (this.lineDash.length % 2) { - // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getLineDash#return_value - return this.lineDash.concat(this.lineDash); - } else { - // The copied value is returned to prevent contamination from outside. - return this.lineDash.slice(); - } - }; - - Context2D.prototype.fill = function() { - pathPreProcess.call(this, "fill", false); - }; - - /** - * Actually draws the path you have defined - * - * @name stroke - * @function - * @description The stroke() method actually draws the path you have defined with all those moveTo() and lineTo() methods. The default color is black. - */ - Context2D.prototype.stroke = function() { - pathPreProcess.call(this, "stroke", false); - }; - - /** - * Begins a path, or resets the current - * - * @name beginPath - * @function - * @description The beginPath() method begins a path, or resets the current path. - */ - Context2D.prototype.beginPath = function() { - this.path = [ - { - type: "begin" - } - ]; - }; - - /** - * Moves the path to the specified point in the canvas, without creating a line - * - * @name moveTo - * @function - * @param x {Number} The x-coordinate of where to move the path to - * @param y {Number} The y-coordinate of where to move the path to - */ - Context2D.prototype.moveTo = function(x, y) { - if (isNaN(x) || isNaN(y)) { - console.error("jsPDF.context2d.moveTo: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.moveTo"); - } - - var pt = this.ctx.transform.applyToPoint(new Point(x, y)); - - this.path.push({ - type: "mt", - x: pt.x, - y: pt.y - }); - this.ctx.lastPoint = new Point(x, y); - }; - - /** - * Creates a path from the current point back to the starting point - * - * @name closePath - * @function - * @description The closePath() method creates a path from the current point back to the starting point. - */ - Context2D.prototype.closePath = function() { - var pathBegin = new Point(0, 0); - var i = 0; - for (i = this.path.length - 1; i !== -1; i--) { - if (this.path[i].type === "begin") { - if ( - typeof this.path[i + 1] === "object" && - typeof this.path[i + 1].x === "number" - ) { - pathBegin = new Point(this.path[i + 1].x, this.path[i + 1].y); - break; - } - } - } - this.path.push({ - type: "close" - }); - this.ctx.lastPoint = new Point(pathBegin.x, pathBegin.y); - }; - - /** - * Adds a new point and creates a line to that point from the last specified point in the canvas - * - * @name lineTo - * @function - * @param x The x-coordinate of where to create the line to - * @param y The y-coordinate of where to create the line to - * @description The lineTo() method adds a new point and creates a line TO that point FROM the last specified point in the canvas (this method does not draw the line). - */ - Context2D.prototype.lineTo = function(x, y) { - if (isNaN(x) || isNaN(y)) { - console.error("jsPDF.context2d.lineTo: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.lineTo"); - } - - var pt = this.ctx.transform.applyToPoint(new Point(x, y)); - - this.path.push({ - type: "lt", - x: pt.x, - y: pt.y - }); - this.ctx.lastPoint = new Point(pt.x, pt.y); - }; - - /** - * Clips a region of any shape and size from the original canvas - * - * @name clip - * @function - * @description The clip() method clips a region of any shape and size from the original canvas. - */ - Context2D.prototype.clip = function() { - this.ctx.clip_path = JSON.parse(JSON.stringify(this.path)); - pathPreProcess.call(this, null, true); - }; - - /** - * Creates a cubic Bézier curve - * - * @name quadraticCurveTo - * @function - * @param cpx {Number} The x-coordinate of the Bézier control point - * @param cpy {Number} The y-coordinate of the Bézier control point - * @param x {Number} The x-coordinate of the ending point - * @param y {Number} The y-coordinate of the ending point - * @description The quadraticCurveTo() method adds a point to the current path by using the specified control points that represent a quadratic Bézier curve.

A quadratic Bézier curve requires two points. The first point is a control point that is used in the quadratic Bézier calculation and the second point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point. - */ - Context2D.prototype.quadraticCurveTo = function(cpx, cpy, x, y) { - if (isNaN(x) || isNaN(y) || isNaN(cpx) || isNaN(cpy)) { - console.error( - "jsPDF.context2d.quadraticCurveTo: Invalid arguments", - arguments - ); - throw new Error( - "Invalid arguments passed to jsPDF.context2d.quadraticCurveTo" - ); - } - - var pt0 = this.ctx.transform.applyToPoint(new Point(x, y)); - var pt1 = this.ctx.transform.applyToPoint(new Point(cpx, cpy)); - - this.path.push({ - type: "qct", - x1: pt1.x, - y1: pt1.y, - x: pt0.x, - y: pt0.y - }); - this.ctx.lastPoint = new Point(pt0.x, pt0.y); - }; - - /** - * Creates a cubic Bézier curve - * - * @name bezierCurveTo - * @function - * @param cp1x {Number} The x-coordinate of the first Bézier control point - * @param cp1y {Number} The y-coordinate of the first Bézier control point - * @param cp2x {Number} The x-coordinate of the second Bézier control point - * @param cp2y {Number} The y-coordinate of the second Bézier control point - * @param x {Number} The x-coordinate of the ending point - * @param y {Number} The y-coordinate of the ending point - * @description The bezierCurveTo() method adds a point to the current path by using the specified control points that represent a cubic Bézier curve.

A cubic bezier curve requires three points. The first two points are control points that are used in the cubic Bézier calculation and the last point is the ending point for the curve. The starting point for the curve is the last point in the current path. If a path does not exist, use the beginPath() and moveTo() methods to define a starting point. - */ - Context2D.prototype.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { - if ( - isNaN(x) || - isNaN(y) || - isNaN(cp1x) || - isNaN(cp1y) || - isNaN(cp2x) || - isNaN(cp2y) - ) { - console.error( - "jsPDF.context2d.bezierCurveTo: Invalid arguments", - arguments - ); - throw new Error( - "Invalid arguments passed to jsPDF.context2d.bezierCurveTo" - ); - } - var pt0 = this.ctx.transform.applyToPoint(new Point(x, y)); - var pt1 = this.ctx.transform.applyToPoint(new Point(cp1x, cp1y)); - var pt2 = this.ctx.transform.applyToPoint(new Point(cp2x, cp2y)); - - this.path.push({ - type: "bct", - x1: pt1.x, - y1: pt1.y, - x2: pt2.x, - y2: pt2.y, - x: pt0.x, - y: pt0.y - }); - this.ctx.lastPoint = new Point(pt0.x, pt0.y); - }; - - /** - * Creates an arc/curve (used to create circles, or parts of circles) - * - * @name arc - * @function - * @param x {Number} The x-coordinate of the center of the circle - * @param y {Number} The y-coordinate of the center of the circle - * @param radius {Number} The radius of the circle - * @param startAngle {Number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {Number} The ending angle, in radians - * @param counterclockwise {Boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @description The arc() method creates an arc/curve (used to create circles, or parts of circles). - */ - Context2D.prototype.arc = function( - x, - y, - radius, - startAngle, - endAngle, - counterclockwise - ) { - if ( - isNaN(x) || - isNaN(y) || - isNaN(radius) || - isNaN(startAngle) || - isNaN(endAngle) - ) { - console.error("jsPDF.context2d.arc: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.arc"); - } - counterclockwise = Boolean(counterclockwise); - - if (!this.ctx.transform.isIdentity) { - var xpt = this.ctx.transform.applyToPoint(new Point(x, y)); - x = xpt.x; - y = xpt.y; - - var x_radPt = this.ctx.transform.applyToPoint(new Point(0, radius)); - var x_radPt0 = this.ctx.transform.applyToPoint(new Point(0, 0)); - radius = Math.sqrt( - Math.pow(x_radPt.x - x_radPt0.x, 2) + - Math.pow(x_radPt.y - x_radPt0.y, 2) - ); - } - if (Math.abs(endAngle - startAngle) >= 2 * Math.PI) { - startAngle = 0; - endAngle = 2 * Math.PI; - } - - this.path.push({ - type: "arc", - x: x, - y: y, - radius: radius, - startAngle: startAngle, - endAngle: endAngle, - counterclockwise: counterclockwise - }); - // this.ctx.lastPoint(new Point(pt.x,pt.y)); - }; - - /** - * Creates an arc/curve between two tangents - * - * @name arcTo - * @function - * @param x1 {Number} The x-coordinate of the first tangent - * @param y1 {Number} The y-coordinate of the first tangent - * @param x2 {Number} The x-coordinate of the second tangent - * @param y2 {Number} The y-coordinate of the second tangent - * @param radius The radius of the arc - * @description The arcTo() method creates an arc/curve between two tangents on the canvas. - */ - // eslint-disable-next-line no-unused-vars - Context2D.prototype.arcTo = function(x1, y1, x2, y2, radius) { - throw new Error("arcTo not implemented."); - }; - - /** - * Creates a rectangle - * - * @name rect - * @function - * @param x {Number} The x-coordinate of the upper-left corner of the rectangle - * @param y {Number} The y-coordinate of the upper-left corner of the rectangle - * @param w {Number} The width of the rectangle, in pixels - * @param h {Number} The height of the rectangle, in pixels - * @description The rect() method creates a rectangle. - */ - Context2D.prototype.rect = function(x, y, w, h) { - if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { - console.error("jsPDF.context2d.rect: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.rect"); - } - this.moveTo(x, y); - this.lineTo(x + w, y); - this.lineTo(x + w, y + h); - this.lineTo(x, y + h); - this.lineTo(x, y); - this.lineTo(x + w, y); - this.lineTo(x, y); - }; - - /** - * Draws a "filled" rectangle - * - * @name fillRect - * @function - * @param x {Number} The x-coordinate of the upper-left corner of the rectangle - * @param y {Number} The y-coordinate of the upper-left corner of the rectangle - * @param w {Number} The width of the rectangle, in pixels - * @param h {Number} The height of the rectangle, in pixels - * @description The fillRect() method draws a "filled" rectangle. The default color of the fill is black. - */ - Context2D.prototype.fillRect = function(x, y, w, h) { - if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { - console.error("jsPDF.context2d.fillRect: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.fillRect"); - } - if (isFillTransparent.call(this)) { - return; - } - var tmp = {}; - if (this.lineCap !== "butt") { - tmp.lineCap = this.lineCap; - this.lineCap = "butt"; - } - if (this.lineJoin !== "miter") { - tmp.lineJoin = this.lineJoin; - this.lineJoin = "miter"; - } - - this.beginPath(); - this.rect(x, y, w, h); - this.fill(); - - if (tmp.hasOwnProperty("lineCap")) { - this.lineCap = tmp.lineCap; - } - if (tmp.hasOwnProperty("lineJoin")) { - this.lineJoin = tmp.lineJoin; - } - }; - - /** - * Draws a rectangle (no fill) - * - * @name strokeRect - * @function - * @param x {Number} The x-coordinate of the upper-left corner of the rectangle - * @param y {Number} The y-coordinate of the upper-left corner of the rectangle - * @param w {Number} The width of the rectangle, in pixels - * @param h {Number} The height of the rectangle, in pixels - * @description The strokeRect() method draws a rectangle (no fill). The default color of the stroke is black. - */ - Context2D.prototype.strokeRect = function strokeRect(x, y, w, h) { - if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { - console.error("jsPDF.context2d.strokeRect: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.strokeRect"); - } - if (isStrokeTransparent.call(this)) { - return; - } - this.beginPath(); - this.rect(x, y, w, h); - this.stroke(); - }; - - /** - * Clears the specified pixels within a given rectangle - * - * @name clearRect - * @function - * @param x {Number} The x-coordinate of the upper-left corner of the rectangle - * @param y {Number} The y-coordinate of the upper-left corner of the rectangle - * @param w {Number} The width of the rectangle to clear, in pixels - * @param h {Number} The height of the rectangle to clear, in pixels - * @description We cannot clear PDF commands that were already written to PDF, so we use white instead.
- * As a special case, read a special flag (ignoreClearRect) and do nothing if it is set. - * This results in all calls to clearRect() to do nothing, and keep the canvas transparent. - * This flag is stored in the save/restore context and is managed the same way as other drawing states. - * - */ - Context2D.prototype.clearRect = function(x, y, w, h) { - if (isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h)) { - console.error("jsPDF.context2d.clearRect: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.clearRect"); - } - if (this.ignoreClearRect) { - return; - } - - this.fillStyle = "#ffffff"; - this.fillRect(x, y, w, h); - }; - - /** - * Saves the state of the current context - * - * @name save - * @function - */ - Context2D.prototype.save = function(doStackPush) { - doStackPush = typeof doStackPush === "boolean" ? doStackPush : true; - var tmpPageNumber = this.pdf.internal.getCurrentPageInfo().pageNumber; - for (var i = 0; i < this.pdf.internal.getNumberOfPages(); i++) { - this.pdf.setPage(i + 1); - this.pdf.internal.out("q"); - } - this.pdf.setPage(tmpPageNumber); - - if (doStackPush) { - this.ctx.fontSize = this.pdf.internal.getFontSize(); - var ctx = new ContextLayer(this.ctx); - this.ctxStack.push(this.ctx); - this.ctx = ctx; - } - }; - - /** - * Returns previously saved path state and attributes - * - * @name restore - * @function - */ - Context2D.prototype.restore = function(doStackPop) { - doStackPop = typeof doStackPop === "boolean" ? doStackPop : true; - var tmpPageNumber = this.pdf.internal.getCurrentPageInfo().pageNumber; - for (var i = 0; i < this.pdf.internal.getNumberOfPages(); i++) { - this.pdf.setPage(i + 1); - this.pdf.internal.out("Q"); - } - this.pdf.setPage(tmpPageNumber); - - if (doStackPop && this.ctxStack.length !== 0) { - this.ctx = this.ctxStack.pop(); - this.fillStyle = this.ctx.fillStyle; - this.strokeStyle = this.ctx.strokeStyle; - this.font = this.ctx.font; - this.lineCap = this.ctx.lineCap; - this.lineWidth = this.ctx.lineWidth; - this.lineJoin = this.ctx.lineJoin; - this.lineDash = this.ctx.lineDash; - this.lineDashOffset = this.ctx.lineDashOffset; - } - }; - - /** - * @name toDataURL - * @function - */ - Context2D.prototype.toDataURL = function() { - throw new Error("toDataUrl not implemented."); - }; - - //helper functions - - /** - * Get the decimal values of r, g, b and a - * - * @name getRGBA - * @function - * @private - * @ignore - */ - var getRGBA = function(style) { - var rxRgb = /rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/; - var rxRgba = /rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([\d.]+)\s*\)/; - var rxTransparent = /transparent|rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*0+\s*\)/; - - var r, g, b, a; - - if (style.isCanvasGradient === true) { - style = style.getColor(); - } - - if (!style) { - return { r: 0, g: 0, b: 0, a: 0, style: style }; - } - - if (rxTransparent.test(style)) { - r = 0; - g = 0; - b = 0; - a = 0; - } else { - var matches = rxRgb.exec(style); - if (matches !== null) { - r = parseInt(matches[1]); - g = parseInt(matches[2]); - b = parseInt(matches[3]); - a = 1; - } else { - matches = rxRgba.exec(style); - if (matches !== null) { - r = parseInt(matches[1]); - g = parseInt(matches[2]); - b = parseInt(matches[3]); - a = parseFloat(matches[4]); - } else { - a = 1; - - if (typeof style === "string" && style.charAt(0) !== "#") { - var rgbColor = new RGBColor(style); - if (rgbColor.ok) { - style = rgbColor.toHex(); - } else { - style = "#000000"; - } - } - - if (style.length === 4) { - r = style.substring(1, 2); - r += r; - g = style.substring(2, 3); - g += g; - b = style.substring(3, 4); - b += b; - } else { - r = style.substring(1, 3); - g = style.substring(3, 5); - b = style.substring(5, 7); - } - r = parseInt(r, 16); - g = parseInt(g, 16); - b = parseInt(b, 16); - } - } - } - return { r: r, g: g, b: b, a: a, style: style }; - }; - - /** - * @name isFillTransparent - * @function - * @private - * @ignore - * @returns {Boolean} - */ - var isFillTransparent = function() { - return this.ctx.isFillTransparent || this.globalAlpha == 0; - }; - - /** - * @name isStrokeTransparent - * @function - * @private - * @ignore - * @returns {Boolean} - */ - var isStrokeTransparent = function() { - return Boolean(this.ctx.isStrokeTransparent || this.globalAlpha == 0); - }; - - /** - * Draws "filled" text on the canvas - * - * @name fillText - * @function - * @param text {String} Specifies the text that will be written on the canvas - * @param x {Number} The x coordinate where to start painting the text (relative to the canvas) - * @param y {Number} The y coordinate where to start painting the text (relative to the canvas) - * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels - * @description The fillText() method draws filled text on the canvas. The default color of the text is black. - */ - Context2D.prototype.fillText = function(text, x, y, maxWidth) { - if (isNaN(x) || isNaN(y) || typeof text !== "string") { - console.error("jsPDF.context2d.fillText: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.fillText"); - } - maxWidth = isNaN(maxWidth) ? undefined : maxWidth; - if (isFillTransparent.call(this)) { - return; - } - - var degs = rad2deg(this.ctx.transform.rotation); - - // We only use X axis as scale hint - var scale = this.ctx.transform.scaleX; - - putText.call(this, { - text: text, - x: x, - y: y, - scale: scale, - angle: degs, - align: this.textAlign, - maxWidth: maxWidth - }); - }; - - /** - * Draws text on the canvas (no fill) - * - * @name strokeText - * @function - * @param text {String} Specifies the text that will be written on the canvas - * @param x {Number} The x coordinate where to start painting the text (relative to the canvas) - * @param y {Number} The y coordinate where to start painting the text (relative to the canvas) - * @param maxWidth {Number} Optional. The maximum allowed width of the text, in pixels - * @description The strokeText() method draws text (with no fill) on the canvas. The default color of the text is black. - */ - Context2D.prototype.strokeText = function(text, x, y, maxWidth) { - if (isNaN(x) || isNaN(y) || typeof text !== "string") { - console.error("jsPDF.context2d.strokeText: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.strokeText"); - } - if (isStrokeTransparent.call(this)) { - return; - } - - maxWidth = isNaN(maxWidth) ? undefined : maxWidth; - - var degs = rad2deg(this.ctx.transform.rotation); - var scale = this.ctx.transform.scaleX; - - putText.call(this, { - text: text, - x: x, - y: y, - scale: scale, - renderingMode: "stroke", - angle: degs, - align: this.textAlign, - maxWidth: maxWidth - }); - }; - - /** - * Returns an object that contains the width of the specified text - * - * @name measureText - * @function - * @param text {String} The text to be measured - * @description The measureText() method returns an object that contains the width of the specified text, in pixels. - * @returns {Number} - */ - Context2D.prototype.measureText = function(text) { - if (typeof text !== "string") { - console.error( - "jsPDF.context2d.measureText: Invalid arguments", - arguments - ); - throw new Error( - "Invalid arguments passed to jsPDF.context2d.measureText" - ); - } - var pdf = this.pdf; - var k = this.pdf.internal.scaleFactor; - - var fontSize = pdf.internal.getFontSize(); - var txtWidth = - (pdf.getStringUnitWidth(text) * fontSize) / pdf.internal.scaleFactor; - txtWidth *= Math.round(((k * 96) / 72) * 10000) / 10000; - - var TextMetrics = function(options) { - options = options || {}; - var _width = options.width || 0; - Object.defineProperty(this, "width", { - get: function() { - return _width; - } - }); - return this; - }; - return new TextMetrics({ width: txtWidth }); - }; - - //Transformations - - /** - * Scales the current drawing bigger or smaller - * - * @name scale - * @function - * @param scalewidth {Number} Scales the width of the current drawing (1=100%, 0.5=50%, 2=200%, etc.) - * @param scaleheight {Number} Scales the height of the current drawing (1=100%, 0.5=50%, 2=200%, etc.) - * @description The scale() method scales the current drawing, bigger or smaller. - */ - Context2D.prototype.scale = function(scalewidth, scaleheight) { - if (isNaN(scalewidth) || isNaN(scaleheight)) { - console.error("jsPDF.context2d.scale: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.scale"); - } - var matrix = new Matrix(scalewidth, 0.0, 0.0, scaleheight, 0.0, 0.0); - this.ctx.transform = this.ctx.transform.multiply(matrix); - }; - - /** - * Rotates the current drawing - * - * @name rotate - * @function - * @param angle {Number} The rotation angle, in radians. - * @description To calculate from degrees to radians: degrees*Math.PI/180.
- * Example: to rotate 5 degrees, specify the following: 5*Math.PI/180 - */ - Context2D.prototype.rotate = function(angle) { - if (isNaN(angle)) { - console.error("jsPDF.context2d.rotate: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.rotate"); - } - var matrix = new Matrix( - Math.cos(angle), - Math.sin(angle), - -Math.sin(angle), - Math.cos(angle), - 0.0, - 0.0 - ); - this.ctx.transform = this.ctx.transform.multiply(matrix); - }; - - /** - * Remaps the (0,0) position on the canvas - * - * @name translate - * @function - * @param x {Number} The value to add to horizontal (x) coordinates - * @param y {Number} The value to add to vertical (y) coordinates - * @description The translate() method remaps the (0,0) position on the canvas. - */ - Context2D.prototype.translate = function(x, y) { - if (isNaN(x) || isNaN(y)) { - console.error("jsPDF.context2d.translate: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.translate"); - } - var matrix = new Matrix(1.0, 0.0, 0.0, 1.0, x, y); - this.ctx.transform = this.ctx.transform.multiply(matrix); - }; - - /** - * Replaces the current transformation matrix for the drawing - * - * @name transform - * @function - * @param a {Number} Horizontal scaling - * @param b {Number} Horizontal skewing - * @param c {Number} Vertical skewing - * @param d {Number} Vertical scaling - * @param e {Number} Horizontal moving - * @param f {Number} Vertical moving - * @description Each object on the canvas has a current transformation matrix.

The transform() method replaces the current transformation matrix. It multiplies the current transformation matrix with the matrix described by:



a c e

b d f

0 0 1

In other words, the transform() method lets you scale, rotate, move, and skew the current context. - */ - Context2D.prototype.transform = function(a, b, c, d, e, f) { - if (isNaN(a) || isNaN(b) || isNaN(c) || isNaN(d) || isNaN(e) || isNaN(f)) { - console.error("jsPDF.context2d.transform: Invalid arguments", arguments); - throw new Error("Invalid arguments passed to jsPDF.context2d.transform"); - } - var matrix = new Matrix(a, b, c, d, e, f); - this.ctx.transform = this.ctx.transform.multiply(matrix); - }; - - /** - * Resets the current transform to the identity matrix. Then runs transform() - * - * @name setTransform - * @function - * @param a {Number} Horizontal scaling - * @param b {Number} Horizontal skewing - * @param c {Number} Vertical skewing - * @param d {Number} Vertical scaling - * @param e {Number} Horizontal moving - * @param f {Number} Vertical moving - * @description Each object on the canvas has a current transformation matrix.

The setTransform() method resets the current transform to the identity matrix, and then runs transform() with the same arguments.

In other words, the setTransform() method lets you scale, rotate, move, and skew the current context. - */ - Context2D.prototype.setTransform = function(a, b, c, d, e, f) { - a = isNaN(a) ? 1 : a; - b = isNaN(b) ? 0 : b; - c = isNaN(c) ? 0 : c; - d = isNaN(d) ? 1 : d; - e = isNaN(e) ? 0 : e; - f = isNaN(f) ? 0 : f; - this.ctx.transform = new Matrix(a, b, c, d, e, f); - }; - - var hasMargins = function() { - return ( - this.margin[0] > 0 || - this.margin[1] > 0 || - this.margin[2] > 0 || - this.margin[3] > 0 - ); - }; - - /** - * Draws an image, canvas, or video onto the canvas - * - * @function - * @param img {} Specifies the image, canvas, or video element to use - * @param sx {Number} Optional. The x coordinate where to start clipping - * @param sy {Number} Optional. The y coordinate where to start clipping - * @param swidth {Number} Optional. The width of the clipped image - * @param sheight {Number} Optional. The height of the clipped image - * @param x {Number} The x coordinate where to place the image on the canvas - * @param y {Number} The y coordinate where to place the image on the canvas - * @param width {Number} Optional. The width of the image to use (stretch or reduce the image) - * @param height {Number} Optional. The height of the image to use (stretch or reduce the image) - */ - Context2D.prototype.drawImage = function( - img, - sx, - sy, - swidth, - sheight, - x, - y, - width, - height - ) { - var imageProperties = this.pdf.getImageProperties(img); - var factorX = 1; - var factorY = 1; - - var clipFactorX = 1; - var clipFactorY = 1; - - if (typeof swidth !== "undefined" && typeof width !== "undefined") { - clipFactorX = width / swidth; - clipFactorY = height / sheight; - factorX = ((imageProperties.width / swidth) * width) / swidth; - factorY = ((imageProperties.height / sheight) * height) / sheight; - } - - //is sx and sy are set and x and y not, set x and y with values of sx and sy - if (typeof x === "undefined") { - x = sx; - y = sy; - sx = 0; - sy = 0; - } - - if (typeof swidth !== "undefined" && typeof width === "undefined") { - width = swidth; - height = sheight; - } - if (typeof swidth === "undefined" && typeof width === "undefined") { - width = imageProperties.width; - height = imageProperties.height; - } - - var decomposedTransformationMatrix = this.ctx.transform.decompose(); - var angle = rad2deg(decomposedTransformationMatrix.rotate.shx); - var matrix = new Matrix(); - matrix = matrix.multiply(decomposedTransformationMatrix.translate); - matrix = matrix.multiply(decomposedTransformationMatrix.skew); - matrix = matrix.multiply(decomposedTransformationMatrix.scale); - var xRect = matrix.applyToRectangle( - new Rectangle( - x - sx * clipFactorX, - y - sy * clipFactorY, - swidth * factorX, - sheight * factorY - ) - ); - - if (this.autoPaging) { - var pageArray = getPagesByPath.call(this, xRect); - var pages = []; - for (var ii = 0; ii < pageArray.length; ii += 1) { - if (pages.indexOf(pageArray[ii]) === -1) { - pages.push(pageArray[ii]); - } - } - - sortPages(pages); - - var clipPath; - var min = pages[0]; - var max = pages[pages.length - 1]; - for (var i = min; i < max + 1; i++) { - this.pdf.setPage(i); - - var pageWidthMinusMargins = - this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; - var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; - var firstPageHeight = - this.pdf.internal.pageSize.height - - this.posY - - this.margin[0] - - this.margin[2]; - var pageHeightMinusMargins = - this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; - var previousPageHeightSum = - i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; - - if (this.ctx.clip_path.length !== 0) { - var tmpPaths = this.path; - clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); - this.path = pathPositionRedo( - clipPath, - this.posX + this.margin[3], - -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset - ); - drawPaths.call(this, "fill", true); - this.path = tmpPaths; - } - var tmpRect = JSON.parse(JSON.stringify(xRect)); - tmpRect = pathPositionRedo( - [tmpRect], - this.posX + this.margin[3], - -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset - )[0]; - - const needsClipping = (i > min || i < max) && hasMargins.call(this); - - if (needsClipping) { - this.pdf.saveGraphicsState(); - this.pdf - .rect( - this.margin[3], - this.margin[0], - pageWidthMinusMargins, - pageHeightMinusMargins, - null - ) - .clip() - .discardPath(); - } - this.pdf.addImage( - img, - "JPEG", - tmpRect.x, - tmpRect.y, - tmpRect.w, - tmpRect.h, - null, - null, - angle - ); - if (needsClipping) { - this.pdf.restoreGraphicsState(); - } - } - } else { - this.pdf.addImage( - img, - "JPEG", - xRect.x, - xRect.y, - xRect.w, - xRect.h, - null, - null, - angle - ); - } - }; - - var getPagesByPath = function(path, pageWrapX, pageWrapY) { - var result = []; - pageWrapX = pageWrapX || this.pdf.internal.pageSize.width; - pageWrapY = - pageWrapY || - this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; - var yOffset = this.posY + this.ctx.prevPageLastElemOffset; - - switch (path.type) { - default: - case "mt": - case "lt": - result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); - break; - case "arc": - result.push( - Math.floor((path.y + yOffset - path.radius) / pageWrapY) + 1 - ); - result.push( - Math.floor((path.y + yOffset + path.radius) / pageWrapY) + 1 - ); - break; - case "qct": - var rectOfQuadraticCurve = getQuadraticCurveBoundary( - this.ctx.lastPoint.x, - this.ctx.lastPoint.y, - path.x1, - path.y1, - path.x, - path.y - ); - result.push( - Math.floor((rectOfQuadraticCurve.y + yOffset) / pageWrapY) + 1 - ); - result.push( - Math.floor( - (rectOfQuadraticCurve.y + rectOfQuadraticCurve.h + yOffset) / - pageWrapY - ) + 1 - ); - break; - case "bct": - var rectOfBezierCurve = getBezierCurveBoundary( - this.ctx.lastPoint.x, - this.ctx.lastPoint.y, - path.x1, - path.y1, - path.x2, - path.y2, - path.x, - path.y - ); - result.push( - Math.floor((rectOfBezierCurve.y + yOffset) / pageWrapY) + 1 - ); - result.push( - Math.floor( - (rectOfBezierCurve.y + rectOfBezierCurve.h + yOffset) / pageWrapY - ) + 1 - ); - break; - case "rect": - result.push(Math.floor((path.y + yOffset) / pageWrapY) + 1); - result.push(Math.floor((path.y + path.h + yOffset) / pageWrapY) + 1); - } - - for (var i = 0; i < result.length; i += 1) { - while (this.pdf.internal.getNumberOfPages() < result[i]) { - addPage.call(this); - } - } - return result; - }; - - var addPage = function() { - var fillStyle = this.fillStyle; - var strokeStyle = this.strokeStyle; - var font = this.font; - var lineCap = this.lineCap; - var lineWidth = this.lineWidth; - var lineJoin = this.lineJoin; - this.pdf.addPage(); - this.fillStyle = fillStyle; - this.strokeStyle = strokeStyle; - this.font = font; - this.lineCap = lineCap; - this.lineWidth = lineWidth; - this.lineJoin = lineJoin; - }; - - var pathPositionRedo = function(paths, x, y) { - for (var i = 0; i < paths.length; i++) { - switch (paths[i].type) { - case "bct": - paths[i].x2 += x; - paths[i].y2 += y; - case "qct": - paths[i].x1 += x; - paths[i].y1 += y; - case "mt": - case "lt": - case "arc": - default: - paths[i].x += x; - paths[i].y += y; - } - } - return paths; - }; - - var sortPages = function(pages) { - return pages.sort(function(a, b) { - return a - b; - }); - }; - - var pathPreProcess = function(rule, isClip) { - var fillStyle = this.fillStyle; - var strokeStyle = this.strokeStyle; - var lineCap = this.lineCap; - var oldLineWidth = this.lineWidth; - var lineWidth = Math.abs(oldLineWidth * this.ctx.transform.scaleX); - var lineJoin = this.lineJoin; - - if (this.autoPaging) { - var origPath = JSON.parse(JSON.stringify(this.path)); - var xPath = JSON.parse(JSON.stringify(this.path)); - var clipPath; - var tmpPath; - var pages = []; - - for (var i = 0; i < xPath.length; i++) { - if (typeof xPath[i].x !== "undefined") { - var page = getPagesByPath.call(this, xPath[i]); - - for (var ii = 0; ii < page.length; ii += 1) { - if (pages.indexOf(page[ii]) === -1) { - pages.push(page[ii]); - } - } - } - } - - for (var j = 0; j < pages.length; j++) { - while (this.pdf.internal.getNumberOfPages() < pages[j]) { - addPage.call(this); - } - } - sortPages(pages); - - var min = pages[0]; - var max = pages[pages.length - 1]; - for (var k = min; k < max + 1; k++) { - this.pdf.setPage(k); - - this.fillStyle = fillStyle; - this.strokeStyle = strokeStyle; - this.lineCap = lineCap; - this.lineWidth = lineWidth; - this.lineJoin = lineJoin; - - var pageWidthMinusMargins = - this.pdf.internal.pageSize.width - this.margin[3] - this.margin[1]; - var topMargin = k === 1 ? this.posY + this.margin[0] : this.margin[0]; - var firstPageHeight = - this.pdf.internal.pageSize.height - - this.posY - - this.margin[0] - - this.margin[2]; - var pageHeightMinusMargins = - this.pdf.internal.pageSize.height - this.margin[0] - this.margin[2]; - var previousPageHeightSum = - k === 1 ? 0 : firstPageHeight + (k - 2) * pageHeightMinusMargins; - - if (this.ctx.clip_path.length !== 0) { - var tmpPaths = this.path; - clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); - this.path = pathPositionRedo( - clipPath, - this.posX + this.margin[3], - -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset - ); - drawPaths.call(this, rule, true); - this.path = tmpPaths; - } - tmpPath = JSON.parse(JSON.stringify(origPath)); - this.path = pathPositionRedo( - tmpPath, - this.posX + this.margin[3], - -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset - ); - if (isClip === false || k === 0) { - const needsClipping = (k > min || k < max) && hasMargins.call(this); - if (needsClipping) { - this.pdf.saveGraphicsState(); - this.pdf - .rect( - this.margin[3], - this.margin[0], - pageWidthMinusMargins, - pageHeightMinusMargins, - null - ) - .clip() - .discardPath(); - } - drawPaths.call(this, rule, isClip); - if (needsClipping) { - this.pdf.restoreGraphicsState(); - } - } - this.lineWidth = oldLineWidth; - } - this.path = origPath; - } else { - this.lineWidth = lineWidth; - drawPaths.call(this, rule, isClip); - this.lineWidth = oldLineWidth; - } - }; - - /** - * Processes the paths - * - * @function - * @param rule {String} - * @param isClip {Boolean} - * @private - * @ignore - */ - var drawPaths = function(rule, isClip) { - if (rule === "stroke" && !isClip && isStrokeTransparent.call(this)) { - return; - } - - if (rule !== "stroke" && !isClip && isFillTransparent.call(this)) { - return; - } - - var moves = []; - - //var alpha = (this.ctx.fillOpacity < 1) ? this.ctx.fillOpacity : this.ctx.globalAlpha; - var delta; - var xPath = this.path; - for (var i = 0; i < xPath.length; i++) { - var pt = xPath[i]; - - switch (pt.type) { - case "begin": - moves.push({ - begin: true - }); - break; - - case "close": - moves.push({ - close: true - }); - break; - - case "mt": - moves.push({ - start: pt, - deltas: [], - abs: [] - }); - break; - - case "lt": - var iii = moves.length; - if (xPath[i - 1] && !isNaN(xPath[i - 1].x)) { - delta = [pt.x - xPath[i - 1].x, pt.y - xPath[i - 1].y]; - if (iii > 0) { - for (iii; iii >= 0; iii--) { - if ( - moves[iii - 1].close !== true && - moves[iii - 1].begin !== true - ) { - moves[iii - 1].deltas.push(delta); - moves[iii - 1].abs.push(pt); - break; - } - } - } - } - break; - - case "bct": - delta = [ - pt.x1 - xPath[i - 1].x, - pt.y1 - xPath[i - 1].y, - pt.x2 - xPath[i - 1].x, - pt.y2 - xPath[i - 1].y, - pt.x - xPath[i - 1].x, - pt.y - xPath[i - 1].y - ]; - moves[moves.length - 1].deltas.push(delta); - break; - - case "qct": - var x1 = xPath[i - 1].x + (2.0 / 3.0) * (pt.x1 - xPath[i - 1].x); - var y1 = xPath[i - 1].y + (2.0 / 3.0) * (pt.y1 - xPath[i - 1].y); - var x2 = pt.x + (2.0 / 3.0) * (pt.x1 - pt.x); - var y2 = pt.y + (2.0 / 3.0) * (pt.y1 - pt.y); - var x3 = pt.x; - var y3 = pt.y; - delta = [ - x1 - xPath[i - 1].x, - y1 - xPath[i - 1].y, - x2 - xPath[i - 1].x, - y2 - xPath[i - 1].y, - x3 - xPath[i - 1].x, - y3 - xPath[i - 1].y - ]; - moves[moves.length - 1].deltas.push(delta); - break; - - case "arc": - moves.push({ - deltas: [], - abs: [], - arc: true - }); - - if (Array.isArray(moves[moves.length - 1].abs)) { - moves[moves.length - 1].abs.push(pt); - } - break; - } - } - var style; - if (!isClip) { - if (rule === "stroke") { - style = "stroke"; - } else { - style = "fill"; - } - } else { - style = null; - } - - var began = false; - for (var k = 0; k < moves.length; k++) { - if (moves[k].arc) { - var arcs = moves[k].abs; - - for (var ii = 0; ii < arcs.length; ii++) { - var arc = arcs[ii]; - - if (arc.type === "arc") { - drawArc.call( - this, - arc.x, - arc.y, - arc.radius, - arc.startAngle, - arc.endAngle, - arc.counterclockwise, - undefined, - isClip, - !began - ); - } else { - drawLine.call(this, arc.x, arc.y); - } - began = true; - } - } else if (moves[k].close === true) { - this.pdf.internal.out("h"); - began = false; - } else if (moves[k].begin !== true) { - var x = moves[k].start.x; - var y = moves[k].start.y; - drawLines.call(this, moves[k].deltas, x, y); - began = true; - } - } - - if (style) { - putStyle.call(this, style); - } - if (isClip) { - doClip.call(this); - } - }; - - var getBaseline = function(y) { - var height = - this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor; - var descent = height * (this.pdf.internal.getLineHeightFactor() - 1); - switch (this.ctx.textBaseline) { - case "bottom": - return y - descent; - case "top": - return y + height - descent; - case "hanging": - return y + height - 2 * descent; - case "middle": - return y + height / 2 - descent; - case "ideographic": - // TODO not implemented - return y; - case "alphabetic": - default: - return y; - } - }; - - var getTextBottom = function(yBaseLine) { - var height = - this.pdf.internal.getFontSize() / this.pdf.internal.scaleFactor; - var descent = height * (this.pdf.internal.getLineHeightFactor() - 1); - return yBaseLine + descent; - }; - - Context2D.prototype.createLinearGradient = function createLinearGradient() { - var canvasGradient = function canvasGradient() {}; - - canvasGradient.colorStops = []; - canvasGradient.addColorStop = function(offset, color) { - this.colorStops.push([offset, color]); - }; - - canvasGradient.getColor = function() { - if (this.colorStops.length === 0) { - return "#000000"; - } - - return this.colorStops[0][1]; - }; - - canvasGradient.isCanvasGradient = true; - return canvasGradient; - }; - Context2D.prototype.createPattern = function createPattern() { - return this.createLinearGradient(); - }; - Context2D.prototype.createRadialGradient = function createRadialGradient() { - return this.createLinearGradient(); - }; - - /** - * - * @param x Edge point X - * @param y Edge point Y - * @param r Radius - * @param a1 start angle - * @param a2 end angle - * @param counterclockwise - * @param style - * @param isClip - */ - var drawArc = function( - x, - y, - r, - a1, - a2, - counterclockwise, - style, - isClip, - includeMove - ) { - // http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html - var curves = createArc.call(this, r, a1, a2, counterclockwise); - - for (var i = 0; i < curves.length; i++) { - var curve = curves[i]; - if (i === 0) { - if (includeMove) { - doMove.call(this, curve.x1 + x, curve.y1 + y); - } else { - drawLine.call(this, curve.x1 + x, curve.y1 + y); - } - } - drawCurve.call( - this, - x, - y, - curve.x2, - curve.y2, - curve.x3, - curve.y3, - curve.x4, - curve.y4 - ); - } - - if (!isClip) { - putStyle.call(this, style); - } else { - doClip.call(this); - } - }; - - var putStyle = function(style) { - switch (style) { - case "stroke": - this.pdf.internal.out("S"); - break; - case "fill": - this.pdf.internal.out("f"); - break; - } - }; - - var doClip = function() { - this.pdf.clip(); - this.pdf.discardPath(); - }; - - var doMove = function(x, y) { - this.pdf.internal.out( - getHorizontalCoordinateString(x) + - " " + - getVerticalCoordinateString(y) + - " m" - ); - }; - - var putText = function(options) { - var textAlign; - switch (options.align) { - case "right": - case "end": - textAlign = "right"; - break; - case "center": - textAlign = "center"; - break; - case "left": - case "start": - default: - textAlign = "left"; - break; - } - - var textDimensions = this.pdf.getTextDimensions(options.text); - var yBaseLine = getBaseline.call(this, options.y); - var yBottom = getTextBottom.call(this, yBaseLine); - var yTop = yBottom - textDimensions.h; - - var pt = this.ctx.transform.applyToPoint(new Point(options.x, yBaseLine)); - - var clipPath, oldSize, oldLineWidth; - - if (this.autoPaging) { - var decomposedTransformationMatrix = this.ctx.transform.decompose(); - var matrix = new Matrix(); - matrix = matrix.multiply(decomposedTransformationMatrix.translate); - matrix = matrix.multiply(decomposedTransformationMatrix.skew); - matrix = matrix.multiply(decomposedTransformationMatrix.scale); - - var baselineRect = this.ctx.transform.applyToRectangle( - new Rectangle(options.x, yBaseLine, textDimensions.w, textDimensions.h) - ); - var textBounds = matrix.applyToRectangle( - new Rectangle(options.x, yTop, textDimensions.w, textDimensions.h) - ); - var pageArray = getPagesByPath.call(this, textBounds); - var pages = []; - for (var ii = 0; ii < pageArray.length; ii += 1) { - if (pages.indexOf(pageArray[ii]) === -1) { - pages.push(pageArray[ii]); - } - } - - sortPages(pages); - - var min = pages[0]; - var max = pages[pages.length - 1]; - for (var i = min; i < max + 1; i++) { - this.pdf.setPage(i); - - var topMargin = i === 1 ? this.posY + this.margin[0] : this.margin[0]; - var firstPageHeight = - this.pdf.internal.pageSize.height - - this.posY - - this.margin[0] - - this.margin[2]; - var pageHeightMinusBottomMargin = - this.pdf.internal.pageSize.height - this.margin[2]; - var pageHeightMinusMargins = - pageHeightMinusBottomMargin - this.margin[0]; - var pageWidthMinusRightMargin = - this.pdf.internal.pageSize.width - this.margin[1]; - var pageWidthMinusMargins = pageWidthMinusRightMargin - this.margin[3]; - var previousPageHeightSum = - i === 1 ? 0 : firstPageHeight + (i - 2) * pageHeightMinusMargins; - - if (this.ctx.clip_path.length !== 0) { - var tmpPaths = this.path; - clipPath = JSON.parse(JSON.stringify(this.ctx.clip_path)); - this.path = pathPositionRedo( - clipPath, - this.posX + this.margin[3], - -1 * previousPageHeightSum + topMargin - ); - drawPaths.call(this, "fill", true); - this.path = tmpPaths; - } - var textBoundsOnPage = pathPositionRedo( - [JSON.parse(JSON.stringify(textBounds))], - this.posX + this.margin[3], - -previousPageHeightSum + topMargin + this.ctx.prevPageLastElemOffset - )[0]; - - if (options.scale >= 0.01) { - oldSize = this.pdf.internal.getFontSize(); - this.pdf.setFontSize(oldSize * options.scale); - oldLineWidth = this.lineWidth; - this.lineWidth = oldLineWidth * options.scale; - } - - var doSlice = this.autoPaging !== "text"; - - if ( - doSlice || - textBoundsOnPage.y + textBoundsOnPage.h <= pageHeightMinusBottomMargin - ) { - if ( - doSlice || - (textBoundsOnPage.y >= topMargin && - textBoundsOnPage.x <= pageWidthMinusRightMargin) - ) { - var croppedText = doSlice - ? options.text - : this.pdf.splitTextToSize( - options.text, - options.maxWidth || - pageWidthMinusRightMargin - textBoundsOnPage.x - )[0]; - var baseLineRectOnPage = pathPositionRedo( - [JSON.parse(JSON.stringify(baselineRect))], - this.posX + this.margin[3], - -previousPageHeightSum + - topMargin + - this.ctx.prevPageLastElemOffset - )[0]; - - const needsClipping = - doSlice && (i > min || i < max) && hasMargins.call(this); - - if (needsClipping) { - this.pdf.saveGraphicsState(); - this.pdf - .rect( - this.margin[3], - this.margin[0], - pageWidthMinusMargins, - pageHeightMinusMargins, - null - ) - .clip() - .discardPath(); - } - - this.pdf.text( - croppedText, - baseLineRectOnPage.x, - baseLineRectOnPage.y, - { - angle: options.angle, - align: textAlign, - renderingMode: options.renderingMode - } - ); - - if (needsClipping) { - this.pdf.restoreGraphicsState(); - } - } - } else { - // This text is the last element of the page, but it got cut off due to the margin - // so we render it in the next page - - if (textBoundsOnPage.y < pageHeightMinusBottomMargin) { - // As a result, all other elements have their y offset increased - this.ctx.prevPageLastElemOffset += - pageHeightMinusBottomMargin - textBoundsOnPage.y; - } - } - - if (options.scale >= 0.01) { - this.pdf.setFontSize(oldSize); - this.lineWidth = oldLineWidth; - } - } - } else { - if (options.scale >= 0.01) { - oldSize = this.pdf.internal.getFontSize(); - this.pdf.setFontSize(oldSize * options.scale); - oldLineWidth = this.lineWidth; - this.lineWidth = oldLineWidth * options.scale; - } - this.pdf.text(options.text, pt.x + this.posX, pt.y + this.posY, { - angle: options.angle, - align: textAlign, - renderingMode: options.renderingMode, - maxWidth: options.maxWidth - }); - - if (options.scale >= 0.01) { - this.pdf.setFontSize(oldSize); - this.lineWidth = oldLineWidth; - } - } - }; - - var drawLine = function(x, y, prevX, prevY) { - prevX = prevX || 0; - prevY = prevY || 0; - - this.pdf.internal.out( - getHorizontalCoordinateString(x + prevX) + - " " + - getVerticalCoordinateString(y + prevY) + - " l" - ); - }; - - var drawLines = function(lines, x, y) { - return this.pdf.lines(lines, x, y, null, null); - }; - - var drawCurve = function(x, y, x1, y1, x2, y2, x3, y3) { - this.pdf.internal.out( - [ - f2(getHorizontalCoordinate(x1 + x)), - f2(getVerticalCoordinate(y1 + y)), - f2(getHorizontalCoordinate(x2 + x)), - f2(getVerticalCoordinate(y2 + y)), - f2(getHorizontalCoordinate(x3 + x)), - f2(getVerticalCoordinate(y3 + y)), - "c" - ].join(" ") - ); - }; - - /** - * Return a array of objects that represent bezier curves which approximate the circular arc centered at the origin, from startAngle to endAngle (radians) with the specified radius. - * - * Each bezier curve is an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. - * @function createArc - */ - var createArc = function(radius, startAngle, endAngle, anticlockwise) { - var EPSILON = 0.00001; // Roughly 1/1000th of a degree, see below - var twoPi = Math.PI * 2; - var halfPi = Math.PI / 2.0; - - while (startAngle > endAngle) { - startAngle = startAngle - twoPi; - } - var totalAngle = Math.abs(endAngle - startAngle); - if (totalAngle < twoPi) { - if (anticlockwise) { - totalAngle = twoPi - totalAngle; - } - } - - // Compute the sequence of arc curves, up to PI/2 at a time. - var curves = []; - - // clockwise or counterclockwise - var sgn = anticlockwise ? -1 : +1; - - var a1 = startAngle; - for (; totalAngle > EPSILON; ) { - var remain = sgn * Math.min(totalAngle, halfPi); - var a2 = a1 + remain; - curves.push(createSmallArc.call(this, radius, a1, a2)); - totalAngle -= Math.abs(a2 - a1); - a1 = a2; - } - - return curves; - }; - - /** - * Cubic bezier approximation of a circular arc centered at the origin, from (radians) a1 to a2, where a2-a1 < pi/2. The arc's radius is r. - * - * Returns an object with four points, where x1,y1 and x4,y4 are the arc's end points and x2,y2 and x3,y3 are the cubic bezier's control points. - * - * This algorithm is based on the approach described in: A. Riškus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa," Information Technology and Control, 35(4), 2006 pp. 371-378. - */ - var createSmallArc = function(r, a1, a2) { - var a = (a2 - a1) / 2.0; - - var x4 = r * Math.cos(a); - var y4 = r * Math.sin(a); - var x1 = x4; - var y1 = -y4; - - var q1 = x1 * x1 + y1 * y1; - var q2 = q1 + x1 * x4 + y1 * y4; - var k2 = ((4 / 3) * (Math.sqrt(2 * q1 * q2) - q2)) / (x1 * y4 - y1 * x4); - - var x2 = x1 - k2 * y1; - var y2 = y1 + k2 * x1; - var x3 = x2; - var y3 = -y2; - - var ar = a + a1; - var cos_ar = Math.cos(ar); - var sin_ar = Math.sin(ar); - - return { - x1: r * Math.cos(a1), - y1: r * Math.sin(a1), - x2: x2 * cos_ar - y2 * sin_ar, - y2: x2 * sin_ar + y2 * cos_ar, - x3: x3 * cos_ar - y3 * sin_ar, - y3: x3 * sin_ar + y3 * cos_ar, - x4: r * Math.cos(a2), - y4: r * Math.sin(a2) - }; - }; - - var rad2deg = function(value) { - return (value * 180) / Math.PI; - }; - - var getQuadraticCurveBoundary = function(sx, sy, cpx, cpy, ex, ey) { - var midX1 = sx + (cpx - sx) * 0.5; - var midY1 = sy + (cpy - sy) * 0.5; - var midX2 = ex + (cpx - ex) * 0.5; - var midY2 = ey + (cpy - ey) * 0.5; - var resultX1 = Math.min(sx, ex, midX1, midX2); - var resultX2 = Math.max(sx, ex, midX1, midX2); - var resultY1 = Math.min(sy, ey, midY1, midY2); - var resultY2 = Math.max(sy, ey, midY1, midY2); - return new Rectangle( - resultX1, - resultY1, - resultX2 - resultX1, - resultY2 - resultY1 - ); - }; - - //De Casteljau algorithm - var getBezierCurveBoundary = function(ax, ay, bx, by, cx, cy, dx, dy) { - var tobx = bx - ax; - var toby = by - ay; - var tocx = cx - bx; - var tocy = cy - by; - var todx = dx - cx; - var tody = dy - cy; - var precision = 40; - var d, - i, - px, - py, - qx, - qy, - rx, - ry, - tx, - ty, - sx, - sy, - x, - y, - minx, - miny, - maxx, - maxy, - toqx, - toqy, - torx, - tory, - totx, - toty; - for (i = 0; i < precision + 1; i++) { - d = i / precision; - px = ax + d * tobx; - py = ay + d * toby; - qx = bx + d * tocx; - qy = by + d * tocy; - rx = cx + d * todx; - ry = cy + d * tody; - toqx = qx - px; - toqy = qy - py; - torx = rx - qx; - tory = ry - qy; - - sx = px + d * toqx; - sy = py + d * toqy; - tx = qx + d * torx; - ty = qy + d * tory; - totx = tx - sx; - toty = ty - sy; - - x = sx + d * totx; - y = sy + d * toty; - if (i == 0) { - minx = x; - miny = y; - maxx = x; - maxy = y; - } else { - minx = Math.min(minx, x); - miny = Math.min(miny, y); - maxx = Math.max(maxx, x); - maxy = Math.max(maxy, y); - } - } - return new Rectangle( - Math.round(minx), - Math.round(miny), - Math.round(maxx - minx), - Math.round(maxy - miny) - ); - }; - - var getPrevLineDashValue = function(lineDash, lineDashOffset) { - return JSON.stringify({ - lineDash: lineDash, - lineDashOffset: lineDashOffset - }); - }; - - var setLineDash = function() { - // Avoid unnecessary line dash declarations. - if ( - !this.prevLineDash && - !this.ctx.lineDash.length && - !this.ctx.lineDashOffset - ) { - return; - } - - // Avoid unnecessary line dash declarations. - const nextLineDash = getPrevLineDashValue( - this.ctx.lineDash, - this.ctx.lineDashOffset - ); - if (this.prevLineDash !== nextLineDash) { - this.pdf.setLineDash(this.ctx.lineDash, this.ctx.lineDashOffset); - this.prevLineDash = nextLineDash; - } - }; -})(jsPDF.API); - -// DEFLATE is a complex format; to read this code, you should probably check the RFC first: - -// aliases for shorter compressed code (most minifers don't do this) -var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array; -// fixed length extra bits -var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); -// fixed distance extra bits -var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); -// code length index map -var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); -// get base, reverse index map from extra bits -var freb = function (eb, start) { - var b = new u16(31); - for (var i = 0; i < 31; ++i) { - b[i] = start += 1 << eb[i - 1]; - } - // numbers here are at max 18 bits - var r = new i32(b[30]); - for (var i = 1; i < 30; ++i) { - for (var j = b[i]; j < b[i + 1]; ++j) { - r[j] = ((j - b[i]) << 5) | i; - } - } - return { b: b, r: r }; -}; -var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r; -// we can ignore the fact that the other numbers are wrong; they never happen anyway -fl[28] = 258, revfl[258] = 28; -var _b = freb(fdeb, 0), revfd = _b.r; -// map of value to reverse (assuming 16 bits) -var rev = new u16(32768); -for (var i = 0; i < 32768; ++i) { - // reverse table algorithm from SO - var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1); - x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2); - x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4); - rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1; -} -// create huffman tree from u8 "map": index -> code length for code index -// mb (max bits) must be at most 15 -// TODO: optimize/split up? -var hMap = (function (cd, mb, r) { - var s = cd.length; - // index - var i = 0; - // u16 "map": index -> # of codes with bit length = index - var l = new u16(mb); - // length of cd must be 288 (total # of codes) - for (; i < s; ++i) { - if (cd[i]) - ++l[cd[i] - 1]; - } - // u16 "map": index -> minimum code for bit length = index - var le = new u16(mb); - for (i = 1; i < mb; ++i) { - le[i] = (le[i - 1] + l[i - 1]) << 1; - } - var co; - if (r) { - // u16 "map": index -> number of actual bits, symbol for code - co = new u16(1 << mb); - // bits to remove for reverser - var rvb = 15 - mb; - for (i = 0; i < s; ++i) { - // ignore 0 lengths - if (cd[i]) { - // num encoding both symbol and bits read - var sv = (i << 4) | cd[i]; - // free bits - var r_1 = mb - cd[i]; - // start value - var v = le[cd[i] - 1]++ << r_1; - // m is end value - for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { - // every 16 bit value starting with the code yields the same result - co[rev[v] >> rvb] = sv; - } - } - } - } - else { - co = new u16(s); - for (i = 0; i < s; ++i) { - if (cd[i]) { - co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]); - } - } - } - return co; -}); -// fixed length tree -var flt = new u8(288); -for (var i = 0; i < 144; ++i) - flt[i] = 8; -for (var i = 144; i < 256; ++i) - flt[i] = 9; -for (var i = 256; i < 280; ++i) - flt[i] = 7; -for (var i = 280; i < 288; ++i) - flt[i] = 8; -// fixed distance tree -var fdt = new u8(32); -for (var i = 0; i < 32; ++i) - fdt[i] = 5; -// fixed length map -var flm = /*#__PURE__*/ hMap(flt, 9, 0); -// fixed distance map -var fdm = /*#__PURE__*/ hMap(fdt, 5, 0); -// get end of byte -var shft = function (p) { return ((p + 7) / 8) | 0; }; -// typed array slice - allows garbage collector to free original reference, -// while being more compatible than .slice -var slc = function (v, s, e) { - if (s == null || s < 0) - s = 0; - if (e == null || e > v.length) - e = v.length; - // can't use .constructor in case user-supplied - return new u8(v.subarray(s, e)); -}; -// starting at p, write the minimum number of bits that can hold v to d -var wbits = function (d, p, v) { - v <<= p & 7; - var o = (p / 8) | 0; - d[o] |= v; - d[o + 1] |= v >> 8; -}; -// starting at p, write the minimum number of bits (>8) that can hold v to d -var wbits16 = function (d, p, v) { - v <<= p & 7; - var o = (p / 8) | 0; - d[o] |= v; - d[o + 1] |= v >> 8; - d[o + 2] |= v >> 16; -}; -// creates code lengths from a frequency table -var hTree = function (d, mb) { - // Need extra info to make a tree - var t = []; - for (var i = 0; i < d.length; ++i) { - if (d[i]) - t.push({ s: i, f: d[i] }); - } - var s = t.length; - var t2 = t.slice(); - if (!s) - return { t: et, l: 0 }; - if (s == 1) { - var v = new u8(t[0].s + 1); - v[t[0].s] = 1; - return { t: v, l: 1 }; - } - t.sort(function (a, b) { return a.f - b.f; }); - // after i2 reaches last ind, will be stopped - // freq must be greater than largest possible number of symbols - t.push({ s: -1, f: 25001 }); - var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; - t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; - // efficient algorithm from UZIP.js - // i0 is lookbehind, i2 is lookahead - after processing two low-freq - // symbols that combined have high freq, will start processing i2 (high-freq, - // non-composite) symbols instead - // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ - while (i1 != s - 1) { - l = t[t[i0].f < t[i2].f ? i0++ : i2++]; - r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; - t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; - } - var maxSym = t2[0].s; - for (var i = 1; i < s; ++i) { - if (t2[i].s > maxSym) - maxSym = t2[i].s; - } - // code lengths - var tr = new u16(maxSym + 1); - // max bits in tree - var mbt = ln(t[i1 - 1], tr, 0); - if (mbt > mb) { - // more algorithms from UZIP.js - // TODO: find out how this code works (debt) - // ind debt - var i = 0, dt = 0; - // left cost - var lft = mbt - mb, cst = 1 << lft; - t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); - for (; i < s; ++i) { - var i2_1 = t2[i].s; - if (tr[i2_1] > mb) { - dt += cst - (1 << (mbt - tr[i2_1])); - tr[i2_1] = mb; - } - else - break; - } - dt >>= lft; - while (dt > 0) { - var i2_2 = t2[i].s; - if (tr[i2_2] < mb) - dt -= 1 << (mb - tr[i2_2]++ - 1); - else - ++i; - } - for (; i >= 0 && dt; --i) { - var i2_3 = t2[i].s; - if (tr[i2_3] == mb) { - --tr[i2_3]; - ++dt; - } - } - mbt = mb; - } - return { t: new u8(tr), l: mbt }; -}; -// get the max length and assign length codes -var ln = function (n, l, d) { - return n.s == -1 - ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) - : (l[n.s] = d); -}; -// length codes generation -var lc = function (c) { - var s = c.length; - // Note that the semicolon was intentional - while (s && !c[--s]) - ; - var cl = new u16(++s); - // ind num streak - var cli = 0, cln = c[0], cls = 1; - var w = function (v) { cl[cli++] = v; }; - for (var i = 1; i <= s; ++i) { - if (c[i] == cln && i != s) - ++cls; - else { - if (!cln && cls > 2) { - for (; cls > 138; cls -= 138) - w(32754); - if (cls > 2) { - w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); - cls = 0; - } - } - else if (cls > 3) { - w(cln), --cls; - for (; cls > 6; cls -= 6) - w(8304); - if (cls > 2) - w(((cls - 3) << 5) | 8208), cls = 0; - } - while (cls--) - w(cln); - cls = 1; - cln = c[i]; - } - } - return { c: cl.subarray(0, cli), n: s }; -}; -// calculate the length of output from tree, code lengths -var clen = function (cf, cl) { - var l = 0; - for (var i = 0; i < cl.length; ++i) - l += cf[i] * cl[i]; - return l; -}; -// writes a fixed block -// returns the new bit pos -var wfblk = function (out, pos, dat) { - // no need to write 00 as type: TypedArray defaults to 0 - var s = dat.length; - var o = shft(pos + 2); - out[o] = s & 255; - out[o + 1] = s >> 8; - out[o + 2] = out[o] ^ 255; - out[o + 3] = out[o + 1] ^ 255; - for (var i = 0; i < s; ++i) - out[o + i + 4] = dat[i]; - return (o + 4 + s) * 8; -}; -// writes a block -var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { - wbits(out, p++, final); - ++lf[256]; - var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l; - var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l; - var _c = lc(dlt), lclt = _c.c, nlc = _c.n; - var _d = lc(ddt), lcdt = _d.c, ndc = _d.n; - var lcfreq = new u16(19); - for (var i = 0; i < lclt.length; ++i) - ++lcfreq[lclt[i] & 31]; - for (var i = 0; i < lcdt.length; ++i) - ++lcfreq[lcdt[i] & 31]; - var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l; - var nlcc = 19; - for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) - ; - var flen = (bl + 5) << 3; - var ftlen = clen(lf, flt) + clen(df, fdt) + eb; - var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]; - if (bs >= 0 && flen <= ftlen && flen <= dtlen) - return wfblk(out, p, dat.subarray(bs, bs + bl)); - var lm, ll, dm, dl; - wbits(out, p, 1 + (dtlen < ftlen)), p += 2; - if (dtlen < ftlen) { - lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; - var llm = hMap(lct, mlcb, 0); - wbits(out, p, nlc - 257); - wbits(out, p + 5, ndc - 1); - wbits(out, p + 10, nlcc - 4); - p += 14; - for (var i = 0; i < nlcc; ++i) - wbits(out, p + 3 * i, lct[clim[i]]); - p += 3 * nlcc; - var lcts = [lclt, lcdt]; - for (var it = 0; it < 2; ++it) { - var clct = lcts[it]; - for (var i = 0; i < clct.length; ++i) { - var len = clct[i] & 31; - wbits(out, p, llm[len]), p += lct[len]; - if (len > 15) - wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12; - } - } - } - else { - lm = flm, ll = flt, dm = fdm, dl = fdt; - } - for (var i = 0; i < li; ++i) { - var sym = syms[i]; - if (sym > 255) { - var len = (sym >> 18) & 31; - wbits16(out, p, lm[len + 257]), p += ll[len + 257]; - if (len > 7) - wbits(out, p, (sym >> 23) & 31), p += fleb[len]; - var dst = sym & 31; - wbits16(out, p, dm[dst]), p += dl[dst]; - if (dst > 3) - wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst]; - } - else { - wbits16(out, p, lm[sym]), p += ll[sym]; - } - } - wbits16(out, p, lm[256]); - return p + ll[256]; -}; -// deflate options (nice << 13) | chain -var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); -// empty -var et = /*#__PURE__*/ new u8(0); -// compresses data into a raw DEFLATE buffer -var dflt = function (dat, lvl, plvl, pre, post, st) { - var s = st.z || dat.length; - var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); - // writing to this writes to the output buffer - var w = o.subarray(pre, o.length - post); - var lst = st.l; - var pos = (st.r || 0) & 7; - if (lvl) { - if (pos) - w[0] = st.r >> 3; - var opt = deo[lvl - 1]; - var n = opt >> 13, c = opt & 8191; - var msk_1 = (1 << plvl) - 1; - // prev 2-byte val map curr 2-byte val map - var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1); - var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; - var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; - // 24576 is an arbitrary number of maximum symbols per block - // 424 buffer for last block - var syms = new i32(25000); - // length/literal freq distance freq - var lf = new u16(288), df = new u16(32); - // l/lcnt exbits index l/lind waitdx blkpos - var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0; - for (; i + 2 < s; ++i) { - // hash value - var hv = hsh(i); - // index mod 32768 previous index mod - var imod = i & 32767, pimod = head[hv]; - prev[imod] = pimod; - head[hv] = imod; - // We always should modify head and prev, but only add symbols if - // this data is not yet processed ("wait" for wait index) - if (wi <= i) { - // bytes remaining - var rem = s - i; - if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) { - pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); - li = lc_1 = eb = 0, bs = i; - for (var j = 0; j < 286; ++j) - lf[j] = 0; - for (var j = 0; j < 30; ++j) - df[j] = 0; - } - // len dist chain - var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767; - if (rem > 2 && hv == hsh(i - dif)) { - var maxn = Math.min(n, rem) - 1; - var maxd = Math.min(32767, i); - // max possible length - // not capped at dif because decompressors implement "rolling" index population - var ml = Math.min(258, rem); - while (dif <= maxd && --ch_1 && imod != pimod) { - if (dat[i + l] == dat[i + l - dif]) { - var nl = 0; - for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) - ; - if (nl > l) { - l = nl, d = dif; - // break out early when we reach "nice" (we are satisfied enough) - if (nl > maxn) - break; - // now, find the rarest 2-byte sequence within this - // length of literals and search for that instead. - // Much faster than just using the start - var mmd = Math.min(dif, nl - 2); - var md = 0; - for (var j = 0; j < mmd; ++j) { - var ti = i - dif + j & 32767; - var pti = prev[ti]; - var cd = ti - pti & 32767; - if (cd > md) - md = cd, pimod = ti; - } - } - } - // check the previous match - imod = pimod, pimod = prev[imod]; - dif += imod - pimod & 32767; - } - } - // d will be nonzero only when a match was found - if (d) { - // store both dist and len data in one int32 - // Make sure this is recognized as a len/dist with 28th bit (2^28) - syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; - var lin = revfl[l] & 31, din = revfd[d] & 31; - eb += fleb[lin] + fdeb[din]; - ++lf[257 + lin]; - ++df[din]; - wi = i + l; - ++lc_1; - } - else { - syms[li++] = dat[i]; - ++lf[dat[i]]; - } - } - } - for (i = Math.max(i, wi); i < s; ++i) { - syms[li++] = dat[i]; - ++lf[dat[i]]; - } - pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); - if (!lst) { - st.r = (pos & 7) | w[(pos / 8) | 0] << 3; - // shft(pos) now 1 less if pos & 7 != 0 - pos -= 7; - st.h = head, st.p = prev, st.i = i, st.w = wi; - } - } - else { - for (var i = st.w || 0; i < s + lst; i += 65535) { - // end - var e = i + 65535; - if (e >= s) { - // write final block - w[(pos / 8) | 0] = lst; - e = s; - } - pos = wfblk(w, pos + 1, dat.subarray(i, e)); - } - st.i = s; - } - return slc(o, 0, pre + shft(pos) + post); -}; -// Adler32 -var adler = function () { - var a = 1, b = 0; - return { - p: function (d) { - // closures have awful performance - var n = a, m = b; - var l = d.length | 0; - for (var i = 0; i != l;) { - var e = Math.min(i + 2655, l); - for (; i < e; ++i) - m += n += d[i]; - n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); - } - a = n, b = m; - }, - d: function () { - a %= 65521, b %= 65521; - return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8); - } - }; -}; -// deflate with opts -var dopt = function (dat, opt, pre, post, st) { - if (!st) { - st = { l: 1 }; - if (opt.dictionary) { - var dict = opt.dictionary.subarray(-32768); - var newDat = new u8(dict.length + dat.length); - newDat.set(dict); - newDat.set(dat, dict.length); - dat = newDat; - st.w = dict.length; - } - } - return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, st); -}; -// write bytes -var wbytes = function (d, b, v) { - for (; v; ++b) - d[b] = v, v >>>= 8; -}; -// zlib header -var zlh = function (c, o) { - var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; - c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32); - c[1] |= 31 - ((c[0] << 8) | c[1]) % 31; - if (o.dictionary) { - var h = adler(); - h.p(o.dictionary); - wbytes(c, 2, h.d()); - } -}; -/** - * Compress data with Zlib - * @param data The data to compress - * @param opts The compression options - * @returns The zlib-compressed version of the data - */ -function zlibSync(data, opts) { - if (!opts) - opts = {}; - var a = adler(); - a.p(data); - var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4); - return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; -} -// text decoder -var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); -// text decoder stream -var tds = 0; -try { - td.decode(et, { stream: true }); - tds = 1; -} -catch (e) { } - -/** - * @license - * jsPDF filters PlugIn - * Copyright (c) 2014 Aras Abbasi - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -(function(jsPDFAPI) { - - var ASCII85Encode = function(a) { - var b, c, d, e, f, g, h, i, j, k; - // eslint-disable-next-line no-control-regex - for ( - !/[^\x00-\xFF]/.test(a), - b = "\x00\x00\x00\x00".slice(a.length % 4 || 4), - a += b, - c = [], - d = 0, - e = a.length; - e > d; - d += 4 - ) - (f = - (a.charCodeAt(d) << 24) + - (a.charCodeAt(d + 1) << 16) + - (a.charCodeAt(d + 2) << 8) + - a.charCodeAt(d + 3)), - 0 !== f - ? ((k = f % 85), - (f = (f - k) / 85), - (j = f % 85), - (f = (f - j) / 85), - (i = f % 85), - (f = (f - i) / 85), - (h = f % 85), - (f = (f - h) / 85), - (g = f % 85), - c.push(g + 33, h + 33, i + 33, j + 33, k + 33)) - : c.push(122); - return ( - (function(a, b) { - for (var c = b; c > 0; c--) a.pop(); - })(c, b.length), - String.fromCharCode.apply(String, c) + "~>" - ); - }; - - var ASCII85Decode = function(a) { - var c, - d, - e, - f, - g, - h = String, - l = "length", - w = 255, - x = "charCodeAt", - y = "slice", - z = "replace"; - for ( - "~>" === a[y](-2), - a = a[y](0, -2) - [z](/\s/g, "") - [z]("z", "!!!!!"), - c = "uuuuu"[y](a[l] % 5 || 5), - a += c, - e = [], - f = 0, - g = a[l]; - g > f; - f += 5 - ) - (d = - 52200625 * (a[x](f) - 33) + - 614125 * (a[x](f + 1) - 33) + - 7225 * (a[x](f + 2) - 33) + - 85 * (a[x](f + 3) - 33) + - (a[x](f + 4) - 33)), - e.push(w & (d >> 24), w & (d >> 16), w & (d >> 8), w & d); - return ( - (function(a, b) { - for (var c = b; c > 0; c--) a.pop(); - })(e, c[l]), - h.fromCharCode.apply(h, e) - ); - }; - - var ASCIIHexEncode = function(value) { - return ( - value - .split("") - .map(function(value) { - return ("0" + value.charCodeAt().toString(16)).slice(-2); - }) - .join("") + ">" - ); - }; - - var ASCIIHexDecode = function(value) { - var regexCheckIfHex = new RegExp(/^([0-9A-Fa-f]{2})+$/); - value = value.replace(/\s/g, ""); - if (value.indexOf(">") !== -1) { - value = value.substr(0, value.indexOf(">")); - } - if (value.length % 2) { - value += "0"; - } - if (regexCheckIfHex.test(value) === false) { - return ""; - } - var result = ""; - for (var i = 0; i < value.length; i += 2) { - result += String.fromCharCode("0x" + (value[i] + value[i + 1])); - } - return result; - }; - /* - var FlatePredictors = { - None: 1, - TIFF: 2, - PNG_None: 10, - PNG_Sub: 11, - PNG_Up: 12, - PNG_Average: 13, - PNG_Paeth: 14, - PNG_Optimum: 15 - }; - */ - - var FlateEncode = function(data) { - var arr = new Uint8Array(data.length); - var i = data.length; - while (i--) { - arr[i] = data.charCodeAt(i); - } - arr = zlibSync(arr); - data = arr.reduce(function(data, byte) { - return data + String.fromCharCode(byte); - }, ""); - return data; - }; - - jsPDFAPI.processDataByFilters = function(origData, filterChain) { - var i = 0; - var data = origData || ""; - var reverseChain = []; - filterChain = filterChain || []; - - if (typeof filterChain === "string") { - filterChain = [filterChain]; - } - - for (i = 0; i < filterChain.length; i += 1) { - switch (filterChain[i]) { - case "ASCII85Decode": - case "/ASCII85Decode": - data = ASCII85Decode(data); - reverseChain.push("/ASCII85Encode"); - break; - case "ASCII85Encode": - case "/ASCII85Encode": - data = ASCII85Encode(data); - reverseChain.push("/ASCII85Decode"); - break; - case "ASCIIHexDecode": - case "/ASCIIHexDecode": - data = ASCIIHexDecode(data); - reverseChain.push("/ASCIIHexEncode"); - break; - case "ASCIIHexEncode": - case "/ASCIIHexEncode": - data = ASCIIHexEncode(data); - reverseChain.push("/ASCIIHexDecode"); - break; - case "FlateEncode": - case "/FlateEncode": - data = FlateEncode(data); - reverseChain.push("/FlateDecode"); - break; - default: - throw new Error( - 'The filter: "' + filterChain[i] + '" is not implemented' - ); - } - } - - return { data: data, reverseChain: reverseChain.reverse().join(" ") }; - }; -})(jsPDF.API); - -/** - * @license - * ==================================================================== - * Copyright (c) 2013 Youssef Beddad, youssef.beddad@gmail.com - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -/** - * jsPDF JavaScript plugin - * - * @name javascript - * @module - */ -(function(jsPDFAPI) { - /** - * @name addJS - * @function - * @param {string} javascript The javascript to be embedded into the PDF-file. - * @returns {jsPDF} - */ - jsPDFAPI.addJS = function(javascript) { - var jsNamesObj; - var jsJsObj; - // Escape only unescaped parentheses, without double-escaping already escaped ones - function escapeParens(str) { - let out = ""; - for (let i = 0; i < str.length; i++) { - const ch = str[i]; - if (ch === "(" || ch === ")") { - // Count preceding backslashes to determine if the paren is already escaped - let bs = 0; - for (let j = i - 1; j >= 0 && str[j] === "\\"; j--) { - bs++; - } - if (bs % 2 === 0) { - out += "\\" + ch; - } else { - out += ch; - } - } else { - out += ch; - } - } - return out; - } - const text = escapeParens(javascript); - - this.internal.events.subscribe("postPutResources", function() { - jsNamesObj = this.internal.newObject(); - this.internal.out("<<"); - this.internal.out("/Names [(EmbeddedJS) " + (jsNamesObj + 1) + " 0 R]"); - this.internal.out(">>"); - this.internal.out("endobj"); - - jsJsObj = this.internal.newObject(); - this.internal.out("<<"); - this.internal.out("/S /JavaScript"); - // The sanitized 'text' is now safe to be enclosed in parentheses - this.internal.out("/JS (" + text + ")"); - this.internal.out(">>"); - this.internal.out("endobj"); - }); - - this.internal.events.subscribe("putCatalog", function() { - if (jsNamesObj !== undefined && jsJsObj !== undefined) { - this.internal.out("/Names <>"); - } - }); - return this; - }; -})(jsPDF.API); - -/** - * @license - * Copyright (c) 2014 Steven Spungin (TwelveTone LLC) steven@twelvetone.tv - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * jsPDF Outline PlugIn - * - * Generates a PDF Outline - * @name outline - * @module - */ -(function(jsPDFAPI) { - - var namesOid; - //var destsGoto = []; - - jsPDFAPI.events.push([ - "postPutResources", - function() { - var pdf = this; - var rx = /^(\d+) 0 obj$/; - - // Write action goto objects for each page - // this.outline.destsGoto = []; - // for (var i = 0; i < totalPages; i++) { - // var id = pdf.internal.newObject(); - // this.outline.destsGoto.push(id); - // pdf.internal.write("<> endobj"); - // } - // - // for (var i = 0; i < dests.length; i++) { - // pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 - // R"); - // } - // - if (this.outline.root.children.length > 0) { - var lines = pdf.outline.render().split(/\r\n/); - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - var m = rx.exec(line); - if (m != null) { - var oid = m[1]; - pdf.internal.newObjectDeferredBegin(oid, false); - } - pdf.internal.write(line); - } - } - - // This code will write named destination for each page reference - // (page_1, etc) - if (this.outline.createNamedDestinations) { - var totalPages = this.internal.pages.length; - // WARNING: this assumes jsPDF starts on page 3 and pageIDs - // follow 5, 7, 9, etc - // Write destination objects for each page - var dests = []; - for (var i = 0; i < totalPages; i++) { - var id = pdf.internal.newObject(); - dests.push(id); - var info = pdf.internal.getPageInfo(i + 1); - pdf.internal.write( - "<< /D[" + info.objId + " 0 R /XYZ null null null]>> endobj" - ); - } - - // assign a name for each destination - var names2Oid = pdf.internal.newObject(); - pdf.internal.write("<< /Names [ "); - for (var i = 0; i < dests.length; i++) { - pdf.internal.write("(page_" + (i + 1) + ")" + dests[i] + " 0 R"); - } - pdf.internal.write(" ] >>", "endobj"); - - // var kids = pdf.internal.newObject(); - // pdf.internal.write('<< /Kids [ ' + names2Oid + ' 0 R'); - // pdf.internal.write(' ] >>', 'endobj'); - - namesOid = pdf.internal.newObject(); - pdf.internal.write("<< /Dests " + names2Oid + " 0 R"); - pdf.internal.write(">>", "endobj"); - } - } - ]); - - jsPDFAPI.events.push([ - "putCatalog", - function() { - var pdf = this; - if (pdf.outline.root.children.length > 0) { - pdf.internal.write( - "/Outlines", - this.outline.makeRef(this.outline.root) - ); - if (this.outline.createNamedDestinations) { - pdf.internal.write("/Names " + namesOid + " 0 R"); - } - // Open with Bookmarks showing - // pdf.internal.write("/PageMode /UseOutlines"); - } - } - ]); - - jsPDFAPI.events.push([ - "initialized", - function() { - var pdf = this; - - pdf.outline = { - createNamedDestinations: false, - root: { - children: [] - } - }; - - /** - * Options: pageNumber - */ - pdf.outline.add = function(parent, title, options) { - var item = { - title: title, - options: options, - children: [] - }; - if (parent == null) { - parent = this.root; - } - parent.children.push(item); - return item; - }; - - pdf.outline.render = function() { - this.ctx = {}; - this.ctx.val = ""; - this.ctx.pdf = pdf; - - this.genIds_r(this.root); - this.renderRoot(this.root); - this.renderItems(this.root); - - return this.ctx.val; - }; - - pdf.outline.genIds_r = function(node) { - node.id = pdf.internal.newObjectDeferred(); - for (var i = 0; i < node.children.length; i++) { - this.genIds_r(node.children[i]); - } - }; - - pdf.outline.renderRoot = function(node) { - this.objStart(node); - this.line("/Type /Outlines"); - if (node.children.length > 0) { - this.line("/First " + this.makeRef(node.children[0])); - this.line( - "/Last " + this.makeRef(node.children[node.children.length - 1]) - ); - } - this.line( - "/Count " + - this.count_r( - { - count: 0 - }, - node - ) - ); - this.objEnd(); - }; - - pdf.outline.renderItems = function(node) { - var getVerticalCoordinateString = this.ctx.pdf.internal - .getVerticalCoordinateString; - for (var i = 0; i < node.children.length; i++) { - var item = node.children[i]; - this.objStart(item); - - this.line("/Title " + this.makeString(item.title)); - - this.line("/Parent " + this.makeRef(node)); - if (i > 0) { - this.line("/Prev " + this.makeRef(node.children[i - 1])); - } - if (i < node.children.length - 1) { - this.line("/Next " + this.makeRef(node.children[i + 1])); - } - if (item.children.length > 0) { - this.line("/First " + this.makeRef(item.children[0])); - this.line( - "/Last " + this.makeRef(item.children[item.children.length - 1]) - ); - } - - var count = (this.count = this.count_r( - { - count: 0 - }, - item - )); - if (count > 0) { - this.line("/Count " + count); - } - - if (item.options) { - if (item.options.pageNumber) { - // Explicit Destination - //WARNING this assumes page ids are 3,5,7, etc. - var info = pdf.internal.getPageInfo(item.options.pageNumber); - this.line( - "/Dest " + - "[" + - info.objId + - " 0 R /XYZ 0 " + - getVerticalCoordinateString(0) + - " 0]" - ); - // this line does not work on all clients (pageNumber instead of page ref) - //this.line('/Dest ' + '[' + (item.options.pageNumber - 1) + ' /XYZ 0 ' + this.ctx.pdf.internal.pageSize.getHeight() + ' 0]'); - - // Named Destination - // this.line('/Dest (page_' + (item.options.pageNumber) + ')'); - - // Action Destination - // var id = pdf.internal.newObject(); - // pdf.internal.write('<> endobj'); - // this.line('/A ' + id + ' 0 R' ); - } - } - this.objEnd(); - } - for (var z = 0; z < node.children.length; z++) { - this.renderItems(node.children[z]); - } - }; - - pdf.outline.line = function(text) { - this.ctx.val += text + "\r\n"; - }; - - pdf.outline.makeRef = function(node) { - return node.id + " 0 R"; - }; - - pdf.outline.makeString = function(val) { - return "(" + pdf.internal.pdfEscape(val) + ")"; - }; - - pdf.outline.objStart = function(node) { - this.ctx.val += "\r\n" + node.id + " 0 obj" + "\r\n<<\r\n"; - }; - - pdf.outline.objEnd = function() { - this.ctx.val += ">> \r\n" + "endobj" + "\r\n"; - }; - - pdf.outline.count_r = function(ctx, node) { - for (var i = 0; i < node.children.length; i++) { - ctx.count++; - this.count_r(ctx, node.children[i]); - } - return ctx.count; - }; - } - ]); - - return this; -})(jsPDF.API); - -function decode(bytes, encoding = 'utf8') { - const decoder = new TextDecoder(encoding); - return decoder.decode(bytes); -} -const encoder = new TextEncoder(); -function encode(str) { - return encoder.encode(str); -} - -const defaultByteLength = 1024 * 8; -const hostBigEndian = (() => { - const array = new Uint8Array(4); - const view = new Uint32Array(array.buffer); - return !((view[0] = 1) & array[0]); -})(); -const typedArrays = { - int8: globalThis.Int8Array, - uint8: globalThis.Uint8Array, - int16: globalThis.Int16Array, - uint16: globalThis.Uint16Array, - int32: globalThis.Int32Array, - uint32: globalThis.Uint32Array, - uint64: globalThis.BigUint64Array, - int64: globalThis.BigInt64Array, - float32: globalThis.Float32Array, - float64: globalThis.Float64Array, -}; -class IOBuffer { - /** - * Reference to the internal ArrayBuffer object. - */ - buffer; - /** - * Byte length of the internal ArrayBuffer. - */ - byteLength; - /** - * Byte offset of the internal ArrayBuffer. - */ - byteOffset; - /** - * Byte length of the internal ArrayBuffer. - */ - length; - /** - * The current offset of the buffer's pointer. - */ - offset; - lastWrittenByte; - littleEndian; - _data; - _mark; - _marks; - /** - * Create a new IOBuffer. - * @param data - The data to construct the IOBuffer with. - * If data is a number, it will be the new buffer's length
- * If data is `undefined`, the buffer will be initialized with a default length of 8Kb
- * If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance, - * or a Node.js Buffer, a view will be created over the underlying ArrayBuffer. - * @param options - An object for the options. - * @returns A new IOBuffer instance. - */ - constructor(data = defaultByteLength, options = {}) { - let dataIsGiven = false; - if (typeof data === 'number') { - data = new ArrayBuffer(data); - } - else { - dataIsGiven = true; - this.lastWrittenByte = data.byteLength; - } - const offset = options.offset ? options.offset >>> 0 : 0; - const byteLength = data.byteLength - offset; - let dvOffset = offset; - if (ArrayBuffer.isView(data) || data instanceof IOBuffer) { - if (data.byteLength !== data.buffer.byteLength) { - dvOffset = data.byteOffset + offset; - } - data = data.buffer; - } - if (dataIsGiven) { - this.lastWrittenByte = byteLength; - } - else { - this.lastWrittenByte = 0; - } - this.buffer = data; - this.length = byteLength; - this.byteLength = byteLength; - this.byteOffset = dvOffset; - this.offset = 0; - this.littleEndian = true; - this._data = new DataView(this.buffer, dvOffset, byteLength); - this._mark = 0; - this._marks = []; - } - /** - * Checks if the memory allocated to the buffer is sufficient to store more - * bytes after the offset. - * @param byteLength - The needed memory in bytes. - * @returns `true` if there is sufficient space and `false` otherwise. - */ - available(byteLength = 1) { - return this.offset + byteLength <= this.length; - } - /** - * Check if little-endian mode is used for reading and writing multi-byte - * values. - * @returns `true` if little-endian mode is used, `false` otherwise. - */ - isLittleEndian() { - return this.littleEndian; - } - /** - * Set little-endian mode for reading and writing multi-byte values. - * @returns This. - */ - setLittleEndian() { - this.littleEndian = true; - return this; - } - /** - * Check if big-endian mode is used for reading and writing multi-byte values. - * @returns `true` if big-endian mode is used, `false` otherwise. - */ - isBigEndian() { - return !this.littleEndian; - } - /** - * Switches to big-endian mode for reading and writing multi-byte values. - * @returns This. - */ - setBigEndian() { - this.littleEndian = false; - return this; - } - /** - * Move the pointer n bytes forward. - * @param n - Number of bytes to skip. - * @returns This. - */ - skip(n = 1) { - this.offset += n; - return this; - } - /** - * Move the pointer n bytes backward. - * @param n - Number of bytes to move back. - * @returns This. - */ - back(n = 1) { - this.offset -= n; - return this; - } - /** - * Move the pointer to the given offset. - * @param offset - The offset to move to. - * @returns This. - */ - seek(offset) { - this.offset = offset; - return this; - } - /** - * Store the current pointer offset. - * @see {@link IOBuffer#reset} - * @returns This. - */ - mark() { - this._mark = this.offset; - return this; - } - /** - * Move the pointer back to the last pointer offset set by mark. - * @see {@link IOBuffer#mark} - * @returns This. - */ - reset() { - this.offset = this._mark; - return this; - } - /** - * Push the current pointer offset to the mark stack. - * @see {@link IOBuffer#popMark} - * @returns This. - */ - pushMark() { - this._marks.push(this.offset); - return this; - } - /** - * Pop the last pointer offset from the mark stack, and set the current - * pointer offset to the popped value. - * @see {@link IOBuffer#pushMark} - * @returns This. - */ - popMark() { - const offset = this._marks.pop(); - if (offset === undefined) { - throw new Error('Mark stack empty'); - } - this.seek(offset); - return this; - } - /** - * Move the pointer offset back to 0. - * @returns This. - */ - rewind() { - this.offset = 0; - return this; - } - /** - * Make sure the buffer has sufficient memory to write a given byteLength at - * the current pointer offset. - * If the buffer's memory is insufficient, this method will create a new - * buffer (a copy) with a length that is twice (byteLength + current offset). - * @param byteLength - The needed memory in bytes. - * @returns This. - */ - ensureAvailable(byteLength = 1) { - if (!this.available(byteLength)) { - const lengthNeeded = this.offset + byteLength; - const newLength = lengthNeeded * 2; - const newArray = new Uint8Array(newLength); - newArray.set(new Uint8Array(this.buffer)); - this.buffer = newArray.buffer; - this.length = newLength; - this.byteLength = newLength; - this._data = new DataView(this.buffer); - } - return this; - } - /** - * Read a byte and return false if the byte's value is 0, or true otherwise. - * Moves pointer forward by one byte. - * @returns The read boolean. - */ - readBoolean() { - return this.readUint8() !== 0; - } - /** - * Read a signed 8-bit integer and move pointer forward by 1 byte. - * @returns The read byte. - */ - readInt8() { - return this._data.getInt8(this.offset++); - } - /** - * Read an unsigned 8-bit integer and move pointer forward by 1 byte. - * @returns The read byte. - */ - readUint8() { - return this._data.getUint8(this.offset++); - } - /** - * Alias for {@link IOBuffer#readUint8}. - * @returns The read byte. - */ - readByte() { - return this.readUint8(); - } - /** - * Read `n` bytes and move pointer forward by `n` bytes. - * @param n - Number of bytes to read. - * @returns The read bytes. - */ - readBytes(n = 1) { - return this.readArray(n, 'uint8'); - } - /** - * Creates an array of corresponding to the type `type` and size `size`. - * For example type `uint8` will create a `Uint8Array`. - * @param size - size of the resulting array - * @param type - number type of elements to read - * @returns The read array. - */ - readArray(size, type) { - const bytes = typedArrays[type].BYTES_PER_ELEMENT * size; - const offset = this.byteOffset + this.offset; - const slice = this.buffer.slice(offset, offset + bytes); - if (this.littleEndian === hostBigEndian && - type !== 'uint8' && - type !== 'int8') { - const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes)); - slice.reverse(); - const returnArray = new typedArrays[type](slice.buffer); - this.offset += bytes; - returnArray.reverse(); - return returnArray; - } - const returnArray = new typedArrays[type](slice); - this.offset += bytes; - return returnArray; - } - /** - * Read a 16-bit signed integer and move pointer forward by 2 bytes. - * @returns The read value. - */ - readInt16() { - const value = this._data.getInt16(this.offset, this.littleEndian); - this.offset += 2; - return value; - } - /** - * Read a 16-bit unsigned integer and move pointer forward by 2 bytes. - * @returns The read value. - */ - readUint16() { - const value = this._data.getUint16(this.offset, this.littleEndian); - this.offset += 2; - return value; - } - /** - * Read a 32-bit signed integer and move pointer forward by 4 bytes. - * @returns The read value. - */ - readInt32() { - const value = this._data.getInt32(this.offset, this.littleEndian); - this.offset += 4; - return value; - } - /** - * Read a 32-bit unsigned integer and move pointer forward by 4 bytes. - * @returns The read value. - */ - readUint32() { - const value = this._data.getUint32(this.offset, this.littleEndian); - this.offset += 4; - return value; - } - /** - * Read a 32-bit floating number and move pointer forward by 4 bytes. - * @returns The read value. - */ - readFloat32() { - const value = this._data.getFloat32(this.offset, this.littleEndian); - this.offset += 4; - return value; - } - /** - * Read a 64-bit floating number and move pointer forward by 8 bytes. - * @returns The read value. - */ - readFloat64() { - const value = this._data.getFloat64(this.offset, this.littleEndian); - this.offset += 8; - return value; - } - /** - * Read a 64-bit signed integer number and move pointer forward by 8 bytes. - * @returns The read value. - */ - readBigInt64() { - const value = this._data.getBigInt64(this.offset, this.littleEndian); - this.offset += 8; - return value; - } - /** - * Read a 64-bit unsigned integer number and move pointer forward by 8 bytes. - * @returns The read value. - */ - readBigUint64() { - const value = this._data.getBigUint64(this.offset, this.littleEndian); - this.offset += 8; - return value; - } - /** - * Read a 1-byte ASCII character and move pointer forward by 1 byte. - * @returns The read character. - */ - readChar() { - // eslint-disable-next-line unicorn/prefer-code-point - return String.fromCharCode(this.readInt8()); - } - /** - * Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes. - * @param n - Number of characters to read. - * @returns The read characters. - */ - readChars(n = 1) { - let result = ''; - for (let i = 0; i < n; i++) { - result += this.readChar(); - } - return result; - } - /** - * Read the next `n` bytes, return a UTF-8 decoded string and move pointer - * forward by `n` bytes. - * @param n - Number of bytes to read. - * @returns The decoded string. - */ - readUtf8(n = 1) { - return decode(this.readBytes(n)); - } - /** - * Read the next `n` bytes, return a string decoded with `encoding` and move pointer - * forward by `n` bytes. - * If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8} - * @param n - Number of bytes to read. - * @param encoding - The encoding to use. Default is 'utf8'. - * @returns The decoded string. - */ - decodeText(n = 1, encoding = 'utf8') { - return decode(this.readBytes(n), encoding); - } - /** - * Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer - * forward by 1 byte. - * @param value - The value to write. - * @returns This. - */ - writeBoolean(value) { - this.writeUint8(value ? 0xff : 0x00); - return this; - } - /** - * Write `value` as an 8-bit signed integer and move pointer forward by 1 byte. - * @param value - The value to write. - * @returns This. - */ - writeInt8(value) { - this.ensureAvailable(1); - this._data.setInt8(this.offset++, value); - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as an 8-bit unsigned integer and move pointer forward by 1 - * byte. - * @param value - The value to write. - * @returns This. - */ - writeUint8(value) { - this.ensureAvailable(1); - this._data.setUint8(this.offset++, value); - this._updateLastWrittenByte(); - return this; - } - /** - * An alias for {@link IOBuffer#writeUint8}. - * @param value - The value to write. - * @returns This. - */ - writeByte(value) { - return this.writeUint8(value); - } - /** - * Write all elements of `bytes` as uint8 values and move pointer forward by - * `bytes.length` bytes. - * @param bytes - The array of bytes to write. - * @returns This. - */ - writeBytes(bytes) { - this.ensureAvailable(bytes.length); - // eslint-disable-next-line @typescript-eslint/prefer-for-of - for (let i = 0; i < bytes.length; i++) { - this._data.setUint8(this.offset++, bytes[i]); - } - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 16-bit signed integer and move pointer forward by 2 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeInt16(value) { - this.ensureAvailable(2); - this._data.setInt16(this.offset, value, this.littleEndian); - this.offset += 2; - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 16-bit unsigned integer and move pointer forward by 2 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeUint16(value) { - this.ensureAvailable(2); - this._data.setUint16(this.offset, value, this.littleEndian); - this.offset += 2; - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 32-bit signed integer and move pointer forward by 4 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeInt32(value) { - this.ensureAvailable(4); - this._data.setInt32(this.offset, value, this.littleEndian); - this.offset += 4; - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 32-bit unsigned integer and move pointer forward by 4 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeUint32(value) { - this.ensureAvailable(4); - this._data.setUint32(this.offset, value, this.littleEndian); - this.offset += 4; - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 32-bit floating number and move pointer forward by 4 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeFloat32(value) { - this.ensureAvailable(4); - this._data.setFloat32(this.offset, value, this.littleEndian); - this.offset += 4; - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 64-bit floating number and move pointer forward by 8 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeFloat64(value) { - this.ensureAvailable(8); - this._data.setFloat64(this.offset, value, this.littleEndian); - this.offset += 8; - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 64-bit signed bigint and move pointer forward by 8 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeBigInt64(value) { - this.ensureAvailable(8); - this._data.setBigInt64(this.offset, value, this.littleEndian); - this.offset += 8; - this._updateLastWrittenByte(); - return this; - } - /** - * Write `value` as a 64-bit unsigned bigint and move pointer forward by 8 - * bytes. - * @param value - The value to write. - * @returns This. - */ - writeBigUint64(value) { - this.ensureAvailable(8); - this._data.setBigUint64(this.offset, value, this.littleEndian); - this.offset += 8; - this._updateLastWrittenByte(); - return this; - } - /** - * Write the charCode of `str`'s first character as an 8-bit unsigned integer - * and move pointer forward by 1 byte. - * @param str - The character to write. - * @returns This. - */ - writeChar(str) { - // eslint-disable-next-line unicorn/prefer-code-point - return this.writeUint8(str.charCodeAt(0)); - } - /** - * Write the charCodes of all `str`'s characters as 8-bit unsigned integers - * and move pointer forward by `str.length` bytes. - * @param str - The characters to write. - * @returns This. - */ - writeChars(str) { - for (let i = 0; i < str.length; i++) { - // eslint-disable-next-line unicorn/prefer-code-point - this.writeUint8(str.charCodeAt(i)); - } - return this; - } - /** - * UTF-8 encode and write `str` to the current pointer offset and move pointer - * forward according to the encoded length. - * @param str - The string to write. - * @returns This. - */ - writeUtf8(str) { - return this.writeBytes(encode(str)); - } - /** - * Export a Uint8Array view of the internal buffer. - * The view starts at the byte offset and its length - * is calculated to stop at the last written byte or the original length. - * @returns A new Uint8Array view. - */ - toArray() { - return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte); - } - /** - * Get the total number of bytes written so far, regardless of the current offset. - * @returns - Total number of bytes. - */ - getWrittenByteLength() { - return this.lastWrittenByte - this.byteOffset; - } - /** - * Update the last written byte offset - * @private - */ - _updateLastWrittenByte() { - if (this.offset > this.lastWrittenByte) { - this.lastWrittenByte = this.offset; - } - } -} - -/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */ -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -/* eslint-disable space-unary-ops */ - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - - -//const Z_FILTERED = 1; -//const Z_HUFFMAN_ONLY = 2; -//const Z_RLE = 3; -const Z_FIXED$1 = 4; -//const Z_DEFAULT_STRATEGY = 0; - -/* Possible values of the data_type field (though see inflate()) */ -const Z_BINARY = 0; -const Z_TEXT = 1; -//const Z_ASCII = 1; // = Z_TEXT -const Z_UNKNOWN$1 = 2; - -/*============================================================================*/ - - -function zero$1(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } } - -// From zutil.h - -const STORED_BLOCK = 0; -const STATIC_TREES = 1; -const DYN_TREES = 2; -/* The three kinds of block type */ - -const MIN_MATCH$1 = 3; -const MAX_MATCH$1 = 258; -/* The minimum and maximum match lengths */ - -// From deflate.h -/* =========================================================================== - * Internal compression state. - */ - -const LENGTH_CODES$1 = 29; -/* number of length codes, not counting the special END_BLOCK code */ - -const LITERALS$1 = 256; -/* number of literal bytes 0..255 */ - -const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1; -/* number of Literal or Length codes, including the END_BLOCK code */ - -const D_CODES$1 = 30; -/* number of distance codes */ - -const BL_CODES$1 = 19; -/* number of codes used to transfer the bit lengths */ - -const HEAP_SIZE$1 = 2 * L_CODES$1 + 1; -/* maximum heap size */ - -const MAX_BITS$1 = 15; -/* All codes must not exceed MAX_BITS bits */ - -const Buf_size = 16; -/* size of bit buffer in bi_buf */ - - -/* =========================================================================== - * Constants - */ - -const MAX_BL_BITS = 7; -/* Bit length codes must not exceed MAX_BL_BITS bits */ - -const END_BLOCK = 256; -/* end of block literal code */ - -const REP_3_6 = 16; -/* repeat previous bit length 3-6 times (2 bits of repeat count) */ - -const REPZ_3_10 = 17; -/* repeat a zero length 3-10 times (3 bits of repeat count) */ - -const REPZ_11_138 = 18; -/* repeat a zero length 11-138 times (7 bits of repeat count) */ - -/* eslint-disable comma-spacing,array-bracket-spacing */ -const extra_lbits = /* extra bits for each length code */ - new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]); - -const extra_dbits = /* extra bits for each distance code */ - new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]); - -const extra_blbits = /* extra bits for each bit length code */ - new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]); - -const bl_order = - new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]); -/* eslint-enable comma-spacing,array-bracket-spacing */ - -/* The lengths of the bit length codes are sent in order of decreasing - * probability, to avoid transmitting the lengths for unused bit length codes. - */ - -/* =========================================================================== - * Local data. These are initialized only once. - */ - -// We pre-fill arrays with 0 to avoid uninitialized gaps - -const DIST_CODE_LEN = 512; /* see definition of array dist_code below */ - -// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1 -const static_ltree = new Array((L_CODES$1 + 2) * 2); -zero$1(static_ltree); -/* The static literal tree. Since the bit lengths are imposed, there is no - * need for the L_CODES extra codes used during heap construction. However - * The codes 286 and 287 are needed to build a canonical tree (see _tr_init - * below). - */ - -const static_dtree = new Array(D_CODES$1 * 2); -zero$1(static_dtree); -/* The static distance tree. (Actually a trivial tree since all codes use - * 5 bits.) - */ - -const _dist_code = new Array(DIST_CODE_LEN); -zero$1(_dist_code); -/* Distance codes. The first 256 values correspond to the distances - * 3 .. 258, the last 256 values correspond to the top 8 bits of - * the 15 bit distances. - */ - -const _length_code = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1); -zero$1(_length_code); -/* length code for each normalized match length (0 == MIN_MATCH) */ - -const base_length = new Array(LENGTH_CODES$1); -zero$1(base_length); -/* First normalized length for each code (0 = MIN_MATCH) */ - -const base_dist = new Array(D_CODES$1); -zero$1(base_dist); -/* First normalized distance for each code (0 = distance of 1) */ - - -function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { - - this.static_tree = static_tree; /* static tree or NULL */ - this.extra_bits = extra_bits; /* extra bits for each code or NULL */ - this.extra_base = extra_base; /* base index for extra_bits */ - this.elems = elems; /* max number of elements in the tree */ - this.max_length = max_length; /* max bit length for the codes */ - - // show if `static_tree` has data or dummy - needed for monomorphic objects - this.has_stree = static_tree && static_tree.length; -} - - -let static_l_desc; -let static_d_desc; -let static_bl_desc; - - -function TreeDesc(dyn_tree, stat_desc) { - this.dyn_tree = dyn_tree; /* the dynamic tree */ - this.max_code = 0; /* largest code with non zero frequency */ - this.stat_desc = stat_desc; /* the corresponding static tree */ -} - - - -const d_code = (dist) => { - - return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)]; -}; - - -/* =========================================================================== - * Output a short LSB first on the stream. - * IN assertion: there is enough room in pendingBuf. - */ -const put_short = (s, w) => { -// put_byte(s, (uch)((w) & 0xff)); -// put_byte(s, (uch)((ush)(w) >> 8)); - s.pending_buf[s.pending++] = (w) & 0xff; - s.pending_buf[s.pending++] = (w >>> 8) & 0xff; -}; - - -/* =========================================================================== - * Send a value on a given number of bits. - * IN assertion: length <= 16 and value fits in length bits. - */ -const send_bits = (s, value, length) => { - - if (s.bi_valid > (Buf_size - length)) { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - put_short(s, s.bi_buf); - s.bi_buf = value >> (Buf_size - s.bi_valid); - s.bi_valid += length - Buf_size; - } else { - s.bi_buf |= (value << s.bi_valid) & 0xffff; - s.bi_valid += length; - } -}; - - -const send_code = (s, c, tree) => { - - send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); -}; - - -/* =========================================================================== - * Reverse the first len bits of a code, using straightforward code (a faster - * method would use a table) - * IN assertion: 1 <= len <= 15 - */ -const bi_reverse = (code, len) => { - - let res = 0; - do { - res |= code & 1; - code >>>= 1; - res <<= 1; - } while (--len > 0); - return res >>> 1; -}; - - -/* =========================================================================== - * Flush the bit buffer, keeping at most 7 bits in it. - */ -const bi_flush = (s) => { - - if (s.bi_valid === 16) { - put_short(s, s.bi_buf); - s.bi_buf = 0; - s.bi_valid = 0; - - } else if (s.bi_valid >= 8) { - s.pending_buf[s.pending++] = s.bi_buf & 0xff; - s.bi_buf >>= 8; - s.bi_valid -= 8; - } -}; - - -/* =========================================================================== - * Compute the optimal bit lengths for a tree and update the total bit length - * for the current block. - * IN assertion: the fields freq and dad are set, heap[heap_max] and - * above are the tree nodes sorted by increasing frequency. - * OUT assertions: the field len is set to the optimal bit length, the - * array bl_count contains the frequencies for each bit length. - * The length opt_len is updated; static_len is also updated if stree is - * not null. - */ -const gen_bitlen = (s, desc) => { -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ - - const tree = desc.dyn_tree; - const max_code = desc.max_code; - const stree = desc.stat_desc.static_tree; - const has_stree = desc.stat_desc.has_stree; - const extra = desc.stat_desc.extra_bits; - const base = desc.stat_desc.extra_base; - const max_length = desc.stat_desc.max_length; - let h; /* heap index */ - let n, m; /* iterate over the tree elements */ - let bits; /* bit length */ - let xbits; /* extra bits */ - let f; /* frequency */ - let overflow = 0; /* number of elements with bit length too large */ - - for (bits = 0; bits <= MAX_BITS$1; bits++) { - s.bl_count[bits] = 0; - } - - /* In a first pass, compute the optimal bit lengths (which may - * overflow in the case of the bit length tree). - */ - tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ - - for (h = s.heap_max + 1; h < HEAP_SIZE$1; h++) { - n = s.heap[h]; - bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; - if (bits > max_length) { - bits = max_length; - overflow++; - } - tree[n * 2 + 1]/*.Len*/ = bits; - /* We overwrite tree[n].Dad which is no longer needed */ - - if (n > max_code) { continue; } /* not a leaf node */ - - s.bl_count[bits]++; - xbits = 0; - if (n >= base) { - xbits = extra[n - base]; - } - f = tree[n * 2]/*.Freq*/; - s.opt_len += f * (bits + xbits); - if (has_stree) { - s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); - } - } - if (overflow === 0) { return; } - - // Tracev((stderr,"\nbit length overflow\n")); - /* This happens for example on obj2 and pic of the Calgary corpus */ - - /* Find the first bit length which could increase: */ - do { - bits = max_length - 1; - while (s.bl_count[bits] === 0) { bits--; } - s.bl_count[bits]--; /* move one leaf down the tree */ - s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ - s.bl_count[max_length]--; - /* The brother of the overflow item also moves one step up, - * but this does not affect bl_count[max_length] - */ - overflow -= 2; - } while (overflow > 0); - - /* Now recompute all bit lengths, scanning in increasing frequency. - * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all - * lengths instead of fixing only the wrong ones. This idea is taken - * from 'ar' written by Haruhiko Okumura.) - */ - for (bits = max_length; bits !== 0; bits--) { - n = s.bl_count[bits]; - while (n !== 0) { - m = s.heap[--h]; - if (m > max_code) { continue; } - if (tree[m * 2 + 1]/*.Len*/ !== bits) { - // Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); - s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; - tree[m * 2 + 1]/*.Len*/ = bits; - } - n--; - } - } -}; - - -/* =========================================================================== - * Generate the codes for a given tree and bit counts (which need not be - * optimal). - * IN assertion: the array bl_count contains the bit length statistics for - * the given tree and the field len is set for all tree elements. - * OUT assertion: the field code is set for all tree elements of non - * zero code length. - */ -const gen_codes = (tree, max_code, bl_count) => { -// ct_data *tree; /* the tree to decorate */ -// int max_code; /* largest code with non zero frequency */ -// ushf *bl_count; /* number of codes at each bit length */ - - const next_code = new Array(MAX_BITS$1 + 1); /* next code value for each bit length */ - let code = 0; /* running code value */ - let bits; /* bit index */ - let n; /* code index */ - - /* The distribution counts are first used to generate the code values - * without bit reversal. - */ - for (bits = 1; bits <= MAX_BITS$1; bits++) { - code = (code + bl_count[bits - 1]) << 1; - next_code[bits] = code; - } - /* Check that the bit counts in bl_count are consistent. The last code - * must be all ones. - */ - //Assert (code + bl_count[MAX_BITS]-1 == (1< { - - let n; /* iterates over tree elements */ - let bits; /* bit counter */ - let length; /* length value */ - let code; /* code value */ - let dist; /* distance index */ - const bl_count = new Array(MAX_BITS$1 + 1); - /* number of codes at each bit length for an optimal tree */ - - // do check in _tr_init() - //if (static_init_done) return; - - /* For some embedded targets, global variables are not initialized: */ -/*#ifdef NO_INIT_GLOBAL_POINTERS - static_l_desc.static_tree = static_ltree; - static_l_desc.extra_bits = extra_lbits; - static_d_desc.static_tree = static_dtree; - static_d_desc.extra_bits = extra_dbits; - static_bl_desc.extra_bits = extra_blbits; -#endif*/ - - /* Initialize the mapping length (0..255) -> length code (0..28) */ - length = 0; - for (code = 0; code < LENGTH_CODES$1 - 1; code++) { - base_length[code] = length; - for (n = 0; n < (1 << extra_lbits[code]); n++) { - _length_code[length++] = code; - } - } - //Assert (length == 256, "tr_static_init: length != 256"); - /* Note that the length 255 (match length 258) can be represented - * in two different ways: code 284 + 5 bits or code 285, so we - * overwrite length_code[255] to use the best encoding: - */ - _length_code[length - 1] = code; - - /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ - dist = 0; - for (code = 0; code < 16; code++) { - base_dist[code] = dist; - for (n = 0; n < (1 << extra_dbits[code]); n++) { - _dist_code[dist++] = code; - } - } - //Assert (dist == 256, "tr_static_init: dist != 256"); - dist >>= 7; /* from now on, all distances are divided by 128 */ - for (; code < D_CODES$1; code++) { - base_dist[code] = dist << 7; - for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { - _dist_code[256 + dist++] = code; - } - } - //Assert (dist == 256, "tr_static_init: 256+dist != 512"); - - /* Construct the codes of the static literal tree */ - for (bits = 0; bits <= MAX_BITS$1; bits++) { - bl_count[bits] = 0; - } - - n = 0; - while (n <= 143) { - static_ltree[n * 2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - while (n <= 255) { - static_ltree[n * 2 + 1]/*.Len*/ = 9; - n++; - bl_count[9]++; - } - while (n <= 279) { - static_ltree[n * 2 + 1]/*.Len*/ = 7; - n++; - bl_count[7]++; - } - while (n <= 287) { - static_ltree[n * 2 + 1]/*.Len*/ = 8; - n++; - bl_count[8]++; - } - /* Codes 286 and 287 do not exist, but we must include them in the - * tree construction to get a canonical Huffman tree (longest code - * all ones) - */ - gen_codes(static_ltree, L_CODES$1 + 1, bl_count); - - /* The static distance tree is trivial: */ - for (n = 0; n < D_CODES$1; n++) { - static_dtree[n * 2 + 1]/*.Len*/ = 5; - static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); - } - - // Now data ready and we can init static trees - static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS$1 + 1, L_CODES$1, MAX_BITS$1); - static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES$1, MAX_BITS$1); - static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES$1, MAX_BL_BITS); - - //static_init_done = true; -}; - - -/* =========================================================================== - * Initialize a new block. - */ -const init_block = (s) => { - - let n; /* iterates over tree elements */ - - /* Initialize the trees. */ - for (n = 0; n < L_CODES$1; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } - for (n = 0; n < D_CODES$1; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } - for (n = 0; n < BL_CODES$1; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } - - s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; - s.opt_len = s.static_len = 0; - s.sym_next = s.matches = 0; -}; - - -/* =========================================================================== - * Flush the bit buffer and align the output on a byte boundary - */ -const bi_windup = (s) => -{ - if (s.bi_valid > 8) { - put_short(s, s.bi_buf); - } else if (s.bi_valid > 0) { - //put_byte(s, (Byte)s->bi_buf); - s.pending_buf[s.pending++] = s.bi_buf; - } - s.bi_buf = 0; - s.bi_valid = 0; -}; - -/* =========================================================================== - * Compares to subtrees, using the tree depth as tie breaker when - * the subtrees have equal frequency. This minimizes the worst case length. - */ -const smaller = (tree, n, m, depth) => { - - const _n2 = n * 2; - const _m2 = m * 2; - return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || - (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); -}; - -/* =========================================================================== - * Restore the heap property by moving down the tree starting at node k, - * exchanging a node with the smallest of its two sons if necessary, stopping - * when the heap property is re-established (each father smaller than its - * two sons). - */ -const pqdownheap = (s, tree, k) => { -// deflate_state *s; -// ct_data *tree; /* the tree to restore */ -// int k; /* node to move down */ - - const v = s.heap[k]; - let j = k << 1; /* left son of k */ - while (j <= s.heap_len) { - /* Set j to the smallest of the two sons: */ - if (j < s.heap_len && - smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { - j++; - } - /* Exit if v is smaller than both sons */ - if (smaller(tree, v, s.heap[j], s.depth)) { break; } - - /* Exchange v with the smallest son */ - s.heap[k] = s.heap[j]; - k = j; - - /* And continue down the tree, setting j to the left son of k */ - j <<= 1; - } - s.heap[k] = v; -}; - - -// inlined manually -// const SMALLEST = 1; - -/* =========================================================================== - * Send the block data compressed using the given Huffman trees - */ -const compress_block = (s, ltree, dtree) => { -// deflate_state *s; -// const ct_data *ltree; /* literal tree */ -// const ct_data *dtree; /* distance tree */ - - let dist; /* distance of matched string */ - let lc; /* match length or unmatched char (if dist == 0) */ - let sx = 0; /* running index in sym_buf */ - let code; /* the code to send */ - let extra; /* number of extra bits to send */ - - if (s.sym_next !== 0) { - do { - dist = s.pending_buf[s.sym_buf + sx++] & 0xff; - dist += (s.pending_buf[s.sym_buf + sx++] & 0xff) << 8; - lc = s.pending_buf[s.sym_buf + sx++]; - if (dist === 0) { - send_code(s, lc, ltree); /* send a literal byte */ - //Tracecv(isgraph(lc), (stderr," '%c' ", lc)); - } else { - /* Here, lc is the match length - MIN_MATCH */ - code = _length_code[lc]; - send_code(s, code + LITERALS$1 + 1, ltree); /* send the length code */ - extra = extra_lbits[code]; - if (extra !== 0) { - lc -= base_length[code]; - send_bits(s, lc, extra); /* send the extra length bits */ - } - dist--; /* dist is now the match distance - 1 */ - code = d_code(dist); - //Assert (code < D_CODES, "bad d_code"); - - send_code(s, code, dtree); /* send the distance code */ - extra = extra_dbits[code]; - if (extra !== 0) { - dist -= base_dist[code]; - send_bits(s, dist, extra); /* send the extra distance bits */ - } - } /* literal or match pair ? */ - - /* Check that the overlay between pending_buf and sym_buf is ok: */ - //Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); - - } while (sx < s.sym_next); - } - - send_code(s, END_BLOCK, ltree); -}; - - -/* =========================================================================== - * Construct one Huffman tree and assigns the code bit strings and lengths. - * Update the total bit length for the current block. - * IN assertion: the field freq is set for all tree elements. - * OUT assertions: the fields len and code are set to the optimal bit length - * and corresponding code. The length opt_len is updated; static_len is - * also updated if stree is not null. The field max_code is set. - */ -const build_tree = (s, desc) => { -// deflate_state *s; -// tree_desc *desc; /* the tree descriptor */ - - const tree = desc.dyn_tree; - const stree = desc.stat_desc.static_tree; - const has_stree = desc.stat_desc.has_stree; - const elems = desc.stat_desc.elems; - let n, m; /* iterate over heap elements */ - let max_code = -1; /* largest code with non zero frequency */ - let node; /* new node being created */ - - /* Construct the initial heap, with least frequent element in - * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. - * heap[0] is not used. - */ - s.heap_len = 0; - s.heap_max = HEAP_SIZE$1; - - for (n = 0; n < elems; n++) { - if (tree[n * 2]/*.Freq*/ !== 0) { - s.heap[++s.heap_len] = max_code = n; - s.depth[n] = 0; - - } else { - tree[n * 2 + 1]/*.Len*/ = 0; - } - } - - /* The pkzip format requires that at least one distance code exists, - * and that at least one bit should be sent even if there is only one - * possible code. So to avoid special checks later on we force at least - * two codes of non zero frequency. - */ - while (s.heap_len < 2) { - node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0); - tree[node * 2]/*.Freq*/ = 1; - s.depth[node] = 0; - s.opt_len--; - - if (has_stree) { - s.static_len -= stree[node * 2 + 1]/*.Len*/; - } - /* node is 0 or 1 so it does not have extra bits */ - } - desc.max_code = max_code; - - /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, - * establish sub-heaps of increasing lengths: - */ - for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); } - - /* Construct the Huffman tree by repeatedly combining the least two - * frequent nodes. - */ - node = elems; /* next internal node of the tree */ - do { - //pqremove(s, tree, n); /* n = node of least frequency */ - /*** pqremove ***/ - n = s.heap[1/*SMALLEST*/]; - s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--]; - pqdownheap(s, tree, 1/*SMALLEST*/); - /***/ - - m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */ - - s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */ - s.heap[--s.heap_max] = m; - - /* Create a new node father of n and m */ - tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; - s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; - tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; - - /* and insert the new node in the heap */ - s.heap[1/*SMALLEST*/] = node++; - pqdownheap(s, tree, 1/*SMALLEST*/); - - } while (s.heap_len >= 2); - - s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/]; - - /* At this point, the fields freq and dad are set. We can now - * generate the bit lengths. - */ - gen_bitlen(s, desc); - - /* The field len is now set, we can generate the bit codes */ - gen_codes(tree, max_code, s.bl_count); -}; - - -/* =========================================================================== - * Scan a literal or distance tree to determine the frequencies of the codes - * in the bit length tree. - */ -const scan_tree = (s, tree, max_code) => { -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ - - let n; /* iterates over all tree elements */ - let prevlen = -1; /* last emitted length */ - let curlen; /* length of current code */ - - let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ - - let count = 0; /* repeat count of the current code */ - let max_count = 7; /* max repeat count */ - let min_count = 4; /* min repeat count */ - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - s.bl_tree[curlen * 2]/*.Freq*/ += count; - - } else if (curlen !== 0) { - - if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } - s.bl_tree[REP_3_6 * 2]/*.Freq*/++; - - } else if (count <= 10) { - s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; - - } else { - s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; - } - - count = 0; - prevlen = curlen; - - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -}; - - -/* =========================================================================== - * Send a literal or distance tree in compressed form, using the codes in - * bl_tree. - */ -const send_tree = (s, tree, max_code) => { -// deflate_state *s; -// ct_data *tree; /* the tree to be scanned */ -// int max_code; /* and its largest code of non zero frequency */ - - let n; /* iterates over all tree elements */ - let prevlen = -1; /* last emitted length */ - let curlen; /* length of current code */ - - let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ - - let count = 0; /* repeat count of the current code */ - let max_count = 7; /* max repeat count */ - let min_count = 4; /* min repeat count */ - - /* tree[max_code+1].Len = -1; */ /* guard already set */ - if (nextlen === 0) { - max_count = 138; - min_count = 3; - } - - for (n = 0; n <= max_code; n++) { - curlen = nextlen; - nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; - - if (++count < max_count && curlen === nextlen) { - continue; - - } else if (count < min_count) { - do { send_code(s, curlen, s.bl_tree); } while (--count !== 0); - - } else if (curlen !== 0) { - if (curlen !== prevlen) { - send_code(s, curlen, s.bl_tree); - count--; - } - //Assert(count >= 3 && count <= 6, " 3_6?"); - send_code(s, REP_3_6, s.bl_tree); - send_bits(s, count - 3, 2); - - } else if (count <= 10) { - send_code(s, REPZ_3_10, s.bl_tree); - send_bits(s, count - 3, 3); - - } else { - send_code(s, REPZ_11_138, s.bl_tree); - send_bits(s, count - 11, 7); - } - - count = 0; - prevlen = curlen; - if (nextlen === 0) { - max_count = 138; - min_count = 3; - - } else if (curlen === nextlen) { - max_count = 6; - min_count = 3; - - } else { - max_count = 7; - min_count = 4; - } - } -}; - - -/* =========================================================================== - * Construct the Huffman tree for the bit lengths and return the index in - * bl_order of the last bit length code to send. - */ -const build_bl_tree = (s) => { - - let max_blindex; /* index of last bit length code of non zero freq */ - - /* Determine the bit length frequencies for literal and distance trees */ - scan_tree(s, s.dyn_ltree, s.l_desc.max_code); - scan_tree(s, s.dyn_dtree, s.d_desc.max_code); - - /* Build the bit length tree: */ - build_tree(s, s.bl_desc); - /* opt_len now includes the length of the tree representations, except - * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. - */ - - /* Determine the number of bit length codes to send. The pkzip format - * requires that at least 4 bit length codes be sent. (appnote.txt says - * 3 but the actual value used is 4.) - */ - for (max_blindex = BL_CODES$1 - 1; max_blindex >= 3; max_blindex--) { - if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { - break; - } - } - /* Update opt_len to include the bit length tree and counts */ - s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; - //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", - // s->opt_len, s->static_len)); - - return max_blindex; -}; - - -/* =========================================================================== - * Send the header for a block using dynamic Huffman trees: the counts, the - * lengths of the bit length codes, the literal tree and the distance tree. - * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. - */ -const send_all_trees = (s, lcodes, dcodes, blcodes) => { -// deflate_state *s; -// int lcodes, dcodes, blcodes; /* number of codes for each tree */ - - let rank; /* index in bl_order */ - - //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); - //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, - // "too many codes"); - //Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes - 1, 5); - send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ - for (rank = 0; rank < blcodes; rank++) { - //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); - send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); - } - //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ - //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); - - send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ - //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); -}; - - -/* =========================================================================== - * Check if the data type is TEXT or BINARY, using the following algorithm: - * - TEXT if the two conditions below are satisfied: - * a) There are no non-portable control characters belonging to the - * "block list" (0..6, 14..25, 28..31). - * b) There is at least one printable character belonging to the - * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). - * - BINARY otherwise. - * - The following partially-portable control characters form a - * "gray list" that is ignored in this detection algorithm: - * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). - * IN assertion: the fields Freq of dyn_ltree are set. - */ -const detect_data_type = (s) => { - /* block_mask is the bit mask of block-listed bytes - * set bits 0..6, 14..25, and 28..31 - * 0xf3ffc07f = binary 11110011111111111100000001111111 - */ - let block_mask = 0xf3ffc07f; - let n; - - /* Check for non-textual ("block-listed") bytes. */ - for (n = 0; n <= 31; n++, block_mask >>>= 1) { - if ((block_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { - return Z_BINARY; - } - } - - /* Check for textual ("allow-listed") bytes. */ - if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 || - s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - for (n = 32; n < LITERALS$1; n++) { - if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) { - return Z_TEXT; - } - } - - /* There are no "block-listed" or "allow-listed" bytes: - * this stream either is empty or has tolerated ("gray-listed") bytes only. - */ - return Z_BINARY; -}; - - -let static_init_done = false; - -/* =========================================================================== - * Initialize the tree data structures for a new zlib stream. - */ -const _tr_init$1 = (s) => -{ - - if (!static_init_done) { - tr_static_init(); - static_init_done = true; - } - - s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc); - s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc); - s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc); - - s.bi_buf = 0; - s.bi_valid = 0; - - /* Initialize the first block of the first file: */ - init_block(s); -}; - - -/* =========================================================================== - * Send a stored block - */ -const _tr_stored_block$1 = (s, buf, stored_len, last) => { -//DeflateState *s; -//charf *buf; /* input block */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ - - send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ - bi_windup(s); /* align on byte boundary */ - put_short(s, stored_len); - put_short(s, ~stored_len); - if (stored_len) { - s.pending_buf.set(s.window.subarray(buf, buf + stored_len), s.pending); - } - s.pending += stored_len; -}; - - -/* =========================================================================== - * Send one empty static block to give enough lookahead for inflate. - * This takes 10 bits, of which 7 may remain in the bit buffer. - */ -const _tr_align$1 = (s) => { - send_bits(s, STATIC_TREES << 1, 3); - send_code(s, END_BLOCK, static_ltree); - bi_flush(s); -}; - - -/* =========================================================================== - * Determine the best encoding for the current block: dynamic trees, static - * trees or store, and write out the encoded block. - */ -const _tr_flush_block$1 = (s, buf, stored_len, last) => { -//DeflateState *s; -//charf *buf; /* input block, or NULL if too old */ -//ulg stored_len; /* length of input block */ -//int last; /* one if this is the last block for a file */ - - let opt_lenb, static_lenb; /* opt_len and static_len in bytes */ - let max_blindex = 0; /* index of last bit length code of non zero freq */ - - /* Build the Huffman trees unless a stored block is forced */ - if (s.level > 0) { - - /* Check if the file is binary or text */ - if (s.strm.data_type === Z_UNKNOWN$1) { - s.strm.data_type = detect_data_type(s); - } - - /* Construct the literal and distance trees */ - build_tree(s, s.l_desc); - // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - - build_tree(s, s.d_desc); - // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, - // s->static_len)); - /* At this point, opt_len and static_len are the total bit lengths of - * the compressed block data, excluding the tree representations. - */ - - /* Build the bit length tree for the above two trees, and get the index - * in bl_order of the last bit length code to send. - */ - max_blindex = build_bl_tree(s); - - /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s.opt_len + 3 + 7) >>> 3; - static_lenb = (s.static_len + 3 + 7) >>> 3; - - // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", - // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, - // s->sym_next / 3)); - - if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; } - - } else { - // Assert(buf != (char*)0, "lost buf"); - opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ - } - - if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { - /* 4: two words for the lengths */ - - /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. - * Otherwise we can't have processed more than WSIZE input bytes since - * the last block flush, because compression would have been - * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to - * transform a block into a stored block. - */ - _tr_stored_block$1(s, buf, stored_len, last); - - } else if (s.strategy === Z_FIXED$1 || static_lenb === opt_lenb) { - - send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); - compress_block(s, static_ltree, static_dtree); - - } else { - send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); - send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); - compress_block(s, s.dyn_ltree, s.dyn_dtree); - } - // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); - /* The above check is made mod 2^32, for files larger than 512 MB - * and uLong implemented on 32 bits. - */ - init_block(s); - - if (last) { - bi_windup(s); - } - // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, - // s->compressed_len-7*last)); -}; - -/* =========================================================================== - * Save the match info and tally the frequency counts. Return true if - * the current block must be flushed. - */ -const _tr_tally$1 = (s, dist, lc) => { -// deflate_state *s; -// unsigned dist; /* distance of matched string */ -// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ - - s.pending_buf[s.sym_buf + s.sym_next++] = dist; - s.pending_buf[s.sym_buf + s.sym_next++] = dist >> 8; - s.pending_buf[s.sym_buf + s.sym_next++] = lc; - if (dist === 0) { - /* lc is the unmatched char */ - s.dyn_ltree[lc * 2]/*.Freq*/++; - } else { - s.matches++; - /* Here, lc is the match length - MIN_MATCH */ - dist--; /* dist = match distance - 1 */ - //Assert((ush)dist < (ush)MAX_DIST(s) && - // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && - // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - - s.dyn_ltree[(_length_code[lc] + LITERALS$1 + 1) * 2]/*.Freq*/++; - s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; - } - - return (s.sym_next === s.sym_end); -}; - -var _tr_init_1 = _tr_init$1; -var _tr_stored_block_1 = _tr_stored_block$1; -var _tr_flush_block_1 = _tr_flush_block$1; -var _tr_tally_1 = _tr_tally$1; -var _tr_align_1 = _tr_align$1; - -var trees = { - _tr_init: _tr_init_1, - _tr_stored_block: _tr_stored_block_1, - _tr_flush_block: _tr_flush_block_1, - _tr_tally: _tr_tally_1, - _tr_align: _tr_align_1 -}; - -// Note: adler32 takes 12% for level 0 and 2% for level 6. -// It isn't worth it to make additional optimizations as in original. -// Small size is preferable. - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const adler32 = (adler, buf, len, pos) => { - let s1 = (adler & 0xffff) |0, - s2 = ((adler >>> 16) & 0xffff) |0, - n = 0; - - while (len !== 0) { - // Set limit ~ twice less than 5552, to keep - // s2 in 31-bits, because we force signed ints. - // in other case %= will fail. - n = len > 2000 ? 2000 : len; - len -= n; - - do { - s1 = (s1 + buf[pos++]) |0; - s2 = (s2 + s1) |0; - } while (--n); - - s1 %= 65521; - s2 %= 65521; - } - - return (s1 | (s2 << 16)) |0; -}; - - -var adler32_1 = adler32; - -// Note: we can't get significant speed boost here. -// So write code to minimize size - no pregenerated tables -// and array tools dependencies. - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -// Use ordinary array, since untyped makes no boost here -const makeTable = () => { - let c, table = []; - - for (var n = 0; n < 256; n++) { - c = n; - for (var k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - table[n] = c; - } - - return table; -}; - -// Create table on load. Just 255 signed longs. Not a problem. -const crcTable$1 = new Uint32Array(makeTable()); - - -const crc32 = (crc, buf, len, pos) => { - const t = crcTable$1; - const end = pos + len; - - crc ^= -1; - - for (let i = pos; i < end; i++) { - crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; - } - - return (crc ^ (-1)); // >>> 0; -}; - - -var crc32_1 = crc32; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -var messages = { - 2: 'need dictionary', /* Z_NEED_DICT 2 */ - 1: 'stream end', /* Z_STREAM_END 1 */ - 0: '', /* Z_OK 0 */ - '-1': 'file error', /* Z_ERRNO (-1) */ - '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ - '-3': 'data error', /* Z_DATA_ERROR (-3) */ - '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */ - '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ - '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -var constants$2 = { - - /* Allowed flush values; see deflate() and inflate() below for details */ - Z_NO_FLUSH: 0, - Z_PARTIAL_FLUSH: 1, - Z_SYNC_FLUSH: 2, - Z_FULL_FLUSH: 3, - Z_FINISH: 4, - Z_BLOCK: 5, - Z_TREES: 6, - - /* Return codes for the compression/decompression functions. Negative values - * are errors, positive values are used for special but normal events. - */ - Z_OK: 0, - Z_STREAM_END: 1, - Z_NEED_DICT: 2, - Z_ERRNO: -1, - Z_STREAM_ERROR: -2, - Z_DATA_ERROR: -3, - Z_MEM_ERROR: -4, - Z_BUF_ERROR: -5, - //Z_VERSION_ERROR: -6, - - /* compression levels */ - Z_NO_COMPRESSION: 0, - Z_BEST_SPEED: 1, - Z_BEST_COMPRESSION: 9, - Z_DEFAULT_COMPRESSION: -1, - - - Z_FILTERED: 1, - Z_HUFFMAN_ONLY: 2, - Z_RLE: 3, - Z_FIXED: 4, - Z_DEFAULT_STRATEGY: 0, - - /* Possible values of the data_type field (though see inflate()) */ - Z_BINARY: 0, - Z_TEXT: 1, - //Z_ASCII: 1, // = Z_TEXT (deprecated) - Z_UNKNOWN: 2, - - /* The deflate compression method */ - Z_DEFLATED: 8 - //Z_NULL: null // Use -1 or null inline, depending on var type -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align } = trees; - - - - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH: Z_NO_FLUSH$2, Z_PARTIAL_FLUSH, Z_FULL_FLUSH: Z_FULL_FLUSH$1, Z_FINISH: Z_FINISH$3, Z_BLOCK: Z_BLOCK$1, - Z_OK: Z_OK$3, Z_STREAM_END: Z_STREAM_END$3, Z_STREAM_ERROR: Z_STREAM_ERROR$2, Z_DATA_ERROR: Z_DATA_ERROR$2, Z_BUF_ERROR: Z_BUF_ERROR$1, - Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1, - Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED, Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1, - Z_UNKNOWN, - Z_DEFLATED: Z_DEFLATED$2 -} = constants$2; - -/*============================================================================*/ - - -const MAX_MEM_LEVEL = 9; -/* Maximum value for memLevel in deflateInit2 */ -const MAX_WBITS$1 = 15; -/* 32K LZ77 window */ -const DEF_MEM_LEVEL = 8; - - -const LENGTH_CODES = 29; -/* number of length codes, not counting the special END_BLOCK code */ -const LITERALS = 256; -/* number of literal bytes 0..255 */ -const L_CODES = LITERALS + 1 + LENGTH_CODES; -/* number of Literal or Length codes, including the END_BLOCK code */ -const D_CODES = 30; -/* number of distance codes */ -const BL_CODES = 19; -/* number of codes used to transfer the bit lengths */ -const HEAP_SIZE = 2 * L_CODES + 1; -/* maximum heap size */ -const MAX_BITS = 15; -/* All codes must not exceed MAX_BITS bits */ - -const MIN_MATCH = 3; -const MAX_MATCH = 258; -const MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1); - -const PRESET_DICT = 0x20; - -const INIT_STATE = 42; /* zlib header -> BUSY_STATE */ -//#ifdef GZIP -const GZIP_STATE = 57; /* gzip header -> BUSY_STATE | EXTRA_STATE */ -//#endif -const EXTRA_STATE = 69; /* gzip extra block -> NAME_STATE */ -const NAME_STATE = 73; /* gzip file name -> COMMENT_STATE */ -const COMMENT_STATE = 91; /* gzip comment -> HCRC_STATE */ -const HCRC_STATE = 103; /* gzip header CRC -> BUSY_STATE */ -const BUSY_STATE = 113; /* deflate -> FINISH_STATE */ -const FINISH_STATE = 666; /* stream complete */ - -const BS_NEED_MORE = 1; /* block not completed, need more input or more output */ -const BS_BLOCK_DONE = 2; /* block flush performed */ -const BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */ -const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */ - -const OS_CODE = 0x03; // Unix :) . Don't detect, use this default. - -const err = (strm, errorCode) => { - strm.msg = messages[errorCode]; - return errorCode; -}; - -const rank = (f) => { - return ((f) * 2) - ((f) > 4 ? 9 : 0); -}; - -const zero = (buf) => { - let len = buf.length; while (--len >= 0) { buf[len] = 0; } -}; - -/* =========================================================================== - * Slide the hash table when sliding the window down (could be avoided with 32 - * bit values at the expense of memory usage). We slide even when level == 0 to - * keep the hash table consistent if we switch back to level > 0 later. - */ -const slide_hash = (s) => { - let n, m; - let p; - let wsize = s.w_size; - - n = s.hash_size; - p = n; - do { - m = s.head[--p]; - s.head[p] = (m >= wsize ? m - wsize : 0); - } while (--n); - n = wsize; -//#ifndef FASTEST - p = n; - do { - m = s.prev[--p]; - s.prev[p] = (m >= wsize ? m - wsize : 0); - /* If n is not on any hash chain, prev[n] is garbage but - * its value will never be used. - */ - } while (--n); -//#endif -}; - -/* eslint-disable new-cap */ -let HASH_ZLIB = (s, prev, data) => ((prev << s.hash_shift) ^ data) & s.hash_mask; -// This hash causes less collisions, https://github.com/nodeca/pako/issues/135 -// But breaks binary compatibility -//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask; -let HASH = HASH_ZLIB; - - -/* ========================================================================= - * Flush as much pending output as possible. All deflate() output, except for - * some deflate_stored() output, goes through this function so some - * applications may wish to modify it to avoid allocating a large - * strm->next_out buffer and copying into it. (See also read_buf()). - */ -const flush_pending = (strm) => { - const s = strm.state; - - //_tr_flush_bits(s); - let len = s.pending; - if (len > strm.avail_out) { - len = strm.avail_out; - } - if (len === 0) { return; } - - strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out); - strm.next_out += len; - s.pending_out += len; - strm.total_out += len; - strm.avail_out -= len; - s.pending -= len; - if (s.pending === 0) { - s.pending_out = 0; - } -}; - - -const flush_block_only = (s, last) => { - _tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); - s.block_start = s.strstart; - flush_pending(s.strm); -}; - - -const put_byte = (s, b) => { - s.pending_buf[s.pending++] = b; -}; - - -/* ========================================================================= - * Put a short in the pending buffer. The 16-bit value is put in MSB order. - * IN assertion: the stream state is correct and there is enough room in - * pending_buf. - */ -const putShortMSB = (s, b) => { - - // put_byte(s, (Byte)(b >> 8)); -// put_byte(s, (Byte)(b & 0xff)); - s.pending_buf[s.pending++] = (b >>> 8) & 0xff; - s.pending_buf[s.pending++] = b & 0xff; -}; - - -/* =========================================================================== - * Read a new buffer from the current input stream, update the adler32 - * and total number of bytes read. All deflate() input goes through - * this function so some applications may wish to modify it to avoid - * allocating a large strm->input buffer and copying from it. - * (See also flush_pending()). - */ -const read_buf = (strm, buf, start, size) => { - - let len = strm.avail_in; - - if (len > size) { len = size; } - if (len === 0) { return 0; } - - strm.avail_in -= len; - - // zmemcpy(buf, strm->next_in, len); - buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start); - if (strm.state.wrap === 1) { - strm.adler = adler32_1(strm.adler, buf, len, start); - } - - else if (strm.state.wrap === 2) { - strm.adler = crc32_1(strm.adler, buf, len, start); - } - - strm.next_in += len; - strm.total_in += len; - - return len; -}; - - -/* =========================================================================== - * Set match_start to the longest match starting at the given string and - * return its length. Matches shorter or equal to prev_length are discarded, - * in which case the result is equal to prev_length and match_start is - * garbage. - * IN assertions: cur_match is the head of the hash chain for the current - * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 - * OUT assertion: the match length is not greater than s->lookahead. - */ -const longest_match = (s, cur_match) => { - - let chain_length = s.max_chain_length; /* max hash chain length */ - let scan = s.strstart; /* current string */ - let match; /* matched string */ - let len; /* length of current match */ - let best_len = s.prev_length; /* best match length so far */ - let nice_match = s.nice_match; /* stop if match long enough */ - const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ? - s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/; - - const _win = s.window; // shortcut - - const wmask = s.w_mask; - const prev = s.prev; - - /* Stop when cur_match becomes <= limit. To simplify the code, - * we prevent matches with the string of window index 0. - */ - - const strend = s.strstart + MAX_MATCH; - let scan_end1 = _win[scan + best_len - 1]; - let scan_end = _win[scan + best_len]; - - /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. - * It is easy to get rid of this optimization if necessary. - */ - // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - - /* Do not waste too much time if we already have a good match: */ - if (s.prev_length >= s.good_match) { - chain_length >>= 2; - } - /* Do not look for matches beyond the end of the input. This is necessary - * to make deflate deterministic. - */ - if (nice_match > s.lookahead) { nice_match = s.lookahead; } - - // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); - - do { - // Assert(cur_match < s->strstart, "no future"); - match = cur_match; - - /* Skip to next match if the match length cannot increase - * or if the match length is less than 2. Note that the checks below - * for insufficient lookahead only occur occasionally for performance - * reasons. Therefore uninitialized memory will be accessed, and - * conditional jumps will be made that depend on those values. - * However the length of the match is limited to the lookahead, so - * the output of deflate is not affected by the uninitialized values. - */ - - if (_win[match + best_len] !== scan_end || - _win[match + best_len - 1] !== scan_end1 || - _win[match] !== _win[scan] || - _win[++match] !== _win[scan + 1]) { - continue; - } - - /* The check at best_len-1 can be removed because it will be made - * again later. (This heuristic is not always a win.) - * It is not necessary to compare scan[2] and match[2] since they - * are always equal when the other bytes match, given that - * the hash keys are equal and that HASH_BITS >= 8. - */ - scan += 2; - match++; - // Assert(*scan == *match, "match[2]?"); - - /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. - */ - do { - /*jshint noempty:false*/ - } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - _win[++scan] === _win[++match] && _win[++scan] === _win[++match] && - scan < strend); - - // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); - - len = MAX_MATCH - (strend - scan); - scan = strend - MAX_MATCH; - - if (len > best_len) { - s.match_start = cur_match; - best_len = len; - if (len >= nice_match) { - break; - } - scan_end1 = _win[scan + best_len - 1]; - scan_end = _win[scan + best_len]; - } - } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0); - - if (best_len <= s.lookahead) { - return best_len; - } - return s.lookahead; -}; - - -/* =========================================================================== - * Fill the window when the lookahead becomes insufficient. - * Updates strstart and lookahead. - * - * IN assertion: lookahead < MIN_LOOKAHEAD - * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD - * At least one byte has been read, or avail_in == 0; reads are - * performed for at least two bytes (required for the zip translate_eol - * option -- not supported here). - */ -const fill_window = (s) => { - - const _w_size = s.w_size; - let n, more, str; - - //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); - - do { - more = s.window_size - s.lookahead - s.strstart; - - // JS ints have 32 bit, block below not needed - /* Deal with !@#$% 64K limit: */ - //if (sizeof(int) <= 2) { - // if (more == 0 && s->strstart == 0 && s->lookahead == 0) { - // more = wsize; - // - // } else if (more == (unsigned)(-1)) { - // /* Very unlikely, but possible on 16 bit machine if - // * strstart == 0 && lookahead == 1 (input done a byte at time) - // */ - // more--; - // } - //} - - - /* If the window is almost full and there is insufficient lookahead, - * move the upper half to the lower one to make room in the upper half. - */ - if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) { - - s.window.set(s.window.subarray(_w_size, _w_size + _w_size - more), 0); - s.match_start -= _w_size; - s.strstart -= _w_size; - /* we now have strstart >= MAX_DIST */ - s.block_start -= _w_size; - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - slide_hash(s); - more += _w_size; - } - if (s.strm.avail_in === 0) { - break; - } - - /* If there was no sliding: - * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && - * more == window_size - lookahead - strstart - * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) - * => more >= window_size - 2*WSIZE + 2 - * In the BIG_MEM or MMAP case (not yet supported), - * window_size == input_size + MIN_LOOKAHEAD && - * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. - * Otherwise, window_size == 2*WSIZE so more >= 2. - * If there was sliding, more >= WSIZE. So in all cases, more >= 2. - */ - //Assert(more >= 2, "more < 2"); - n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more); - s.lookahead += n; - - /* Initialize the hash value now that we have some input: */ - if (s.lookahead + s.insert >= MIN_MATCH) { - str = s.strstart - s.insert; - s.ins_h = s.window[str]; - - /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + 1]); -//#if MIN_MATCH != 3 -// Call update_hash() MIN_MATCH-3 more times -//#endif - while (s.insert) { - /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); - - s.prev[str & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = str; - str++; - s.insert--; - if (s.lookahead + s.insert < MIN_MATCH) { - break; - } - } - } - /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, - * but this is not important since only literal bytes will be emitted. - */ - - } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0); - - /* If the WIN_INIT bytes after the end of the current data have never been - * written, then zero those bytes in order to avoid memory check reports of - * the use of uninitialized (or uninitialised as Julian writes) bytes by - * the longest match routines. Update the high water mark for the next - * time through here. WIN_INIT is set to MAX_MATCH since the longest match - * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. - */ -// if (s.high_water < s.window_size) { -// const curr = s.strstart + s.lookahead; -// let init = 0; -// -// if (s.high_water < curr) { -// /* Previous high water mark below current data -- zero WIN_INIT -// * bytes or up to end of window, whichever is less. -// */ -// init = s.window_size - curr; -// if (init > WIN_INIT) -// init = WIN_INIT; -// zmemzero(s->window + curr, (unsigned)init); -// s->high_water = curr + init; -// } -// else if (s->high_water < (ulg)curr + WIN_INIT) { -// /* High water mark at or above current data, but below current data -// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up -// * to end of window, whichever is less. -// */ -// init = (ulg)curr + WIN_INIT - s->high_water; -// if (init > s->window_size - s->high_water) -// init = s->window_size - s->high_water; -// zmemzero(s->window + s->high_water, (unsigned)init); -// s->high_water += init; -// } -// } -// -// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, -// "not enough room for search"); -}; - -/* =========================================================================== - * Copy without compression as much as possible from the input stream, return - * the current block state. - * - * In case deflateParams() is used to later switch to a non-zero compression - * level, s->matches (otherwise unused when storing) keeps track of the number - * of hash table slides to perform. If s->matches is 1, then one hash table - * slide will be done when switching. If s->matches is 2, the maximum value - * allowed here, then the hash table will be cleared, since two or more slides - * is the same as a clear. - * - * deflate_stored() is written to minimize the number of times an input byte is - * copied. It is most efficient with large input and output buffers, which - * maximizes the opportunites to have a single copy from next_in to next_out. - */ -const deflate_stored = (s, flush) => { - - /* Smallest worthy block size when not flushing or finishing. By default - * this is 32K. This can be as small as 507 bytes for memLevel == 1. For - * large input and output buffers, the stored block size will be larger. - */ - let min_block = s.pending_buf_size - 5 > s.w_size ? s.w_size : s.pending_buf_size - 5; - - /* Copy as many min_block or larger stored blocks directly to next_out as - * possible. If flushing, copy the remaining available input to next_out as - * stored blocks, if there is enough space. - */ - let len, left, have, last = 0; - let used = s.strm.avail_in; - do { - /* Set len to the maximum size block that we can copy directly with the - * available input data and output space. Set left to how much of that - * would be copied from what's left in the window. - */ - len = 65535/* MAX_STORED */; /* maximum deflate stored block length */ - have = (s.bi_valid + 42) >> 3; /* number of header bytes */ - if (s.strm.avail_out < have) { /* need room for header */ - break; - } - /* maximum stored block length that will fit in avail_out: */ - have = s.strm.avail_out - have; - left = s.strstart - s.block_start; /* bytes left in window */ - if (len > left + s.strm.avail_in) { - len = left + s.strm.avail_in; /* limit len to the input */ - } - if (len > have) { - len = have; /* limit len to the output */ - } - - /* If the stored block would be less than min_block in length, or if - * unable to copy all of the available input when flushing, then try - * copying to the window and the pending buffer instead. Also don't - * write an empty block when flushing -- deflate() does that. - */ - if (len < min_block && ((len === 0 && flush !== Z_FINISH$3) || - flush === Z_NO_FLUSH$2 || - len !== left + s.strm.avail_in)) { - break; - } - - /* Make a dummy stored block in pending to get the header bytes, - * including any pending bits. This also updates the debugging counts. - */ - last = flush === Z_FINISH$3 && len === left + s.strm.avail_in ? 1 : 0; - _tr_stored_block(s, 0, 0, last); - - /* Replace the lengths in the dummy stored block with len. */ - s.pending_buf[s.pending - 4] = len; - s.pending_buf[s.pending - 3] = len >> 8; - s.pending_buf[s.pending - 2] = ~len; - s.pending_buf[s.pending - 1] = ~len >> 8; - - /* Write the stored block header bytes. */ - flush_pending(s.strm); - -//#ifdef ZLIB_DEBUG -// /* Update debugging counts for the data about to be copied. */ -// s->compressed_len += len << 3; -// s->bits_sent += len << 3; -//#endif - - /* Copy uncompressed bytes from the window to next_out. */ - if (left) { - if (left > len) { - left = len; - } - //zmemcpy(s->strm->next_out, s->window + s->block_start, left); - s.strm.output.set(s.window.subarray(s.block_start, s.block_start + left), s.strm.next_out); - s.strm.next_out += left; - s.strm.avail_out -= left; - s.strm.total_out += left; - s.block_start += left; - len -= left; - } - - /* Copy uncompressed bytes directly from next_in to next_out, updating - * the check value. - */ - if (len) { - read_buf(s.strm, s.strm.output, s.strm.next_out, len); - s.strm.next_out += len; - s.strm.avail_out -= len; - s.strm.total_out += len; - } - } while (last === 0); - - /* Update the sliding window with the last s->w_size bytes of the copied - * data, or append all of the copied data to the existing window if less - * than s->w_size bytes were copied. Also update the number of bytes to - * insert in the hash tables, in the event that deflateParams() switches to - * a non-zero compression level. - */ - used -= s.strm.avail_in; /* number of input bytes directly copied */ - if (used) { - /* If any input was used, then no unused input remains in the window, - * therefore s->block_start == s->strstart. - */ - if (used >= s.w_size) { /* supplant the previous history */ - s.matches = 2; /* clear hash */ - //zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size); - s.window.set(s.strm.input.subarray(s.strm.next_in - s.w_size, s.strm.next_in), 0); - s.strstart = s.w_size; - s.insert = s.strstart; - } - else { - if (s.window_size - s.strstart <= used) { - /* Slide the window down. */ - s.strstart -= s.w_size; - //zmemcpy(s->window, s->window + s->w_size, s->strstart); - s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); - if (s.matches < 2) { - s.matches++; /* add a pending slide_hash() */ - } - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - } - //zmemcpy(s->window + s->strstart, s->strm->next_in - used, used); - s.window.set(s.strm.input.subarray(s.strm.next_in - used, s.strm.next_in), s.strstart); - s.strstart += used; - s.insert += used > s.w_size - s.insert ? s.w_size - s.insert : used; - } - s.block_start = s.strstart; - } - if (s.high_water < s.strstart) { - s.high_water = s.strstart; - } - - /* If the last block was written to next_out, then done. */ - if (last) { - return BS_FINISH_DONE; - } - - /* If flushing and all input has been consumed, then done. */ - if (flush !== Z_NO_FLUSH$2 && flush !== Z_FINISH$3 && - s.strm.avail_in === 0 && s.strstart === s.block_start) { - return BS_BLOCK_DONE; - } - - /* Fill the window with any remaining input. */ - have = s.window_size - s.strstart; - if (s.strm.avail_in > have && s.block_start >= s.w_size) { - /* Slide the window down. */ - s.block_start -= s.w_size; - s.strstart -= s.w_size; - //zmemcpy(s->window, s->window + s->w_size, s->strstart); - s.window.set(s.window.subarray(s.w_size, s.w_size + s.strstart), 0); - if (s.matches < 2) { - s.matches++; /* add a pending slide_hash() */ - } - have += s.w_size; /* more space now */ - if (s.insert > s.strstart) { - s.insert = s.strstart; - } - } - if (have > s.strm.avail_in) { - have = s.strm.avail_in; - } - if (have) { - read_buf(s.strm, s.window, s.strstart, have); - s.strstart += have; - s.insert += have > s.w_size - s.insert ? s.w_size - s.insert : have; - } - if (s.high_water < s.strstart) { - s.high_water = s.strstart; - } - - /* There was not enough avail_out to write a complete worthy or flushed - * stored block to next_out. Write a stored block to pending instead, if we - * have enough input for a worthy block, or if flushing and there is enough - * room for the remaining input as a stored block in the pending buffer. - */ - have = (s.bi_valid + 42) >> 3; /* number of header bytes */ - /* maximum stored block length that will fit in pending: */ - have = s.pending_buf_size - have > 65535/* MAX_STORED */ ? 65535/* MAX_STORED */ : s.pending_buf_size - have; - min_block = have > s.w_size ? s.w_size : have; - left = s.strstart - s.block_start; - if (left >= min_block || - ((left || flush === Z_FINISH$3) && flush !== Z_NO_FLUSH$2 && - s.strm.avail_in === 0 && left <= have)) { - len = left > have ? have : left; - last = flush === Z_FINISH$3 && s.strm.avail_in === 0 && - len === left ? 1 : 0; - _tr_stored_block(s, s.block_start, len, last); - s.block_start += len; - flush_pending(s.strm); - } - - /* We've done all we can with the available input and output. */ - return last ? BS_FINISH_STARTED : BS_NEED_MORE; -}; - - -/* =========================================================================== - * Compress as much as possible from the input stream, return the current - * block state. - * This function does not perform lazy evaluation of matches and inserts - * new strings in the dictionary only for unmatched strings or for short - * matches. It is used only for the fast compression options. - */ -const deflate_fast = (s, flush) => { - - let hash_head; /* head of the hash chain */ - let bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { - break; /* flush the current block */ - } - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - * At this point we have always match_length < MIN_MATCH - */ - if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - } - if (s.match_length >= MIN_MATCH) { - // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only - - /*** _tr_tally_dist(s, s.strstart - s.match_start, - s.match_length - MIN_MATCH, bflush); ***/ - bflush = _tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - - /* Insert new strings in the hash table only if the match length - * is not too large. This saves time but degrades compression. - */ - if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) { - s.match_length--; /* string at strstart already in table */ - do { - s.strstart++; - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - /* strstart never exceeds WSIZE-MAX_MATCH, so there are - * always MIN_MATCH bytes ahead. - */ - } while (--s.match_length !== 0); - s.strstart++; - } else - { - s.strstart += s.match_length; - s.match_length = 0; - s.ins_h = s.window[s.strstart]; - /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]); - -//#if MIN_MATCH != 3 -// Call UPDATE_HASH() MIN_MATCH-3 more times -//#endif - /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not - * matter since it will be recomputed at next deflate call. - */ - } - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s.window[s.strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* =========================================================================== - * Same as above, but achieves better compression. We use a lazy - * evaluation for matches: a match is finally adopted only if there is - * no better match at the next window position. - */ -const deflate_slow = (s, flush) => { - - let hash_head; /* head of hash chain */ - let bflush; /* set if current block must be flushed */ - - let max_insert; - - /* Process the input block. */ - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the next match, plus MIN_MATCH bytes to insert the - * string following the next match. - */ - if (s.lookahead < MIN_LOOKAHEAD) { - fill_window(s); - if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* Insert the string window[strstart .. strstart+2] in the - * dictionary, and set hash_head to the head of the hash chain: - */ - hash_head = 0/*NIL*/; - if (s.lookahead >= MIN_MATCH) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - - /* Find the longest match, discarding those <= prev_length. - */ - s.prev_length = s.match_length; - s.prev_match = s.match_start; - s.match_length = MIN_MATCH - 1; - - if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && - s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { - /* To simplify the code, we prevent matches with the string - * of window index 0 (in particular we have to avoid a match - * of the string with itself at the start of the input file). - */ - s.match_length = longest_match(s, hash_head); - /* longest_match() sets match_start */ - - if (s.match_length <= 5 && - (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) { - - /* If prev_match is also MIN_MATCH, match_start is garbage - * but we will ignore the current match anyway. - */ - s.match_length = MIN_MATCH - 1; - } - } - /* If there was a match at the previous step and the current - * match is not better, output the previous match: - */ - if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) { - max_insert = s.strstart + s.lookahead - MIN_MATCH; - /* Do not insert strings in hash table beyond this. */ - - //check_match(s, s.strstart-1, s.prev_match, s.prev_length); - - /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, - s.prev_length - MIN_MATCH, bflush);***/ - bflush = _tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); - /* Insert in hash table all strings up to the end of the match. - * strstart-1 and strstart are already inserted. If there is not - * enough lookahead, the last two strings are not inserted in - * the hash table. - */ - s.lookahead -= s.prev_length - 1; - s.prev_length -= 2; - do { - if (++s.strstart <= max_insert) { - /*** INSERT_STRING(s, s.strstart, hash_head); ***/ - s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]); - hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h]; - s.head[s.ins_h] = s.strstart; - /***/ - } - } while (--s.prev_length !== 0); - s.match_available = 0; - s.match_length = MIN_MATCH - 1; - s.strstart++; - - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - } else if (s.match_available) { - /* If there was no match at the previous position, output a - * single literal. If there was a match but the current match - * is longer, truncate the previous match to a single literal. - */ - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); - - if (bflush) { - /*** FLUSH_BLOCK_ONLY(s, 0) ***/ - flush_block_only(s, false); - /***/ - } - s.strstart++; - s.lookahead--; - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - } else { - /* There is no previous match to compare with, wait for - * the next step to decide. - */ - s.match_available = 1; - s.strstart++; - s.lookahead--; - } - } - //Assert (flush != Z_NO_FLUSH, "no flush?"); - if (s.match_available) { - //Tracevv((stderr,"%c", s->window[s->strstart-1])); - /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart - 1]); - - s.match_available = 0; - } - s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - - return BS_BLOCK_DONE; -}; - - -/* =========================================================================== - * For Z_RLE, simply look for runs of bytes, generate matches only of distance - * one. Do not maintain a hash table. (It will be regenerated if this run of - * deflate switches away from Z_RLE.) - */ -const deflate_rle = (s, flush) => { - - let bflush; /* set if current block must be flushed */ - let prev; /* byte at distance one to match */ - let scan, strend; /* scan goes up to strend for length of run */ - - const _win = s.window; - - for (;;) { - /* Make sure that we always have enough lookahead, except - * at the end of the input file. We need MAX_MATCH bytes - * for the longest run, plus one for the unrolled loop. - */ - if (s.lookahead <= MAX_MATCH) { - fill_window(s); - if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - if (s.lookahead === 0) { break; } /* flush the current block */ - } - - /* See how many times the previous byte repeats */ - s.match_length = 0; - if (s.lookahead >= MIN_MATCH && s.strstart > 0) { - scan = s.strstart - 1; - prev = _win[scan]; - if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) { - strend = s.strstart + MAX_MATCH; - do { - /*jshint noempty:false*/ - } while (prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - prev === _win[++scan] && prev === _win[++scan] && - scan < strend); - s.match_length = MAX_MATCH - (strend - scan); - if (s.match_length > s.lookahead) { - s.match_length = s.lookahead; - } - } - //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); - } - - /* Emit match if have run of MIN_MATCH or longer, else emit literal */ - if (s.match_length >= MIN_MATCH) { - //check_match(s, s.strstart, s.strstart - 1, s.match_length); - - /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/ - bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH); - - s.lookahead -= s.match_length; - s.strstart += s.match_length; - s.match_length = 0; - } else { - /* No match, output a literal byte */ - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - - s.lookahead--; - s.strstart++; - } - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* =========================================================================== - * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. - * (It will be regenerated if this run of deflate switches away from Huffman.) - */ -const deflate_huff = (s, flush) => { - - let bflush; /* set if current block must be flushed */ - - for (;;) { - /* Make sure that we have a literal to write. */ - if (s.lookahead === 0) { - fill_window(s); - if (s.lookahead === 0) { - if (flush === Z_NO_FLUSH$2) { - return BS_NEED_MORE; - } - break; /* flush the current block */ - } - } - - /* Output a literal byte */ - s.match_length = 0; - //Tracevv((stderr,"%c", s->window[s->strstart])); - /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/ - bflush = _tr_tally(s, 0, s.window[s.strstart]); - s.lookahead--; - s.strstart++; - if (bflush) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - } - s.insert = 0; - if (flush === Z_FINISH$3) { - /*** FLUSH_BLOCK(s, 1); ***/ - flush_block_only(s, true); - if (s.strm.avail_out === 0) { - return BS_FINISH_STARTED; - } - /***/ - return BS_FINISH_DONE; - } - if (s.sym_next) { - /*** FLUSH_BLOCK(s, 0); ***/ - flush_block_only(s, false); - if (s.strm.avail_out === 0) { - return BS_NEED_MORE; - } - /***/ - } - return BS_BLOCK_DONE; -}; - -/* Values for max_lazy_match, good_match and max_chain_length, depending on - * the desired pack level (0..9). The values given below have been tuned to - * exclude worst case performance for pathological files. Better values may be - * found for specific files. - */ -function Config(good_length, max_lazy, nice_length, max_chain, func) { - - this.good_length = good_length; - this.max_lazy = max_lazy; - this.nice_length = nice_length; - this.max_chain = max_chain; - this.func = func; -} - -const configuration_table = [ - /* good lazy nice chain */ - new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */ - new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */ - new Config(4, 5, 16, 8, deflate_fast), /* 2 */ - new Config(4, 6, 32, 32, deflate_fast), /* 3 */ - - new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */ - new Config(8, 16, 32, 32, deflate_slow), /* 5 */ - new Config(8, 16, 128, 128, deflate_slow), /* 6 */ - new Config(8, 32, 128, 256, deflate_slow), /* 7 */ - new Config(32, 128, 258, 1024, deflate_slow), /* 8 */ - new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */ -]; - - -/* =========================================================================== - * Initialize the "longest match" routines for a new zlib stream - */ -const lm_init = (s) => { - - s.window_size = 2 * s.w_size; - - /*** CLEAR_HASH(s); ***/ - zero(s.head); // Fill with NIL (= 0); - - /* Set the default configuration parameters: - */ - s.max_lazy_match = configuration_table[s.level].max_lazy; - s.good_match = configuration_table[s.level].good_length; - s.nice_match = configuration_table[s.level].nice_length; - s.max_chain_length = configuration_table[s.level].max_chain; - - s.strstart = 0; - s.block_start = 0; - s.lookahead = 0; - s.insert = 0; - s.match_length = s.prev_length = MIN_MATCH - 1; - s.match_available = 0; - s.ins_h = 0; -}; - - -function DeflateState() { - this.strm = null; /* pointer back to this zlib stream */ - this.status = 0; /* as the name implies */ - this.pending_buf = null; /* output still pending */ - this.pending_buf_size = 0; /* size of pending_buf */ - this.pending_out = 0; /* next pending byte to output to the stream */ - this.pending = 0; /* nb of bytes in the pending buffer */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */ - this.gzhead = null; /* gzip header information to write */ - this.gzindex = 0; /* where in extra, name, or comment */ - this.method = Z_DEFLATED$2; /* can only be DEFLATED */ - this.last_flush = -1; /* value of flush param for previous deflate call */ - - this.w_size = 0; /* LZ77 window size (32K by default) */ - this.w_bits = 0; /* log2(w_size) (8..16) */ - this.w_mask = 0; /* w_size - 1 */ - - this.window = null; - /* Sliding window. Input bytes are read into the second half of the window, - * and move to the first half later to keep a dictionary of at least wSize - * bytes. With this organization, matches are limited to a distance of - * wSize-MAX_MATCH bytes, but this ensures that IO is always - * performed with a length multiple of the block size. - */ - - this.window_size = 0; - /* Actual size of window: 2*wSize, except when the user input buffer - * is directly used as sliding window. - */ - - this.prev = null; - /* Link to older string with same hash index. To limit the size of this - * array to 64K, this link is maintained only for the last 32K strings. - * An index in this array is thus a window index modulo 32K. - */ - - this.head = null; /* Heads of the hash chains or NIL. */ - - this.ins_h = 0; /* hash index of string to be inserted */ - this.hash_size = 0; /* number of elements in hash table */ - this.hash_bits = 0; /* log2(hash_size) */ - this.hash_mask = 0; /* hash_size-1 */ - - this.hash_shift = 0; - /* Number of bits by which ins_h must be shifted at each input - * step. It must be such that after MIN_MATCH steps, the oldest - * byte no longer takes part in the hash key, that is: - * hash_shift * MIN_MATCH >= hash_bits - */ - - this.block_start = 0; - /* Window position at the beginning of the current output block. Gets - * negative when the window is moved backwards. - */ - - this.match_length = 0; /* length of best match */ - this.prev_match = 0; /* previous match */ - this.match_available = 0; /* set if previous match exists */ - this.strstart = 0; /* start of string to insert */ - this.match_start = 0; /* start of matching string */ - this.lookahead = 0; /* number of valid bytes ahead in window */ - - this.prev_length = 0; - /* Length of the best match at previous step. Matches not greater than this - * are discarded. This is used in the lazy match evaluation. - */ - - this.max_chain_length = 0; - /* To speed up deflation, hash chains are never searched beyond this - * length. A higher limit improves compression ratio but degrades the - * speed. - */ - - this.max_lazy_match = 0; - /* Attempt to find a better match only when the current match is strictly - * smaller than this value. This mechanism is used only for compression - * levels >= 4. - */ - // That's alias to max_lazy_match, don't use directly - //this.max_insert_length = 0; - /* Insert new strings in the hash table only if the match length is not - * greater than this length. This saves time but degrades compression. - * max_insert_length is used only for compression levels <= 3. - */ - - this.level = 0; /* compression level (1..9) */ - this.strategy = 0; /* favor or force Huffman coding*/ - - this.good_match = 0; - /* Use a faster search when the previous match is longer than this */ - - this.nice_match = 0; /* Stop searching when current match exceeds this */ - - /* used by trees.c: */ - - /* Didn't use ct_data typedef below to suppress compiler warning */ - - // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ - // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ - // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ - - // Use flat array of DOUBLE size, with interleaved fata, - // because JS does not support effective - this.dyn_ltree = new Uint16Array(HEAP_SIZE * 2); - this.dyn_dtree = new Uint16Array((2 * D_CODES + 1) * 2); - this.bl_tree = new Uint16Array((2 * BL_CODES + 1) * 2); - zero(this.dyn_ltree); - zero(this.dyn_dtree); - zero(this.bl_tree); - - this.l_desc = null; /* desc. for literal tree */ - this.d_desc = null; /* desc. for distance tree */ - this.bl_desc = null; /* desc. for bit length tree */ - - //ush bl_count[MAX_BITS+1]; - this.bl_count = new Uint16Array(MAX_BITS + 1); - /* number of codes at each bit length for an optimal tree */ - - //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ - this.heap = new Uint16Array(2 * L_CODES + 1); /* heap used to build the Huffman trees */ - zero(this.heap); - - this.heap_len = 0; /* number of elements in the heap */ - this.heap_max = 0; /* element of largest frequency */ - /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. - * The same heap array is used to build all trees. - */ - - this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; - zero(this.depth); - /* Depth of each subtree used as tie breaker for trees of equal frequency - */ - - this.sym_buf = 0; /* buffer for distances and literals/lengths */ - - this.lit_bufsize = 0; - /* Size of match buffer for literals/lengths. There are 4 reasons for - * limiting lit_bufsize to 64K: - * - frequencies can be kept in 16 bit counters - * - if compression is not successful for the first block, all input - * data is still in the window so we can still emit a stored block even - * when input comes from standard input. (This can also be done for - * all blocks if lit_bufsize is not greater than 32K.) - * - if compression is not successful for a file smaller than 64K, we can - * even emit a stored file instead of a stored block (saving 5 bytes). - * This is applicable only for zip (not gzip or zlib). - * - creating new Huffman trees less frequently may not provide fast - * adaptation to changes in the input data statistics. (Take for - * example a binary file with poorly compressible code followed by - * a highly compressible string table.) Smaller buffer sizes give - * fast adaptation but have of course the overhead of transmitting - * trees more frequently. - * - I can't count above 4 - */ - - this.sym_next = 0; /* running index in sym_buf */ - this.sym_end = 0; /* symbol table full when sym_next reaches this */ - - this.opt_len = 0; /* bit length of current block with optimal trees */ - this.static_len = 0; /* bit length of current block with static trees */ - this.matches = 0; /* number of string matches in current block */ - this.insert = 0; /* bytes at end of window left to insert */ - - - this.bi_buf = 0; - /* Output buffer. bits are inserted starting at the bottom (least - * significant bits). - */ - this.bi_valid = 0; - /* Number of valid bits in bi_buf. All bits above the last valid bit - * are always zero. - */ - - // Used for window memory init. We safely ignore it for JS. That makes - // sense only for pointers and memory check tools. - //this.high_water = 0; - /* High water mark offset in window for initialized bytes -- bytes above - * this are set to zero in order to avoid memory check warnings when - * longest match routines access bytes past the input. This is then - * updated to the new high water mark. - */ -} - - -/* ========================================================================= - * Check for a valid deflate stream state. Return 0 if ok, 1 if not. - */ -const deflateStateCheck = (strm) => { - - if (!strm) { - return 1; - } - const s = strm.state; - if (!s || s.strm !== strm || (s.status !== INIT_STATE && -//#ifdef GZIP - s.status !== GZIP_STATE && -//#endif - s.status !== EXTRA_STATE && - s.status !== NAME_STATE && - s.status !== COMMENT_STATE && - s.status !== HCRC_STATE && - s.status !== BUSY_STATE && - s.status !== FINISH_STATE)) { - return 1; - } - return 0; -}; - - -const deflateResetKeep = (strm) => { - - if (deflateStateCheck(strm)) { - return err(strm, Z_STREAM_ERROR$2); - } - - strm.total_in = strm.total_out = 0; - strm.data_type = Z_UNKNOWN; - - const s = strm.state; - s.pending = 0; - s.pending_out = 0; - - if (s.wrap < 0) { - s.wrap = -s.wrap; - /* was made negative by deflate(..., Z_FINISH); */ - } - s.status = -//#ifdef GZIP - s.wrap === 2 ? GZIP_STATE : -//#endif - s.wrap ? INIT_STATE : BUSY_STATE; - strm.adler = (s.wrap === 2) ? - 0 // crc32(0, Z_NULL, 0) - : - 1; // adler32(0, Z_NULL, 0) - s.last_flush = -2; - _tr_init(s); - return Z_OK$3; -}; - - -const deflateReset = (strm) => { - - const ret = deflateResetKeep(strm); - if (ret === Z_OK$3) { - lm_init(strm.state); - } - return ret; -}; - - -const deflateSetHeader = (strm, head) => { - - if (deflateStateCheck(strm) || strm.state.wrap !== 2) { - return Z_STREAM_ERROR$2; - } - strm.state.gzhead = head; - return Z_OK$3; -}; - - -const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => { - - if (!strm) { // === Z_NULL - return Z_STREAM_ERROR$2; - } - let wrap = 1; - - if (level === Z_DEFAULT_COMPRESSION$1) { - level = 6; - } - - if (windowBits < 0) { /* suppress zlib wrapper */ - wrap = 0; - windowBits = -windowBits; - } - - else if (windowBits > 15) { - wrap = 2; /* write gzip wrapper instead */ - windowBits -= 16; - } - - - if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 || - windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || - strategy < 0 || strategy > Z_FIXED || (windowBits === 8 && wrap !== 1)) { - return err(strm, Z_STREAM_ERROR$2); - } - - - if (windowBits === 8) { - windowBits = 9; - } - /* until 256-byte window bug fixed */ - - const s = new DeflateState(); - - strm.state = s; - s.strm = strm; - s.status = INIT_STATE; /* to pass state test in deflateReset() */ - - s.wrap = wrap; - s.gzhead = null; - s.w_bits = windowBits; - s.w_size = 1 << s.w_bits; - s.w_mask = s.w_size - 1; - - s.hash_bits = memLevel + 7; - s.hash_size = 1 << s.hash_bits; - s.hash_mask = s.hash_size - 1; - s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH); - - s.window = new Uint8Array(s.w_size * 2); - s.head = new Uint16Array(s.hash_size); - s.prev = new Uint16Array(s.w_size); - - // Don't need mem init magic for JS. - //s.high_water = 0; /* nothing written to s->window yet */ - - s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ - - /* We overlay pending_buf and sym_buf. This works since the average size - * for length/distance pairs over any compressed block is assured to be 31 - * bits or less. - * - * Analysis: The longest fixed codes are a length code of 8 bits plus 5 - * extra bits, for lengths 131 to 257. The longest fixed distance codes are - * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest - * possible fixed-codes length/distance pair is then 31 bits total. - * - * sym_buf starts one-fourth of the way into pending_buf. So there are - * three bytes in sym_buf for every four bytes in pending_buf. Each symbol - * in sym_buf is three bytes -- two for the distance and one for the - * literal/length. As each symbol is consumed, the pointer to the next - * sym_buf value to read moves forward three bytes. From that symbol, up to - * 31 bits are written to pending_buf. The closest the written pending_buf - * bits gets to the next sym_buf symbol to read is just before the last - * code is written. At that time, 31*(n-2) bits have been written, just - * after 24*(n-2) bits have been consumed from sym_buf. sym_buf starts at - * 8*n bits into pending_buf. (Note that the symbol buffer fills when n-1 - * symbols are written.) The closest the writing gets to what is unread is - * then n+14 bits. Here n is lit_bufsize, which is 16384 by default, and - * can range from 128 to 32768. - * - * Therefore, at a minimum, there are 142 bits of space between what is - * written and what is read in the overlain buffers, so the symbols cannot - * be overwritten by the compressed data. That space is actually 139 bits, - * due to the three-bit fixed-code block header. - * - * That covers the case where either Z_FIXED is specified, forcing fixed - * codes, or when the use of fixed codes is chosen, because that choice - * results in a smaller compressed block than dynamic codes. That latter - * condition then assures that the above analysis also covers all dynamic - * blocks. A dynamic-code block will only be chosen to be emitted if it has - * fewer bits than a fixed-code block would for the same set of symbols. - * Therefore its average symbol length is assured to be less than 31. So - * the compressed data for a dynamic block also cannot overwrite the - * symbols from which it is being constructed. - */ - - s.pending_buf_size = s.lit_bufsize * 4; - s.pending_buf = new Uint8Array(s.pending_buf_size); - - // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) - //s->sym_buf = s->pending_buf + s->lit_bufsize; - s.sym_buf = s.lit_bufsize; - - //s->sym_end = (s->lit_bufsize - 1) * 3; - s.sym_end = (s.lit_bufsize - 1) * 3; - /* We avoid equality with lit_bufsize*3 because of wraparound at 64K - * on 16 bit machines and because stored blocks are restricted to - * 64K-1 bytes. - */ - - s.level = level; - s.strategy = strategy; - s.method = method; - - return deflateReset(strm); -}; - -const deflateInit = (strm, level) => { - - return deflateInit2(strm, level, Z_DEFLATED$2, MAX_WBITS$1, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY$1); -}; - - -/* ========================================================================= */ -const deflate$2 = (strm, flush) => { - - if (deflateStateCheck(strm) || flush > Z_BLOCK$1 || flush < 0) { - return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2; - } - - const s = strm.state; - - if (!strm.output || - (strm.avail_in !== 0 && !strm.input) || - (s.status === FINISH_STATE && flush !== Z_FINISH$3)) { - return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2); - } - - const old_flush = s.last_flush; - s.last_flush = flush; - - /* Flush as much pending output as possible */ - if (s.pending !== 0) { - flush_pending(strm); - if (strm.avail_out === 0) { - /* Since avail_out is 0, deflate will be called again with - * more output space, but possibly with both pending and - * avail_in equal to zero. There won't be anything to do, - * but this is not an error situation so make sure we - * return OK instead of BUF_ERROR at next call of deflate: - */ - s.last_flush = -1; - return Z_OK$3; - } - - /* Make sure there is something to do and avoid duplicate consecutive - * flushes. For repeated and useless calls with Z_FINISH, we keep - * returning Z_STREAM_END instead of Z_BUF_ERROR. - */ - } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) && - flush !== Z_FINISH$3) { - return err(strm, Z_BUF_ERROR$1); - } - - /* User must not provide more input after the first FINISH: */ - if (s.status === FINISH_STATE && strm.avail_in !== 0) { - return err(strm, Z_BUF_ERROR$1); - } - - /* Write the header */ - if (s.status === INIT_STATE && s.wrap === 0) { - s.status = BUSY_STATE; - } - if (s.status === INIT_STATE) { - /* zlib header */ - let header = (Z_DEFLATED$2 + ((s.w_bits - 8) << 4)) << 8; - let level_flags = -1; - - if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) { - level_flags = 0; - } else if (s.level < 6) { - level_flags = 1; - } else if (s.level === 6) { - level_flags = 2; - } else { - level_flags = 3; - } - header |= (level_flags << 6); - if (s.strstart !== 0) { header |= PRESET_DICT; } - header += 31 - (header % 31); - - putShortMSB(s, header); - - /* Save the adler32 of the preset dictionary: */ - if (s.strstart !== 0) { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - strm.adler = 1; // adler32(0L, Z_NULL, 0); - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } -//#ifdef GZIP - if (s.status === GZIP_STATE) { - /* gzip header */ - strm.adler = 0; //crc32(0L, Z_NULL, 0); - put_byte(s, 31); - put_byte(s, 139); - put_byte(s, 8); - if (!s.gzhead) { // s->gzhead == Z_NULL - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, 0); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, OS_CODE); - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } - else { - put_byte(s, (s.gzhead.text ? 1 : 0) + - (s.gzhead.hcrc ? 2 : 0) + - (!s.gzhead.extra ? 0 : 4) + - (!s.gzhead.name ? 0 : 8) + - (!s.gzhead.comment ? 0 : 16) - ); - put_byte(s, s.gzhead.time & 0xff); - put_byte(s, (s.gzhead.time >> 8) & 0xff); - put_byte(s, (s.gzhead.time >> 16) & 0xff); - put_byte(s, (s.gzhead.time >> 24) & 0xff); - put_byte(s, s.level === 9 ? 2 : - (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ? - 4 : 0)); - put_byte(s, s.gzhead.os & 0xff); - if (s.gzhead.extra && s.gzhead.extra.length) { - put_byte(s, s.gzhead.extra.length & 0xff); - put_byte(s, (s.gzhead.extra.length >> 8) & 0xff); - } - if (s.gzhead.hcrc) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0); - } - s.gzindex = 0; - s.status = EXTRA_STATE; - } - } - if (s.status === EXTRA_STATE) { - if (s.gzhead.extra/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let left = (s.gzhead.extra.length & 0xffff) - s.gzindex; - while (s.pending + left > s.pending_buf_size) { - let copy = s.pending_buf_size - s.pending; - // zmemcpy(s.pending_buf + s.pending, - // s.gzhead.extra + s.gzindex, copy); - s.pending_buf.set(s.gzhead.extra.subarray(s.gzindex, s.gzindex + copy), s.pending); - s.pending = s.pending_buf_size; - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex += copy; - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - left -= copy; - } - // JS specific: s.gzhead.extra may be TypedArray or Array for backward compatibility - // TypedArray.slice and TypedArray.from don't exist in IE10-IE11 - let gzhead_extra = new Uint8Array(s.gzhead.extra); - // zmemcpy(s->pending_buf + s->pending, - // s->gzhead->extra + s->gzindex, left); - s.pending_buf.set(gzhead_extra.subarray(s.gzindex, s.gzindex + left), s.pending); - s.pending += left; - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex = 0; - } - s.status = NAME_STATE; - } - if (s.status === NAME_STATE) { - if (s.gzhead.name/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let val; - do { - if (s.pending === s.pending_buf_size) { - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.name.length) { - val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - s.gzindex = 0; - } - s.status = COMMENT_STATE; - } - if (s.status === COMMENT_STATE) { - if (s.gzhead.comment/* != Z_NULL*/) { - let beg = s.pending; /* start of bytes to update crc */ - let val; - do { - if (s.pending === s.pending_buf_size) { - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - beg = 0; - } - // JS specific: little magic to add zero terminator to end of string - if (s.gzindex < s.gzhead.comment.length) { - val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff; - } else { - val = 0; - } - put_byte(s, val); - } while (val !== 0); - //--- HCRC_UPDATE(beg) ---// - if (s.gzhead.hcrc && s.pending > beg) { - strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg); - } - //---// - } - s.status = HCRC_STATE; - } - if (s.status === HCRC_STATE) { - if (s.gzhead.hcrc) { - if (s.pending + 2 > s.pending_buf_size) { - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - strm.adler = 0; //crc32(0L, Z_NULL, 0); - } - s.status = BUSY_STATE; - - /* Compression must start with an empty pending buffer */ - flush_pending(strm); - if (s.pending !== 0) { - s.last_flush = -1; - return Z_OK$3; - } - } -//#endif - - /* Start a new block or continue the current one. - */ - if (strm.avail_in !== 0 || s.lookahead !== 0 || - (flush !== Z_NO_FLUSH$2 && s.status !== FINISH_STATE)) { - let bstate = s.level === 0 ? deflate_stored(s, flush) : - s.strategy === Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : - s.strategy === Z_RLE ? deflate_rle(s, flush) : - configuration_table[s.level].func(s, flush); - - if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) { - s.status = FINISH_STATE; - } - if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) { - if (strm.avail_out === 0) { - s.last_flush = -1; - /* avoid BUF_ERROR next call, see above */ - } - return Z_OK$3; - /* If flush != Z_NO_FLUSH && avail_out == 0, the next call - * of deflate should use the same flush parameter to make sure - * that the flush is complete. So we don't have to output an - * empty block here, this will be done at next call. This also - * ensures that for a very small output buffer, we emit at most - * one empty block. - */ - } - if (bstate === BS_BLOCK_DONE) { - if (flush === Z_PARTIAL_FLUSH) { - _tr_align(s); - } - else if (flush !== Z_BLOCK$1) { /* FULL_FLUSH or SYNC_FLUSH */ - - _tr_stored_block(s, 0, 0, false); - /* For a full flush, this empty block will be recognized - * as a special marker by inflate_sync(). - */ - if (flush === Z_FULL_FLUSH$1) { - /*** CLEAR_HASH(s); ***/ /* forget history */ - zero(s.head); // Fill with NIL (= 0); - - if (s.lookahead === 0) { - s.strstart = 0; - s.block_start = 0; - s.insert = 0; - } - } - } - flush_pending(strm); - if (strm.avail_out === 0) { - s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */ - return Z_OK$3; - } - } - } - - if (flush !== Z_FINISH$3) { return Z_OK$3; } - if (s.wrap <= 0) { return Z_STREAM_END$3; } - - /* Write the trailer */ - if (s.wrap === 2) { - put_byte(s, strm.adler & 0xff); - put_byte(s, (strm.adler >> 8) & 0xff); - put_byte(s, (strm.adler >> 16) & 0xff); - put_byte(s, (strm.adler >> 24) & 0xff); - put_byte(s, strm.total_in & 0xff); - put_byte(s, (strm.total_in >> 8) & 0xff); - put_byte(s, (strm.total_in >> 16) & 0xff); - put_byte(s, (strm.total_in >> 24) & 0xff); - } - else - { - putShortMSB(s, strm.adler >>> 16); - putShortMSB(s, strm.adler & 0xffff); - } - - flush_pending(strm); - /* If avail_out is zero, the application will call deflate again - * to flush the rest. - */ - if (s.wrap > 0) { s.wrap = -s.wrap; } - /* write the trailer only once! */ - return s.pending !== 0 ? Z_OK$3 : Z_STREAM_END$3; -}; - - -const deflateEnd = (strm) => { - - if (deflateStateCheck(strm)) { - return Z_STREAM_ERROR$2; - } - - const status = strm.state.status; - - strm.state = null; - - return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3; -}; - - -/* ========================================================================= - * Initializes the compression dictionary from the given byte - * sequence without producing any compressed output. - */ -const deflateSetDictionary = (strm, dictionary) => { - - let dictLength = dictionary.length; - - if (deflateStateCheck(strm)) { - return Z_STREAM_ERROR$2; - } - - const s = strm.state; - const wrap = s.wrap; - - if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { - return Z_STREAM_ERROR$2; - } - - /* when using zlib wrappers, compute Adler-32 for provided dictionary */ - if (wrap === 1) { - /* adler32(strm->adler, dictionary, dictLength); */ - strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0); - } - - s.wrap = 0; /* avoid computing Adler-32 in read_buf */ - - /* if dictionary would fill window, just replace the history */ - if (dictLength >= s.w_size) { - if (wrap === 0) { /* already empty otherwise */ - /*** CLEAR_HASH(s); ***/ - zero(s.head); // Fill with NIL (= 0); - s.strstart = 0; - s.block_start = 0; - s.insert = 0; - } - /* use the tail */ - // dictionary = dictionary.slice(dictLength - s.w_size); - let tmpDict = new Uint8Array(s.w_size); - tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0); - dictionary = tmpDict; - dictLength = s.w_size; - } - /* insert dictionary into window and hash */ - const avail = strm.avail_in; - const next = strm.next_in; - const input = strm.input; - strm.avail_in = dictLength; - strm.next_in = 0; - strm.input = dictionary; - fill_window(s); - while (s.lookahead >= MIN_MATCH) { - let str = s.strstart; - let n = s.lookahead - (MIN_MATCH - 1); - do { - /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]); - - s.prev[str & s.w_mask] = s.head[s.ins_h]; - - s.head[s.ins_h] = str; - str++; - } while (--n); - s.strstart = str; - s.lookahead = MIN_MATCH - 1; - fill_window(s); - } - s.strstart += s.lookahead; - s.block_start = s.strstart; - s.insert = s.lookahead; - s.lookahead = 0; - s.match_length = s.prev_length = MIN_MATCH - 1; - s.match_available = 0; - strm.next_in = next; - strm.input = input; - strm.avail_in = avail; - s.wrap = wrap; - return Z_OK$3; -}; - - -var deflateInit_1 = deflateInit; -var deflateInit2_1 = deflateInit2; -var deflateReset_1 = deflateReset; -var deflateResetKeep_1 = deflateResetKeep; -var deflateSetHeader_1 = deflateSetHeader; -var deflate_2$1 = deflate$2; -var deflateEnd_1 = deflateEnd; -var deflateSetDictionary_1 = deflateSetDictionary; -var deflateInfo = 'pako deflate (from Nodeca project)'; - -/* Not implemented -module.exports.deflateBound = deflateBound; -module.exports.deflateCopy = deflateCopy; -module.exports.deflateGetDictionary = deflateGetDictionary; -module.exports.deflateParams = deflateParams; -module.exports.deflatePending = deflatePending; -module.exports.deflatePrime = deflatePrime; -module.exports.deflateTune = deflateTune; -*/ - -var deflate_1$2 = { - deflateInit: deflateInit_1, - deflateInit2: deflateInit2_1, - deflateReset: deflateReset_1, - deflateResetKeep: deflateResetKeep_1, - deflateSetHeader: deflateSetHeader_1, - deflate: deflate_2$1, - deflateEnd: deflateEnd_1, - deflateSetDictionary: deflateSetDictionary_1, - deflateInfo: deflateInfo -}; - -const _has = (obj, key) => { - return Object.prototype.hasOwnProperty.call(obj, key); -}; - -var assign = function (obj /*from1, from2, from3, ...*/) { - const sources = Array.prototype.slice.call(arguments, 1); - while (sources.length) { - const source = sources.shift(); - if (!source) { continue; } - - if (typeof source !== 'object') { - throw new TypeError(source + 'must be non-object'); - } - - for (const p in source) { - if (_has(source, p)) { - obj[p] = source[p]; - } - } - } - - return obj; -}; - - -// Join array of chunks to single array. -var flattenChunks = (chunks) => { - // calculate data length - let len = 0; - - for (let i = 0, l = chunks.length; i < l; i++) { - len += chunks[i].length; - } - - // join chunks - const result = new Uint8Array(len); - - for (let i = 0, pos = 0, l = chunks.length; i < l; i++) { - let chunk = chunks[i]; - result.set(chunk, pos); - pos += chunk.length; - } - - return result; -}; - -var common = { - assign: assign, - flattenChunks: flattenChunks -}; - -// String encode/decode helpers - - -// Quick check if we can use fast array to bin string conversion -// -// - apply(Array) can fail on Android 2.2 -// - apply(Uint8Array) can fail on iOS 5.1 Safari -// -let STR_APPLY_UIA_OK = true; - -try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; } - - -// Table with utf8 lengths (calculated by first byte of sequence) -// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS, -// because max possible codepoint is 0x10ffff -const _utf8len = new Uint8Array(256); -for (let q = 0; q < 256; q++) { - _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); -} -_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start - - -// convert string to array (typed, when possible) -var string2buf = (str) => { - if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) { - return new TextEncoder().encode(str); - } - - let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; - - // count binary size - for (m_pos = 0; m_pos < str_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { - c2 = str.charCodeAt(m_pos + 1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; - } - - // allocate buffer - buf = new Uint8Array(buf_len); - - // convert - for (i = 0, m_pos = 0; i < buf_len; m_pos++) { - c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { - c2 = str.charCodeAt(m_pos + 1); - if ((c2 & 0xfc00) === 0xdc00) { - c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); - m_pos++; - } - } - if (c < 0x80) { - /* one byte */ - buf[i++] = c; - } else if (c < 0x800) { - /* two bytes */ - buf[i++] = 0xC0 | (c >>> 6); - buf[i++] = 0x80 | (c & 0x3f); - } else if (c < 0x10000) { - /* three bytes */ - buf[i++] = 0xE0 | (c >>> 12); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } else { - /* four bytes */ - buf[i++] = 0xf0 | (c >>> 18); - buf[i++] = 0x80 | (c >>> 12 & 0x3f); - buf[i++] = 0x80 | (c >>> 6 & 0x3f); - buf[i++] = 0x80 | (c & 0x3f); - } - } - - return buf; -}; - -// Helper -const buf2binstring = (buf, len) => { - // On Chrome, the arguments in a function call that are allowed is `65534`. - // If the length of the buffer is smaller than that, we can use this optimization, - // otherwise we will take a slower path. - if (len < 65534) { - if (buf.subarray && STR_APPLY_UIA_OK) { - return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len)); - } - } - - let result = ''; - for (let i = 0; i < len; i++) { - result += String.fromCharCode(buf[i]); - } - return result; -}; - - -// convert array to string -var buf2string = (buf, max) => { - const len = max || buf.length; - - if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) { - return new TextDecoder().decode(buf.subarray(0, max)); - } - - let i, out; - - // Reserve max possible length (2 words per char) - // NB: by unknown reasons, Array is significantly faster for - // String.fromCharCode.apply than Uint16Array. - const utf16buf = new Array(len * 2); - - for (out = 0, i = 0; i < len;) { - let c = buf[i++]; - // quick process ascii - if (c < 0x80) { utf16buf[out++] = c; continue; } - - let c_len = _utf8len[c]; - // skip 5 & 6 byte codes - if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } - - // apply mask on first byte - c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; - // join the rest - while (c_len > 1 && i < len) { - c = (c << 6) | (buf[i++] & 0x3f); - c_len--; - } - - // terminated by end of string? - if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; } - - if (c < 0x10000) { - utf16buf[out++] = c; - } else { - c -= 0x10000; - utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff); - utf16buf[out++] = 0xdc00 | (c & 0x3ff); - } - } - - return buf2binstring(utf16buf, out); -}; - - -// Calculate max possible position in utf8 buffer, -// that will not break sequence. If that's not possible -// - (very small limits) return max size as is. -// -// buf[] - utf8 bytes array -// max - length limit (mandatory); -var utf8border = (buf, max) => { - - max = max || buf.length; - if (max > buf.length) { max = buf.length; } - - // go back from last position, until start of sequence found - let pos = max - 1; - while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } - - // Very small and broken sequence, - // return max, because we should return something anyway. - if (pos < 0) { return max; } - - // If we came to start of buffer - that means buffer is too small, - // return max too. - if (pos === 0) { return max; } - - return (pos + _utf8len[buf[pos]] > max) ? pos : max; -}; - -var strings = { - string2buf: string2buf, - buf2string: buf2string, - utf8border: utf8border -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -function ZStream() { - /* next input byte */ - this.input = null; // JS specific, because we have no pointers - this.next_in = 0; - /* number of bytes available at input */ - this.avail_in = 0; - /* total number of input bytes read so far */ - this.total_in = 0; - /* next output byte should be put there */ - this.output = null; // JS specific, because we have no pointers - this.next_out = 0; - /* remaining free space at output */ - this.avail_out = 0; - /* total number of bytes output so far */ - this.total_out = 0; - /* last error message, NULL if no error */ - this.msg = ''/*Z_NULL*/; - /* not visible by applications */ - this.state = null; - /* best guess about the data type: binary or text */ - this.data_type = 2/*Z_UNKNOWN*/; - /* adler32 value of the uncompressed data */ - this.adler = 0; -} - -var zstream = ZStream; - -const toString$1 = Object.prototype.toString; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH: Z_NO_FLUSH$1, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH: Z_FINISH$2, - Z_OK: Z_OK$2, Z_STREAM_END: Z_STREAM_END$2, - Z_DEFAULT_COMPRESSION, - Z_DEFAULT_STRATEGY, - Z_DEFLATED: Z_DEFLATED$1 -} = constants$2; - -/* ===========================================================================*/ - - -/** - * class Deflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[deflate]], - * [[deflateRaw]] and [[gzip]]. - **/ - -/* internal - * Deflate.chunks -> Array - * - * Chunks of output data, if [[Deflate#onData]] not overridden. - **/ - -/** - * Deflate.result -> Uint8Array - * - * Compressed result, generated by default [[Deflate#onData]] - * and [[Deflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Deflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Deflate.err -> Number - * - * Error code after deflate finished. 0 (Z_OK) on success. - * You will not need it in real life, because deflate errors - * are possible only on wrong options or bad `onData` / `onEnd` - * custom handlers. - **/ - -/** - * Deflate.msg -> String - * - * Error message, if [[Deflate.err]] != 0 - **/ - - -/** - * new Deflate(options) - * - options (Object): zlib deflate options. - * - * Creates new deflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `level` - * - `windowBits` - * - `memLevel` - * - `strategy` - * - `dictionary` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw deflate - * - `gzip` (Boolean) - create gzip wrapper - * - `header` (Object) - custom header for gzip - * - `text` (Boolean) - true if compressed data believed to be text - * - `time` (Number) - modification time, unix timestamp - * - `os` (Number) - operation system code - * - `extra` (Array) - array of bytes with extra data (max 65536) - * - `name` (String) - file name (binary string) - * - `comment` (String) - comment (binary string) - * - `hcrc` (Boolean) - true if header crc should be added - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) - * , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * const deflate = new pako.Deflate({ level: 3}); - * - * deflate.push(chunk1, false); - * deflate.push(chunk2, true); // true -> last chunk - * - * if (deflate.err) { throw new Error(deflate.err); } - * - * console.log(deflate.result); - * ``` - **/ -function Deflate$1(options) { - this.options = common.assign({ - level: Z_DEFAULT_COMPRESSION, - method: Z_DEFLATED$1, - chunkSize: 16384, - windowBits: 15, - memLevel: 8, - strategy: Z_DEFAULT_STRATEGY - }, options || {}); - - let opt = this.options; - - if (opt.raw && (opt.windowBits > 0)) { - opt.windowBits = -opt.windowBits; - } - - else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) { - opt.windowBits += 16; - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - let status = deflate_1$2.deflateInit2( - this.strm, - opt.level, - opt.method, - opt.windowBits, - opt.memLevel, - opt.strategy - ); - - if (status !== Z_OK$2) { - throw new Error(messages[status]); - } - - if (opt.header) { - deflate_1$2.deflateSetHeader(this.strm, opt.header); - } - - if (opt.dictionary) { - let dict; - // Convert data if needed - if (typeof opt.dictionary === 'string') { - // If we need to compress text, change encoding to utf8. - dict = strings.string2buf(opt.dictionary); - } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') { - dict = new Uint8Array(opt.dictionary); - } else { - dict = opt.dictionary; - } - - status = deflate_1$2.deflateSetDictionary(this.strm, dict); - - if (status !== Z_OK$2) { - throw new Error(messages[status]); - } - - this._dict_set = true; - } -} - -/** - * Deflate#push(data[, flush_mode]) -> Boolean - * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be - * converted to utf8 byte sequence. - * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. - * See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH. - * - * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with - * new compressed chunks. Returns `true` on success. The last data block must - * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending - * buffers and call [[Deflate#onEnd]]. - * - * On fail call [[Deflate#onEnd]] with error code and return false. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Deflate$1.prototype.push = function (data, flush_mode) { - const strm = this.strm; - const chunkSize = this.options.chunkSize; - let status, _flush_mode; - - if (this.ended) { return false; } - - if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; - else _flush_mode = flush_mode === true ? Z_FINISH$2 : Z_NO_FLUSH$1; - - // Convert data if needed - if (typeof data === 'string') { - // If we need to compress text, change encoding to utf8. - strm.input = strings.string2buf(data); - } else if (toString$1.call(data) === '[object ArrayBuffer]') { - strm.input = new Uint8Array(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - for (;;) { - if (strm.avail_out === 0) { - strm.output = new Uint8Array(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - - // Make sure avail_out > 6 to avoid repeating markers - if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && strm.avail_out <= 6) { - this.onData(strm.output.subarray(0, strm.next_out)); - strm.avail_out = 0; - continue; - } - - status = deflate_1$2.deflate(strm, _flush_mode); - - // Ended => flush and finish - if (status === Z_STREAM_END$2) { - if (strm.next_out > 0) { - this.onData(strm.output.subarray(0, strm.next_out)); - } - status = deflate_1$2.deflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return status === Z_OK$2; - } - - // Flush if out buffer full - if (strm.avail_out === 0) { - this.onData(strm.output); - continue; - } - - // Flush if requested and has data - if (_flush_mode > 0 && strm.next_out > 0) { - this.onData(strm.output.subarray(0, strm.next_out)); - strm.avail_out = 0; - continue; - } - - if (strm.avail_in === 0) break; - } - - return true; -}; - - -/** - * Deflate#onData(chunk) -> Void - * - chunk (Uint8Array): output data. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Deflate$1.prototype.onData = function (chunk) { - this.chunks.push(chunk); -}; - - -/** - * Deflate#onEnd(status) -> Void - * - status (Number): deflate status. 0 (Z_OK) on success, - * other if not. - * - * Called once after you tell deflate that the input stream is - * complete (Z_FINISH). By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Deflate$1.prototype.onEnd = function (status) { - // On success - join - if (status === Z_OK$2) { - this.result = common.flattenChunks(this.chunks); - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -// See state defs from inflate.js -const BAD$1 = 16209; /* got a data error -- remain here until reset */ -const TYPE$1 = 16191; /* i: waiting for type bits, including last-flag bit */ - -/* - Decode literal, length, and distance codes and write out the resulting - literal and match bytes until either not enough input or output is - available, an end-of-block is encountered, or a data error is encountered. - When large enough input and output buffers are supplied to inflate(), for - example, a 16K input buffer and a 64K output buffer, more than 95% of the - inflate execution time is spent in this routine. - - Entry assumptions: - - state.mode === LEN - strm.avail_in >= 6 - strm.avail_out >= 258 - start >= strm.avail_out - state.bits < 8 - - On return, state.mode is one of: - - LEN -- ran out of enough output space or enough available input - TYPE -- reached end of block code, inflate() to interpret next block - BAD -- error in block data - - Notes: - - - The maximum input bits used by a length/distance pair is 15 bits for the - length code, 5 bits for the length extra, 15 bits for the distance code, - and 13 bits for the distance extra. This totals 48 bits, or six bytes. - Therefore if strm.avail_in >= 6, then there is enough input to avoid - checking for available input while decoding. - - - The maximum bytes that a single length/distance pair can output is 258 - bytes, which is the maximum length that can be coded. inflate_fast() - requires strm.avail_out >= 258 for each loop to avoid checking for - output space. - */ -var inffast = function inflate_fast(strm, start) { - let _in; /* local strm.input */ - let last; /* have enough input while in < last */ - let _out; /* local strm.output */ - let beg; /* inflate()'s initial strm.output */ - let end; /* while out < end, enough space available */ -//#ifdef INFLATE_STRICT - let dmax; /* maximum distance from zlib header */ -//#endif - let wsize; /* window size or zero if not using window */ - let whave; /* valid bytes in the window */ - let wnext; /* window write index */ - // Use `s_window` instead `window`, avoid conflict with instrumentation tools - let s_window; /* allocated sliding window, if wsize != 0 */ - let hold; /* local strm.hold */ - let bits; /* local strm.bits */ - let lcode; /* local strm.lencode */ - let dcode; /* local strm.distcode */ - let lmask; /* mask for first level of length codes */ - let dmask; /* mask for first level of distance codes */ - let here; /* retrieved table entry */ - let op; /* code bits, operation, extra bits, or */ - /* window position, window bytes to copy */ - let len; /* match length, unused bytes */ - let dist; /* match distance */ - let from; /* where to copy match from */ - let from_source; - - - let input, output; // JS specific, because we have no pointers - - /* copy state to local variables */ - const state = strm.state; - //here = state.here; - _in = strm.next_in; - input = strm.input; - last = _in + (strm.avail_in - 5); - _out = strm.next_out; - output = strm.output; - beg = _out - (start - strm.avail_out); - end = _out + (strm.avail_out - 257); -//#ifdef INFLATE_STRICT - dmax = state.dmax; -//#endif - wsize = state.wsize; - whave = state.whave; - wnext = state.wnext; - s_window = state.window; - hold = state.hold; - bits = state.bits; - lcode = state.lencode; - dcode = state.distcode; - lmask = (1 << state.lenbits) - 1; - dmask = (1 << state.distbits) - 1; - - - /* decode literals and length/distances until end-of-block or not enough - input data or output space */ - - top: - do { - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - - here = lcode[hold & lmask]; - - dolen: - for (;;) { // Goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - if (op === 0) { /* literal */ - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - output[_out++] = here & 0xffff/*here.val*/; - } - else if (op & 16) { /* length base */ - len = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (op) { - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - len += hold & ((1 << op) - 1); - hold >>>= op; - bits -= op; - } - //Tracevv((stderr, "inflate: length %u\n", len)); - if (bits < 15) { - hold += input[_in++] << bits; - bits += 8; - hold += input[_in++] << bits; - bits += 8; - } - here = dcode[hold & dmask]; - - dodist: - for (;;) { // goto emulation - op = here >>> 24/*here.bits*/; - hold >>>= op; - bits -= op; - op = (here >>> 16) & 0xff/*here.op*/; - - if (op & 16) { /* distance base */ - dist = here & 0xffff/*here.val*/; - op &= 15; /* number of extra bits */ - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - if (bits < op) { - hold += input[_in++] << bits; - bits += 8; - } - } - dist += hold & ((1 << op) - 1); -//#ifdef INFLATE_STRICT - if (dist > dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD$1; - break top; - } -//#endif - hold >>>= op; - bits -= op; - //Tracevv((stderr, "inflate: distance %u\n", dist)); - op = _out - beg; /* max distance in output */ - if (dist > op) { /* see if copy from window */ - op = dist - op; /* distance back in window */ - if (op > whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD$1; - break top; - } - -// (!) This block is disabled in zlib defaults, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// if (len <= op - whave) { -// do { -// output[_out++] = 0; -// } while (--len); -// continue top; -// } -// len -= op - whave; -// do { -// output[_out++] = 0; -// } while (--op > whave); -// if (op === 0) { -// from = _out - dist; -// do { -// output[_out++] = output[from++]; -// } while (--len); -// continue top; -// } -//#endif - } - from = 0; // window index - from_source = s_window; - if (wnext === 0) { /* very common case */ - from += wsize - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - else if (wnext < op) { /* wrap around window */ - from += wsize + wnext - op; - op -= wnext; - if (op < len) { /* some from end of window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = 0; - if (wnext < len) { /* some from start of window */ - op = wnext; - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - } - else { /* contiguous in window */ - from += wnext - op; - if (op < len) { /* some from window */ - len -= op; - do { - output[_out++] = s_window[from++]; - } while (--op); - from = _out - dist; /* rest from output */ - from_source = output; - } - } - while (len > 2) { - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - output[_out++] = from_source[from++]; - len -= 3; - } - if (len) { - output[_out++] = from_source[from++]; - if (len > 1) { - output[_out++] = from_source[from++]; - } - } - } - else { - from = _out - dist; /* copy direct from output */ - do { /* minimum length is three */ - output[_out++] = output[from++]; - output[_out++] = output[from++]; - output[_out++] = output[from++]; - len -= 3; - } while (len > 2); - if (len) { - output[_out++] = output[from++]; - if (len > 1) { - output[_out++] = output[from++]; - } - } - } - } - else if ((op & 64) === 0) { /* 2nd level distance code */ - here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dodist; - } - else { - strm.msg = 'invalid distance code'; - state.mode = BAD$1; - break top; - } - - break; // need to emulate goto via "continue" - } - } - else if ((op & 64) === 0) { /* 2nd level length code */ - here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))]; - continue dolen; - } - else if (op & 32) { /* end-of-block */ - //Tracevv((stderr, "inflate: end of block\n")); - state.mode = TYPE$1; - break top; - } - else { - strm.msg = 'invalid literal/length code'; - state.mode = BAD$1; - break top; - } - - break; // need to emulate goto via "continue" - } - } while (_in < last && _out < end); - - /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ - len = bits >> 3; - _in -= len; - bits -= len << 3; - hold &= (1 << bits) - 1; - - /* update state and return */ - strm.next_in = _in; - strm.next_out = _out; - strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last)); - strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end)); - state.hold = hold; - state.bits = bits; - return; -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -const MAXBITS = 15; -const ENOUGH_LENS$1 = 852; -const ENOUGH_DISTS$1 = 592; -//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -const CODES$1 = 0; -const LENS$1 = 1; -const DISTS$1 = 2; - -const lbase = new Uint16Array([ /* Length codes 257..285 base */ - 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, - 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 -]); - -const lext = new Uint8Array([ /* Length codes 257..285 extra */ - 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78 -]); - -const dbase = new Uint16Array([ /* Distance codes 0..29 base */ - 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, - 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, - 8193, 12289, 16385, 24577, 0, 0 -]); - -const dext = new Uint8Array([ /* Distance codes 0..29 extra */ - 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, - 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, - 28, 28, 29, 29, 64, 64 -]); - -const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) => -{ - const bits = opts.bits; - //here = opts.here; /* table entry for duplication */ - - let len = 0; /* a code's length in bits */ - let sym = 0; /* index of code symbols */ - let min = 0, max = 0; /* minimum and maximum code lengths */ - let root = 0; /* number of index bits for root table */ - let curr = 0; /* number of index bits for current table */ - let drop = 0; /* code bits to drop for sub-table */ - let left = 0; /* number of prefix codes available */ - let used = 0; /* code entries in table used */ - let huff = 0; /* Huffman code */ - let incr; /* for incrementing code, index */ - let fill; /* index for replicating entries */ - let low; /* low bits for current root entry */ - let mask; /* mask for low root bits */ - let next; /* next available space in table */ - let base = null; /* base value table to use */ -// let shoextra; /* extra bits table to use */ - let match; /* use base and extra for symbol >= match */ - const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ - const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ - let extra = null; - - let here_bits, here_op, here_val; - - /* - Process a set of code lengths to create a canonical Huffman code. The - code lengths are lens[0..codes-1]. Each length corresponds to the - symbols 0..codes-1. The Huffman code is generated by first sorting the - symbols by length from short to long, and retaining the symbol order - for codes with equal lengths. Then the code starts with all zero bits - for the first code of the shortest length, and the codes are integer - increments for the same length, and zeros are appended as the length - increases. For the deflate format, these bits are stored backwards - from their more natural integer increment ordering, and so when the - decoding tables are built in the large loop below, the integer codes - are incremented backwards. - - This routine assumes, but does not check, that all of the entries in - lens[] are in the range 0..MAXBITS. The caller must assure this. - 1..MAXBITS is interpreted as that code length. zero means that that - symbol does not occur in this code. - - The codes are sorted by computing a count of codes for each length, - creating from that a table of starting indices for each length in the - sorted table, and then entering the symbols in order in the sorted - table. The sorted table is work[], with that space being provided by - the caller. - - The length counts are used for other purposes as well, i.e. finding - the minimum and maximum length codes, determining if there are any - codes at all, checking for a valid set of lengths, and looking ahead - at length counts to determine sub-table sizes when building the - decoding tables. - */ - - /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ - for (len = 0; len <= MAXBITS; len++) { - count[len] = 0; - } - for (sym = 0; sym < codes; sym++) { - count[lens[lens_index + sym]]++; - } - - /* bound code lengths, force root to be within code lengths */ - root = bits; - for (max = MAXBITS; max >= 1; max--) { - if (count[max] !== 0) { break; } - } - if (root > max) { - root = max; - } - if (max === 0) { /* no symbols to code at all */ - //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */ - //table.bits[opts.table_index] = 1; //here.bits = (var char)1; - //table.val[opts.table_index++] = 0; //here.val = (var short)0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - - //table.op[opts.table_index] = 64; - //table.bits[opts.table_index] = 1; - //table.val[opts.table_index++] = 0; - table[table_index++] = (1 << 24) | (64 << 16) | 0; - - opts.bits = 1; - return 0; /* no symbols, but wait for decoding to report error */ - } - for (min = 1; min < max; min++) { - if (count[min] !== 0) { break; } - } - if (root < min) { - root = min; - } - - /* check for an over-subscribed or incomplete set of lengths */ - left = 1; - for (len = 1; len <= MAXBITS; len++) { - left <<= 1; - left -= count[len]; - if (left < 0) { - return -1; - } /* over-subscribed */ - } - if (left > 0 && (type === CODES$1 || max !== 1)) { - return -1; /* incomplete set */ - } - - /* generate offsets into symbol table for each length for sorting */ - offs[1] = 0; - for (len = 1; len < MAXBITS; len++) { - offs[len + 1] = offs[len] + count[len]; - } - - /* sort symbols by length, by symbol order within each length */ - for (sym = 0; sym < codes; sym++) { - if (lens[lens_index + sym] !== 0) { - work[offs[lens[lens_index + sym]]++] = sym; - } - } - - /* - Create and fill in decoding tables. In this loop, the table being - filled is at next and has curr index bits. The code being used is huff - with length len. That code is converted to an index by dropping drop - bits off of the bottom. For codes where len is less than drop + curr, - those top drop + curr - len bits are incremented through all values to - fill the table with replicated entries. - - root is the number of index bits for the root table. When len exceeds - root, sub-tables are created pointed to by the root entry with an index - of the low root bits of huff. This is saved in low to check for when a - new sub-table should be started. drop is zero when the root table is - being filled, and drop is root when sub-tables are being filled. - - When a new sub-table is needed, it is necessary to look ahead in the - code lengths to determine what size sub-table is needed. The length - counts are used for this, and so count[] is decremented as codes are - entered in the tables. - - used keeps track of how many table entries have been allocated from the - provided *table space. It is checked for LENS and DIST tables against - the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in - the initial root table size constants. See the comments in inftrees.h - for more information. - - sym increments through all symbols, and the loop terminates when - all codes of length max, i.e. all codes, have been processed. This - routine permits incomplete codes, so another loop after this one fills - in the rest of the decoding tables with invalid code markers. - */ - - /* set up for code type */ - // poor man optimization - use if-else instead of switch, - // to avoid deopts in old v8 - if (type === CODES$1) { - base = extra = work; /* dummy value--not used */ - match = 20; - - } else if (type === LENS$1) { - base = lbase; - extra = lext; - match = 257; - - } else { /* DISTS */ - base = dbase; - extra = dext; - match = 0; - } - - /* initialize opts for loop */ - huff = 0; /* starting code */ - sym = 0; /* starting code symbol */ - len = min; /* starting code length */ - next = table_index; /* current table to fill in */ - curr = root; /* current table index bits */ - drop = 0; /* current bits to drop from code for index */ - low = -1; /* trigger new sub-table when len > root */ - used = 1 << root; /* use root table entries */ - mask = used - 1; /* mask for comparing low */ - - /* check available table space */ - if ((type === LENS$1 && used > ENOUGH_LENS$1) || - (type === DISTS$1 && used > ENOUGH_DISTS$1)) { - return 1; - } - - /* process all codes and make table entries */ - for (;;) { - /* create table entry */ - here_bits = len - drop; - if (work[sym] + 1 < match) { - here_op = 0; - here_val = work[sym]; - } - else if (work[sym] >= match) { - here_op = extra[work[sym] - match]; - here_val = base[work[sym] - match]; - } - else { - here_op = 32 + 64; /* end of block */ - here_val = 0; - } - - /* replicate for those indices with low len bits equal to huff */ - incr = 1 << (len - drop); - fill = 1 << curr; - min = fill; /* save offset to next table */ - do { - fill -= incr; - table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0; - } while (fill !== 0); - - /* backwards increment the len-bit code huff */ - incr = 1 << (len - 1); - while (huff & incr) { - incr >>= 1; - } - if (incr !== 0) { - huff &= incr - 1; - huff += incr; - } else { - huff = 0; - } - - /* go to next symbol, update count, len */ - sym++; - if (--count[len] === 0) { - if (len === max) { break; } - len = lens[lens_index + work[sym]]; - } - - /* create new sub-table if needed */ - if (len > root && (huff & mask) !== low) { - /* if first time, transition to sub-tables */ - if (drop === 0) { - drop = root; - } - - /* increment past last table */ - next += min; /* here min is 1 << curr */ - - /* determine length of next table */ - curr = len - drop; - left = 1 << curr; - while (curr + drop < max) { - left -= count[curr + drop]; - if (left <= 0) { break; } - curr++; - left <<= 1; - } - - /* check for enough space */ - used += 1 << curr; - if ((type === LENS$1 && used > ENOUGH_LENS$1) || - (type === DISTS$1 && used > ENOUGH_DISTS$1)) { - return 1; - } - - /* point entry in root table to sub-table */ - low = huff & mask; - /*table.op[low] = curr; - table.bits[low] = root; - table.val[low] = next - opts.table_index;*/ - table[low] = (root << 24) | (curr << 16) | (next - table_index) |0; - } - } - - /* fill in remaining table entry if code is incomplete (guaranteed to have - at most one remaining entry, since if the code is incomplete, the - maximum code length that was allowed to get this far is one bit) */ - if (huff !== 0) { - //table.op[next + huff] = 64; /* invalid code marker */ - //table.bits[next + huff] = len - drop; - //table.val[next + huff] = 0; - table[next + huff] = ((len - drop) << 24) | (64 << 16) |0; - } - - /* set return parameters */ - //opts.table_index += used; - opts.bits = root; - return 0; -}; - - -var inftrees = inflate_table; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - - - - - - -const CODES = 0; -const LENS = 1; -const DISTS = 2; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_FINISH: Z_FINISH$1, Z_BLOCK, Z_TREES, - Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR: Z_MEM_ERROR$1, Z_BUF_ERROR, - Z_DEFLATED -} = constants$2; - - -/* STATES ====================================================================*/ -/* ===========================================================================*/ - - -const HEAD = 16180; /* i: waiting for magic header */ -const FLAGS = 16181; /* i: waiting for method and flags (gzip) */ -const TIME = 16182; /* i: waiting for modification time (gzip) */ -const OS = 16183; /* i: waiting for extra flags and operating system (gzip) */ -const EXLEN = 16184; /* i: waiting for extra length (gzip) */ -const EXTRA = 16185; /* i: waiting for extra bytes (gzip) */ -const NAME = 16186; /* i: waiting for end of file name (gzip) */ -const COMMENT = 16187; /* i: waiting for end of comment (gzip) */ -const HCRC = 16188; /* i: waiting for header crc (gzip) */ -const DICTID = 16189; /* i: waiting for dictionary check value */ -const DICT = 16190; /* waiting for inflateSetDictionary() call */ -const TYPE = 16191; /* i: waiting for type bits, including last-flag bit */ -const TYPEDO = 16192; /* i: same, but skip check to exit inflate on new block */ -const STORED = 16193; /* i: waiting for stored size (length and complement) */ -const COPY_ = 16194; /* i/o: same as COPY below, but only first time in */ -const COPY = 16195; /* i/o: waiting for input or output to copy stored block */ -const TABLE = 16196; /* i: waiting for dynamic block table lengths */ -const LENLENS = 16197; /* i: waiting for code length code lengths */ -const CODELENS = 16198; /* i: waiting for length/lit and distance code lengths */ -const LEN_ = 16199; /* i: same as LEN below, but only first time in */ -const LEN = 16200; /* i: waiting for length/lit/eob code */ -const LENEXT = 16201; /* i: waiting for length extra bits */ -const DIST = 16202; /* i: waiting for distance code */ -const DISTEXT = 16203; /* i: waiting for distance extra bits */ -const MATCH = 16204; /* o: waiting for output space to copy string */ -const LIT = 16205; /* o: waiting for output space to write literal */ -const CHECK = 16206; /* i: waiting for 32-bit check value */ -const LENGTH = 16207; /* i: waiting for 32-bit length (gzip) */ -const DONE = 16208; /* finished check, done -- remain here until reset */ -const BAD = 16209; /* got a data error -- remain here until reset */ -const MEM = 16210; /* got an inflate() memory error -- remain here until reset */ -const SYNC = 16211; /* looking for synchronization bytes to restart inflate() */ - -/* ===========================================================================*/ - - - -const ENOUGH_LENS = 852; -const ENOUGH_DISTS = 592; -//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS); - -const MAX_WBITS = 15; -/* 32K LZ77 window */ -const DEF_WBITS = MAX_WBITS; - - -const zswap32 = (q) => { - - return (((q >>> 24) & 0xff) + - ((q >>> 8) & 0xff00) + - ((q & 0xff00) << 8) + - ((q & 0xff) << 24)); -}; - - -function InflateState() { - this.strm = null; /* pointer back to this zlib stream */ - this.mode = 0; /* current inflate mode */ - this.last = false; /* true if processing last block */ - this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip, - bit 2 true to validate check value */ - this.havedict = false; /* true if dictionary provided */ - this.flags = 0; /* gzip header method and flags (0 if zlib), or - -1 if raw or no header yet */ - this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */ - this.check = 0; /* protected copy of check value */ - this.total = 0; /* protected copy of output count */ - // TODO: may be {} - this.head = null; /* where to save gzip header information */ - - /* sliding window */ - this.wbits = 0; /* log base 2 of requested window size */ - this.wsize = 0; /* window size or zero if not using window */ - this.whave = 0; /* valid bytes in the window */ - this.wnext = 0; /* window write index */ - this.window = null; /* allocated sliding window, if needed */ - - /* bit accumulator */ - this.hold = 0; /* input bit accumulator */ - this.bits = 0; /* number of bits in "in" */ - - /* for string and stored block copying */ - this.length = 0; /* literal or length of data to copy */ - this.offset = 0; /* distance back to copy string from */ - - /* for table and code decoding */ - this.extra = 0; /* extra bits needed */ - - /* fixed and dynamic code tables */ - this.lencode = null; /* starting table for length/literal codes */ - this.distcode = null; /* starting table for distance codes */ - this.lenbits = 0; /* index bits for lencode */ - this.distbits = 0; /* index bits for distcode */ - - /* dynamic table building */ - this.ncode = 0; /* number of code length code lengths */ - this.nlen = 0; /* number of length code lengths */ - this.ndist = 0; /* number of distance code lengths */ - this.have = 0; /* number of code lengths in lens[] */ - this.next = null; /* next available space in codes[] */ - - this.lens = new Uint16Array(320); /* temporary storage for code lengths */ - this.work = new Uint16Array(288); /* work area for code table building */ - - /* - because we don't have pointers in js, we use lencode and distcode directly - as buffers so we don't need codes - */ - //this.codes = new Int32Array(ENOUGH); /* space for code tables */ - this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */ - this.distdyn = null; /* dynamic table for distance codes (JS specific) */ - this.sane = 0; /* if false, allow invalid distance too far */ - this.back = 0; /* bits back of last unprocessed length/lit */ - this.was = 0; /* initial length of match */ -} - - -const inflateStateCheck = (strm) => { - - if (!strm) { - return 1; - } - const state = strm.state; - if (!state || state.strm !== strm || - state.mode < HEAD || state.mode > SYNC) { - return 1; - } - return 0; -}; - - -const inflateResetKeep = (strm) => { - - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - strm.total_in = strm.total_out = state.total = 0; - strm.msg = ''; /*Z_NULL*/ - if (state.wrap) { /* to support ill-conceived Java test suite */ - strm.adler = state.wrap & 1; - } - state.mode = HEAD; - state.last = 0; - state.havedict = 0; - state.flags = -1; - state.dmax = 32768; - state.head = null/*Z_NULL*/; - state.hold = 0; - state.bits = 0; - //state.lencode = state.distcode = state.next = state.codes; - state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS); - state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS); - - state.sane = 1; - state.back = -1; - //Tracev((stderr, "inflate: reset\n")); - return Z_OK$1; -}; - - -const inflateReset = (strm) => { - - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - state.wsize = 0; - state.whave = 0; - state.wnext = 0; - return inflateResetKeep(strm); - -}; - - -const inflateReset2 = (strm, windowBits) => { - let wrap; - - /* get the state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - - /* extract wrap request from windowBits parameter */ - if (windowBits < 0) { - wrap = 0; - windowBits = -windowBits; - } - else { - wrap = (windowBits >> 4) + 5; - if (windowBits < 48) { - windowBits &= 15; - } - } - - /* set number of window bits, free window if different */ - if (windowBits && (windowBits < 8 || windowBits > 15)) { - return Z_STREAM_ERROR$1; - } - if (state.window !== null && state.wbits !== windowBits) { - state.window = null; - } - - /* update state and reset the rest of it */ - state.wrap = wrap; - state.wbits = windowBits; - return inflateReset(strm); -}; - - -const inflateInit2 = (strm, windowBits) => { - - if (!strm) { return Z_STREAM_ERROR$1; } - //strm.msg = Z_NULL; /* in case we return an error */ - - const state = new InflateState(); - - //if (state === Z_NULL) return Z_MEM_ERROR; - //Tracev((stderr, "inflate: allocated\n")); - strm.state = state; - state.strm = strm; - state.window = null/*Z_NULL*/; - state.mode = HEAD; /* to pass state test in inflateReset2() */ - const ret = inflateReset2(strm, windowBits); - if (ret !== Z_OK$1) { - strm.state = null/*Z_NULL*/; - } - return ret; -}; - - -const inflateInit = (strm) => { - - return inflateInit2(strm, DEF_WBITS); -}; - - -/* - Return state with length and distance decoding tables and index sizes set to - fixed code decoding. Normally this returns fixed tables from inffixed.h. - If BUILDFIXED is defined, then instead this routine builds the tables the - first time it's called, and returns those tables the first time and - thereafter. This reduces the size of the code by about 2K bytes, in - exchange for a little execution time. However, BUILDFIXED should not be - used for threaded applications, since the rewriting of the tables and virgin - may not be thread-safe. - */ -let virgin = true; - -let lenfix, distfix; // We have no pointers in JS, so keep tables separate - - -const fixedtables = (state) => { - - /* build fixed huffman tables if first call (may not be thread safe) */ - if (virgin) { - lenfix = new Int32Array(512); - distfix = new Int32Array(32); - - /* literal/length table */ - let sym = 0; - while (sym < 144) { state.lens[sym++] = 8; } - while (sym < 256) { state.lens[sym++] = 9; } - while (sym < 280) { state.lens[sym++] = 7; } - while (sym < 288) { state.lens[sym++] = 8; } - - inftrees(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); - - /* distance table */ - sym = 0; - while (sym < 32) { state.lens[sym++] = 5; } - - inftrees(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); - - /* do this just once */ - virgin = false; - } - - state.lencode = lenfix; - state.lenbits = 9; - state.distcode = distfix; - state.distbits = 5; -}; - - -/* - Update the window with the last wsize (normally 32K) bytes written before - returning. If window does not exist yet, create it. This is only called - when a window is already in use, or when output has been written during this - inflate call, but the end of the deflate stream has not been reached yet. - It is also called to create a window for dictionary data when a dictionary - is loaded. - - Providing output buffers larger than 32K to inflate() should provide a speed - advantage, since only the last 32K of output is copied to the sliding window - upon return from inflate(), and since all distances after the first 32K of - output will fall in the output data, making match copies simpler and faster. - The advantage may be dependent on the size of the processor's data caches. - */ -const updatewindow = (strm, src, end, copy) => { - - let dist; - const state = strm.state; - - /* if it hasn't been done already, allocate space for the window */ - if (state.window === null) { - state.wsize = 1 << state.wbits; - state.wnext = 0; - state.whave = 0; - - state.window = new Uint8Array(state.wsize); - } - - /* copy state->wsize or less output bytes into the circular window */ - if (copy >= state.wsize) { - state.window.set(src.subarray(end - state.wsize, end), 0); - state.wnext = 0; - state.whave = state.wsize; - } - else { - dist = state.wsize - state.wnext; - if (dist > copy) { - dist = copy; - } - //zmemcpy(state->window + state->wnext, end - copy, dist); - state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext); - copy -= dist; - if (copy) { - //zmemcpy(state->window, end - copy, copy); - state.window.set(src.subarray(end - copy, end), 0); - state.wnext = copy; - state.whave = state.wsize; - } - else { - state.wnext += dist; - if (state.wnext === state.wsize) { state.wnext = 0; } - if (state.whave < state.wsize) { state.whave += dist; } - } - } - return 0; -}; - - -const inflate$2 = (strm, flush) => { - - let state; - let input, output; // input/output buffers - let next; /* next input INDEX */ - let put; /* next output INDEX */ - let have, left; /* available input and output */ - let hold; /* bit buffer */ - let bits; /* bits in bit buffer */ - let _in, _out; /* save starting available input and output */ - let copy; /* number of stored or match bytes to copy */ - let from; /* where to copy match bytes from */ - let from_source; - let here = 0; /* current decoding table entry */ - let here_bits, here_op, here_val; // paked "here" denormalized (JS specific) - //let last; /* parent table entry */ - let last_bits, last_op, last_val; // paked "last" denormalized (JS specific) - let len; /* length to copy for repeats, bits to drop */ - let ret; /* return code */ - const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */ - let opts; - - let n; // temporary variable for NEED_BITS - - const order = /* permutation of code lengths */ - new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]); - - - if (inflateStateCheck(strm) || !strm.output || - (!strm.input && strm.avail_in !== 0)) { - return Z_STREAM_ERROR$1; - } - - state = strm.state; - if (state.mode === TYPE) { state.mode = TYPEDO; } /* skip check */ - - - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - _in = have; - _out = left; - ret = Z_OK$1; - - inf_leave: // goto emulation - for (;;) { - switch (state.mode) { - case HEAD: - if (state.wrap === 0) { - state.mode = TYPEDO; - break; - } - //=== NEEDBITS(16); - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */ - if (state.wbits === 0) { - state.wbits = 15; - } - state.check = 0/*crc32(0L, Z_NULL, 0)*/; - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = FLAGS; - break; - } - if (state.head) { - state.head.done = false; - } - if (!(state.wrap & 1) || /* check if zlib header allowed */ - (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) { - strm.msg = 'incorrect header check'; - state.mode = BAD; - break; - } - if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// - len = (hold & 0x0f)/*BITS(4)*/ + 8; - if (state.wbits === 0) { - state.wbits = len; - } - if (len > 15 || len > state.wbits) { - strm.msg = 'invalid window size'; - state.mode = BAD; - break; - } - - // !!! pako patch. Force use `options.windowBits` if passed. - // Required to always use max window size by default. - state.dmax = 1 << state.wbits; - //state.dmax = 1 << len; - - state.flags = 0; /* indicate zlib header */ - //Tracev((stderr, "inflate: zlib header ok\n")); - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = hold & 0x200 ? DICTID : TYPE; - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - break; - case FLAGS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.flags = hold; - if ((state.flags & 0xff) !== Z_DEFLATED) { - strm.msg = 'unknown compression method'; - state.mode = BAD; - break; - } - if (state.flags & 0xe000) { - strm.msg = 'unknown header flags set'; - state.mode = BAD; - break; - } - if (state.head) { - state.head.text = ((hold >> 8) & 1); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = TIME; - /* falls through */ - case TIME: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.time = hold; - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC4(state.check, hold) - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - hbuf[2] = (hold >>> 16) & 0xff; - hbuf[3] = (hold >>> 24) & 0xff; - state.check = crc32_1(state.check, hbuf, 4, 0); - //=== - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = OS; - /* falls through */ - case OS: - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if (state.head) { - state.head.xflags = (hold & 0xff); - state.head.os = (hold >> 8); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = EXLEN; - /* falls through */ - case EXLEN: - if (state.flags & 0x0400) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length = hold; - if (state.head) { - state.head.extra_len = hold; - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - //=== CRC2(state.check, hold); - hbuf[0] = hold & 0xff; - hbuf[1] = (hold >>> 8) & 0xff; - state.check = crc32_1(state.check, hbuf, 2, 0); - //===// - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - else if (state.head) { - state.head.extra = null/*Z_NULL*/; - } - state.mode = EXTRA; - /* falls through */ - case EXTRA: - if (state.flags & 0x0400) { - copy = state.length; - if (copy > have) { copy = have; } - if (copy) { - if (state.head) { - len = state.head.extra_len - state.length; - if (!state.head.extra) { - // Use untyped array for more convenient processing later - state.head.extra = new Uint8Array(state.head.extra_len); - } - state.head.extra.set( - input.subarray( - next, - // extra field is limited to 65536 bytes - // - no need for additional size check - next + copy - ), - /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/ - len - ); - //zmemcpy(state.head.extra + len, next, - // len + copy > state.head.extra_max ? - // state.head.extra_max - len : copy); - } - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - state.length -= copy; - } - if (state.length) { break inf_leave; } - } - state.length = 0; - state.mode = NAME; - /* falls through */ - case NAME: - if (state.flags & 0x0800) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - // TODO: 2 or 1 bytes? - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.name_max*/)) { - state.head.name += String.fromCharCode(len); - } - } while (len && copy < have); - - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.name = null; - } - state.length = 0; - state.mode = COMMENT; - /* falls through */ - case COMMENT: - if (state.flags & 0x1000) { - if (have === 0) { break inf_leave; } - copy = 0; - do { - len = input[next + copy++]; - /* use constant limit because in js we should not preallocate memory */ - if (state.head && len && - (state.length < 65536 /*state.head.comm_max*/)) { - state.head.comment += String.fromCharCode(len); - } - } while (len && copy < have); - if ((state.flags & 0x0200) && (state.wrap & 4)) { - state.check = crc32_1(state.check, input, copy, next); - } - have -= copy; - next += copy; - if (len) { break inf_leave; } - } - else if (state.head) { - state.head.comment = null; - } - state.mode = HCRC; - /* falls through */ - case HCRC: - if (state.flags & 0x0200) { - //=== NEEDBITS(16); */ - while (bits < 16) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 4) && hold !== (state.check & 0xffff)) { - strm.msg = 'header crc mismatch'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - } - if (state.head) { - state.head.hcrc = ((state.flags >> 9) & 1); - state.head.done = true; - } - strm.adler = state.check = 0; - state.mode = TYPE; - break; - case DICTID: - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - strm.adler = state.check = zswap32(hold); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = DICT; - /* falls through */ - case DICT: - if (state.havedict === 0) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - return Z_NEED_DICT$1; - } - strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/; - state.mode = TYPE; - /* falls through */ - case TYPE: - if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; } - /* falls through */ - case TYPEDO: - if (state.last) { - //--- BYTEBITS() ---// - hold >>>= bits & 7; - bits -= bits & 7; - //---// - state.mode = CHECK; - break; - } - //=== NEEDBITS(3); */ - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.last = (hold & 0x01)/*BITS(1)*/; - //--- DROPBITS(1) ---// - hold >>>= 1; - bits -= 1; - //---// - - switch ((hold & 0x03)/*BITS(2)*/) { - case 0: /* stored block */ - //Tracev((stderr, "inflate: stored block%s\n", - // state.last ? " (last)" : "")); - state.mode = STORED; - break; - case 1: /* fixed block */ - fixedtables(state); - //Tracev((stderr, "inflate: fixed codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = LEN_; /* decode codes */ - if (flush === Z_TREES) { - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break inf_leave; - } - break; - case 2: /* dynamic block */ - //Tracev((stderr, "inflate: dynamic codes block%s\n", - // state.last ? " (last)" : "")); - state.mode = TABLE; - break; - case 3: - strm.msg = 'invalid block type'; - state.mode = BAD; - } - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - break; - case STORED: - //--- BYTEBITS() ---// /* go to byte boundary */ - hold >>>= bits & 7; - bits -= bits & 7; - //---// - //=== NEEDBITS(32); */ - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) { - strm.msg = 'invalid stored block lengths'; - state.mode = BAD; - break; - } - state.length = hold & 0xffff; - //Tracev((stderr, "inflate: stored length %u\n", - // state.length)); - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - state.mode = COPY_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case COPY_: - state.mode = COPY; - /* falls through */ - case COPY: - copy = state.length; - if (copy) { - if (copy > have) { copy = have; } - if (copy > left) { copy = left; } - if (copy === 0) { break inf_leave; } - //--- zmemcpy(put, next, copy); --- - output.set(input.subarray(next, next + copy), put); - //---// - have -= copy; - next += copy; - left -= copy; - put += copy; - state.length -= copy; - break; - } - //Tracev((stderr, "inflate: stored end\n")); - state.mode = TYPE; - break; - case TABLE: - //=== NEEDBITS(14); */ - while (bits < 14) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1; - //--- DROPBITS(5) ---// - hold >>>= 5; - bits -= 5; - //---// - state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4; - //--- DROPBITS(4) ---// - hold >>>= 4; - bits -= 4; - //---// -//#ifndef PKZIP_BUG_WORKAROUND - if (state.nlen > 286 || state.ndist > 30) { - strm.msg = 'too many length or distance symbols'; - state.mode = BAD; - break; - } -//#endif - //Tracev((stderr, "inflate: table sizes ok\n")); - state.have = 0; - state.mode = LENLENS; - /* falls through */ - case LENLENS: - while (state.have < state.ncode) { - //=== NEEDBITS(3); - while (bits < 3) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.lens[order[state.have++]] = (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - while (state.have < 19) { - state.lens[order[state.have++]] = 0; - } - // We have separate tables & no pointers. 2 commented lines below not needed. - //state.next = state.codes; - //state.lencode = state.next; - // Switch to use dynamic table - state.lencode = state.lendyn; - state.lenbits = 7; - - opts = { bits: state.lenbits }; - ret = inftrees(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); - state.lenbits = opts.bits; - - if (ret) { - strm.msg = 'invalid code lengths set'; - state.mode = BAD; - break; - } - //Tracev((stderr, "inflate: code lengths ok\n")); - state.have = 0; - state.mode = CODELENS; - /* falls through */ - case CODELENS: - while (state.have < state.nlen + state.ndist) { - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_val < 16) { - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.lens[state.have++] = here_val; - } - else { - if (here_val === 16) { - //=== NEEDBITS(here.bits + 2); - n = here_bits + 2; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - if (state.have === 0) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - len = state.lens[state.have - 1]; - copy = 3 + (hold & 0x03);//BITS(2); - //--- DROPBITS(2) ---// - hold >>>= 2; - bits -= 2; - //---// - } - else if (here_val === 17) { - //=== NEEDBITS(here.bits + 3); - n = here_bits + 3; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 3 + (hold & 0x07);//BITS(3); - //--- DROPBITS(3) ---// - hold >>>= 3; - bits -= 3; - //---// - } - else { - //=== NEEDBITS(here.bits + 7); - n = here_bits + 7; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - len = 0; - copy = 11 + (hold & 0x7f);//BITS(7); - //--- DROPBITS(7) ---// - hold >>>= 7; - bits -= 7; - //---// - } - if (state.have + copy > state.nlen + state.ndist) { - strm.msg = 'invalid bit length repeat'; - state.mode = BAD; - break; - } - while (copy--) { - state.lens[state.have++] = len; - } - } - } - - /* handle error breaks in while */ - if (state.mode === BAD) { break; } - - /* check for end-of-block code (better have one) */ - if (state.lens[256] === 0) { - strm.msg = 'invalid code -- missing end-of-block'; - state.mode = BAD; - break; - } - - /* build code tables -- note: do not change the lenbits or distbits - values here (9 and 6) without reading the comments in inftrees.h - concerning the ENOUGH constants, which depend on those values */ - state.lenbits = 9; - - opts = { bits: state.lenbits }; - ret = inftrees(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.lenbits = opts.bits; - // state.lencode = state.next; - - if (ret) { - strm.msg = 'invalid literal/lengths set'; - state.mode = BAD; - break; - } - - state.distbits = 6; - //state.distcode.copy(state.codes); - // Switch to use dynamic table - state.distcode = state.distdyn; - opts = { bits: state.distbits }; - ret = inftrees(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); - // We have separate tables & no pointers. 2 commented lines below not needed. - // state.next_index = opts.table_index; - state.distbits = opts.bits; - // state.distcode = state.next; - - if (ret) { - strm.msg = 'invalid distances set'; - state.mode = BAD; - break; - } - //Tracev((stderr, 'inflate: codes ok\n')); - state.mode = LEN_; - if (flush === Z_TREES) { break inf_leave; } - /* falls through */ - case LEN_: - state.mode = LEN; - /* falls through */ - case LEN: - if (have >= 6 && left >= 258) { - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - inffast(strm, _out); - //--- LOAD() --- - put = strm.next_out; - output = strm.output; - left = strm.avail_out; - next = strm.next_in; - input = strm.input; - have = strm.avail_in; - hold = state.hold; - bits = state.bits; - //--- - - if (state.mode === TYPE) { - state.back = -1; - } - break; - } - state.back = 0; - for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if (here_bits <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if (here_op && (here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.lencode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - state.length = here_val; - if (here_op === 0) { - //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? - // "inflate: literal '%c'\n" : - // "inflate: literal 0x%02x\n", here.val)); - state.mode = LIT; - break; - } - if (here_op & 32) { - //Tracevv((stderr, "inflate: end of block\n")); - state.back = -1; - state.mode = TYPE; - break; - } - if (here_op & 64) { - strm.msg = 'invalid literal/length code'; - state.mode = BAD; - break; - } - state.extra = here_op & 15; - state.mode = LENEXT; - /* falls through */ - case LENEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } - //Tracevv((stderr, "inflate: length %u\n", state.length)); - state.was = state.length; - state.mode = DIST; - /* falls through */ - case DIST: - for (;;) { - here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - if ((here_op & 0xf0) === 0) { - last_bits = here_bits; - last_op = here_op; - last_val = here_val; - for (;;) { - here = state.distcode[last_val + - ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; - here_bits = here >>> 24; - here_op = (here >>> 16) & 0xff; - here_val = here & 0xffff; - - if ((last_bits + here_bits) <= bits) { break; } - //--- PULLBYTE() ---// - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - //---// - } - //--- DROPBITS(last.bits) ---// - hold >>>= last_bits; - bits -= last_bits; - //---// - state.back += last_bits; - } - //--- DROPBITS(here.bits) ---// - hold >>>= here_bits; - bits -= here_bits; - //---// - state.back += here_bits; - if (here_op & 64) { - strm.msg = 'invalid distance code'; - state.mode = BAD; - break; - } - state.offset = here_val; - state.extra = (here_op) & 15; - state.mode = DISTEXT; - /* falls through */ - case DISTEXT: - if (state.extra) { - //=== NEEDBITS(state.extra); - n = state.extra; - while (bits < n) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; - //--- DROPBITS(state.extra) ---// - hold >>>= state.extra; - bits -= state.extra; - //---// - state.back += state.extra; - } -//#ifdef INFLATE_STRICT - if (state.offset > state.dmax) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -//#endif - //Tracevv((stderr, "inflate: distance %u\n", state.offset)); - state.mode = MATCH; - /* falls through */ - case MATCH: - if (left === 0) { break inf_leave; } - copy = _out - left; - if (state.offset > copy) { /* copy from window */ - copy = state.offset - copy; - if (copy > state.whave) { - if (state.sane) { - strm.msg = 'invalid distance too far back'; - state.mode = BAD; - break; - } -// (!) This block is disabled in zlib defaults, -// don't enable it for binary compatibility -//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR -// Trace((stderr, "inflate.c too far\n")); -// copy -= state.whave; -// if (copy > state.length) { copy = state.length; } -// if (copy > left) { copy = left; } -// left -= copy; -// state.length -= copy; -// do { -// output[put++] = 0; -// } while (--copy); -// if (state.length === 0) { state.mode = LEN; } -// break; -//#endif - } - if (copy > state.wnext) { - copy -= state.wnext; - from = state.wsize - copy; - } - else { - from = state.wnext - copy; - } - if (copy > state.length) { copy = state.length; } - from_source = state.window; - } - else { /* copy from output */ - from_source = output; - from = put - state.offset; - copy = state.length; - } - if (copy > left) { copy = left; } - left -= copy; - state.length -= copy; - do { - output[put++] = from_source[from++]; - } while (--copy); - if (state.length === 0) { state.mode = LEN; } - break; - case LIT: - if (left === 0) { break inf_leave; } - output[put++] = state.length; - left--; - state.mode = LEN; - break; - case CHECK: - if (state.wrap) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - // Use '|' instead of '+' to make sure that result is signed - hold |= input[next++] << bits; - bits += 8; - } - //===// - _out -= left; - strm.total_out += _out; - state.total += _out; - if ((state.wrap & 4) && _out) { - strm.adler = state.check = - /*UPDATE_CHECK(state.check, put - _out, _out);*/ - (state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out)); - - } - _out = left; - // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too - if ((state.wrap & 4) && (state.flags ? hold : zswap32(hold)) !== state.check) { - strm.msg = 'incorrect data check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: check matches trailer\n")); - } - state.mode = LENGTH; - /* falls through */ - case LENGTH: - if (state.wrap && state.flags) { - //=== NEEDBITS(32); - while (bits < 32) { - if (have === 0) { break inf_leave; } - have--; - hold += input[next++] << bits; - bits += 8; - } - //===// - if ((state.wrap & 4) && hold !== (state.total & 0xffffffff)) { - strm.msg = 'incorrect length check'; - state.mode = BAD; - break; - } - //=== INITBITS(); - hold = 0; - bits = 0; - //===// - //Tracev((stderr, "inflate: length matches trailer\n")); - } - state.mode = DONE; - /* falls through */ - case DONE: - ret = Z_STREAM_END$1; - break inf_leave; - case BAD: - ret = Z_DATA_ERROR$1; - break inf_leave; - case MEM: - return Z_MEM_ERROR$1; - case SYNC: - /* falls through */ - default: - return Z_STREAM_ERROR$1; - } - } - - // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave" - - /* - Return from inflate(), updating the total counts and the check value. - If there was no progress during the inflate() call, return a buffer - error. Call updatewindow() to create and/or update the window state. - Note: a memory error from inflate() is non-recoverable. - */ - - //--- RESTORE() --- - strm.next_out = put; - strm.avail_out = left; - strm.next_in = next; - strm.avail_in = have; - state.hold = hold; - state.bits = bits; - //--- - - if (state.wsize || (_out !== strm.avail_out && state.mode < BAD && - (state.mode < CHECK || flush !== Z_FINISH$1))) { - if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ; - } - _in -= strm.avail_in; - _out -= strm.avail_out; - strm.total_in += _in; - strm.total_out += _out; - state.total += _out; - if ((state.wrap & 4) && _out) { - strm.adler = state.check = /*UPDATE_CHECK(state.check, strm.next_out - _out, _out);*/ - (state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out)); - } - strm.data_type = state.bits + (state.last ? 64 : 0) + - (state.mode === TYPE ? 128 : 0) + - (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0); - if (((_in === 0 && _out === 0) || flush === Z_FINISH$1) && ret === Z_OK$1) { - ret = Z_BUF_ERROR; - } - return ret; -}; - - -const inflateEnd = (strm) => { - - if (inflateStateCheck(strm)) { - return Z_STREAM_ERROR$1; - } - - let state = strm.state; - if (state.window) { - state.window = null; - } - strm.state = null; - return Z_OK$1; -}; - - -const inflateGetHeader = (strm, head) => { - - /* check state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - const state = strm.state; - if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR$1; } - - /* save header structure */ - state.head = head; - head.done = false; - return Z_OK$1; -}; - - -const inflateSetDictionary = (strm, dictionary) => { - const dictLength = dictionary.length; - - let state; - let dictid; - let ret; - - /* check state */ - if (inflateStateCheck(strm)) { return Z_STREAM_ERROR$1; } - state = strm.state; - - if (state.wrap !== 0 && state.mode !== DICT) { - return Z_STREAM_ERROR$1; - } - - /* check for correct dictionary identifier */ - if (state.mode === DICT) { - dictid = 1; /* adler32(0, null, 0)*/ - /* dictid = adler32(dictid, dictionary, dictLength); */ - dictid = adler32_1(dictid, dictionary, dictLength, 0); - if (dictid !== state.check) { - return Z_DATA_ERROR$1; - } - } - /* copy dictionary to window using updatewindow(), which will amend the - existing dictionary if appropriate */ - ret = updatewindow(strm, dictionary, dictLength, dictLength); - if (ret) { - state.mode = MEM; - return Z_MEM_ERROR$1; - } - state.havedict = 1; - // Tracev((stderr, "inflate: dictionary set\n")); - return Z_OK$1; -}; - - -var inflateReset_1 = inflateReset; -var inflateReset2_1 = inflateReset2; -var inflateResetKeep_1 = inflateResetKeep; -var inflateInit_1 = inflateInit; -var inflateInit2_1 = inflateInit2; -var inflate_2$1 = inflate$2; -var inflateEnd_1 = inflateEnd; -var inflateGetHeader_1 = inflateGetHeader; -var inflateSetDictionary_1 = inflateSetDictionary; -var inflateInfo = 'pako inflate (from Nodeca project)'; - -/* Not implemented -module.exports.inflateCodesUsed = inflateCodesUsed; -module.exports.inflateCopy = inflateCopy; -module.exports.inflateGetDictionary = inflateGetDictionary; -module.exports.inflateMark = inflateMark; -module.exports.inflatePrime = inflatePrime; -module.exports.inflateSync = inflateSync; -module.exports.inflateSyncPoint = inflateSyncPoint; -module.exports.inflateUndermine = inflateUndermine; -module.exports.inflateValidate = inflateValidate; -*/ - -var inflate_1$2 = { - inflateReset: inflateReset_1, - inflateReset2: inflateReset2_1, - inflateResetKeep: inflateResetKeep_1, - inflateInit: inflateInit_1, - inflateInit2: inflateInit2_1, - inflate: inflate_2$1, - inflateEnd: inflateEnd_1, - inflateGetHeader: inflateGetHeader_1, - inflateSetDictionary: inflateSetDictionary_1, - inflateInfo: inflateInfo -}; - -// (C) 1995-2013 Jean-loup Gailly and Mark Adler -// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin -// -// This software is provided 'as-is', without any express or implied -// warranty. In no event will the authors be held liable for any damages -// arising from the use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software -// in a product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// 2. Altered source versions must be plainly marked as such, and must not be -// misrepresented as being the original software. -// 3. This notice may not be removed or altered from any source distribution. - -function GZheader() { - /* true if compressed data believed to be text */ - this.text = 0; - /* modification time */ - this.time = 0; - /* extra flags (not used when writing a gzip file) */ - this.xflags = 0; - /* operating system */ - this.os = 0; - /* pointer to extra field or Z_NULL if none */ - this.extra = null; - /* extra field length (valid if extra != Z_NULL) */ - this.extra_len = 0; // Actually, we don't need it in JS, - // but leave for few code modifications - - // - // Setup limits is not necessary because in js we should not preallocate memory - // for inflate use constant limit in 65536 bytes - // - - /* space at extra (only when reading header) */ - // this.extra_max = 0; - /* pointer to zero-terminated file name or Z_NULL */ - this.name = ''; - /* space at name (only when reading header) */ - // this.name_max = 0; - /* pointer to zero-terminated comment or Z_NULL */ - this.comment = ''; - /* space at comment (only when reading header) */ - // this.comm_max = 0; - /* true if there was or will be a header crc */ - this.hcrc = 0; - /* true when done reading gzip header (not used when writing a gzip file) */ - this.done = false; -} - -var gzheader = GZheader; - -const toString = Object.prototype.toString; - -/* Public constants ==========================================================*/ -/* ===========================================================================*/ - -const { - Z_NO_FLUSH, Z_FINISH, - Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR -} = constants$2; - -/* ===========================================================================*/ - - -/** - * class Inflate - * - * Generic JS-style wrapper for zlib calls. If you don't need - * streaming behaviour - use more simple functions: [[inflate]] - * and [[inflateRaw]]. - **/ - -/* internal - * inflate.chunks -> Array - * - * Chunks of output data, if [[Inflate#onData]] not overridden. - **/ - -/** - * Inflate.result -> Uint8Array|String - * - * Uncompressed result, generated by default [[Inflate#onData]] - * and [[Inflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Inflate#push]] with `Z_FINISH` / `true` param). - **/ - -/** - * Inflate.err -> Number - * - * Error code after inflate finished. 0 (Z_OK) on success. - * Should be checked if broken data possible. - **/ - -/** - * Inflate.msg -> String - * - * Error message, if [[Inflate.err]] != 0 - **/ - - -/** - * new Inflate(options) - * - options (Object): zlib inflate options. - * - * Creates new inflator instance with specified params. Throws exception - * on bad params. Supported options: - * - * - `windowBits` - * - `dictionary` - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information on these. - * - * Additional options, for internal needs: - * - * - `chunkSize` - size of generated data chunks (16K by default) - * - `raw` (Boolean) - do raw inflate - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * By default, when no options set, autodetect deflate/gzip data format via - * wrapper header. - * - * ##### Example: - * - * ```javascript - * const pako = require('pako') - * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9]) - * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]); - * - * const inflate = new pako.Inflate({ level: 3}); - * - * inflate.push(chunk1, false); - * inflate.push(chunk2, true); // true -> last chunk - * - * if (inflate.err) { throw new Error(inflate.err); } - * - * console.log(inflate.result); - * ``` - **/ -function Inflate$1(options) { - this.options = common.assign({ - chunkSize: 1024 * 64, - windowBits: 15, - to: '' - }, options || {}); - - const opt = this.options; - - // Force window size for `raw` data, if not set directly, - // because we have no header for autodetect. - if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) { - opt.windowBits = -opt.windowBits; - if (opt.windowBits === 0) { opt.windowBits = -15; } - } - - // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate - if ((opt.windowBits >= 0) && (opt.windowBits < 16) && - !(options && options.windowBits)) { - opt.windowBits += 32; - } - - // Gzip header has no info about windows size, we can do autodetect only - // for deflate. So, if window size not set, force it to max when gzip possible - if ((opt.windowBits > 15) && (opt.windowBits < 48)) { - // bit 3 (16) -> gzipped data - // bit 4 (32) -> autodetect gzip/deflate - if ((opt.windowBits & 15) === 0) { - opt.windowBits |= 15; - } - } - - this.err = 0; // error code, if happens (0 = Z_OK) - this.msg = ''; // error message - this.ended = false; // used to avoid multiple onEnd() calls - this.chunks = []; // chunks of compressed data - - this.strm = new zstream(); - this.strm.avail_out = 0; - - let status = inflate_1$2.inflateInit2( - this.strm, - opt.windowBits - ); - - if (status !== Z_OK) { - throw new Error(messages[status]); - } - - this.header = new gzheader(); - - inflate_1$2.inflateGetHeader(this.strm, this.header); - - // Setup dictionary - if (opt.dictionary) { - // Convert data if needed - if (typeof opt.dictionary === 'string') { - opt.dictionary = strings.string2buf(opt.dictionary); - } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { - opt.dictionary = new Uint8Array(opt.dictionary); - } - if (opt.raw) { //In raw mode we need to set the dictionary early - status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary); - if (status !== Z_OK) { - throw new Error(messages[status]); - } - } - } -} - -/** - * Inflate#push(data[, flush_mode]) -> Boolean - * - data (Uint8Array|ArrayBuffer): input data - * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE - * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH, - * `true` means Z_FINISH. - * - * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with - * new output chunks. Returns `true` on success. If end of stream detected, - * [[Inflate#onEnd]] will be called. - * - * `flush_mode` is not needed for normal operation, because end of stream - * detected automatically. You may try to use it for advanced things, but - * this functionality was not tested. - * - * On fail call [[Inflate#onEnd]] with error code and return false. - * - * ##### Example - * - * ```javascript - * push(chunk, false); // push one of data chunks - * ... - * push(chunk, true); // push last chunk - * ``` - **/ -Inflate$1.prototype.push = function (data, flush_mode) { - const strm = this.strm; - const chunkSize = this.options.chunkSize; - const dictionary = this.options.dictionary; - let status, _flush_mode, last_avail_out; - - if (this.ended) return false; - - if (flush_mode === ~~flush_mode) _flush_mode = flush_mode; - else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH; - - // Convert data if needed - if (toString.call(data) === '[object ArrayBuffer]') { - strm.input = new Uint8Array(data); - } else { - strm.input = data; - } - - strm.next_in = 0; - strm.avail_in = strm.input.length; - - for (;;) { - if (strm.avail_out === 0) { - strm.output = new Uint8Array(chunkSize); - strm.next_out = 0; - strm.avail_out = chunkSize; - } - - status = inflate_1$2.inflate(strm, _flush_mode); - - if (status === Z_NEED_DICT && dictionary) { - status = inflate_1$2.inflateSetDictionary(strm, dictionary); - - if (status === Z_OK) { - status = inflate_1$2.inflate(strm, _flush_mode); - } else if (status === Z_DATA_ERROR) { - // Replace code with more verbose - status = Z_NEED_DICT; - } - } - - // Skip snyc markers if more data follows and not raw mode - while (strm.avail_in > 0 && - status === Z_STREAM_END && - strm.state.wrap > 0 && - data[strm.next_in] !== 0) - { - inflate_1$2.inflateReset(strm); - status = inflate_1$2.inflate(strm, _flush_mode); - } - - switch (status) { - case Z_STREAM_ERROR: - case Z_DATA_ERROR: - case Z_NEED_DICT: - case Z_MEM_ERROR: - this.onEnd(status); - this.ended = true; - return false; - } - - // Remember real `avail_out` value, because we may patch out buffer content - // to align utf8 strings boundaries. - last_avail_out = strm.avail_out; - - if (strm.next_out) { - if (strm.avail_out === 0 || status === Z_STREAM_END) { - - if (this.options.to === 'string') { - - let next_out_utf8 = strings.utf8border(strm.output, strm.next_out); - - let tail = strm.next_out - next_out_utf8; - let utf8str = strings.buf2string(strm.output, next_out_utf8); - - // move tail & realign counters - strm.next_out = tail; - strm.avail_out = chunkSize - tail; - if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0); - - this.onData(utf8str); - - } else { - this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out)); - } - } - } - - // Must repeat iteration if out buffer is full - if (status === Z_OK && last_avail_out === 0) continue; - - // Finalize if end of stream reached. - if (status === Z_STREAM_END) { - status = inflate_1$2.inflateEnd(this.strm); - this.onEnd(status); - this.ended = true; - return true; - } - - if (strm.avail_in === 0) break; - } - - return true; -}; - - -/** - * Inflate#onData(chunk) -> Void - * - chunk (Uint8Array|String): output data. When string output requested, - * each chunk will be string. - * - * By default, stores data blocks in `chunks[]` property and glue - * those in `onEnd`. Override this handler, if you need another behaviour. - **/ -Inflate$1.prototype.onData = function (chunk) { - this.chunks.push(chunk); -}; - - -/** - * Inflate#onEnd(status) -> Void - * - status (Number): inflate status. 0 (Z_OK) on success, - * other if not. - * - * Called either after you tell inflate that the input stream is - * complete (Z_FINISH). By default - join collected chunks, - * free memory and fill `results` / `err` properties. - **/ -Inflate$1.prototype.onEnd = function (status) { - // On success - join - if (status === Z_OK) { - if (this.options.to === 'string') { - this.result = this.chunks.join(''); - } else { - this.result = common.flattenChunks(this.chunks); - } - } - this.chunks = []; - this.err = status; - this.msg = this.strm.msg; -}; - - -/** - * inflate(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * Decompress `data` with inflate/ungzip and `options`. Autodetect - * format via wrapper header by default. That's why we don't provide - * separate `ungzip` method. - * - * Supported options are: - * - * - windowBits - * - * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) - * for more information. - * - * Sugar (options): - * - * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify - * negative windowBits implicitly. - * - `to` (String) - if equal to 'string', then result will be converted - * from utf8 to utf16 (javascript) string. When string output requested, - * chunk length can differ from `chunkSize`, depending on content. - * - * - * ##### Example: - * - * ```javascript - * const pako = require('pako'); - * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9])); - * let output; - * - * try { - * output = pako.inflate(input); - * } catch (err) { - * console.log(err); - * } - * ``` - **/ -function inflate$1(input, options) { - const inflator = new Inflate$1(options); - - inflator.push(input); - - // That will never happens, if you don't cheat with options :) - if (inflator.err) throw inflator.msg || messages[inflator.err]; - - return inflator.result; -} - - -/** - * inflateRaw(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * The same as [[inflate]], but creates raw data, without wrapper - * (header and adler32 crc). - **/ -function inflateRaw$1(input, options) { - options = options || {}; - options.raw = true; - return inflate$1(input, options); -} - - -/** - * ungzip(data[, options]) -> Uint8Array|String - * - data (Uint8Array|ArrayBuffer): input data to decompress. - * - options (Object): zlib inflate options. - * - * Just shortcut to [[inflate]], because it autodetects format - * by header.content. Done for convenience. - **/ - - -var Inflate_1$1 = Inflate$1; -var inflate_2 = inflate$1; -var inflateRaw_1$1 = inflateRaw$1; -var ungzip$1 = inflate$1; -var constants = constants$2; - -var inflate_1$1 = { - Inflate: Inflate_1$1, - inflate: inflate_2, - inflateRaw: inflateRaw_1$1, - ungzip: ungzip$1, - constants: constants -}; - -const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1; -var Inflate_1 = Inflate; -var inflate_1 = inflate; - -const crcTable = []; -for (let n = 0; n < 256; n++) { - let c = n; - for (let k = 0; k < 8; k++) { - if (c & 1) { - c = 0xedb88320 ^ (c >>> 1); - } - else { - c = c >>> 1; - } - } - crcTable[n] = c; -} -const initialCrc = 0xffffffff; -function updateCrc(currentCrc, data, length) { - let c = currentCrc; - for (let n = 0; n < length; n++) { - c = crcTable[(c ^ data[n]) & 0xff] ^ (c >>> 8); - } - return c; -} -function crc(data, length) { - return (updateCrc(initialCrc, data, length) ^ initialCrc) >>> 0; -} -function checkCrc(buffer, crcLength, chunkName) { - const expectedCrc = buffer.readUint32(); - const actualCrc = crc(new Uint8Array(buffer.buffer, buffer.byteOffset + buffer.offset - crcLength - 4, crcLength), crcLength); // "- 4" because we already advanced by reading the CRC - if (actualCrc !== expectedCrc) { - throw new Error(`CRC mismatch for chunk ${chunkName}. Expected ${expectedCrc}, found ${actualCrc}`); - } -} - -function unfilterNone(currentLine, newLine, bytesPerLine) { - for (let i = 0; i < bytesPerLine; i++) { - newLine[i] = currentLine[i]; - } -} -function unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel) { - let i = 0; - for (; i < bytesPerPixel; i++) { - // just copy first bytes - newLine[i] = currentLine[i]; - } - for (; i < bytesPerLine; i++) { - newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff; - } -} -function unfilterUp(currentLine, newLine, prevLine, bytesPerLine) { - let i = 0; - if (prevLine.length === 0) { - // just copy bytes for first line - for (; i < bytesPerLine; i++) { - newLine[i] = currentLine[i]; - } - } - else { - for (; i < bytesPerLine; i++) { - newLine[i] = (currentLine[i] + prevLine[i]) & 0xff; - } - } -} -function unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) { - let i = 0; - if (prevLine.length === 0) { - for (; i < bytesPerPixel; i++) { - newLine[i] = currentLine[i]; - } - for (; i < bytesPerLine; i++) { - newLine[i] = (currentLine[i] + (newLine[i - bytesPerPixel] >> 1)) & 0xff; - } - } - else { - for (; i < bytesPerPixel; i++) { - newLine[i] = (currentLine[i] + (prevLine[i] >> 1)) & 0xff; - } - for (; i < bytesPerLine; i++) { - newLine[i] = - (currentLine[i] + ((newLine[i - bytesPerPixel] + prevLine[i]) >> 1)) & - 0xff; - } - } -} -function unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) { - let i = 0; - if (prevLine.length === 0) { - for (; i < bytesPerPixel; i++) { - newLine[i] = currentLine[i]; - } - for (; i < bytesPerLine; i++) { - newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff; - } - } - else { - for (; i < bytesPerPixel; i++) { - newLine[i] = (currentLine[i] + prevLine[i]) & 0xff; - } - for (; i < bytesPerLine; i++) { - newLine[i] = - (currentLine[i] + - paethPredictor$1(newLine[i - bytesPerPixel], prevLine[i], prevLine[i - bytesPerPixel])) & - 0xff; - } - } -} -function paethPredictor$1(a, b, c) { - const p = a + b - c; - const pa = Math.abs(p - a); - const pb = Math.abs(p - b); - const pc = Math.abs(p - c); - if (pa <= pb && pa <= pc) - return a; - else if (pb <= pc) - return b; - else - return c; -} - -/** - * Apllies filter on scanline based on the filter type. - * @param filterType - The filter type to apply. - * @param currentLine - The current line of pixel data. - * @param newLine - The new line of pixel data. - * @param prevLine - The previous line of pixel data. - * @param passLineBytes - The number of bytes in the pass line. - * @param bytesPerPixel - The number of bytes per pixel. - */ -function applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel) { - switch (filterType) { - case 0: - unfilterNone(currentLine, newLine, passLineBytes); - break; - case 1: - unfilterSub(currentLine, newLine, passLineBytes, bytesPerPixel); - break; - case 2: - unfilterUp(currentLine, newLine, prevLine, passLineBytes); - break; - case 3: - unfilterAverage(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); - break; - case 4: - unfilterPaeth(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); - break; - default: - throw new Error(`Unsupported filter: ${filterType}`); - } -} - -const uint16$1 = new Uint16Array([0x00ff]); -const uint8$1 = new Uint8Array(uint16$1.buffer); -const osIsLittleEndian$1 = uint8$1[0] === 0xff; -/** - * Decodes the Adam7 interlaced PNG data. - * - * @param params - DecodeInterlaceNullParams - * @returns - array of pixel data. - */ -function decodeInterlaceAdam7(params) { - const { data, width, height, channels, depth } = params; - // Adam7 interlacing pattern - const passes = [ - { x: 0, y: 0, xStep: 8, yStep: 8 }, // Pass 1 - { x: 4, y: 0, xStep: 8, yStep: 8 }, // Pass 2 - { x: 0, y: 4, xStep: 4, yStep: 8 }, // Pass 3 - { x: 2, y: 0, xStep: 4, yStep: 4 }, // Pass 4 - { x: 0, y: 2, xStep: 2, yStep: 4 }, // Pass 5 - { x: 1, y: 0, xStep: 2, yStep: 2 }, // Pass 6 - { x: 0, y: 1, xStep: 1, yStep: 2 }, // Pass 7 - ]; - const bytesPerPixel = Math.ceil(depth / 8) * channels; - const resultData = new Uint8Array(height * width * bytesPerPixel); - let offset = 0; - // Process each pass - for (let passIndex = 0; passIndex < 7; passIndex++) { - const pass = passes[passIndex]; - // Calculate pass dimensions - const passWidth = Math.ceil((width - pass.x) / pass.xStep); - const passHeight = Math.ceil((height - pass.y) / pass.yStep); - if (passWidth <= 0 || passHeight <= 0) - continue; - const passLineBytes = passWidth * bytesPerPixel; - const prevLine = new Uint8Array(passLineBytes); - // Process each scanline in this pass - for (let y = 0; y < passHeight; y++) { - // First byte is the filter type - const filterType = data[offset++]; - const currentLine = data.subarray(offset, offset + passLineBytes); - offset += passLineBytes; - // Create a new line for the unfiltered data - const newLine = new Uint8Array(passLineBytes); - // Apply the appropriate unfilter - applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel); - prevLine.set(newLine); - for (let x = 0; x < passWidth; x++) { - const outputX = pass.x + x * pass.xStep; - const outputY = pass.y + y * pass.yStep; - if (outputX >= width || outputY >= height) - continue; - for (let i = 0; i < bytesPerPixel; i++) { - resultData[(outputY * width + outputX) * bytesPerPixel + i] = - newLine[x * bytesPerPixel + i]; - } - } - } - } - if (depth === 16) { - const uint16Data = new Uint16Array(resultData.buffer); - if (osIsLittleEndian$1) { - for (let k = 0; k < uint16Data.length; k++) { - // PNG is always big endian. Swap the bytes. - uint16Data[k] = swap16$1(uint16Data[k]); - } - } - return uint16Data; - } - else { - return resultData; - } -} -function swap16$1(val) { - return ((val & 0xff) << 8) | ((val >> 8) & 0xff); -} - -const uint16 = new Uint16Array([0x00ff]); -const uint8 = new Uint8Array(uint16.buffer); -const osIsLittleEndian = uint8[0] === 0xff; -const empty = new Uint8Array(0); -function decodeInterlaceNull(params) { - const { data, width, height, channels, depth } = params; - const bytesPerPixel = Math.ceil(depth / 8) * channels; - const bytesPerLine = Math.ceil((depth / 8) * channels * width); - const newData = new Uint8Array(height * bytesPerLine); - let prevLine = empty; - let offset = 0; - let currentLine; - let newLine; - for (let i = 0; i < height; i++) { - currentLine = data.subarray(offset + 1, offset + 1 + bytesPerLine); - newLine = newData.subarray(i * bytesPerLine, (i + 1) * bytesPerLine); - switch (data[offset]) { - case 0: - unfilterNone(currentLine, newLine, bytesPerLine); - break; - case 1: - unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel); - break; - case 2: - unfilterUp(currentLine, newLine, prevLine, bytesPerLine); - break; - case 3: - unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel); - break; - case 4: - unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel); - break; - default: - throw new Error(`Unsupported filter: ${data[offset]}`); - } - prevLine = newLine; - offset += bytesPerLine + 1; - } - if (depth === 16) { - const uint16Data = new Uint16Array(newData.buffer); - if (osIsLittleEndian) { - for (let k = 0; k < uint16Data.length; k++) { - // PNG is always big endian. Swap the bytes. - uint16Data[k] = swap16(uint16Data[k]); - } - } - return uint16Data; - } - else { - return newData; - } -} -function swap16(val) { - return ((val & 0xff) << 8) | ((val >> 8) & 0xff); -} - -// https://www.w3.org/TR/PNG/#5PNG-file-signature -const pngSignature = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10); -function checkSignature(buffer) { - if (!hasPngSignature(buffer.readBytes(pngSignature.length))) { - throw new Error('wrong PNG signature'); - } -} -function hasPngSignature(array) { - if (array.length < pngSignature.length) { - return false; - } - for (let i = 0; i < pngSignature.length; i++) { - if (array[i] !== pngSignature[i]) { - return false; - } - } - return true; -} - -// https://www.w3.org/TR/png/#11tEXt -const textChunkName = 'tEXt'; -const NULL = 0; -const latin1Decoder = new TextDecoder('latin1'); -function validateKeyword(keyword) { - validateLatin1(keyword); - if (keyword.length === 0 || keyword.length > 79) { - throw new Error('keyword length must be between 1 and 79'); - } -} -// eslint-disable-next-line no-control-regex -const latin1Regex = /^[\u0000-\u00FF]*$/; -function validateLatin1(text) { - if (!latin1Regex.test(text)) { - throw new Error('invalid latin1 text'); - } -} -function decodetEXt(text, buffer, length) { - const keyword = readKeyword(buffer); - text[keyword] = readLatin1(buffer, length - keyword.length - 1); -} -// https://www.w3.org/TR/png/#11keywords -function readKeyword(buffer) { - buffer.mark(); - while (buffer.readByte() !== NULL) { - /* advance */ - } - const end = buffer.offset; - buffer.reset(); - const keyword = latin1Decoder.decode(buffer.readBytes(end - buffer.offset - 1)); - // NULL - buffer.skip(1); - validateKeyword(keyword); - return keyword; -} -function readLatin1(buffer, length) { - return latin1Decoder.decode(buffer.readBytes(length)); -} - -const ColorType = { - UNKNOWN: -1, - GREYSCALE: 0, - TRUECOLOUR: 2, - INDEXED_COLOUR: 3, - GREYSCALE_ALPHA: 4, - TRUECOLOUR_ALPHA: 6, -}; -const CompressionMethod = { - UNKNOWN: -1, - DEFLATE: 0, -}; -const FilterMethod = { - UNKNOWN: -1, - ADAPTIVE: 0, -}; -const InterlaceMethod = { - UNKNOWN: -1, - NO_INTERLACE: 0, - ADAM7: 1, -}; -const DisposeOpType = { - NONE: 0, - BACKGROUND: 1, - PREVIOUS: 2, -}; -const BlendOpType = { - SOURCE: 0, - OVER: 1, -}; - -class PngDecoder extends IOBuffer { - _checkCrc; - _inflator; - _png; - _apng; - _end; - _hasPalette; - _palette; - _hasTransparency; - _transparency; - _compressionMethod; - _filterMethod; - _interlaceMethod; - _colorType; - _isAnimated; - _numberOfFrames; - _numberOfPlays; - _frames; - _writingDataChunks; - constructor(data, options = {}) { - super(data); - const { checkCrc = false } = options; - this._checkCrc = checkCrc; - this._inflator = new Inflate_1(); - this._png = { - width: -1, - height: -1, - channels: -1, - data: new Uint8Array(0), - depth: 1, - text: {}, - }; - this._apng = { - width: -1, - height: -1, - channels: -1, - depth: 1, - numberOfFrames: 1, - numberOfPlays: 0, - text: {}, - frames: [], - }; - this._end = false; - this._hasPalette = false; - this._palette = []; - this._hasTransparency = false; - this._transparency = new Uint16Array(0); - this._compressionMethod = CompressionMethod.UNKNOWN; - this._filterMethod = FilterMethod.UNKNOWN; - this._interlaceMethod = InterlaceMethod.UNKNOWN; - this._colorType = ColorType.UNKNOWN; - this._isAnimated = false; - this._numberOfFrames = 1; - this._numberOfPlays = 0; - this._frames = []; - this._writingDataChunks = false; - // PNG is always big endian - // https://www.w3.org/TR/PNG/#7Integers-and-byte-order - this.setBigEndian(); - } - decode() { - checkSignature(this); - while (!this._end) { - const length = this.readUint32(); - const type = this.readChars(4); - this.decodeChunk(length, type); - } - this.decodeImage(); - return this._png; - } - decodeApng() { - checkSignature(this); - while (!this._end) { - const length = this.readUint32(); - const type = this.readChars(4); - this.decodeApngChunk(length, type); - } - this.decodeApngImage(); - return this._apng; - } - // https://www.w3.org/TR/PNG/#5Chunk-layout - decodeChunk(length, type) { - const offset = this.offset; - switch (type) { - // 11.2 Critical chunks - case 'IHDR': // 11.2.2 IHDR Image header - this.decodeIHDR(); - break; - case 'PLTE': // 11.2.3 PLTE Palette - this.decodePLTE(length); - break; - case 'IDAT': // 11.2.4 IDAT Image data - this.decodeIDAT(length); - break; - case 'IEND': // 11.2.5 IEND Image trailer - this._end = true; - break; - // 11.3 Ancillary chunks - case 'tRNS': // 11.3.2.1 tRNS Transparency - this.decodetRNS(length); - break; - case 'iCCP': // 11.3.3.3 iCCP Embedded ICC profile - this.decodeiCCP(length); - break; - case textChunkName: // 11.3.4.3 tEXt Textual data - decodetEXt(this._png.text, this, length); - break; - case 'pHYs': // 11.3.5.3 pHYs Physical pixel dimensions - this.decodepHYs(); - break; - default: - this.skip(length); - break; - } - if (this.offset - offset !== length) { - throw new Error(`Length mismatch while decoding chunk ${type}`); - } - if (this._checkCrc) { - checkCrc(this, length + 4, type); - } - else { - this.skip(4); - } - } - decodeApngChunk(length, type) { - const offset = this.offset; - if (type !== 'fdAT' && type !== 'IDAT' && this._writingDataChunks) { - this.pushDataToFrame(); - } - switch (type) { - case 'acTL': - this.decodeACTL(); - break; - case 'fcTL': - this.decodeFCTL(); - break; - case 'fdAT': - this.decodeFDAT(length); - break; - default: - this.decodeChunk(length, type); - this.offset = offset + length; - break; - } - if (this.offset - offset !== length) { - throw new Error(`Length mismatch while decoding chunk ${type}`); - } - if (this._checkCrc) { - checkCrc(this, length + 4, type); - } - else { - this.skip(4); - } - } - // https://www.w3.org/TR/PNG/#11IHDR - decodeIHDR() { - const image = this._png; - image.width = this.readUint32(); - image.height = this.readUint32(); - image.depth = checkBitDepth(this.readUint8()); - const colorType = this.readUint8(); - this._colorType = colorType; - let channels; - switch (colorType) { - case ColorType.GREYSCALE: - channels = 1; - break; - case ColorType.TRUECOLOUR: - channels = 3; - break; - case ColorType.INDEXED_COLOUR: - channels = 1; - break; - case ColorType.GREYSCALE_ALPHA: - channels = 2; - break; - case ColorType.TRUECOLOUR_ALPHA: - channels = 4; - break; - // Kept for exhaustiveness. - // eslint-disable-next-line unicorn/no-useless-switch-case - case ColorType.UNKNOWN: - default: - throw new Error(`Unknown color type: ${colorType}`); - } - this._png.channels = channels; - this._compressionMethod = this.readUint8(); - if (this._compressionMethod !== CompressionMethod.DEFLATE) { - throw new Error(`Unsupported compression method: ${this._compressionMethod}`); - } - this._filterMethod = this.readUint8(); - this._interlaceMethod = this.readUint8(); - } - decodeACTL() { - this._numberOfFrames = this.readUint32(); - this._numberOfPlays = this.readUint32(); - this._isAnimated = true; - } - decodeFCTL() { - const image = { - sequenceNumber: this.readUint32(), - width: this.readUint32(), - height: this.readUint32(), - xOffset: this.readUint32(), - yOffset: this.readUint32(), - delayNumber: this.readUint16(), - delayDenominator: this.readUint16(), - disposeOp: this.readUint8(), - blendOp: this.readUint8(), - data: new Uint8Array(0), - }; - this._frames.push(image); - } - // https://www.w3.org/TR/PNG/#11PLTE - decodePLTE(length) { - if (length % 3 !== 0) { - throw new RangeError(`PLTE field length must be a multiple of 3. Got ${length}`); - } - const l = length / 3; - this._hasPalette = true; - const palette = []; - this._palette = palette; - for (let i = 0; i < l; i++) { - palette.push([this.readUint8(), this.readUint8(), this.readUint8()]); - } - } - // https://www.w3.org/TR/PNG/#11IDAT - decodeIDAT(length) { - this._writingDataChunks = true; - const dataLength = length; - const dataOffset = this.offset + this.byteOffset; - this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength)); - if (this._inflator.err) { - throw new Error(`Error while decompressing the data: ${this._inflator.err}`); - } - this.skip(length); - } - decodeFDAT(length) { - this._writingDataChunks = true; - let dataLength = length; - let dataOffset = this.offset + this.byteOffset; - dataOffset += 4; - dataLength -= 4; - this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength)); - if (this._inflator.err) { - throw new Error(`Error while decompressing the data: ${this._inflator.err}`); - } - this.skip(length); - } - // https://www.w3.org/TR/PNG/#11tRNS - decodetRNS(length) { - switch (this._colorType) { - case ColorType.GREYSCALE: - case ColorType.TRUECOLOUR: { - if (length % 2 !== 0) { - throw new RangeError(`tRNS chunk length must be a multiple of 2. Got ${length}`); - } - if (length / 2 > this._png.width * this._png.height) { - throw new Error(`tRNS chunk contains more alpha values than there are pixels (${length / 2} vs ${this._png.width * this._png.height})`); - } - this._hasTransparency = true; - this._transparency = new Uint16Array(length / 2); - for (let i = 0; i < length / 2; i++) { - this._transparency[i] = this.readUint16(); - } - break; - } - case ColorType.INDEXED_COLOUR: { - if (length > this._palette.length) { - throw new Error(`tRNS chunk contains more alpha values than there are palette colors (${length} vs ${this._palette.length})`); - } - let i = 0; - for (; i < length; i++) { - const alpha = this.readByte(); - this._palette[i].push(alpha); - } - for (; i < this._palette.length; i++) { - this._palette[i].push(255); - } - break; - } - // Kept for exhaustiveness. - /* eslint-disable unicorn/no-useless-switch-case */ - case ColorType.UNKNOWN: - case ColorType.GREYSCALE_ALPHA: - case ColorType.TRUECOLOUR_ALPHA: - default: { - throw new Error(`tRNS chunk is not supported for color type ${this._colorType}`); - } - /* eslint-enable unicorn/no-useless-switch-case */ - } - } - // https://www.w3.org/TR/PNG/#11iCCP - decodeiCCP(length) { - const name = readKeyword(this); - const compressionMethod = this.readUint8(); - if (compressionMethod !== CompressionMethod.DEFLATE) { - throw new Error(`Unsupported iCCP compression method: ${compressionMethod}`); - } - const compressedProfile = this.readBytes(length - name.length - 2); - this._png.iccEmbeddedProfile = { - name, - profile: inflate_1(compressedProfile), - }; - } - // https://www.w3.org/TR/PNG/#11pHYs - decodepHYs() { - const ppuX = this.readUint32(); - const ppuY = this.readUint32(); - const unitSpecifier = this.readByte(); - this._png.resolution = { x: ppuX, y: ppuY, unit: unitSpecifier }; - } - decodeApngImage() { - this._apng.width = this._png.width; - this._apng.height = this._png.height; - this._apng.channels = this._png.channels; - this._apng.depth = this._png.depth; - this._apng.numberOfFrames = this._numberOfFrames; - this._apng.numberOfPlays = this._numberOfPlays; - this._apng.text = this._png.text; - this._apng.resolution = this._png.resolution; - for (let i = 0; i < this._numberOfFrames; i++) { - const newFrame = { - sequenceNumber: this._frames[i].sequenceNumber, - delayNumber: this._frames[i].delayNumber, - delayDenominator: this._frames[i].delayDenominator, - data: this._apng.depth === 8 - ? new Uint8Array(this._apng.width * this._apng.height * this._apng.channels) - : new Uint16Array(this._apng.width * this._apng.height * this._apng.channels), - }; - const frame = this._frames.at(i); - if (frame) { - frame.data = decodeInterlaceNull({ - data: frame.data, - width: frame.width, - height: frame.height, - channels: this._apng.channels, - depth: this._apng.depth, - }); - if (this._hasPalette) { - this._apng.palette = this._palette; - } - if (this._hasTransparency) { - this._apng.transparency = this._transparency; - } - if (i === 0 || - (frame.xOffset === 0 && - frame.yOffset === 0 && - frame.width === this._png.width && - frame.height === this._png.height)) { - newFrame.data = frame.data; - } - else { - const prevFrame = this._apng.frames.at(i - 1); - this.disposeFrame(frame, prevFrame, newFrame); - this.addFrameDataToCanvas(newFrame, frame); - } - this._apng.frames.push(newFrame); - } - } - return this._apng; - } - disposeFrame(frame, prevFrame, imageFrame) { - switch (frame.disposeOp) { - case DisposeOpType.NONE: - break; - case DisposeOpType.BACKGROUND: - for (let row = 0; row < this._png.height; row++) { - for (let col = 0; col < this._png.width; col++) { - const index = (row * frame.width + col) * this._png.channels; - for (let channel = 0; channel < this._png.channels; channel++) { - imageFrame.data[index + channel] = 0; - } - } - } - break; - case DisposeOpType.PREVIOUS: - imageFrame.data.set(prevFrame.data); - break; - default: - throw new Error('Unknown disposeOp'); - } - } - addFrameDataToCanvas(imageFrame, frame) { - const maxValue = 1 << this._png.depth; - const calculatePixelIndices = (row, col) => { - const index = ((row + frame.yOffset) * this._png.width + frame.xOffset + col) * - this._png.channels; - const frameIndex = (row * frame.width + col) * this._png.channels; - return { index, frameIndex }; - }; - switch (frame.blendOp) { - case BlendOpType.SOURCE: - for (let row = 0; row < frame.height; row++) { - for (let col = 0; col < frame.width; col++) { - const { index, frameIndex } = calculatePixelIndices(row, col); - for (let channel = 0; channel < this._png.channels; channel++) { - imageFrame.data[index + channel] = - frame.data[frameIndex + channel]; - } - } - } - break; - // https://www.w3.org/TR/png-3/#13Alpha-channel-processing - case BlendOpType.OVER: - for (let row = 0; row < frame.height; row++) { - for (let col = 0; col < frame.width; col++) { - const { index, frameIndex } = calculatePixelIndices(row, col); - for (let channel = 0; channel < this._png.channels; channel++) { - const sourceAlpha = frame.data[frameIndex + this._png.channels - 1] / maxValue; - const foregroundValue = channel % (this._png.channels - 1) === 0 - ? 1 - : frame.data[frameIndex + channel]; - const value = Math.floor(sourceAlpha * foregroundValue + - (1 - sourceAlpha) * imageFrame.data[index + channel]); - imageFrame.data[index + channel] += value; - } - } - } - break; - default: - throw new Error('Unknown blendOp'); - } - } - decodeImage() { - if (this._inflator.err) { - throw new Error(`Error while decompressing the data: ${this._inflator.err}`); - } - const data = this._isAnimated - ? (this._frames?.at(0)).data - : this._inflator.result; - if (this._filterMethod !== FilterMethod.ADAPTIVE) { - throw new Error(`Filter method ${this._filterMethod} not supported`); - } - if (this._interlaceMethod === InterlaceMethod.NO_INTERLACE) { - this._png.data = decodeInterlaceNull({ - data: data, - width: this._png.width, - height: this._png.height, - channels: this._png.channels, - depth: this._png.depth, - }); - } - else if (this._interlaceMethod === InterlaceMethod.ADAM7) { - this._png.data = decodeInterlaceAdam7({ - data: data, - width: this._png.width, - height: this._png.height, - channels: this._png.channels, - depth: this._png.depth, - }); - } - else { - throw new Error(`Interlace method ${this._interlaceMethod} not supported`); - } - if (this._hasPalette) { - this._png.palette = this._palette; - } - if (this._hasTransparency) { - this._png.transparency = this._transparency; - } - } - pushDataToFrame() { - const result = this._inflator.result; - const lastFrame = this._frames.at(-1); - if (lastFrame) { - lastFrame.data = result; - } - else { - this._frames.push({ - sequenceNumber: 0, - width: this._png.width, - height: this._png.height, - xOffset: 0, - yOffset: 0, - delayNumber: 0, - delayDenominator: 0, - disposeOp: DisposeOpType.NONE, - blendOp: BlendOpType.SOURCE, - data: result, - }); - } - this._inflator = new Inflate_1(); - this._writingDataChunks = false; - } -} -function checkBitDepth(value) { - if (value !== 1 && - value !== 2 && - value !== 4 && - value !== 8 && - value !== 16) { - throw new Error(`invalid bit depth: ${value}`); - } - return value; -} - -var ResolutionUnitSpecifier; -(function (ResolutionUnitSpecifier) { - /** - * Unit is unknown - */ - ResolutionUnitSpecifier[ResolutionUnitSpecifier["UNKNOWN"] = 0] = "UNKNOWN"; - /** - * Unit is the metre - */ - ResolutionUnitSpecifier[ResolutionUnitSpecifier["METRE"] = 1] = "METRE"; -})(ResolutionUnitSpecifier || (ResolutionUnitSpecifier = {})); - -function decodePng(data, options) { - const decoder = new PngDecoder(data, options); - return decoder.decode(); -} - -/** - * @license - * - * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -/* - * @see http://www.w3.org/TR/PNG-Chunks.html - * - Color Allowed Interpretation - Type Bit Depths - - 0 1,2,4,8,16 Each pixel is a grayscale sample. - - 2 8,16 Each pixel is an R,G,B triple. - - 3 1,2,4,8 Each pixel is a palette index; - a PLTE chunk must appear. - - 4 8,16 Each pixel is a grayscale sample, - followed by an alpha sample. - - 6 8,16 Each pixel is an R,G,B triple, - followed by an alpha sample. -*/ - -/* - * @name processPNG - * Entry point: process a PNG and return image dict and metadata for jsPDF - */ -jsPDF.API.processPNG = function(imageData, index, alias, compression) { - if (this.__addimage__.isArrayBuffer(imageData)) { - imageData = new Uint8Array(imageData); - } - if (!this.__addimage__.isArrayBufferView(imageData)) { - return; - } - - const decodedPng = decodePng(imageData, { checkCrc: true }); - const { - width, - height, - channels, - palette: decodedPalette, - depth: bitsPerComponent - } = decodedPng; - - let result; - if (decodedPalette && channels === 1) { - result = processIndexedPNG(decodedPng); - } else if (channels === 2 || channels === 4) { - result = processAlphaPNG(decodedPng); - } else { - result = processOpaquePNG(decodedPng); - } - - const { - colorSpace, - colorsPerPixel, - sMaskBitsPerComponent, - colorBytes, - alphaBytes, - needSMask, - palette, - mask - } = result; - - let predictor = null; - - let filter, decodeParameters, sMask; - if (canCompress(compression)) { - predictor = getPredictorFromCompression(compression); - filter = this.decode.FLATE_DECODE; - decodeParameters = `/Predictor ${predictor} /Colors ${colorsPerPixel} /BitsPerComponent ${bitsPerComponent} /Columns ${width}`; - - const rowByteLength = Math.ceil( - (width * colorsPerPixel * bitsPerComponent) / 8 - ); - - imageData = compressBytes( - colorBytes, - rowByteLength, - colorsPerPixel, - bitsPerComponent, - compression - ); - if (needSMask) { - const sMaskRowByteLength = Math.ceil((width * sMaskBitsPerComponent) / 8); - sMask = compressBytes( - alphaBytes, - sMaskRowByteLength, - 1, - sMaskBitsPerComponent, - compression - ); - } - } else { - filter = undefined; - decodeParameters = undefined; - imageData = colorBytes; - if (needSMask) sMask = alphaBytes; - } - - if ( - this.__addimage__.isArrayBuffer(imageData) || - this.__addimage__.isArrayBufferView(imageData) - ) { - imageData = this.__addimage__.arrayBufferToBinaryString(imageData); - } - - if ( - (sMask && this.__addimage__.isArrayBuffer(sMask)) || - this.__addimage__.isArrayBufferView(sMask) - ) { - sMask = this.__addimage__.arrayBufferToBinaryString(sMask); - } - - return { - alias, - data: imageData, - index, - filter, - decodeParameters, - transparency: mask, - palette, - sMask, - predictor, - width, - height, - bitsPerComponent, - sMaskBitsPerComponent, - colorSpace - }; -}; - -/* - * PNG filter method types - * - * @see http://www.w3.org/TR/PNG-Filters.html - * @see http://www.libpng.org/pub/png/book/chapter09.html - * - * This is what the value 'Predictor' in decode params relates to - * - * 15 is "optimal prediction", which means the prediction algorithm can change from line to line. - * In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte. - * - 0 None - 1 Sub - 2 Up - 3 Average - 4 Paeth - */ - -function canCompress(value) { - return value !== jsPDF.API.image_compression.NONE && hasCompressionJS(); -} - -function hasCompressionJS() { - return typeof zlibSync === "function"; -} -function compressBytes( - bytes, - lineByteLength, - channels, - bitsPerComponent, - compression -) { - let level = 4; - let filter_method = filterUp; - - switch (compression) { - case jsPDF.API.image_compression.FAST: - level = 1; - filter_method = filterSub; - break; - - case jsPDF.API.image_compression.MEDIUM: - level = 6; - filter_method = filterAverage; - break; - - case jsPDF.API.image_compression.SLOW: - level = 9; - filter_method = filterPaeth; - break; - } - - const bytesPerPixel = Math.ceil((channels * bitsPerComponent) / 8); - bytes = applyPngFilterMethod( - bytes, - lineByteLength, - bytesPerPixel, - filter_method - ); - const dat = zlibSync(bytes, { level: level }); - return jsPDF.API.__addimage__.arrayBufferToBinaryString(dat); -} - -function applyPngFilterMethod( - bytes, - lineByteLength, - bytesPerPixel, - filter_method -) { - const lines = bytes.length / lineByteLength; - const result = new Uint8Array(bytes.length + lines); - const filter_methods = getFilterMethods(); - let prevLine; - - for (let i = 0; i < lines; i += 1) { - const offset = i * lineByteLength; - const line = bytes.subarray(offset, offset + lineByteLength); - - if (filter_method) { - result.set(filter_method(line, bytesPerPixel, prevLine), offset + i); - } else { - const len = filter_methods.length; - const results = []; - - for (let j = 0; j < len; j += 1) { - results[j] = filter_methods[j](line, bytesPerPixel, prevLine); - } - - const ind = getIndexOfSmallestSum(results.concat()); - - result.set(results[ind], offset + i); - } - - prevLine = line; - } - - return result; -} - -function filterNone(line) { - /*const result = new Uint8Array(line.length + 1); - result[0] = 0; - result.set(line, 1);*/ - - const result = Array.apply([], line); - result.unshift(0); - - return result; -} - -function filterSub(line, colorsPerPixel) { - const len = line.length; - const result = []; - - result[0] = 1; - - for (let i = 0; i < len; i += 1) { - const left = line[i - colorsPerPixel] || 0; - result[i + 1] = (line[i] - left + 0x0100) & 0xff; - } - - return result; -} - -function filterUp(line, colorsPerPixel, prevLine) { - const len = line.length; - const result = []; - - result[0] = 2; - - for (let i = 0; i < len; i += 1) { - const up = (prevLine && prevLine[i]) || 0; - result[i + 1] = (line[i] - up + 0x0100) & 0xff; - } - - return result; -} - -function filterAverage(line, colorsPerPixel, prevLine) { - const len = line.length; - const result = []; - - result[0] = 3; - - for (let i = 0; i < len; i += 1) { - const left = line[i - colorsPerPixel] || 0; - const up = (prevLine && prevLine[i]) || 0; - result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff; - } - - return result; -} - -function filterPaeth(line, colorsPerPixel, prevLine) { - const len = line.length; - const result = []; - - result[0] = 4; - - for (let i = 0; i < len; i += 1) { - const left = line[i - colorsPerPixel] || 0; - const up = (prevLine && prevLine[i]) || 0; - const upLeft = (prevLine && prevLine[i - colorsPerPixel]) || 0; - const paeth = paethPredictor(left, up, upLeft); - result[i + 1] = (line[i] - paeth + 0x0100) & 0xff; - } - - return result; -} - -function paethPredictor(left, up, upLeft) { - if (left === up && up === upLeft) { - return left; - } - const pLeft = Math.abs(up - upLeft), - pUp = Math.abs(left - upLeft), - pUpLeft = Math.abs(left + up - upLeft - upLeft); - return pLeft <= pUp && pLeft <= pUpLeft ? left : pUp <= pUpLeft ? up : upLeft; -} - -function getFilterMethods() { - return [filterNone, filterSub, filterUp, filterAverage, filterPaeth]; -} - -function getIndexOfSmallestSum(arrays) { - const sum = arrays.map(function(value) { - return value.reduce(function(pv, cv) { - return pv + Math.abs(cv); - }, 0); - }); - return sum.indexOf(Math.min.apply(null, sum)); -} - -function getPredictorFromCompression(compression) { - let predictor; - switch (compression) { - case jsPDF.API.image_compression.FAST: - predictor = 11; - break; - - case jsPDF.API.image_compression.MEDIUM: - predictor = 13; - break; - - case jsPDF.API.image_compression.SLOW: - predictor = 14; - break; - - default: - predictor = 12; - break; - } - return predictor; -} - -// Extracted helper for Indexed PNGs (palette-based) -function processIndexedPNG(decodedPng) { - const { width, height, data, palette: decodedPalette, depth } = decodedPng; - let needSMask = false; - let palette = []; - let mask = []; - let alphaBytes = undefined; - let hasSemiTransparency = false; - - const maxMaskLength = 1; - let maskLength = 0; - - for (let i = 0; i < decodedPalette.length; i++) { - const [r, g, b, a] = decodedPalette[i]; - palette.push(r, g, b); - if (a != null) { - if (a === 0) { - maskLength++; - if (mask.length < maxMaskLength) { - mask.push(i); - } - } else if (a < 255) { - hasSemiTransparency = true; - } - } - } - - if (hasSemiTransparency || maskLength > maxMaskLength) { - needSMask = true; - mask = undefined; - - const totalPixels = width * height; - // per PNG spec, palettes always use 8 bits per component - alphaBytes = new Uint8Array(totalPixels); - const dataView = new DataView(data.buffer); - for (let p = 0; p < totalPixels; p++) { - const paletteIndex = readSample(dataView, p, depth); - const [, , , alpha] = decodedPalette[paletteIndex]; - alphaBytes[p] = alpha; - } - } else if (maskLength === 0) { - mask = undefined; - } - - return { - colorSpace: "Indexed", - colorsPerPixel: 1, - sMaskBitsPerComponent: needSMask ? 8 : undefined, - colorBytes: data, - alphaBytes, - needSMask, - palette, - mask - }; -} - -/* - * Splits color and alpha values into separate buffers - */ -function processAlphaPNG(decodedPng) { - const { data, width, height, channels, depth } = decodedPng; - - const colorSpace = channels === 2 ? "DeviceGray" : "DeviceRGB"; - const colorsPerPixel = channels - 1; - - const totalPixels = width * height; - const colorChannels = colorsPerPixel; // 1 for Gray, 3 for RGB - const alphaChannels = 1; - const totalColorSamples = totalPixels * colorChannels; - const totalAlphaSamples = totalPixels * alphaChannels; - - const colorByteLen = Math.ceil((totalColorSamples * depth) / 8); - const alphaByteLen = Math.ceil((totalAlphaSamples * depth) / 8); - const colorBytes = new Uint8Array(colorByteLen); - const alphaBytes = new Uint8Array(alphaByteLen); - - const dataView = new DataView(data.buffer); - const colorView = new DataView(colorBytes.buffer); - const alphaView = new DataView(alphaBytes.buffer); - - let needSMask = false; - for (let p = 0; p < totalPixels; p++) { - const pixelStartIndex = p * channels; - for (let s = 0; s < colorChannels; s++) { - const sampleIndex = pixelStartIndex + s; - const colorValue = readSample(dataView, sampleIndex, depth); - writeSample(colorView, colorValue, p * colorChannels + s, depth); - } - const sampleIndex = pixelStartIndex + colorChannels; - const alphaValue = readSample(dataView, sampleIndex, depth); - if (alphaValue < (1 << depth) - 1) { - needSMask = true; - } - writeSample(alphaView, alphaValue, p * alphaChannels, depth); - } - - return { - colorSpace, - colorsPerPixel, - sMaskBitsPerComponent: needSMask ? depth : undefined, - colorBytes, - alphaBytes, - needSMask - }; -} - -function processOpaquePNG(decodedPng) { - const { data, channels } = decodedPng; - const colorSpace = channels === 1 ? "DeviceGray" : "DeviceRGB"; - const colorsPerPixel = colorSpace === "DeviceGray" ? 1 : 3; - - let colorBytes; - if (data instanceof Uint16Array) { - colorBytes = convertUint16ArrayToUint8Array(data); - } else { - colorBytes = data; - } - - return { colorSpace, colorsPerPixel, colorBytes, needSMask: false }; -} - -function convertUint16ArrayToUint8Array(data) { - // PNG/PDF expect MSB-first byte order. Since EcmaScript does not specify - // the byte order of Uint16Array, we need to use a DataView to ensure the - // correct byte order. - const sampleCount = data.length; - const out = new Uint8Array(sampleCount * 2); - const outView = new DataView(out.buffer, out.byteOffset, out.byteLength); - - for (let i = 0; i < sampleCount; i++) { - outView.setUint16(i * 2, data[i], false); - } - return out; -} - -function readSample(view, sampleIndex, depth) { - const bitIndex = sampleIndex * depth; - const byteIndex = Math.floor(bitIndex / 8); - const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); - const bitMask = (1 << depth) - 1; - const word = safeGetUint16(view, byteIndex); - return (word >> bitOffset) & bitMask; -} - -function writeSample(view, value, sampleIndex, depth) { - const bitIndex = sampleIndex * depth; - const byteIndex = Math.floor(bitIndex / 8); - const bitOffset = 16 - (bitIndex - byteIndex * 8 + depth); - const bitMask = (1 << depth) - 1; - const writeValue = (value & bitMask) << bitOffset; - const word = - safeGetUint16(view, byteIndex) & ~(bitMask << bitOffset) & 0xffff; - safeSetUint16(view, byteIndex, word | writeValue); -} - -function safeGetUint16(view, byteIndex) { - if (byteIndex + 1 < view.byteLength) { - return view.getUint16(byteIndex, false); - } - const b0 = view.getUint8(byteIndex); - return b0 << 8; -} - -function safeSetUint16(view, byteIndex, value) { - if (byteIndex + 1 < view.byteLength) { - view.setUint16(byteIndex, value, false); - return; - } - const byteToWrite = (value >> 8) & 0xff; - view.setUint8(byteIndex, byteToWrite); -} - -/** - * @license - * - * Copyright (c) 2021 Antti Palola, https://github.com/Pantura - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -/** - * jsPDF RGBA array PlugIn - * @name rgba_support - * @module - */ -(function(jsPDFAPI) { - - /** - * @name processRGBA - * @function - * - * Process RGBA Array. This is a one-dimension array with pixel data [red, green, blue, alpha, red, green, ...]. - * RGBA array data can be obtained from DOM canvas getImageData. - * @ignore - */ - jsPDFAPI.processRGBA = function(imageData, index, alias) { - - var imagePixels = imageData.data; - var length = imagePixels.length; - // jsPDF takes alpha data separately so extract that. - var rgbOut = new Uint8Array((length / 4) * 3); - var alphaOut = new Uint8Array(length / 4); - var outIndex = 0; - var alphaIndex = 0; - - for (var i = 0; i < length; i += 4) { - var r = imagePixels[i]; - var g = imagePixels[i + 1]; - var b = imagePixels[i + 2]; - var alpha = imagePixels[i + 3]; - rgbOut[outIndex++] = r; - rgbOut[outIndex++] = g; - rgbOut[outIndex++] = b; - alphaOut[alphaIndex++] = alpha; - } - - var rgbData = this.__addimage__.arrayBufferToBinaryString(rgbOut); - var alphaData = this.__addimage__.arrayBufferToBinaryString(alphaOut); - - return { - alpha: alphaData, - data: rgbData, - index: index, - alias: alias, - colorSpace: "DeviceRGB", - bitsPerComponent: 8, - width: imageData.width, - height: imageData.height - }; - }; -})(jsPDF.API); - -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * jsPDF setLanguage Plugin - * - * @name setLanguage - * @module - */ -(function(jsPDFAPI) { - - /** - * Add Language Tag to the generated PDF - * - * @name setLanguage - * @function - * @param {string} langCode The Language code as ISO-639-1 (e.g. 'en') or as country language code (e.g. 'en-GB'). - * @returns {jsPDF} - * @example - * var doc = new jsPDF() - * doc.text(10, 10, 'This is a test') - * doc.setLanguage("en-US") - * doc.save('english.pdf') - */ - jsPDFAPI.setLanguage = function(langCode) { - - var langCodes = { - af: "Afrikaans", - sq: "Albanian", - ar: "Arabic (Standard)", - "ar-DZ": "Arabic (Algeria)", - "ar-BH": "Arabic (Bahrain)", - "ar-EG": "Arabic (Egypt)", - "ar-IQ": "Arabic (Iraq)", - "ar-JO": "Arabic (Jordan)", - "ar-KW": "Arabic (Kuwait)", - "ar-LB": "Arabic (Lebanon)", - "ar-LY": "Arabic (Libya)", - "ar-MA": "Arabic (Morocco)", - "ar-OM": "Arabic (Oman)", - "ar-QA": "Arabic (Qatar)", - "ar-SA": "Arabic (Saudi Arabia)", - "ar-SY": "Arabic (Syria)", - "ar-TN": "Arabic (Tunisia)", - "ar-AE": "Arabic (U.A.E.)", - "ar-YE": "Arabic (Yemen)", - an: "Aragonese", - hy: "Armenian", - as: "Assamese", - ast: "Asturian", - az: "Azerbaijani", - eu: "Basque", - be: "Belarusian", - bn: "Bengali", - bs: "Bosnian", - br: "Breton", - bg: "Bulgarian", - my: "Burmese", - ca: "Catalan", - ch: "Chamorro", - ce: "Chechen", - zh: "Chinese", - "zh-HK": "Chinese (Hong Kong)", - "zh-CN": "Chinese (PRC)", - "zh-SG": "Chinese (Singapore)", - "zh-TW": "Chinese (Taiwan)", - cv: "Chuvash", - co: "Corsican", - cr: "Cree", - hr: "Croatian", - cs: "Czech", - da: "Danish", - nl: "Dutch (Standard)", - "nl-BE": "Dutch (Belgian)", - en: "English", - "en-AU": "English (Australia)", - "en-BZ": "English (Belize)", - "en-CA": "English (Canada)", - "en-IE": "English (Ireland)", - "en-JM": "English (Jamaica)", - "en-NZ": "English (New Zealand)", - "en-PH": "English (Philippines)", - "en-ZA": "English (South Africa)", - "en-TT": "English (Trinidad & Tobago)", - "en-GB": "English (United Kingdom)", - "en-US": "English (United States)", - "en-ZW": "English (Zimbabwe)", - eo: "Esperanto", - et: "Estonian", - fo: "Faeroese", - fj: "Fijian", - fi: "Finnish", - fr: "French (Standard)", - "fr-BE": "French (Belgium)", - "fr-CA": "French (Canada)", - "fr-FR": "French (France)", - "fr-LU": "French (Luxembourg)", - "fr-MC": "French (Monaco)", - "fr-CH": "French (Switzerland)", - fy: "Frisian", - fur: "Friulian", - gd: "Gaelic (Scots)", - "gd-IE": "Gaelic (Irish)", - gl: "Galacian", - ka: "Georgian", - de: "German (Standard)", - "de-AT": "German (Austria)", - "de-DE": "German (Germany)", - "de-LI": "German (Liechtenstein)", - "de-LU": "German (Luxembourg)", - "de-CH": "German (Switzerland)", - el: "Greek", - gu: "Gujurati", - ht: "Haitian", - he: "Hebrew", - hi: "Hindi", - hu: "Hungarian", - is: "Icelandic", - id: "Indonesian", - iu: "Inuktitut", - ga: "Irish", - it: "Italian (Standard)", - "it-CH": "Italian (Switzerland)", - ja: "Japanese", - kn: "Kannada", - ks: "Kashmiri", - kk: "Kazakh", - km: "Khmer", - ky: "Kirghiz", - tlh: "Klingon", - ko: "Korean", - "ko-KP": "Korean (North Korea)", - "ko-KR": "Korean (South Korea)", - la: "Latin", - lv: "Latvian", - lt: "Lithuanian", - lb: "Luxembourgish", - mk: "North Macedonia", - ms: "Malay", - ml: "Malayalam", - mt: "Maltese", - mi: "Maori", - mr: "Marathi", - mo: "Moldavian", - nv: "Navajo", - ng: "Ndonga", - ne: "Nepali", - no: "Norwegian", - nb: "Norwegian (Bokmal)", - nn: "Norwegian (Nynorsk)", - oc: "Occitan", - or: "Oriya", - om: "Oromo", - fa: "Persian", - "fa-IR": "Persian/Iran", - pl: "Polish", - pt: "Portuguese", - "pt-BR": "Portuguese (Brazil)", - pa: "Punjabi", - "pa-IN": "Punjabi (India)", - "pa-PK": "Punjabi (Pakistan)", - qu: "Quechua", - rm: "Rhaeto-Romanic", - ro: "Romanian", - "ro-MO": "Romanian (Moldavia)", - ru: "Russian", - "ru-MO": "Russian (Moldavia)", - sz: "Sami (Lappish)", - sg: "Sango", - sa: "Sanskrit", - sc: "Sardinian", - sd: "Sindhi", - si: "Singhalese", - sr: "Serbian", - sk: "Slovak", - sl: "Slovenian", - so: "Somani", - sb: "Sorbian", - es: "Spanish", - "es-AR": "Spanish (Argentina)", - "es-BO": "Spanish (Bolivia)", - "es-CL": "Spanish (Chile)", - "es-CO": "Spanish (Colombia)", - "es-CR": "Spanish (Costa Rica)", - "es-DO": "Spanish (Dominican Republic)", - "es-EC": "Spanish (Ecuador)", - "es-SV": "Spanish (El Salvador)", - "es-GT": "Spanish (Guatemala)", - "es-HN": "Spanish (Honduras)", - "es-MX": "Spanish (Mexico)", - "es-NI": "Spanish (Nicaragua)", - "es-PA": "Spanish (Panama)", - "es-PY": "Spanish (Paraguay)", - "es-PE": "Spanish (Peru)", - "es-PR": "Spanish (Puerto Rico)", - "es-ES": "Spanish (Spain)", - "es-UY": "Spanish (Uruguay)", - "es-VE": "Spanish (Venezuela)", - sx: "Sutu", - sw: "Swahili", - sv: "Swedish", - "sv-FI": "Swedish (Finland)", - "sv-SV": "Swedish (Sweden)", - ta: "Tamil", - tt: "Tatar", - te: "Teluga", - th: "Thai", - tig: "Tigre", - ts: "Tsonga", - tn: "Tswana", - tr: "Turkish", - tk: "Turkmen", - uk: "Ukrainian", - hsb: "Upper Sorbian", - ur: "Urdu", - ve: "Venda", - vi: "Vietnamese", - vo: "Volapuk", - wa: "Walloon", - cy: "Welsh", - xh: "Xhosa", - ji: "Yiddish", - zu: "Zulu" - }; - - if (this.internal.languageSettings === undefined) { - this.internal.languageSettings = {}; - this.internal.languageSettings.isSubscribed = false; - } - - if (langCodes[langCode] !== undefined) { - this.internal.languageSettings.languageCode = langCode; - if (this.internal.languageSettings.isSubscribed === false) { - this.internal.events.subscribe("putCatalog", function() { - this.internal.write( - "/Lang (" + this.internal.languageSettings.languageCode + ")" - ); - }); - this.internal.languageSettings.isSubscribed = true; - } - } - return this; - }; -})(jsPDF.API); - -/** @license - * MIT license. - * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems - * 2014 Diego Casorran, https://github.com/diegocr - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -/** - * jsPDF split_text_to_size plugin - * - * @name split_text_to_size - * @module - */ -(function(API) { - /** - * Returns an array of length matching length of the 'word' string, with each - * cell occupied by the width of the char in that position. - * - * @name getCharWidthsArray - * @function - * @param {string} text - * @param {Object} options - * @returns {Array} - */ - var getCharWidthsArray = (API.getCharWidthsArray = function(text, options) { - options = options || {}; - - var activeFont = options.font || this.internal.getFont(); - var fontSize = options.fontSize || this.internal.getFontSize(); - var charSpace = options.charSpace || this.internal.getCharSpace(); - - var widths = options.widths - ? options.widths - : activeFont.metadata.Unicode.widths; - var widthsFractionOf = widths.fof ? widths.fof : 1; - var kerning = options.kerning - ? options.kerning - : activeFont.metadata.Unicode.kerning; - var kerningFractionOf = kerning.fof ? kerning.fof : 1; - var doKerning = options.doKerning === false ? false : true; - var kerningValue = 0; - - var i; - var length = text.length; - var char_code; - var prior_char_code = 0; //for kerning - var default_char_width = widths[0] || widthsFractionOf; - var output = []; - - for (i = 0; i < length; i++) { - char_code = text.charCodeAt(i); - - if (typeof activeFont.metadata.widthOfString === "function") { - output.push( - (activeFont.metadata.widthOfGlyph( - activeFont.metadata.characterToGlyph(char_code) - ) + - charSpace * (1000 / fontSize) || 0) / 1000 - ); - } else { - if ( - doKerning && - typeof kerning[char_code] === "object" && - !isNaN(parseInt(kerning[char_code][prior_char_code], 10)) - ) { - kerningValue = - kerning[char_code][prior_char_code] / kerningFractionOf; - } else { - kerningValue = 0; - } - output.push( - (widths[char_code] || default_char_width) / widthsFractionOf + - kerningValue - ); - } - prior_char_code = char_code; - } - - return output; - }); - - /** - * Returns a widths of string in a given font, if the font size is set as 1 point. - * - * In other words, this is "proportional" value. For 1 unit of font size, the length - * of the string will be that much. - * - * Multiply by font size to get actual width in *points* - * Then divide by 72 to get inches or divide by (72/25.4) to get 'mm' etc. - * - * @name getStringUnitWidth - * @public - * @function - * @param {string} text - * @param {string} options - * @returns {number} result - */ - var getStringUnitWidth = (API.getStringUnitWidth = function(text, options) { - options = options || {}; - - var fontSize = options.fontSize || this.internal.getFontSize(); - var font = options.font || this.internal.getFont(); - var charSpace = options.charSpace || this.internal.getCharSpace(); - var result = 0; - - if (API.processArabic) { - text = API.processArabic(text); - } - - if (typeof font.metadata.widthOfString === "function") { - result = - font.metadata.widthOfString(text, fontSize, charSpace) / fontSize; - } else { - result = getCharWidthsArray - .apply(this, arguments) - .reduce(function(pv, cv) { - return pv + cv; - }, 0); - } - return result; - }); - - /** - returns array of lines - */ - var splitLongWord = function(word, widths_array, firstLineMaxLen, maxLen) { - var answer = []; - - // 1st, chop off the piece that can fit on the hanging line. - var i = 0, - l = word.length, - workingLen = 0; - while (i !== l && workingLen + widths_array[i] < firstLineMaxLen) { - workingLen += widths_array[i]; - i++; - } - // this is first line. - answer.push(word.slice(0, i)); - - // 2nd. Split the rest into maxLen pieces. - var startOfLine = i; - workingLen = 0; - while (i !== l) { - if (workingLen + widths_array[i] > maxLen) { - answer.push(word.slice(startOfLine, i)); - workingLen = 0; - startOfLine = i; - } - workingLen += widths_array[i]; - i++; - } - if (startOfLine !== i) { - answer.push(word.slice(startOfLine, i)); - } - - return answer; - }; - - // Note, all sizing inputs for this function must be in "font measurement units" - // By default, for PDF, it's "point". - var splitParagraphIntoLines = function(text, maxlen, options) { - // at this time works only on Western scripts, ones with space char - // separating the words. Feel free to expand. - - if (!options) { - options = {}; - } - - var line = [], - lines = [line], - line_length = options.textIndent || 0, - separator_length = 0, - current_word_length = 0, - word, - widths_array, - words = text.split(" "), - spaceCharWidth = getCharWidthsArray.apply(this, [" ", options])[0], - i, - l, - tmp, - lineIndent; - - if (options.lineIndent === -1) { - lineIndent = words[0].length + 2; - } else { - lineIndent = options.lineIndent || 0; - } - if (lineIndent) { - var pad = Array(lineIndent).join(" "), - wrds = []; - words.map(function(wrd) { - wrd = wrd.split(/\s*\n/); - if (wrd.length > 1) { - wrds = wrds.concat( - wrd.map(function(wrd, idx) { - return (idx && wrd.length ? "\n" : "") + wrd; - }) - ); - } else { - wrds.push(wrd[0]); - } - }); - words = wrds; - lineIndent = getStringUnitWidth.apply(this, [pad, options]); - } - - for (i = 0, l = words.length; i < l; i++) { - var force = 0; - - word = words[i]; - if (lineIndent && word[0] == "\n") { - word = word.substr(1); - force = 1; - } - widths_array = getCharWidthsArray.apply(this, [word, options]); - current_word_length = widths_array.reduce(function(pv, cv) { - return pv + cv; - }, 0); - - if ( - line_length + separator_length + current_word_length > maxlen || - force - ) { - if (current_word_length > maxlen) { - // this happens when you have space-less long URLs for example. - // we just chop these to size. We do NOT insert hiphens - tmp = splitLongWord.apply(this, [ - word, - widths_array, - maxlen - (line_length + separator_length), - maxlen - ]); - // first line we add to existing line object - line.push(tmp.shift()); // it's ok to have extra space indicator there - // last line we make into new line object - line = [tmp.pop()]; - // lines in the middle we apped to lines object as whole lines - while (tmp.length) { - lines.push([tmp.shift()]); // single fragment occupies whole line - } - current_word_length = widths_array - .slice(word.length - (line[0] ? line[0].length : 0)) - .reduce(function(pv, cv) { - return pv + cv; - }, 0); - } else { - // just put it on a new line - line = [word]; - } - - // now we attach new line to lines - lines.push(line); - line_length = current_word_length + lineIndent; - separator_length = spaceCharWidth; - } else { - line.push(word); - - line_length += separator_length + current_word_length; - separator_length = spaceCharWidth; - } - } - - var postProcess; - if (lineIndent) { - postProcess = function(ln, idx) { - return (idx ? pad : "") + ln.join(" "); - }; - } else { - postProcess = function(ln) { - return ln.join(" "); - }; - } - - return lines.map(postProcess); - }; - - /** - * Splits a given string into an array of strings. Uses 'size' value - * (in measurement units declared as default for the jsPDF instance) - * and the font's "widths" and "Kerning" tables, where available, to - * determine display length of a given string for a given font. - * - * We use character's 100% of unit size (height) as width when Width - * table or other default width is not available. - * - * @name splitTextToSize - * @public - * @function - * @param {string} text Unencoded, regular JavaScript (Unicode, UTF-16 / UCS-2) string. - * @param {number} size Nominal number, measured in units default to this instance of jsPDF. - * @param {Object} options Optional flags needed for chopper to do the right thing. - * @returns {Array} array Array with strings chopped to size. - */ - API.splitTextToSize = function(text, maxlen, options) { - - options = options || {}; - - var fsize = options.fontSize || this.internal.getFontSize(), - newOptions = function(options) { - var widths = { - 0: 1 - }, - kerning = {}; - - if (!options.widths || !options.kerning) { - var f = this.internal.getFont(options.fontName, options.fontStyle), - encoding = "Unicode"; - // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE - // Actual JavaScript-native String's 16bit char codes used. - // no multi-byte logic here - - if (f.metadata[encoding]) { - return { - widths: f.metadata[encoding].widths || widths, - kerning: f.metadata[encoding].kerning || kerning - }; - } else { - return { - font: f.metadata, - fontSize: this.internal.getFontSize(), - charSpace: this.internal.getCharSpace() - }; - } - } else { - return { - widths: options.widths, - kerning: options.kerning - }; - } - }.call(this, options); - - // first we split on end-of-line chars - var paragraphs; - if (Array.isArray(text)) { - paragraphs = text; - } else { - paragraphs = String(text).split(/\r?\n/); - } - - // now we convert size (max length of line) into "font size units" - // at present time, the "font size unit" is always 'point' - // 'proportional' means, "in proportion to font size" - var fontUnit_maxLen = (1.0 * this.internal.scaleFactor * maxlen) / fsize; - // at this time, fsize is always in "points" regardless of the default measurement unit of the doc. - // this may change in the future? - // until then, proportional_maxlen is likely to be in 'points' - - // If first line is to be indented (shorter or longer) than maxLen - // we indicate that by using CSS-style "text-indent" option. - // here it's in font units too (which is likely 'points') - // it can be negative (which makes the first line longer than maxLen) - newOptions.textIndent = options.textIndent - ? (options.textIndent * 1.0 * this.internal.scaleFactor) / fsize - : 0; - newOptions.lineIndent = options.lineIndent; - - var i, - l, - output = []; - for (i = 0, l = paragraphs.length; i < l; i++) { - output = output.concat( - splitParagraphIntoLines.apply(this, [ - paragraphs[i], - fontUnit_maxLen, - newOptions - ]) - ); - } - - return output; - }; -})(jsPDF.API); - -/** @license - jsPDF standard_fonts_metrics plugin - * Copyright (c) 2012 Willow Systems Corporation, https://github.com/willowsystems - * MIT license. - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -/** - * This file adds the standard font metrics to jsPDF. - * - * Font metrics data is reprocessed derivative of contents of - * "Font Metrics for PDF Core 14 Fonts" package, which exhibits the following copyright and license: - * - * Copyright (c) 1989, 1990, 1991, 1992, 1993, 1997 Adobe Systems Incorporated. All Rights Reserved. - * - * This file and the 14 PostScript(R) AFM files it accompanies may be used, - * copied, and distributed for any purpose and without charge, with or without - * modification, provided that all copyright notices are retained; that the AFM - * files are not distributed without this file; that all modifications to this - * file or any of the AFM files are prominently noted in the modified file(s); - * and that this paragraph is not modified. Adobe Systems has no responsibility - * or obligation to support the use of the AFM files. - * - * @name standard_fonts_metrics - * @module - */ - -(function(API) { - API.__fontmetrics__ = API.__fontmetrics__ || {}; - - var decoded = "0123456789abcdef", - encoded = "klmnopqrstuvwxyz", - mappingUncompress = {}, - mappingCompress = {}; - - for (var i = 0; i < encoded.length; i++) { - mappingUncompress[encoded[i]] = decoded[i]; - mappingCompress[decoded[i]] = encoded[i]; - } - - var hex = function(value) { - return "0x" + parseInt(value, 10).toString(16); - }; - - var compress = (API.__fontmetrics__.compress = function(data) { - var vals = ["{"]; - var value, keystring, valuestring, numberprefix; - - for (var key in data) { - value = data[key]; - - if (!isNaN(parseInt(key, 10))) { - key = parseInt(key, 10); - keystring = hex(key).slice(2); - keystring = - keystring.slice(0, -1) + mappingCompress[keystring.slice(-1)]; - } else { - keystring = "'" + key + "'"; - } - - if (typeof value == "number") { - if (value < 0) { - valuestring = hex(value).slice(3); - numberprefix = "-"; - } else { - valuestring = hex(value).slice(2); - numberprefix = ""; - } - valuestring = - numberprefix + - valuestring.slice(0, -1) + - mappingCompress[valuestring.slice(-1)]; - } else { - if (typeof value === "object") { - valuestring = compress(value); - } else { - throw new Error( - "Don't know what to do with value type " + typeof value + "." - ); - } - } - vals.push(keystring + valuestring); - } - vals.push("}"); - return vals.join(""); - }); - - /** - * Uncompresses data compressed into custom, base16-like format. - * - * @public - * @function - * @param - * @returns {Type} - */ - var uncompress = (API.__fontmetrics__.uncompress = function(data) { - if (typeof data !== "string") { - throw new Error("Invalid argument passed to uncompress."); - } - - var output = {}, - sign = 1, - stringparts, // undef. will be [] in string mode - activeobject = output, - parentchain = [], - parent_key_pair, - keyparts = "", - valueparts = "", - key, // undef. will be Truthy when Key is resolved. - datalen = data.length - 1, // stripping ending } - ch; - - for (var i = 1; i < datalen; i += 1) { - // - { } ' are special. - - ch = data[i]; - - if (ch == "'") { - if (stringparts) { - // end of string mode - key = stringparts.join(""); - stringparts = undefined; - } else { - // start of string mode - stringparts = []; - } - } else if (stringparts) { - stringparts.push(ch); - } else if (ch == "{") { - // start of object - parentchain.push([activeobject, key]); - activeobject = {}; - key = undefined; - } else if (ch == "}") { - // end of object - parent_key_pair = parentchain.pop(); - parent_key_pair[0][parent_key_pair[1]] = activeobject; - key = undefined; - activeobject = parent_key_pair[0]; - } else if (ch == "-") { - sign = -1; - } else { - // must be number - if (key === undefined) { - if (mappingUncompress.hasOwnProperty(ch)) { - keyparts += mappingUncompress[ch]; - key = parseInt(keyparts, 16) * sign; - sign = +1; - keyparts = ""; - } else { - keyparts += ch; - } - } else { - if (mappingUncompress.hasOwnProperty(ch)) { - valueparts += mappingUncompress[ch]; - activeobject[key] = parseInt(valueparts, 16) * sign; - sign = +1; - key = undefined; - valueparts = ""; - } else { - valueparts += ch; - } - } - } - } - return output; - }); - - // encoding = 'Unicode' - // NOT UTF8, NOT UTF16BE/LE, NOT UCS2BE/LE. NO clever BOM behavior - // Actual 16bit char codes used. - // no multi-byte logic here - - // Unicode characters to WinAnsiEncoding: - // {402: 131, 8211: 150, 8212: 151, 8216: 145, 8217: 146, 8218: 130, 8220: 147, 8221: 148, 8222: 132, 8224: 134, 8225: 135, 8226: 149, 8230: 133, 8364: 128, 8240:137, 8249: 139, 8250: 155, 710: 136, 8482: 153, 338: 140, 339: 156, 732: 152, 352: 138, 353: 154, 376: 159, 381: 142, 382: 158} - // as you can see, all Unicode chars are outside of 0-255 range. No char code conflicts. - // this means that you can give Win cp1252 encoded strings to jsPDF for rendering directly - // as well as give strings with some (supported by these fonts) Unicode characters and - // these will be mapped to win cp1252 - // for example, you can send char code (cp1252) 0x80 or (unicode) 0x20AC, getting "Euro" glyph displayed in both cases. - - var encodingBlock = { - codePages: ["WinAnsiEncoding"], - WinAnsiEncoding: uncompress( - "{19m8n201n9q201o9r201s9l201t9m201u8m201w9n201x9o201y8o202k8q202l8r202m9p202q8p20aw8k203k8t203t8v203u9v2cq8s212m9t15m8w15n9w2dw9s16k8u16l9u17s9z17x8y17y9y}" - ) - }; - var encodings = { - Unicode: { - Courier: encodingBlock, - "Courier-Bold": encodingBlock, - "Courier-BoldOblique": encodingBlock, - "Courier-Oblique": encodingBlock, - Helvetica: encodingBlock, - "Helvetica-Bold": encodingBlock, - "Helvetica-BoldOblique": encodingBlock, - "Helvetica-Oblique": encodingBlock, - "Times-Roman": encodingBlock, - "Times-Bold": encodingBlock, - "Times-BoldItalic": encodingBlock, - "Times-Italic": encodingBlock - // , 'Symbol' - // , 'ZapfDingbats' - } - }; - - var fontMetrics = { - Unicode: { - // all sizing numbers are n/fontMetricsFractionOf = one font size unit - // this means that if fontMetricsFractionOf = 1000, and letter A's width is 476, it's - // width is 476/1000 or 47.6% of its height (regardless of font size) - // At this time this value applies to "widths" and "kerning" numbers. - - // char code 0 represents "default" (average) width - use it for chars missing in this table. - // key 'fof' represents the "fontMetricsFractionOf" value - - "Courier-Oblique": uncompress( - "{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}" - ), - "Times-BoldItalic": uncompress( - "{'widths'{k3o2q4ycx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2r202m2n2n3m2o3m2p5n202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5n4l4m4m4m4n4m4o4s4p4m4q4m4r4s4s4y4t2r4u3m4v4m4w3x4x5t4y4s4z4s5k3x5l4s5m4m5n3r5o3x5p4s5q4m5r5t5s4m5t3x5u3x5v2l5w1w5x2l5y3t5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q2l6r3m6s3r6t1w6u1w6v3m6w1w6x4y6y3r6z3m7k3m7l3m7m2r7n2r7o1w7p3r7q2w7r4m7s3m7t2w7u2r7v2n7w1q7x2n7y3t202l3mcl4mal2ram3man3mao3map3mar3mas2lat4uau1uav3maw3way4uaz2lbk2sbl3t'fof'6obo2lbp3tbq3mbr1tbs2lbu1ybv3mbz3mck4m202k3mcm4mcn4mco4mcp4mcq5ycr4mcs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz2w203k6o212m6o2dw2l2cq2l3t3m3u2l17s3x19m3m}'kerning'{cl{4qu5kt5qt5rs17ss5ts}201s{201ss}201t{cks4lscmscnscoscpscls2wu2yu201ts}201x{2wu2yu}2k{201ts}2w{4qx5kx5ou5qx5rs17su5tu}2x{17su5tu5ou}2y{4qx5kx5ou5qx5rs17ss5ts}'fof'-6ofn{17sw5tw5ou5qw5rs}7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qs}3v{17su5tu5os5qs}7p{17su5tu}ck{4qu5kt5qt5rs17ss5ts}4l{4qu5kt5qt5rs17ss5ts}cm{4qu5kt5qt5rs17ss5ts}cn{4qu5kt5qt5rs17ss5ts}co{4qu5kt5qt5rs17ss5ts}cp{4qu5kt5qt5rs17ss5ts}6l{4qu5ou5qw5rt17su5tu}5q{ckuclucmucnucoucpu4lu}5r{ckuclucmucnucoucpu4lu}7q{cksclscmscnscoscps4ls}6p{4qu5ou5qw5rt17sw5tw}ek{4qu5ou5qw5rt17su5tu}el{4qu5ou5qw5rt17su5tu}em{4qu5ou5qw5rt17su5tu}en{4qu5ou5qw5rt17su5tu}eo{4qu5ou5qw5rt17su5tu}ep{4qu5ou5qw5rt17su5tu}es{17ss5ts5qs4qu}et{4qu5ou5qw5rt17sw5tw}eu{4qu5ou5qw5rt17ss5ts}ev{17ss5ts5qs4qu}6z{17sw5tw5ou5qw5rs}fm{17sw5tw5ou5qw5rs}7n{201ts}fo{17sw5tw5ou5qw5rs}fp{17sw5tw5ou5qw5rs}fq{17sw5tw5ou5qw5rs}7r{cksclscmscnscoscps4ls}fs{17sw5tw5ou5qw5rs}ft{17su5tu}fu{17su5tu}fv{17su5tu}fw{17su5tu}fz{cksclscmscnscoscps4ls}}}" - ), - "Helvetica-Bold": uncompress( - "{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}" - ), - Courier: uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"), - "Courier-BoldOblique": uncompress( - "{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}" - ), - "Times-Bold": uncompress( - "{'widths'{k3q2q5ncx2r201n3m201o6o201s2l201t2l201u2l201w3m201x3m201y3m2k1t2l2l202m2n2n3m2o3m2p6o202q6o2r1w2s2l2t2l2u3m2v3t2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w3t3x3t3y3t3z3m4k5x4l4s4m4m4n4s4o4s4p4m4q3x4r4y4s4y4t2r4u3m4v4y4w4m4x5y4y4s4z4y5k3x5l4y5m4s5n3r5o4m5p4s5q4s5r6o5s4s5t4s5u4m5v2l5w1w5x2l5y3u5z3m6k2l6l3m6m3r6n2w6o3r6p2w6q2l6r3m6s3r6t1w6u2l6v3r6w1w6x5n6y3r6z3m7k3r7l3r7m2w7n2r7o2l7p3r7q3m7r4s7s3m7t3m7u2w7v2r7w1q7x2r7y3o202l3mcl4sal2lam3man3mao3map3mar3mas2lat4uau1yav3maw3tay4uaz2lbk2sbl3t'fof'6obo2lbp3rbr1tbs2lbu2lbv3mbz3mck4s202k3mcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw2r2m3rcy2rcz2rdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3rek3mel3mem3men3meo3mep3meq4ser2wes2wet2weu2wev2wew1wex1wey1wez1wfl3rfm3mfn3mfo3mfp3mfq3mfr3tfs3mft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3m3u2l17s4s19m3m}'kerning'{cl{4qt5ks5ot5qy5rw17sv5tv}201t{cks4lscmscnscoscpscls4wv}2k{201ts}2w{4qu5ku7mu5os5qx5ru17su5tu}2x{17su5tu5ou5qs}2y{4qv5kv7mu5ot5qz5ru17su5tu}'fof'-6o7t{cksclscmscnscoscps4ls}3u{17su5tu5os5qu}3v{17su5tu5os5qu}fu{17su5tu5ou5qu}7p{17su5tu5ou5qu}ck{4qt5ks5ot5qy5rw17sv5tv}4l{4qt5ks5ot5qy5rw17sv5tv}cm{4qt5ks5ot5qy5rw17sv5tv}cn{4qt5ks5ot5qy5rw17sv5tv}co{4qt5ks5ot5qy5rw17sv5tv}cp{4qt5ks5ot5qy5rw17sv5tv}6l{17st5tt5ou5qu}17s{ckuclucmucnucoucpu4lu4wu}5o{ckuclucmucnucoucpu4lu4wu}5q{ckzclzcmzcnzcozcpz4lz4wu}5r{ckxclxcmxcnxcoxcpx4lx4wu}5t{ckuclucmucnucoucpu4lu4wu}7q{ckuclucmucnucoucpu4lu}6p{17sw5tw5ou5qu}ek{17st5tt5qu}el{17st5tt5ou5qu}em{17st5tt5qu}en{17st5tt5qu}eo{17st5tt5qu}ep{17st5tt5ou5qu}es{17ss5ts5qu}et{17sw5tw5ou5qu}eu{17sw5tw5ou5qu}ev{17ss5ts5qu}6z{17sw5tw5ou5qu5rs}fm{17sw5tw5ou5qu5rs}fn{17sw5tw5ou5qu5rs}fo{17sw5tw5ou5qu5rs}fp{17sw5tw5ou5qu5rs}fq{17sw5tw5ou5qu5rs}7r{cktcltcmtcntcotcpt4lt5os}fs{17sw5tw5ou5qu5rs}ft{17su5tu5ou5qu}7m{5os}fv{17su5tu5ou5qu}fw{17su5tu5ou5qu}fz{cksclscmscnscoscps4ls}}}" - ), - Symbol: uncompress( - "{'widths'{k3uaw4r19m3m2k1t2l2l202m2y2n3m2p5n202q6o3k3m2s2l2t2l2v3r2w1t3m3m2y1t2z1wbk2sbl3r'fof'6o3n3m3o3m3p3m3q3m3r3m3s3m3t3m3u1w3v1w3w3r3x3r3y3r3z2wbp3t3l3m5v2l5x2l5z3m2q4yfr3r7v3k7w1o7x3k}'kerning'{'fof'-6o}}" - ), - Helvetica: uncompress( - "{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}" - ), - "Helvetica-BoldOblique": uncompress( - "{'widths'{k3s2q4scx1w201n3r201o6o201s1w201t1w201u1w201w3m201x3m201y3m2k1w2l2l202m2n2n3r2o3r2p5t202q6o2r1s2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v2l3w3u3x3u3y3u3z3x4k6l4l4s4m4s4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3r4v4s4w3x4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v2l5w1w5x2l5y3u5z3r6k2l6l3r6m3x6n3r6o3x6p3r6q2l6r3x6s3x6t1w6u1w6v3r6w1w6x5t6y3x6z3x7k3x7l3x7m2r7n3r7o2l7p3x7q3r7r4y7s3r7t3r7u3m7v2r7w1w7x2r7y3u202l3rcl4sal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3xbq3rbr1wbs2lbu2obv3rbz3xck4s202k3rcm4scn4sco4scp4scq6ocr4scs4mct4mcu4mcv4mcw1w2m2zcy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3res3ret3reu3rev3rew1wex1wey1wez1wfl3xfm3xfn3xfo3xfp3xfq3xfr3ufs3xft3xfu3xfv3xfw3xfz3r203k6o212m6o2dw2l2cq2l3t3r3u2l17s4m19m3r}'kerning'{cl{4qs5ku5ot5qs17sv5tv}201t{2ww4wy2yw}201w{2ks}201x{2ww4wy2yw}2k{201ts201xs}2w{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}2x{5ow5qs}2y{7qs4qu5kw5os5qw5rs17su5tu7tsfzs}'fof'-6o7p{17su5tu5ot}ck{4qs5ku5ot5qs17sv5tv}4l{4qs5ku5ot5qs17sv5tv}cm{4qs5ku5ot5qs17sv5tv}cn{4qs5ku5ot5qs17sv5tv}co{4qs5ku5ot5qs17sv5tv}cp{4qs5ku5ot5qs17sv5tv}6l{17st5tt5os}17s{2kwclvcmvcnvcovcpv4lv4wwckv}5o{2kucltcmtcntcotcpt4lt4wtckt}5q{2ksclscmscnscoscps4ls4wvcks}5r{2ks4ws}5t{2kwclvcmvcnvcovcpv4lv4wwckv}eo{17st5tt5os}fu{17su5tu5ot}6p{17ss5ts}ek{17st5tt5os}el{17st5tt5os}em{17st5tt5os}en{17st5tt5os}6o{201ts}ep{17st5tt5os}es{17ss5ts}et{17ss5ts}eu{17ss5ts}ev{17ss5ts}6z{17su5tu5os5qt}fm{17su5tu5os5qt}fn{17su5tu5os5qt}fo{17su5tu5os5qt}fp{17su5tu5os5qt}fq{17su5tu5os5qt}fs{17su5tu5os5qt}ft{17su5tu5ot}7m{5os}fv{17su5tu5ot}fw{17su5tu5ot}}}" - ), - ZapfDingbats: uncompress("{'widths'{k4u2k1w'fof'6o}'kerning'{'fof'-6o}}"), - "Courier-Bold": uncompress("{'widths'{k3w'fof'6o}'kerning'{'fof'-6o}}"), - "Times-Italic": uncompress( - "{'widths'{k3n2q4ycx2l201n3m201o5t201s2l201t2l201u2l201w3r201x3r201y3r2k1t2l2l202m2n2n3m2o3m2p5n202q5t2r1p2s2l2t2l2u3m2v4n2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v2l3w4n3x4n3y4n3z3m4k5w4l3x4m3x4n4m4o4s4p3x4q3x4r4s4s4s4t2l4u2w4v4m4w3r4x5n4y4m4z4s5k3x5l4s5m3x5n3m5o3r5p4s5q3x5r5n5s3x5t3r5u3r5v2r5w1w5x2r5y2u5z3m6k2l6l3m6m3m6n2w6o3m6p2w6q1w6r3m6s3m6t1w6u1w6v2w6w1w6x4s6y3m6z3m7k3m7l3m7m2r7n2r7o1w7p3m7q2w7r4m7s2w7t2w7u2r7v2s7w1v7x2s7y3q202l3mcl3xal2ram3man3mao3map3mar3mas2lat4wau1vav3maw4nay4waz2lbk2sbl4n'fof'6obo2lbp3mbq3obr1tbs2lbu1zbv3mbz3mck3x202k3mcm3xcn3xco3xcp3xcq5tcr4mcs3xct3xcu3xcv3xcw2l2m2ucy2lcz2ldl4mdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek3mel3mem3men3meo3mep3meq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr4nfs3mft3mfu3mfv3mfw3mfz2w203k6o212m6m2dw2l2cq2l3t3m3u2l17s3r19m3m}'kerning'{cl{5kt4qw}201s{201sw}201t{201tw2wy2yy6q-t}201x{2wy2yy}2k{201tw}2w{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}2x{17ss5ts5os}2y{7qs4qy7rs5ky7mw5os5qx5ru17su5tu}'fof'-6o6t{17ss5ts5qs}7t{5os}3v{5qs}7p{17su5tu5qs}ck{5kt4qw}4l{5kt4qw}cm{5kt4qw}cn{5kt4qw}co{5kt4qw}cp{5kt4qw}6l{4qs5ks5ou5qw5ru17su5tu}17s{2ks}5q{ckvclvcmvcnvcovcpv4lv}5r{ckuclucmucnucoucpu4lu}5t{2ks}6p{4qs5ks5ou5qw5ru17su5tu}ek{4qs5ks5ou5qw5ru17su5tu}el{4qs5ks5ou5qw5ru17su5tu}em{4qs5ks5ou5qw5ru17su5tu}en{4qs5ks5ou5qw5ru17su5tu}eo{4qs5ks5ou5qw5ru17su5tu}ep{4qs5ks5ou5qw5ru17su5tu}es{5ks5qs4qs}et{4qs5ks5ou5qw5ru17su5tu}eu{4qs5ks5qw5ru17su5tu}ev{5ks5qs4qs}ex{17ss5ts5qs}6z{4qv5ks5ou5qw5ru17su5tu}fm{4qv5ks5ou5qw5ru17su5tu}fn{4qv5ks5ou5qw5ru17su5tu}fo{4qv5ks5ou5qw5ru17su5tu}fp{4qv5ks5ou5qw5ru17su5tu}fq{4qv5ks5ou5qw5ru17su5tu}7r{5os}fs{4qv5ks5ou5qw5ru17su5tu}ft{17su5tu5qs}fu{17su5tu5qs}fv{17su5tu5qs}fw{17su5tu5qs}}}" - ), - "Times-Roman": uncompress( - "{'widths'{k3n2q4ycx2l201n3m201o6o201s2l201t2l201u2l201w2w201x2w201y2w2k1t2l2l202m2n2n3m2o3m2p5n202q6o2r1m2s2l2t2l2u3m2v3s2w1t2x2l2y1t2z1w3k3m3l3m3m3m3n3m3o3m3p3m3q3m3r3m3s3m203t2l203u2l3v1w3w3s3x3s3y3s3z2w4k5w4l4s4m4m4n4m4o4s4p3x4q3r4r4s4s4s4t2l4u2r4v4s4w3x4x5t4y4s4z4s5k3r5l4s5m4m5n3r5o3x5p4s5q4s5r5y5s4s5t4s5u3x5v2l5w1w5x2l5y2z5z3m6k2l6l2w6m3m6n2w6o3m6p2w6q2l6r3m6s3m6t1w6u1w6v3m6w1w6x4y6y3m6z3m7k3m7l3m7m2l7n2r7o1w7p3m7q3m7r4s7s3m7t3m7u2w7v3k7w1o7x3k7y3q202l3mcl4sal2lam3man3mao3map3mar3mas2lat4wau1vav3maw3say4waz2lbk2sbl3s'fof'6obo2lbp3mbq2xbr1tbs2lbu1zbv3mbz2wck4s202k3mcm4scn4sco4scp4scq5tcr4mcs3xct3xcu3xcv3xcw2l2m2tcy2lcz2ldl4sdm4sdn4sdo4sdp4sdq4sds4sdt4sdu4sdv4sdw4sdz3mek2wel2wem2wen2weo2wep2weq4mer2wes2wet2weu2wev2wew1wex1wey1wez1wfl3mfm3mfn3mfo3mfp3mfq3mfr3sfs3mft3mfu3mfv3mfw3mfz3m203k6o212m6m2dw2l2cq2l3t3m3u1w17s4s19m3m}'kerning'{cl{4qs5ku17sw5ou5qy5rw201ss5tw201ws}201s{201ss}201t{ckw4lwcmwcnwcowcpwclw4wu201ts}2k{201ts}2w{4qs5kw5os5qx5ru17sx5tx}2x{17sw5tw5ou5qu}2y{4qs5kw5os5qx5ru17sx5tx}'fof'-6o7t{ckuclucmucnucoucpu4lu5os5rs}3u{17su5tu5qs}3v{17su5tu5qs}7p{17sw5tw5qs}ck{4qs5ku17sw5ou5qy5rw201ss5tw201ws}4l{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cm{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cn{4qs5ku17sw5ou5qy5rw201ss5tw201ws}co{4qs5ku17sw5ou5qy5rw201ss5tw201ws}cp{4qs5ku17sw5ou5qy5rw201ss5tw201ws}6l{17su5tu5os5qw5rs}17s{2ktclvcmvcnvcovcpv4lv4wuckv}5o{ckwclwcmwcnwcowcpw4lw4wu}5q{ckyclycmycnycoycpy4ly4wu5ms}5r{cktcltcmtcntcotcpt4lt4ws}5t{2ktclvcmvcnvcovcpv4lv4wuckv}7q{cksclscmscnscoscps4ls}6p{17su5tu5qw5rs}ek{5qs5rs}el{17su5tu5os5qw5rs}em{17su5tu5os5qs5rs}en{17su5qs5rs}eo{5qs5rs}ep{17su5tu5os5qw5rs}es{5qs}et{17su5tu5qw5rs}eu{17su5tu5qs5rs}ev{5qs}6z{17sv5tv5os5qx5rs}fm{5os5qt5rs}fn{17sv5tv5os5qx5rs}fo{17sv5tv5os5qx5rs}fp{5os5qt5rs}fq{5os5qt5rs}7r{ckuclucmucnucoucpu4lu5os}fs{17sv5tv5os5qx5rs}ft{17ss5ts5qs}fu{17sw5tw5qs}fv{17sw5tw5qs}fw{17ss5ts5qs}fz{ckuclucmucnucoucpu4lu5os5rs}}}" - ), - "Helvetica-Oblique": uncompress( - "{'widths'{k3p2q4mcx1w201n3r201o6o201s1q201t1q201u1q201w2l201x2l201y2l2k1w2l1w202m2n2n3r2o3r2p5t202q6o2r1n2s2l2t2l2u2r2v3u2w1w2x2l2y1w2z1w3k3r3l3r3m3r3n3r3o3r3p3r3q3r3r3r3s3r203t2l203u2l3v1w3w3u3x3u3y3u3z3r4k6p4l4m4m4m4n4s4o4s4p4m4q3x4r4y4s4s4t1w4u3m4v4m4w3r4x5n4y4s4z4y5k4m5l4y5m4s5n4m5o3x5p4s5q4m5r5y5s4m5t4m5u3x5v1w5w1w5x1w5y2z5z3r6k2l6l3r6m3r6n3m6o3r6p3r6q1w6r3r6s3r6t1q6u1q6v3m6w1q6x5n6y3r6z3r7k3r7l3r7m2l7n3m7o1w7p3r7q3m7r4s7s3m7t3m7u3m7v2l7w1u7x2l7y3u202l3rcl4mal2lam3ran3rao3rap3rar3ras2lat4tau2pav3raw3uay4taz2lbk2sbl3u'fof'6obo2lbp3rbr1wbs2lbu2obv3rbz3xck4m202k3rcm4mcn4mco4mcp4mcq6ocr4scs4mct4mcu4mcv4mcw1w2m2ncy1wcz1wdl4sdm4ydn4ydo4ydp4ydq4yds4ydt4sdu4sdv4sdw4sdz3xek3rel3rem3ren3reo3rep3req5ter3mes3ret3reu3rev3rew1wex1wey1wez1wfl3rfm3rfn3rfo3rfp3rfq3rfr3ufs3xft3rfu3rfv3rfw3rfz3m203k6o212m6o2dw2l2cq2l3t3r3u1w17s4m19m3r}'kerning'{5q{4wv}cl{4qs5kw5ow5qs17sv5tv}201t{2wu4w1k2yu}201x{2wu4wy2yu}17s{2ktclucmucnu4otcpu4lu4wycoucku}2w{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}2x{17sy5ty5oy5qs}2y{7qs4qz5k1m17sy5ow5qx5rsfsu5ty7tufzu}'fof'-6o7p{17sv5tv5ow}ck{4qs5kw5ow5qs17sv5tv}4l{4qs5kw5ow5qs17sv5tv}cm{4qs5kw5ow5qs17sv5tv}cn{4qs5kw5ow5qs17sv5tv}co{4qs5kw5ow5qs17sv5tv}cp{4qs5kw5ow5qs17sv5tv}6l{17sy5ty5ow}do{17st5tt}4z{17st5tt}7s{fst}dm{17st5tt}dn{17st5tt}5o{ckwclwcmwcnwcowcpw4lw4wv}dp{17st5tt}dq{17st5tt}7t{5ow}ds{17st5tt}5t{2ktclucmucnu4otcpu4lu4wycoucku}fu{17sv5tv5ow}6p{17sy5ty5ow5qs}ek{17sy5ty5ow}el{17sy5ty5ow}em{17sy5ty5ow}en{5ty}eo{17sy5ty5ow}ep{17sy5ty5ow}es{17sy5ty5qs}et{17sy5ty5ow5qs}eu{17sy5ty5ow5qs}ev{17sy5ty5ow5qs}6z{17sy5ty5ow5qs}fm{17sy5ty5ow5qs}fn{17sy5ty5ow5qs}fo{17sy5ty5ow5qs}fp{17sy5ty5qs}fq{17sy5ty5ow5qs}7r{5ow}fs{17sy5ty5ow5qs}ft{17sv5tv5ow}7m{5ow}fv{17sv5tv5ow}fw{17sv5tv5ow}}}" - ) - } - }; - - /* - This event handler is fired when a new jsPDF object is initialized - This event handler appends metrics data to standard fonts within - that jsPDF instance. The metrics are mapped over Unicode character - codes, NOT CIDs or other codes matching the StandardEncoding table of the - standard PDF fonts. - Future: - Also included is the encoding maping table, converting Unicode (UCS-2, UTF-16) - char codes to StandardEncoding character codes. The encoding table is to be used - somewhere around "pdfEscape" call. - */ - API.events.push([ - "addFont", - function(data) { - var font = data.font; - - var metrics = fontMetrics["Unicode"][font.postScriptName]; - if (metrics) { - font.metadata["Unicode"] = {}; - font.metadata["Unicode"].widths = metrics.widths; - font.metadata["Unicode"].kerning = metrics.kerning; - } - - var encodingBlock = encodings["Unicode"][font.postScriptName]; - if (encodingBlock) { - font.metadata["Unicode"].encoding = encodingBlock; - font.encoding = encodingBlock.codePages[0]; - } - } - ]); // end of adding event handler -})(jsPDF.API); - -/** - * @license - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * @name ttfsupport - * @module - */ -(function(jsPDF) { - - var binaryStringToUint8Array = function(binary_string) { - var len = binary_string.length; - var bytes = new Uint8Array(len); - for (var i = 0; i < len; i++) { - bytes[i] = binary_string.charCodeAt(i); - } - return bytes; - }; - - var addFont = function(font, file) { - // eslint-disable-next-line no-control-regex - if (/^\x00\x01\x00\x00/.test(file)) { - file = binaryStringToUint8Array(file); - } else { - file = binaryStringToUint8Array(atob(file)); - } - font.metadata = jsPDF.API.TTFFont.open(file); - font.metadata.Unicode = font.metadata.Unicode || { - encoding: {}, - kerning: {}, - widths: [] - }; - font.metadata.glyIdsUsed = [0]; - }; - - jsPDF.API.events.push([ - "addFont", - function(data) { - var file = undefined; - var font = data.font; - var instance = data.instance; - if (font.isStandardFont) { - return; - } - if (typeof instance !== "undefined") { - if (instance.existsFileInVFS(font.postScriptName) === false) { - file = instance.loadFile(font.postScriptName); - } else { - file = instance.getFileFromVFS(font.postScriptName); - } - if (typeof file !== "string") { - throw new Error( - "Font is not stored as string-data in vFS, import fonts or remove declaration doc.addFont('" + - font.postScriptName + - "')." - ); - } - addFont(font, file); - } else { - throw new Error( - "Font does not exist in vFS, import fonts or remove declaration doc.addFont('" + - font.postScriptName + - "')." - ); - } - } - ]); // end of adding event handler -})(jsPDF); - -/** - * @license - * ==================================================================== - * Copyright (c) 2013 Eduardo Menezes de Morais, eduardo.morais@usp.br - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -/** - * jsPDF total_pages plugin - * @name total_pages - * @module - */ -(function(jsPDFAPI) { - /** - * @name putTotalPages - * @function - * @param {string} pageExpression Regular Expression - * @returns {jsPDF} jsPDF-instance - */ - - jsPDFAPI.putTotalPages = function(pageExpression) { - - var replaceExpression; - var totalNumberOfPages = 0; - if (parseInt(this.internal.getFont().id.substr(1), 10) < 15) { - replaceExpression = new RegExp(pageExpression, "g"); - totalNumberOfPages = this.internal.getNumberOfPages(); - } else { - replaceExpression = new RegExp( - this.pdfEscape16(pageExpression, this.internal.getFont()), - "g" - ); - totalNumberOfPages = this.pdfEscape16( - this.internal.getNumberOfPages() + "", - this.internal.getFont() - ); - } - - for (var n = 1; n <= this.internal.getNumberOfPages(); n++) { - for (var i = 0; i < this.internal.pages[n].length; i++) { - this.internal.pages[n][i] = this.internal.pages[n][i].replace( - replaceExpression, - totalNumberOfPages - ); - } - } - - return this; - }; -})(jsPDF.API); - -/** - * @license - * jsPDF viewerPreferences Plugin - * @author Aras Abbasi (github.com/arasabbasi) - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * Adds the ability to set ViewerPreferences and by thus - * controlling the way the document is to be presented on the - * screen or in print. - * @name viewerpreferences - * @module - */ -(function(jsPDFAPI) { - /** - * Set the ViewerPreferences of the generated PDF - * - * @name viewerPreferences - * @function - * @public - * @param {Object} options Array with the ViewerPreferences
- * Example: doc.viewerPreferences({"FitWindow":true});
- *
- * You can set following preferences:
- *
- * HideToolbar (boolean)
- * Default value: false
- *
- * HideMenubar (boolean)
- * Default value: false.
- *
- * HideWindowUI (boolean)
- * Default value: false.
- *
- * FitWindow (boolean)
- * Default value: false.
- *
- * CenterWindow (boolean)
- * Default value: false
- *
- * DisplayDocTitle (boolean)
- * Default value: false.
- *
- * NonFullScreenPageMode (string)
- * Possible values: UseNone, UseOutlines, UseThumbs, UseOC
- * Default value: UseNone
- *
- * Direction (string)
- * Possible values: L2R, R2L
- * Default value: L2R.
- *
- * ViewArea (string)
- * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
- * Default value: CropBox.
- *
- * ViewClip (string)
- * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
- * Default value: CropBox
- *
- * PrintArea (string)
- * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
- * Default value: CropBox
- *
- * PrintClip (string)
- * Possible values: MediaBox, CropBox, TrimBox, BleedBox, ArtBox
- * Default value: CropBox.
- *
- * PrintScaling (string)
- * Possible values: AppDefault, None
- * Default value: AppDefault.
- *
- * Duplex (string)
- * Possible values: Simplex, DuplexFlipLongEdge, DuplexFlipShortEdge - * Default value: none
- *
- * PickTrayByPDFSize (boolean)
- * Default value: false
- *
- * PrintPageRange (Array)
- * Example: [[1,5], [7,9]]
- * Default value: as defined by PDF viewer application
- *
- * NumCopies (Number)
- * Possible values: 1, 2, 3, 4, 5
- * Default value: 1
- *
- * For more information see the PDF Reference, sixth edition on Page 577 - * @param {boolean} doReset True to reset the settings - * @function - * @returns jsPDF jsPDF-instance - * @example - * var doc = new jsPDF() - * doc.text('This is a test', 10, 10) - * doc.viewerPreferences({'FitWindow': true}, true) - * doc.save("viewerPreferences.pdf") - * - * // Example printing 10 copies, using cropbox, and hiding UI. - * doc.viewerPreferences({ - * 'HideWindowUI': true, - * 'PrintArea': 'CropBox', - * 'NumCopies': 10 - * }) - */ - jsPDFAPI.viewerPreferences = function(options, doReset) { - options = options || {}; - doReset = doReset || false; - - var configuration; - var configurationTemplate = { - HideToolbar: { - defaultValue: false, - value: false, - type: "boolean", - explicitSet: false, - valueSet: [true, false], - pdfVersion: 1.3 - }, - HideMenubar: { - defaultValue: false, - value: false, - type: "boolean", - explicitSet: false, - valueSet: [true, false], - pdfVersion: 1.3 - }, - HideWindowUI: { - defaultValue: false, - value: false, - type: "boolean", - explicitSet: false, - valueSet: [true, false], - pdfVersion: 1.3 - }, - FitWindow: { - defaultValue: false, - value: false, - type: "boolean", - explicitSet: false, - valueSet: [true, false], - pdfVersion: 1.3 - }, - CenterWindow: { - defaultValue: false, - value: false, - type: "boolean", - explicitSet: false, - valueSet: [true, false], - pdfVersion: 1.3 - }, - DisplayDocTitle: { - defaultValue: false, - value: false, - type: "boolean", - explicitSet: false, - valueSet: [true, false], - pdfVersion: 1.4 - }, - NonFullScreenPageMode: { - defaultValue: "UseNone", - value: "UseNone", - type: "name", - explicitSet: false, - valueSet: ["UseNone", "UseOutlines", "UseThumbs", "UseOC"], - pdfVersion: 1.3 - }, - Direction: { - defaultValue: "L2R", - value: "L2R", - type: "name", - explicitSet: false, - valueSet: ["L2R", "R2L"], - pdfVersion: 1.3 - }, - ViewArea: { - defaultValue: "CropBox", - value: "CropBox", - type: "name", - explicitSet: false, - valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], - pdfVersion: 1.4 - }, - ViewClip: { - defaultValue: "CropBox", - value: "CropBox", - type: "name", - explicitSet: false, - valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], - pdfVersion: 1.4 - }, - PrintArea: { - defaultValue: "CropBox", - value: "CropBox", - type: "name", - explicitSet: false, - valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], - pdfVersion: 1.4 - }, - PrintClip: { - defaultValue: "CropBox", - value: "CropBox", - type: "name", - explicitSet: false, - valueSet: ["MediaBox", "CropBox", "TrimBox", "BleedBox", "ArtBox"], - pdfVersion: 1.4 - }, - PrintScaling: { - defaultValue: "AppDefault", - value: "AppDefault", - type: "name", - explicitSet: false, - valueSet: ["AppDefault", "None"], - pdfVersion: 1.6 - }, - Duplex: { - defaultValue: "", - value: "none", - type: "name", - explicitSet: false, - valueSet: [ - "Simplex", - "DuplexFlipShortEdge", - "DuplexFlipLongEdge", - "none" - ], - pdfVersion: 1.7 - }, - PickTrayByPDFSize: { - defaultValue: false, - value: false, - type: "boolean", - explicitSet: false, - valueSet: [true, false], - pdfVersion: 1.7 - }, - PrintPageRange: { - defaultValue: "", - value: "", - type: "array", - explicitSet: false, - valueSet: null, - pdfVersion: 1.7 - }, - NumCopies: { - defaultValue: 1, - value: 1, - type: "integer", - explicitSet: false, - valueSet: null, - pdfVersion: 1.7 - } - }; - - var configurationKeys = Object.keys(configurationTemplate); - - var rangeArray = []; - var i = 0; - var j = 0; - var k = 0; - var isValid; - - var method; - var value; - - function arrayContainsElement(array, element) { - var iterator; - var result = false; - - for (iterator = 0; iterator < array.length; iterator += 1) { - if (array[iterator] === element) { - result = true; - } - } - return result; - } - - if (this.internal.viewerpreferences === undefined) { - this.internal.viewerpreferences = {}; - this.internal.viewerpreferences.configuration = JSON.parse( - JSON.stringify(configurationTemplate) - ); - this.internal.viewerpreferences.isSubscribed = false; - } - configuration = this.internal.viewerpreferences.configuration; - - if (options === "reset" || doReset === true) { - var len = configurationKeys.length; - - for (k = 0; k < len; k += 1) { - configuration[configurationKeys[k]].value = - configuration[configurationKeys[k]].defaultValue; - configuration[configurationKeys[k]].explicitSet = false; - } - } - - if (typeof options === "object") { - for (method in options) { - value = options[method]; - if ( - arrayContainsElement(configurationKeys, method) && - value !== undefined - ) { - if ( - configuration[method].type === "boolean" && - typeof value === "boolean" - ) { - configuration[method].value = value; - } else if ( - configuration[method].type === "name" && - arrayContainsElement(configuration[method].valueSet, value) - ) { - configuration[method].value = value; - } else if ( - configuration[method].type === "integer" && - Number.isInteger(value) - ) { - configuration[method].value = value; - } else if (configuration[method].type === "array") { - for (i = 0; i < value.length; i += 1) { - isValid = true; - if (value[i].length === 1 && typeof value[i][0] === "number") { - rangeArray.push(String(value[i] - 1)); - } else if (value[i].length > 1) { - for (j = 0; j < value[i].length; j += 1) { - if (typeof value[i][j] !== "number") { - isValid = false; - } - } - if (isValid === true) { - rangeArray.push([value[i][0] - 1, value[i][1] - 1].join(" ")); - } - } - } - configuration[method].value = "[" + rangeArray.join(" ") + "]"; - } else { - configuration[method].value = configuration[method].defaultValue; - } - - configuration[method].explicitSet = true; - } - } - } - - if (this.internal.viewerpreferences.isSubscribed === false) { - this.internal.events.subscribe("putCatalog", function() { - var pdfDict = []; - var vPref; - for (vPref in configuration) { - if (configuration[vPref].explicitSet === true) { - if (configuration[vPref].type === "name") { - pdfDict.push("/" + vPref + " /" + configuration[vPref].value); - } else { - pdfDict.push("/" + vPref + " " + configuration[vPref].value); - } - } - } - if (pdfDict.length !== 0) { - this.internal.write( - "/ViewerPreferences\n<<\n" + pdfDict.join("\n") + "\n>>" - ); - } - }); - this.internal.viewerpreferences.isSubscribed = true; - } - - this.internal.viewerpreferences.configuration = configuration; - return this; - }; -})(jsPDF.API); - -/** ==================================================================== - * @license - * jsPDF XMP metadata plugin - * Copyright (c) 2016 Jussi Utunen, u-jussi@suomi24.fi - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * ==================================================================== - */ - -function postPutResources() { - const metadata = this.internal.__metadata__.metadata; - const utf8Metadata = unescape(encodeURIComponent(metadata)); - - const rawXml = this.internal.__metadata__.rawXml; - let content; - if (rawXml) { - content = utf8Metadata; - } else { - const xmpmetaBeginning = ''; - const rdfBeginning = - ''; - const rdfEnding = ""; - const xmpmetaEnding = ""; - - content = - xmpmetaBeginning + - rdfBeginning + - escapeXml(utf8Metadata) + - rdfEnding + - xmpmetaEnding; - } - - this.internal.__metadata__.metadataObjectNumber = this.internal.newObject(); - this.internal.write( - "<< /Type /Metadata /Subtype /XML /Length " + content.length + " >>" - ); - this.internal.write("stream"); - this.internal.write(content); - this.internal.write("endstream"); - this.internal.write("endobj"); -} - -function putCatalog() { - if (this.internal.__metadata__.metadataObjectNumber) { - this.internal.write( - "/Metadata " + this.internal.__metadata__.metadataObjectNumber + " 0 R" - ); - } -} - -function escapeXml(str) { - return str - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} - -/** - * Adds XMP formatted metadata to PDF. - * - * WARNING: Passing raw XML is potentially insecure! Always sanitize user input before passing it to this function! - * @name addMetadata - * @function - * @param {string} metadata The actual metadata to be added. The interpretation of this parameter depends on the - * second parameter. - * @param {boolean|string|undefined} rawXmlOrNamespaceUri If a string is passed it sets the namespace URI for the - * metadata and the metadata shall be stored as XMP simple value. The last character should be a slash or hash. - * - * If this argument is omitted, a string is passed, or `false` is passed, the `metadata` argument will be - * XML-escaped before including it in the PDF. - * - * If `true` is passed, the `metadata` argument will be interpreted as raw XMP and will be included verbatim - * in the PDF. The passed metadata must be complete (including surrounding `xmpmeta` and `RDF` tags). - * @returns {jsPDF} jsPDF-instance - */ -jsPDF.API.addMetadata = function(metadata, rawXmlOrNamespaceUri) { - if (typeof this.internal.__metadata__ === "undefined") { - this.internal.__metadata__ = { - metadata: metadata, - namespaceUri: - rawXmlOrNamespaceUri ?? "http://jspdf.default.namespaceuri/", - rawXml: - typeof rawXmlOrNamespaceUri === "boolean" ? rawXmlOrNamespaceUri : false - }; - this.internal.events.subscribe("putCatalog", putCatalog); - - this.internal.events.subscribe("postPutResources", postPutResources); - } - return this; -}; - -/** - * @name utf8 - * @module - */ -(function(jsPDF) { - var jsPDFAPI = jsPDF.API; - - /***************************************************************************************************/ - /* function : pdfEscape16 */ - /* comment : The character id of a 2-byte string is converted to a hexadecimal number by obtaining */ - /* the corresponding glyph id and width, and then adding padding to the string. */ - /***************************************************************************************************/ - var pdfEscape16 = (jsPDFAPI.pdfEscape16 = function(text, font) { - var widths = font.metadata.Unicode.widths; - var padz = ["", "0", "00", "000", "0000"]; - var ar = [""]; - for (var i = 0, l = text.length, t; i < l; ++i) { - t = font.metadata.characterToGlyph(text.charCodeAt(i)); - font.metadata.glyIdsUsed.push(t); - font.metadata.toUnicode[t] = text.charCodeAt(i); - if (widths.indexOf(t) == -1) { - widths.push(t); - widths.push([parseInt(font.metadata.widthOfGlyph(t), 10)]); - } - if (t == "0") { - //Spaces are not allowed in cmap. - return ar.join(""); - } else { - t = t.toString(16); - ar.push(padz[4 - t.length], t); - } - } - return ar.join(""); - }); - - var toUnicodeCmap = function(map) { - var code, codes, range, unicode, unicodeMap, _i, _len; - unicodeMap = - "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo <<\n /Registry (Adobe)\n /Ordering (UCS)\n /Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000>\nendcodespacerange"; - codes = Object.keys(map).sort(function(a, b) { - return a - b; - }); - - range = []; - for (_i = 0, _len = codes.length; _i < _len; _i++) { - code = codes[_i]; - if (range.length >= 100) { - unicodeMap += - "\n" + - range.length + - " beginbfchar\n" + - range.join("\n") + - "\nendbfchar"; - range = []; - } - - if ( - map[code] !== undefined && - map[code] !== null && - typeof map[code].toString === "function" - ) { - unicode = ("0000" + map[code].toString(16)).slice(-4); - code = ("0000" + (+code).toString(16)).slice(-4); - range.push("<" + code + "><" + unicode + ">"); - } - } - - if (range.length) { - unicodeMap += - "\n" + - range.length + - " beginbfchar\n" + - range.join("\n") + - "\nendbfchar\n"; - } - unicodeMap += - "endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"; - return unicodeMap; - }; - - var identityHFunction = function(options) { - var font = options.font; - var out = options.out; - var newObject = options.newObject; - var putStream = options.putStream; - - if ( - font.metadata instanceof jsPDF.API.TTFFont && - font.encoding === "Identity-H" - ) { - //Tag with Identity-H - var widths = font.metadata.Unicode.widths; - var data = font.metadata.subset.encode(font.metadata.glyIdsUsed, 1); - var pdfOutput = data; - var pdfOutput2 = ""; - for (var i = 0; i < pdfOutput.length; i++) { - pdfOutput2 += String.fromCharCode(pdfOutput[i]); - } - var fontTable = newObject(); - putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable }); - out("endobj"); - - var cmap = newObject(); - var cmapData = toUnicodeCmap(font.metadata.toUnicode); - putStream({ data: cmapData, addLength1: true, objectId: cmap }); - out("endobj"); - - var fontDescriptor = newObject(); - out("<<"); - out("/Type /FontDescriptor"); - out("/FontName /" + toPDFName(font.fontName)); - out("/FontFile2 " + fontTable + " 0 R"); - out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); - out("/Flags " + font.metadata.flags); - out("/StemV " + font.metadata.stemV); - out("/ItalicAngle " + font.metadata.italicAngle); - out("/Ascent " + font.metadata.ascender); - out("/Descent " + font.metadata.decender); - out("/CapHeight " + font.metadata.capHeight); - out(">>"); - out("endobj"); - - var DescendantFont = newObject(); - out("<<"); - out("/Type /Font"); - out("/BaseFont /" + toPDFName(font.fontName)); - out("/FontDescriptor " + fontDescriptor + " 0 R"); - out("/W " + jsPDF.API.PDFObject.convert(widths)); - out("/CIDToGIDMap /Identity"); - out("/DW 1000"); - out("/Subtype /CIDFontType2"); - out("/CIDSystemInfo"); - out("<<"); - out("/Supplement 0"); - out("/Registry (Adobe)"); - out("/Ordering (" + font.encoding + ")"); - out(">>"); - out(">>"); - out("endobj"); - - font.objectNumber = newObject(); - out("<<"); - out("/Type /Font"); - out("/Subtype /Type0"); - out("/ToUnicode " + cmap + " 0 R"); - out("/BaseFont /" + toPDFName(font.fontName)); - out("/Encoding /" + font.encoding); - out("/DescendantFonts [" + DescendantFont + " 0 R]"); - out(">>"); - out("endobj"); - - font.isAlreadyPutted = true; - } - }; - - jsPDFAPI.events.push([ - "putFont", - function(args) { - identityHFunction(args); - } - ]); - - var winAnsiEncodingFunction = function(options) { - var font = options.font; - var out = options.out; - var newObject = options.newObject; - var putStream = options.putStream; - - if ( - font.metadata instanceof jsPDF.API.TTFFont && - font.encoding === "WinAnsiEncoding" - ) { - //Tag with WinAnsi encoding - var data = font.metadata.rawData; - var pdfOutput = data; - var pdfOutput2 = ""; - for (var i = 0; i < pdfOutput.length; i++) { - pdfOutput2 += String.fromCharCode(pdfOutput[i]); - } - var fontTable = newObject(); - putStream({ data: pdfOutput2, addLength1: true, objectId: fontTable }); - out("endobj"); - - var cmap = newObject(); - var cmapData = toUnicodeCmap(font.metadata.toUnicode); - putStream({ data: cmapData, addLength1: true, objectId: cmap }); - out("endobj"); - - var fontDescriptor = newObject(); - out("<<"); - out("/Descent " + font.metadata.decender); - out("/CapHeight " + font.metadata.capHeight); - out("/StemV " + font.metadata.stemV); - out("/Type /FontDescriptor"); - out("/FontFile2 " + fontTable + " 0 R"); - out("/Flags 96"); - out("/FontBBox " + jsPDF.API.PDFObject.convert(font.metadata.bbox)); - out("/FontName /" + toPDFName(font.fontName)); - out("/ItalicAngle " + font.metadata.italicAngle); - out("/Ascent " + font.metadata.ascender); - out(">>"); - out("endobj"); - font.objectNumber = newObject(); - for (var j = 0; j < font.metadata.hmtx.widths.length; j++) { - font.metadata.hmtx.widths[j] = parseInt( - font.metadata.hmtx.widths[j] * (1000 / font.metadata.head.unitsPerEm) - ); //Change the width of Em units to Point units. - } - out( - "<>" - ); - out("endobj"); - font.isAlreadyPutted = true; - } - }; - - jsPDFAPI.events.push([ - "putFont", - function(args) { - winAnsiEncodingFunction(args); - } - ]); - - var utf8TextFunction = function(args) { - var text = args.text || ""; - var x = args.x; - var y = args.y; - var options = args.options || {}; - var mutex = args.mutex || {}; - - var pdfEscape = mutex.pdfEscape; - var activeFontKey = mutex.activeFontKey; - var fonts = mutex.fonts; - var key = activeFontKey; - - var str = "", - s = 0, - cmapConfirm; - var strText = ""; - var encoding = fonts[key].encoding; - - if (fonts[key].encoding !== "Identity-H") { - return { - text: text, - x: x, - y: y, - options: options, - mutex: mutex - }; - } - strText = text; - - key = activeFontKey; - if (Array.isArray(text)) { - strText = text[0]; - } - for (s = 0; s < strText.length; s += 1) { - if (fonts[key].metadata.hasOwnProperty("cmap")) { - cmapConfirm = - fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; - /* - if (Object.prototype.toString.call(text) === '[object Array]') { - var i = 0; - // for (i = 0; i < text.length; i += 1) { - if (Object.prototype.toString.call(text[s]) === '[object Array]') { - cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s][0].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id - } else { - - } - //} - - } else { - cmapConfirm = fonts[key].metadata.cmap.unicode.codeMap[strText[s].charCodeAt(0)]; //Make sure the cmap has the corresponding glyph id - }*/ - } - if (!cmapConfirm) { - if ( - strText[s].charCodeAt(0) < 256 && - fonts[key].metadata.hasOwnProperty("Unicode") - ) { - str += strText[s]; - } else { - str += ""; - } - } else { - str += strText[s]; - } - } - var result = ""; - if (parseInt(key.slice(1)) < 14 || encoding === "WinAnsiEncoding") { - //For the default 13 font - result = pdfEscape(str, key) - .split("") - .map(function(cv) { - return cv.charCodeAt(0).toString(16); - }) - .join(""); - } else if (encoding === "Identity-H") { - result = pdfEscape16(str, fonts[key]); - } - mutex.isHex = true; - - return { - text: result, - x: x, - y: y, - options: options, - mutex: mutex - }; - }; - - var utf8EscapeFunction = function(parms) { - var text = parms.text || "", - x = parms.x, - y = parms.y, - options = parms.options, - mutex = parms.mutex; - var tmpText = []; - var args = { - text: text, - x: x, - y: y, - options: options, - mutex: mutex - }; - - if (Array.isArray(text)) { - var i = 0; - for (i = 0; i < text.length; i += 1) { - if (Array.isArray(text[i])) { - if (text[i].length === 3) { - tmpText.push([ - utf8TextFunction(Object.assign({}, args, { text: text[i][0] })) - .text, - text[i][1], - text[i][2] - ]); - } else { - tmpText.push( - utf8TextFunction(Object.assign({}, args, { text: text[i] })).text - ); - } - } else { - tmpText.push( - utf8TextFunction(Object.assign({}, args, { text: text[i] })).text - ); - } - } - parms.text = tmpText; - } else { - parms.text = utf8TextFunction( - Object.assign({}, args, { text: text }) - ).text; - } - }; - - jsPDFAPI.events.push(["postProcessText", utf8EscapeFunction]); -})(jsPDF); - -/** - * @license - * jsPDF virtual FileSystem functionality - * - * Licensed under the MIT License. - * http://opensource.org/licenses/mit-license - */ - -/** - * Use the vFS to handle files - * - * @name vFS - * @module - */ -(function(jsPDFAPI) { - - var _initializeVFS = function() { - if (typeof this.internal.vFS === "undefined") { - this.internal.vFS = {}; - } - return true; - }; - - /** - * Check if the file exists in the vFS - * - * @name existsFileInVFS - * @function - * @param {string} Possible filename in the vFS. - * @returns {boolean} - * @example - * doc.existsFileInVFS("someFile.txt"); - */ - jsPDFAPI.existsFileInVFS = function(filename) { - _initializeVFS.call(this); - return typeof this.internal.vFS[filename] !== "undefined"; - }; - - /** - * Add a file to the vFS - * - * @name addFileToVFS - * @function - * @param {string} filename The name of the file which should be added. - * @param {string} filecontent The content of the file. - * @returns {jsPDF} - * @example - * doc.addFileToVFS("someFile.txt", "BADFACE1"); - */ - jsPDFAPI.addFileToVFS = function(filename, filecontent) { - _initializeVFS.call(this); - this.internal.vFS[filename] = filecontent; - return this; - }; - - /** - * Get the file from the vFS - * - * @name getFileFromVFS - * @function - * @param {string} The name of the file which gets requested. - * @returns {string} - * @example - * doc.getFileFromVFS("someFile.txt"); - */ - jsPDFAPI.getFileFromVFS = function(filename) { - _initializeVFS.call(this); - - if (typeof this.internal.vFS[filename] !== "undefined") { - return this.internal.vFS[filename]; - } - return null; - }; -})(jsPDF.API); - -/** - * @license - * Unicode Bidi Engine based on the work of Alex Shensis (@asthensis) - * MIT License - */ - -(function(jsPDF) { - /** - * Table of Unicode types. - * - * Generated by: - * - * var bidi = require("./bidi/index"); - * var bidi_accumulate = bidi.slice(0, 256).concat(bidi.slice(0x0500, 0x0500 + 256 * 3)). - * concat(bidi.slice(0x2000, 0x2000 + 256)).concat(bidi.slice(0xFB00, 0xFB00 + 256)). - * concat(bidi.slice(0xFE00, 0xFE00 + 2 * 256)); - * - * for( var i = 0; i < bidi_accumulate.length; i++) { - * if(bidi_accumulate[i] === undefined || bidi_accumulate[i] === 'ON') - * bidi_accumulate[i] = 'N'; //mark as neutral to conserve space and substitute undefined - * } - * var bidiAccumulateStr = 'return [ "' + bidi_accumulate.toString().replace(/,/g, '", "') + '" ];'; - * require("fs").writeFile('unicode-types.js', bidiAccumulateStr); - * - * Based on: - * https://github.com/mathiasbynens/unicode-8.0.0 - */ - var bidiUnicodeTypes = [ - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "S", - "B", - "S", - "WS", - "B", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "B", - "B", - "B", - "S", - "WS", - "N", - "N", - "ET", - "ET", - "ET", - "N", - "N", - "N", - "N", - "N", - "ES", - "CS", - "ES", - "CS", - "CS", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "CS", - "N", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "N", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "B", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "CS", - "N", - "ET", - "ET", - "ET", - "ET", - "N", - "N", - "N", - "N", - "L", - "N", - "N", - "BN", - "N", - "N", - "ET", - "ET", - "EN", - "EN", - "N", - "L", - "N", - "N", - "N", - "EN", - "L", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "L", - "N", - "N", - "N", - "N", - "N", - "ET", - "N", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "R", - "NSM", - "R", - "NSM", - "NSM", - "R", - "NSM", - "NSM", - "R", - "NSM", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "N", - "N", - "N", - "N", - "N", - "R", - "R", - "R", - "R", - "R", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "AN", - "AN", - "AN", - "AN", - "AN", - "AN", - "N", - "N", - "AL", - "ET", - "ET", - "AL", - "CS", - "AL", - "N", - "N", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "AL", - "AL", - "N", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "AN", - "AN", - "AN", - "AN", - "AN", - "AN", - "AN", - "AN", - "AN", - "AN", - "ET", - "AN", - "AN", - "AL", - "AL", - "AL", - "NSM", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "AN", - "N", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "AL", - "AL", - "NSM", - "NSM", - "N", - "NSM", - "NSM", - "NSM", - "NSM", - "AL", - "AL", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "N", - "AL", - "AL", - "NSM", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "N", - "N", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "AL", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "R", - "R", - "N", - "N", - "N", - "N", - "R", - "N", - "N", - "N", - "N", - "N", - "WS", - "WS", - "WS", - "WS", - "WS", - "WS", - "WS", - "WS", - "WS", - "WS", - "WS", - "BN", - "BN", - "BN", - "L", - "R", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "WS", - "B", - "LRE", - "RLE", - "PDF", - "LRO", - "RLO", - "CS", - "ET", - "ET", - "ET", - "ET", - "ET", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "CS", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "WS", - "BN", - "BN", - "BN", - "BN", - "BN", - "N", - "LRI", - "RLI", - "FSI", - "PDI", - "BN", - "BN", - "BN", - "BN", - "BN", - "BN", - "EN", - "L", - "N", - "N", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "ES", - "ES", - "N", - "N", - "N", - "L", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "ES", - "ES", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "ET", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "N", - "N", - "R", - "NSM", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "ES", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "N", - "R", - "R", - "R", - "R", - "R", - "N", - "R", - "N", - "R", - "R", - "N", - "R", - "R", - "N", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "R", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "NSM", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "CS", - "N", - "CS", - "N", - "N", - "CS", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "ET", - "N", - "N", - "ES", - "ES", - "N", - "N", - "N", - "N", - "N", - "ET", - "ET", - "N", - "N", - "N", - "N", - "N", - "AL", - "AL", - "AL", - "AL", - "AL", - "N", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "AL", - "N", - "N", - "BN", - "N", - "N", - "N", - "ET", - "ET", - "ET", - "N", - "N", - "N", - "N", - "N", - "ES", - "CS", - "ES", - "CS", - "CS", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "EN", - "CS", - "N", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "L", - "L", - "L", - "L", - "L", - "L", - "N", - "N", - "L", - "L", - "L", - "N", - "N", - "N", - "ET", - "ET", - "N", - "N", - "N", - "ET", - "ET", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N", - "N" - ]; - - /** - * Unicode Bidi algorithm compliant Bidi engine. - * For reference see http://unicode.org/reports/tr9/ - */ - - /** - * constructor ( options ) - * - * Initializes Bidi engine - * - * @param {Object} See 'setOptions' below for detailed description. - * options are cashed between invocation of 'doBidiReorder' method - * - * sample usage pattern of BidiEngine: - * var opt = { - * isInputVisual: true, - * isInputRtl: false, - * isOutputVisual: false, - * isOutputRtl: false, - * isSymmetricSwapping: true - * } - * var sourceToTarget = [], levels = []; - * var bidiEng = Globalize.bidiEngine(opt); - * var src = "text string to be reordered"; - * var ret = bidiEng.doBidiReorder(src, sourceToTarget, levels); - */ - - jsPDF.__bidiEngine__ = jsPDF.prototype.__bidiEngine__ = function(options) { - var _UNICODE_TYPES = _bidiUnicodeTypes; - - var _STATE_TABLE_LTR = [ - [0, 3, 0, 1, 0, 0, 0], - [0, 3, 0, 1, 2, 2, 0], - [0, 3, 0, 0x11, 2, 0, 1], - [0, 3, 5, 5, 4, 1, 0], - [0, 3, 0x15, 0x15, 4, 0, 1], - [0, 3, 5, 5, 4, 2, 0] - ]; - - var _STATE_TABLE_RTL = [ - [2, 0, 1, 1, 0, 1, 0], - [2, 0, 1, 1, 0, 2, 0], - [2, 0, 2, 1, 3, 2, 0], - [2, 0, 2, 0x21, 3, 1, 1] - ]; - - var _TYPE_NAMES_MAP = { L: 0, R: 1, EN: 2, AN: 3, N: 4, B: 5, S: 6 }; - - var _UNICODE_RANGES_MAP = { - 0: 0, - 5: 1, - 6: 2, - 7: 3, - 0x20: 4, - 0xfb: 5, - 0xfe: 6, - 0xff: 7 - }; - - var _SWAP_TABLE = [ - "\u0028", - "\u0029", - "\u0028", - "\u003C", - "\u003E", - "\u003C", - "\u005B", - "\u005D", - "\u005B", - "\u007B", - "\u007D", - "\u007B", - "\u00AB", - "\u00BB", - "\u00AB", - "\u2039", - "\u203A", - "\u2039", - "\u2045", - "\u2046", - "\u2045", - "\u207D", - "\u207E", - "\u207D", - "\u208D", - "\u208E", - "\u208D", - "\u2264", - "\u2265", - "\u2264", - "\u2329", - "\u232A", - "\u2329", - "\uFE59", - "\uFE5A", - "\uFE59", - "\uFE5B", - "\uFE5C", - "\uFE5B", - "\uFE5D", - "\uFE5E", - "\uFE5D", - "\uFE64", - "\uFE65", - "\uFE64" - ]; - - var _LTR_RANGES_REG_EXPR = new RegExp( - /^([1-4|9]|1[0-9]|2[0-9]|3[0168]|4[04589]|5[012]|7[78]|159|16[0-9]|17[0-2]|21[569]|22[03489]|250)$/ - ); - - var _lastArabic = false, - _hasUbatB, - _hasUbatS, - DIR_LTR = 0, - DIR_RTL = 1, - _isInVisual, - _isInRtl, - _isOutVisual, - _isOutRtl, - _isSymmetricSwapping, - _dir = DIR_LTR; - - this.__bidiEngine__ = {}; - - var _init = function(text, sourceToTargetMap) { - if (sourceToTargetMap) { - for (var i = 0; i < text.length; i++) { - sourceToTargetMap[i] = i; - } - } - if (_isInRtl === undefined) { - _isInRtl = _isContextualDirRtl(text); - } - if (_isOutRtl === undefined) { - _isOutRtl = _isContextualDirRtl(text); - } - }; - - // for reference see 3.2 in http://unicode.org/reports/tr9/ - // - var _getCharType = function(ch) { - var charCode = ch.charCodeAt(), - range = charCode >> 8, - rangeIdx = _UNICODE_RANGES_MAP[range]; - - if (rangeIdx !== undefined) { - return _UNICODE_TYPES[rangeIdx * 256 + (charCode & 0xff)]; - } else if (range === 0xfc || range === 0xfd) { - return "AL"; - } else if (_LTR_RANGES_REG_EXPR.test(range)) { - //unlikely case - return "L"; - } else if (range === 8) { - // even less likely - return "R"; - } - return "N"; //undefined type, mark as neutral - }; - - var _isContextualDirRtl = function(text) { - for (var i = 0, charType; i < text.length; i++) { - charType = _getCharType(text.charAt(i)); - if (charType === "L") { - return false; - } else if (charType === "R") { - return true; - } - } - return false; - }; - - // for reference see 3.3.4 & 3.3.5 in http://unicode.org/reports/tr9/ - // - var _resolveCharType = function(chars, types, resolvedTypes, index) { - var cType = types[index], - wType, - nType, - i, - len; - switch (cType) { - case "L": - case "R": - _lastArabic = false; - break; - case "N": - case "AN": - break; - - case "EN": - if (_lastArabic) { - cType = "AN"; - } - break; - - case "AL": - _lastArabic = true; - cType = "R"; - break; - - case "WS": - cType = "N"; - break; - - case "CS": - if ( - index < 1 || - index + 1 >= types.length || - ((wType = resolvedTypes[index - 1]) !== "EN" && wType !== "AN") || - ((nType = types[index + 1]) !== "EN" && nType !== "AN") - ) { - cType = "N"; - } else if (_lastArabic) { - nType = "AN"; - } - cType = nType === wType ? nType : "N"; - break; - - case "ES": - wType = index > 0 ? resolvedTypes[index - 1] : "B"; - cType = - wType === "EN" && - index + 1 < types.length && - types[index + 1] === "EN" - ? "EN" - : "N"; - break; - - case "ET": - if (index > 0 && resolvedTypes[index - 1] === "EN") { - cType = "EN"; - break; - } else if (_lastArabic) { - cType = "N"; - break; - } - i = index + 1; - len = types.length; - while (i < len && types[i] === "ET") { - i++; - } - if (i < len && types[i] === "EN") { - cType = "EN"; - } else { - cType = "N"; - } - break; - - case "NSM": - if (_isInVisual && !_isInRtl) { - //V->L - len = types.length; - i = index + 1; - while (i < len && types[i] === "NSM") { - i++; - } - if (i < len) { - var c = chars[index]; - var rtlCandidate = (c >= 0x0591 && c <= 0x08ff) || c === 0xfb1e; - wType = types[i]; - if (rtlCandidate && (wType === "R" || wType === "AL")) { - cType = "R"; - break; - } - } - } - if (index < 1 || (wType = types[index - 1]) === "B") { - cType = "N"; - } else { - cType = resolvedTypes[index - 1]; - } - break; - - case "B": - _lastArabic = false; - _hasUbatB = true; - cType = _dir; - break; - - case "S": - _hasUbatS = true; - cType = "N"; - break; - - case "LRE": - case "RLE": - case "LRO": - case "RLO": - case "PDF": - _lastArabic = false; - break; - case "BN": - cType = "N"; - break; - } - return cType; - }; - - var _handleUbatS = function(types, levels, length) { - for (var i = 0; i < length; i++) { - if (types[i] === "S") { - levels[i] = _dir; - for (var j = i - 1; j >= 0; j--) { - if (types[j] === "WS") { - levels[j] = _dir; - } else { - break; - } - } - } - } - }; - - var _invertString = function(text, sourceToTargetMap, levels) { - var charArray = text.split(""); - if (levels) { - _computeLevels(charArray, levels, { hiLevel: _dir }); - } - charArray.reverse(); - sourceToTargetMap && sourceToTargetMap.reverse(); - return charArray.join(""); - }; - - // For reference see 3.3 in http://unicode.org/reports/tr9/ - // - var _computeLevels = function(chars, levels, params) { - var action, - condition, - i, - index, - newLevel, - prevState, - condPos = -1, - len = chars.length, - newState = 0, - resolvedTypes = [], - stateTable = _dir ? _STATE_TABLE_RTL : _STATE_TABLE_LTR, - types = []; - - _lastArabic = false; - _hasUbatB = false; - _hasUbatS = false; - for (i = 0; i < len; i++) { - types[i] = _getCharType(chars[i]); - } - for (index = 0; index < len; index++) { - prevState = newState; - resolvedTypes[index] = _resolveCharType( - chars, - types, - resolvedTypes, - index - ); - newState = stateTable[prevState][_TYPE_NAMES_MAP[resolvedTypes[index]]]; - action = newState & 0xf0; - newState &= 0x0f; - levels[index] = newLevel = stateTable[newState][5]; - if (action > 0) { - if (action === 0x10) { - for (i = condPos; i < index; i++) { - levels[i] = 1; - } - condPos = -1; - } else { - condPos = -1; - } - } - condition = stateTable[newState][6]; - if (condition) { - if (condPos === -1) { - condPos = index; - } - } else { - if (condPos > -1) { - for (i = condPos; i < index; i++) { - levels[i] = newLevel; - } - condPos = -1; - } - } - if (types[index] === "B") { - levels[index] = 0; - } - params.hiLevel |= newLevel; - } - if (_hasUbatS) { - _handleUbatS(types, levels, len); - } - }; - - // for reference see 3.4 in http://unicode.org/reports/tr9/ - // - var _invertByLevel = function( - level, - charArray, - sourceToTargetMap, - levels, - params - ) { - if (params.hiLevel < level) { - return; - } - if (level === 1 && _dir === DIR_RTL && !_hasUbatB) { - charArray.reverse(); - sourceToTargetMap && sourceToTargetMap.reverse(); - return; - } - var ch, - high, - end, - low, - len = charArray.length, - start = 0; - - while (start < len) { - if (levels[start] >= level) { - end = start + 1; - while (end < len && levels[end] >= level) { - end++; - } - for (low = start, high = end - 1; low < high; low++, high--) { - ch = charArray[low]; - charArray[low] = charArray[high]; - charArray[high] = ch; - if (sourceToTargetMap) { - ch = sourceToTargetMap[low]; - sourceToTargetMap[low] = sourceToTargetMap[high]; - sourceToTargetMap[high] = ch; - } - } - start = end; - } - start++; - } - }; - - // for reference see 7 & BD16 in http://unicode.org/reports/tr9/ - // - var _symmetricSwap = function(charArray, levels, params) { - if (params.hiLevel !== 0 && _isSymmetricSwapping) { - for (var i = 0, index; i < charArray.length; i++) { - if (levels[i] === 1) { - index = _SWAP_TABLE.indexOf(charArray[i]); - if (index >= 0) { - charArray[i] = _SWAP_TABLE[index + 1]; - } - } - } - } - }; - - var _reorder = function(text, sourceToTargetMap, levels) { - var charArray = text.split(""), - params = { hiLevel: _dir }; - - if (!levels) { - levels = []; - } - _computeLevels(charArray, levels, params); - _symmetricSwap(charArray, levels, params); - _invertByLevel(DIR_RTL + 1, charArray, sourceToTargetMap, levels, params); - _invertByLevel(DIR_RTL, charArray, sourceToTargetMap, levels, params); - return charArray.join(""); - }; - - // doBidiReorder( text, sourceToTargetMap, levels ) - // Performs Bidi reordering by implementing Unicode Bidi algorithm. - // Returns reordered string - // @text [String]: - // - input string to be reordered, this is input parameter - // $sourceToTargetMap [Array] (optional) - // - resultant mapping between input and output strings, this is output parameter - // $levels [Array] (optional) - // - array of calculated Bidi levels, , this is output parameter - this.__bidiEngine__.doBidiReorder = function( - text, - sourceToTargetMap, - levels - ) { - _init(text, sourceToTargetMap); - if (!_isInVisual && _isOutVisual && !_isOutRtl) { - // LLTR->VLTR, LRTL->VLTR - _dir = _isInRtl ? DIR_RTL : DIR_LTR; - text = _reorder(text, sourceToTargetMap, levels); - } else if (_isInVisual && _isOutVisual && _isInRtl ^ _isOutRtl) { - // VRTL->VLTR, VLTR->VRTL - _dir = _isInRtl ? DIR_RTL : DIR_LTR; - text = _invertString(text, sourceToTargetMap, levels); - } else if (!_isInVisual && _isOutVisual && _isOutRtl) { - // LLTR->VRTL, LRTL->VRTL - _dir = _isInRtl ? DIR_RTL : DIR_LTR; - text = _reorder(text, sourceToTargetMap, levels); - text = _invertString(text, sourceToTargetMap); - } else if (_isInVisual && !_isInRtl && !_isOutVisual && !_isOutRtl) { - // VLTR->LLTR - _dir = DIR_LTR; - text = _reorder(text, sourceToTargetMap, levels); - } else if (_isInVisual && !_isOutVisual && _isInRtl ^ _isOutRtl) { - // VLTR->LRTL, VRTL->LLTR - text = _invertString(text, sourceToTargetMap); - if (_isInRtl) { - //LLTR -> VLTR - _dir = DIR_LTR; - text = _reorder(text, sourceToTargetMap, levels); - } else { - //LRTL -> VRTL - _dir = DIR_RTL; - text = _reorder(text, sourceToTargetMap, levels); - text = _invertString(text, sourceToTargetMap); - } - } else if (_isInVisual && _isInRtl && !_isOutVisual && _isOutRtl) { - // VRTL->LRTL - _dir = DIR_RTL; - text = _reorder(text, sourceToTargetMap, levels); - text = _invertString(text, sourceToTargetMap); - } else if (!_isInVisual && !_isOutVisual && _isInRtl ^ _isOutRtl) { - // LRTL->LLTR, LLTR->LRTL - var isSymmetricSwappingOrig = _isSymmetricSwapping; - if (_isInRtl) { - //LRTL->LLTR - _dir = DIR_RTL; - text = _reorder(text, sourceToTargetMap, levels); - _dir = DIR_LTR; - _isSymmetricSwapping = false; - text = _reorder(text, sourceToTargetMap, levels); - _isSymmetricSwapping = isSymmetricSwappingOrig; - } else { - //LLTR->LRTL - _dir = DIR_LTR; - text = _reorder(text, sourceToTargetMap, levels); - text = _invertString(text, sourceToTargetMap); - _dir = DIR_RTL; - _isSymmetricSwapping = false; - text = _reorder(text, sourceToTargetMap, levels); - _isSymmetricSwapping = isSymmetricSwappingOrig; - text = _invertString(text, sourceToTargetMap); - } - } - return text; - }; - - /** - * @name setOptions( options ) - * @function - * Sets options for Bidi conversion - * @param {Object}: - * - isInputVisual {boolean} (defaults to false): allowed values: true(Visual mode), false(Logical mode) - * - isInputRtl {boolean}: allowed values true(Right-to-left direction), false (Left-to-right directiion), undefined(Contectual direction, i.e.direction defined by first strong character of input string) - * - isOutputVisual {boolean} (defaults to false): allowed values: true(Visual mode), false(Logical mode) - * - isOutputRtl {boolean}: allowed values true(Right-to-left direction), false (Left-to-right directiion), undefined(Contectual direction, i.e.direction defined by first strong characterof input string) - * - isSymmetricSwapping {boolean} (defaults to false): allowed values true(needs symmetric swapping), false (no need in symmetric swapping), - */ - this.__bidiEngine__.setOptions = function(options) { - if (options) { - _isInVisual = options.isInputVisual; - _isOutVisual = options.isOutputVisual; - _isInRtl = options.isInputRtl; - _isOutRtl = options.isOutputRtl; - _isSymmetricSwapping = options.isSymmetricSwapping; - } - }; - - this.__bidiEngine__.setOptions(options); - return this.__bidiEngine__; - }; - - var _bidiUnicodeTypes = bidiUnicodeTypes; - - var bidiEngine = new jsPDF.__bidiEngine__({ isInputVisual: true }); - - var bidiEngineFunction = function(args) { - var text = args.text; - args.x; - args.y; - var options = args.options || {}; - args.mutex || {}; - options.lang; - var tmpText = []; - - options.isInputVisual = - typeof options.isInputVisual === "boolean" ? options.isInputVisual : true; - bidiEngine.setOptions(options); - - if (Object.prototype.toString.call(text) === "[object Array]") { - var i = 0; - tmpText = []; - for (i = 0; i < text.length; i += 1) { - if (Object.prototype.toString.call(text[i]) === "[object Array]") { - tmpText.push([ - bidiEngine.doBidiReorder(text[i][0]), - text[i][1], - text[i][2] - ]); - } else { - tmpText.push([bidiEngine.doBidiReorder(text[i])]); - } - } - args.text = tmpText; - } else { - args.text = bidiEngine.doBidiReorder(text); - } - bidiEngine.setOptions({ isInputVisual: true }); - }; - - jsPDF.API.events.push(["postProcessText", bidiEngineFunction]); -})(jsPDF); - -/* eslint-disable no-control-regex */ - -jsPDF.API.TTFFont = (function() { - /************************************************************************/ - /* function : open */ - /* comment : Decode the encoded ttf content and create a TTFFont object. */ - /************************************************************************/ - TTFFont.open = function(file) { - return new TTFFont(file); - }; - /***************************************************************/ - /* function : TTFFont gernerator */ - /* comment : Decode TTF contents are parsed, Data, */ - /* Subset object is created, and registerTTF function is called.*/ - /***************************************************************/ - function TTFFont(rawData) { - var data; - this.rawData = rawData; - data = this.contents = new Data(rawData); - this.contents.pos = 4; - if (data.readString(4) === "ttcf") { - throw new Error("TTCF not supported."); - } else { - data.pos = 0; - this.parse(); - this.subset = new Subset(this); - this.registerTTF(); - } - } - /********************************************************/ - /* function : parse */ - /* comment : TTF Parses the file contents by each table.*/ - /********************************************************/ - TTFFont.prototype.parse = function() { - this.directory = new Directory(this.contents); - this.head = new HeadTable(this); - this.name = new NameTable(this); - this.cmap = new CmapTable(this); - this.toUnicode = {}; - this.hhea = new HheaTable(this); - this.maxp = new MaxpTable(this); - this.hmtx = new HmtxTable(this); - this.post = new PostTable(this); - this.os2 = new OS2Table(this); - this.loca = new LocaTable(this); - this.glyf = new GlyfTable(this); - this.ascender = - (this.os2.exists && this.os2.ascender) || this.hhea.ascender; - this.decender = - (this.os2.exists && this.os2.decender) || this.hhea.decender; - this.lineGap = (this.os2.exists && this.os2.lineGap) || this.hhea.lineGap; - return (this.bbox = [ - this.head.xMin, - this.head.yMin, - this.head.xMax, - this.head.yMax - ]); - }; - /***************************************************************/ - /* function : registerTTF */ - /* comment : Get the value to assign pdf font descriptors. */ - /***************************************************************/ - TTFFont.prototype.registerTTF = function() { - var e, hi, low, raw, _ref; - this.scaleFactor = 1000.0 / this.head.unitsPerEm; - this.bbox = function() { - var _i, _len, _ref, _results; - _ref = this.bbox; - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - e = _ref[_i]; - _results.push(Math.round(e * this.scaleFactor)); - } - return _results; - }.call(this); - this.stemV = 0; - if (this.post.exists) { - raw = this.post.italic_angle; - hi = raw >> 16; - low = raw & 0xff; - if ((hi & 0x8000) !== 0) { - hi = -((hi ^ 0xffff) + 1); - } - this.italicAngle = +("" + hi + "." + low); - } else { - this.italicAngle = 0; - } - this.ascender = Math.round(this.ascender * this.scaleFactor); - this.decender = Math.round(this.decender * this.scaleFactor); - this.lineGap = Math.round(this.lineGap * this.scaleFactor); - this.capHeight = (this.os2.exists && this.os2.capHeight) || this.ascender; - this.xHeight = (this.os2.exists && this.os2.xHeight) || 0; - this.familyClass = ((this.os2.exists && this.os2.familyClass) || 0) >> 8; - this.isSerif = - (_ref = this.familyClass) === 1 || - _ref === 2 || - _ref === 3 || - _ref === 4 || - _ref === 5 || - _ref === 7; - this.isScript = this.familyClass === 10; - this.flags = 0; - if (this.post.isFixedPitch) { - this.flags |= 1 << 0; - } - if (this.isSerif) { - this.flags |= 1 << 1; - } - if (this.isScript) { - this.flags |= 1 << 3; - } - if (this.italicAngle !== 0) { - this.flags |= 1 << 6; - } - this.flags |= 1 << 5; - if (!this.cmap.unicode) { - throw new Error("No unicode cmap for font"); - } - }; - TTFFont.prototype.characterToGlyph = function(character) { - var _ref; - return ( - ((_ref = this.cmap.unicode) != null ? _ref.codeMap[character] : void 0) || - 0 - ); - }; - TTFFont.prototype.widthOfGlyph = function(glyph) { - var scale; - scale = 1000.0 / this.head.unitsPerEm; - return this.hmtx.forGlyph(glyph).advance * scale; - }; - TTFFont.prototype.widthOfString = function(string, size, charSpace) { - var charCode, i, scale, width, _ref; - string = "" + string; - width = 0; - for ( - i = 0, _ref = string.length; - 0 <= _ref ? i < _ref : i > _ref; - i = 0 <= _ref ? ++i : --i - ) { - charCode = string.charCodeAt(i); - width += - this.widthOfGlyph(this.characterToGlyph(charCode)) + - charSpace * (1000 / size) || 0; - } - scale = size / 1000; - return width * scale; - }; - TTFFont.prototype.lineHeight = function(size, includeGap) { - var gap; - if (includeGap == null) { - includeGap = false; - } - gap = includeGap ? this.lineGap : 0; - return ((this.ascender + gap - this.decender) / 1000) * size; - }; - return TTFFont; -})(); - -/************************************************************************************************/ -/* function : Data */ -/* comment : The ttf data decoded and stored in an array is read and written to the Data object.*/ -/************************************************************************************************/ -var Data = (function() { - function Data(data) { - this.data = data != null ? data : []; - this.pos = 0; - this.length = this.data.length; - } - Data.prototype.readByte = function() { - return this.data[this.pos++]; - }; - Data.prototype.writeByte = function(byte) { - return (this.data[this.pos++] = byte); - }; - Data.prototype.readUInt32 = function() { - var b1, b2, b3, b4; - b1 = this.readByte() * 0x1000000; - b2 = this.readByte() << 16; - b3 = this.readByte() << 8; - b4 = this.readByte(); - return b1 + b2 + b3 + b4; - }; - Data.prototype.writeUInt32 = function(val) { - this.writeByte((val >>> 24) & 0xff); - this.writeByte((val >> 16) & 0xff); - this.writeByte((val >> 8) & 0xff); - return this.writeByte(val & 0xff); - }; - Data.prototype.readInt32 = function() { - var int; - int = this.readUInt32(); - if (int >= 0x80000000) { - return int - 0x100000000; - } else { - return int; - } - }; - Data.prototype.writeInt32 = function(val) { - if (val < 0) { - val += 0x100000000; - } - return this.writeUInt32(val); - }; - Data.prototype.readUInt16 = function() { - var b1, b2; - b1 = this.readByte() << 8; - b2 = this.readByte(); - return b1 | b2; - }; - Data.prototype.writeUInt16 = function(val) { - this.writeByte((val >> 8) & 0xff); - return this.writeByte(val & 0xff); - }; - Data.prototype.readInt16 = function() { - var int; - int = this.readUInt16(); - if (int >= 0x8000) { - return int - 0x10000; - } else { - return int; - } - }; - Data.prototype.writeInt16 = function(val) { - if (val < 0) { - val += 0x10000; - } - return this.writeUInt16(val); - }; - Data.prototype.readString = function(length) { - var i, ret; - ret = []; - for ( - i = 0; - 0 <= length ? i < length : i > length; - i = 0 <= length ? ++i : --i - ) { - ret[i] = String.fromCharCode(this.readByte()); - } - return ret.join(""); - }; - Data.prototype.writeString = function(val) { - var i, _ref, _results; - _results = []; - for ( - i = 0, _ref = val.length; - 0 <= _ref ? i < _ref : i > _ref; - i = 0 <= _ref ? ++i : --i - ) { - _results.push(this.writeByte(val.charCodeAt(i))); - } - return _results; - }; - /*Data.prototype.stringAt = function (pos, length) { - this.pos = pos; - return this.readString(length); - };*/ - Data.prototype.readShort = function() { - return this.readInt16(); - }; - Data.prototype.writeShort = function(val) { - return this.writeInt16(val); - }; - Data.prototype.readLongLong = function() { - var b1, b2, b3, b4, b5, b6, b7, b8; - b1 = this.readByte(); - b2 = this.readByte(); - b3 = this.readByte(); - b4 = this.readByte(); - b5 = this.readByte(); - b6 = this.readByte(); - b7 = this.readByte(); - b8 = this.readByte(); - if (b1 & 0x80) { - return ( - ((b1 ^ 0xff) * 0x100000000000000 + - (b2 ^ 0xff) * 0x1000000000000 + - (b3 ^ 0xff) * 0x10000000000 + - (b4 ^ 0xff) * 0x100000000 + - (b5 ^ 0xff) * 0x1000000 + - (b6 ^ 0xff) * 0x10000 + - (b7 ^ 0xff) * 0x100 + - (b8 ^ 0xff) + - 1) * - -1 - ); - } - return ( - b1 * 0x100000000000000 + - b2 * 0x1000000000000 + - b3 * 0x10000000000 + - b4 * 0x100000000 + - b5 * 0x1000000 + - b6 * 0x10000 + - b7 * 0x100 + - b8 - ); - }; - Data.prototype.writeLongLong = function(val) { - var high, low; - high = Math.floor(val / 0x100000000); - low = val & 0xffffffff; - this.writeByte((high >> 24) & 0xff); - this.writeByte((high >> 16) & 0xff); - this.writeByte((high >> 8) & 0xff); - this.writeByte(high & 0xff); - this.writeByte((low >> 24) & 0xff); - this.writeByte((low >> 16) & 0xff); - this.writeByte((low >> 8) & 0xff); - return this.writeByte(low & 0xff); - }; - Data.prototype.readInt = function() { - return this.readInt32(); - }; - Data.prototype.writeInt = function(val) { - return this.writeInt32(val); - }; - /*Data.prototype.slice = function (start, end) { - return this.data.slice(start, end); - };*/ - Data.prototype.read = function(bytes) { - var buf, i; - buf = []; - for ( - i = 0; - 0 <= bytes ? i < bytes : i > bytes; - i = 0 <= bytes ? ++i : --i - ) { - buf.push(this.readByte()); - } - return buf; - }; - Data.prototype.write = function(bytes) { - var byte, i, _len, _results; - _results = []; - for (i = 0, _len = bytes.length; i < _len; i++) { - byte = bytes[i]; - _results.push(this.writeByte(byte)); - } - return _results; - }; - return Data; -})(); - -var Directory = (function() { - var checksum; - - /*****************************************************************************************************/ - /* function : Directory generator */ - /* comment : Initialize the offset, tag, length, and checksum for each table for the font to be used.*/ - /*****************************************************************************************************/ - function Directory(data) { - var entry, i, _ref; - this.scalarType = data.readInt(); - this.tableCount = data.readShort(); - this.searchRange = data.readShort(); - this.entrySelector = data.readShort(); - this.rangeShift = data.readShort(); - this.tables = {}; - for ( - i = 0, _ref = this.tableCount; - 0 <= _ref ? i < _ref : i > _ref; - i = 0 <= _ref ? ++i : --i - ) { - entry = { - tag: data.readString(4), - checksum: data.readInt(), - offset: data.readInt(), - length: data.readInt() - }; - this.tables[entry.tag] = entry; - } - } - /********************************************************************************************************/ - /* function : encode */ - /* comment : It encodes and stores the font table object and information used for the directory object. */ - /********************************************************************************************************/ - Directory.prototype.encode = function(tables) { - var adjustment, - directory, - directoryLength, - entrySelector, - headOffset, - log2, - offset, - rangeShift, - searchRange, - sum, - table, - tableCount, - tableData, - tag; - tableCount = Object.keys(tables).length; - log2 = Math.log(2); - searchRange = Math.floor(Math.log(tableCount) / log2) * 16; - entrySelector = Math.floor(searchRange / log2); - rangeShift = tableCount * 16 - searchRange; - directory = new Data(); - directory.writeInt(this.scalarType); - directory.writeShort(tableCount); - directory.writeShort(searchRange); - directory.writeShort(entrySelector); - directory.writeShort(rangeShift); - directoryLength = tableCount * 16; - offset = directory.pos + directoryLength; - headOffset = null; - tableData = []; - for (tag in tables) { - table = tables[tag]; - directory.writeString(tag); - directory.writeInt(checksum(table)); - directory.writeInt(offset); - directory.writeInt(table.length); - tableData = tableData.concat(table); - if (tag === "head") { - headOffset = offset; - } - offset += table.length; - while (offset % 4) { - tableData.push(0); - offset++; - } - } - directory.write(tableData); - sum = checksum(directory.data); - adjustment = 0xb1b0afba - sum; - directory.pos = headOffset + 8; - directory.writeUInt32(adjustment); - return directory.data; - }; - /***************************************************************/ - /* function : checksum */ - /* comment : Duplicate the table for the tag. */ - /***************************************************************/ - checksum = function(data) { - var i, sum, tmp, _ref; - data = __slice.call(data); - while (data.length % 4) { - data.push(0); - } - tmp = new Data(data); - sum = 0; - for (i = 0, _ref = data.length; i < _ref; i = i += 4) { - sum += tmp.readUInt32(); - } - return sum & 0xffffffff; - }; - return Directory; -})(); - -var Table, - __hasProp = {}.hasOwnProperty, - __extends = function(child, parent) { - for (var key in parent) { - if (__hasProp.call(parent, key)) child[key] = parent[key]; - } - - function ctor() { - this.constructor = child; - } - ctor.prototype = parent.prototype; - child.prototype = new ctor(); - child.__super__ = parent.prototype; - return child; - }; - -/***************************************************************/ -/* function : Table */ -/* comment : Save info for each table, and parse the table. */ -/***************************************************************/ -Table = (function() { - function Table(file) { - var info; - this.file = file; - info = this.file.directory.tables[this.tag]; - this.exists = !!info; - if (info) { - (this.offset = info.offset), (this.length = info.length); - this.parse(this.file.contents); - } - } - Table.prototype.parse = function() {}; - Table.prototype.encode = function() {}; - Table.prototype.raw = function() { - if (!this.exists) { - return null; - } - this.file.contents.pos = this.offset; - return this.file.contents.read(this.length); - }; - return Table; -})(); - -var HeadTable = (function(_super) { - __extends(HeadTable, _super); - - function HeadTable() { - return HeadTable.__super__.constructor.apply(this, arguments); - } - HeadTable.prototype.tag = "head"; - HeadTable.prototype.parse = function(data) { - data.pos = this.offset; - this.version = data.readInt(); - this.revision = data.readInt(); - this.checkSumAdjustment = data.readInt(); - this.magicNumber = data.readInt(); - this.flags = data.readShort(); - this.unitsPerEm = data.readShort(); - this.created = data.readLongLong(); - this.modified = data.readLongLong(); - this.xMin = data.readShort(); - this.yMin = data.readShort(); - this.xMax = data.readShort(); - this.yMax = data.readShort(); - this.macStyle = data.readShort(); - this.lowestRecPPEM = data.readShort(); - this.fontDirectionHint = data.readShort(); - this.indexToLocFormat = data.readShort(); - return (this.glyphDataFormat = data.readShort()); - }; - HeadTable.prototype.encode = function(indexToLocFormat) { - var table; - table = new Data(); - table.writeInt(this.version); - table.writeInt(this.revision); - table.writeInt(this.checkSumAdjustment); - table.writeInt(this.magicNumber); - table.writeShort(this.flags); - table.writeShort(this.unitsPerEm); - table.writeLongLong(this.created); - table.writeLongLong(this.modified); - table.writeShort(this.xMin); - table.writeShort(this.yMin); - table.writeShort(this.xMax); - table.writeShort(this.yMax); - table.writeShort(this.macStyle); - table.writeShort(this.lowestRecPPEM); - table.writeShort(this.fontDirectionHint); - table.writeShort(indexToLocFormat); - table.writeShort(this.glyphDataFormat); - return table.data; - }; - return HeadTable; -})(Table); - -/************************************************************************************/ -/* function : CmapEntry */ -/* comment : Cmap Initializes and encodes object information (required by pdf spec).*/ -/************************************************************************************/ -var CmapEntry = (function() { - function CmapEntry(data, offset) { - var code, - count, - endCode, - glyphId, - glyphIds, - i, - idDelta, - idRangeOffset, - index, - saveOffset, - segCount, - segCountX2, - start, - startCode, - tail, - _j, - _k, - _len; - this.platformID = data.readUInt16(); - this.encodingID = data.readShort(); - this.offset = offset + data.readInt(); - saveOffset = data.pos; - data.pos = this.offset; - this.format = data.readUInt16(); - this.length = data.readUInt16(); - this.language = data.readUInt16(); - this.isUnicode = - (this.platformID === 3 && this.encodingID === 1 && this.format === 4) || - (this.platformID === 0 && this.format === 4) || - (this.platformID === 1 && this.encodingID === 0 && this.format === 0); - this.codeMap = {}; - switch (this.format) { - case 0: - for (i = 0; i < 256; ++i) { - this.codeMap[i] = data.readByte(); - } - break; - case 4: - segCountX2 = data.readUInt16(); - segCount = segCountX2 / 2; - data.pos += 6; - endCode = (function() { - var _j, _results; - _results = []; - for ( - i = _j = 0; - 0 <= segCount ? _j < segCount : _j > segCount; - i = 0 <= segCount ? ++_j : --_j - ) { - _results.push(data.readUInt16()); - } - return _results; - })(); - data.pos += 2; - startCode = (function() { - var _j, _results; - _results = []; - for ( - i = _j = 0; - 0 <= segCount ? _j < segCount : _j > segCount; - i = 0 <= segCount ? ++_j : --_j - ) { - _results.push(data.readUInt16()); - } - return _results; - })(); - idDelta = (function() { - var _j, _results; - _results = []; - for ( - i = _j = 0; - 0 <= segCount ? _j < segCount : _j > segCount; - i = 0 <= segCount ? ++_j : --_j - ) { - _results.push(data.readUInt16()); - } - return _results; - })(); - idRangeOffset = (function() { - var _j, _results; - _results = []; - for ( - i = _j = 0; - 0 <= segCount ? _j < segCount : _j > segCount; - i = 0 <= segCount ? ++_j : --_j - ) { - _results.push(data.readUInt16()); - } - return _results; - })(); - count = (this.length - data.pos + this.offset) / 2; - glyphIds = (function() { - var _j, _results; - _results = []; - for ( - i = _j = 0; - 0 <= count ? _j < count : _j > count; - i = 0 <= count ? ++_j : --_j - ) { - _results.push(data.readUInt16()); - } - return _results; - })(); - for (i = _j = 0, _len = endCode.length; _j < _len; i = ++_j) { - tail = endCode[i]; - start = startCode[i]; - for ( - code = _k = start; - start <= tail ? _k <= tail : _k >= tail; - code = start <= tail ? ++_k : --_k - ) { - if (idRangeOffset[i] === 0) { - glyphId = code + idDelta[i]; - } else { - index = idRangeOffset[i] / 2 + (code - start) - (segCount - i); - glyphId = glyphIds[index] || 0; - if (glyphId !== 0) { - glyphId += idDelta[i]; - } - } - this.codeMap[code] = glyphId & 0xffff; - } - } - } - data.pos = saveOffset; - } - CmapEntry.encode = function(charmap, encoding) { - var charMap, - code, - codeMap, - codes, - delta, - deltas, - diff, - endCode, - endCodes, - entrySelector, - glyphIDs, - i, - id, - indexes, - last, - map, - nextID, - offset, - old, - rangeOffsets, - rangeShift, - searchRange, - segCount, - segCountX2, - startCode, - startCodes, - startGlyph, - subtable, - _i, - _j, - _k, - _l, - _len, - _len1, - _len2, - _len3, - _len4, - _len5, - _len6, - _len7, - _m, - _n, - _name, - _o, - _p, - _q; - subtable = new Data(); - codes = Object.keys(charmap).sort(function(a, b) { - return a - b; - }); - switch (encoding) { - case "macroman": - id = 0; - indexes = (function() { - var _results = []; - for (i = 0; i < 256; ++i) { - _results.push(0); - } - return _results; - })(); - map = { - 0: 0 - }; - codeMap = {}; - for (_i = 0, _len = codes.length; _i < _len; _i++) { - code = codes[_i]; - if (map[(_name = charmap[code])] == null) { - map[_name] = ++id; - } - codeMap[code] = { - old: charmap[code], - new: map[charmap[code]] - }; - indexes[code] = map[charmap[code]]; - } - subtable.writeUInt16(1); - subtable.writeUInt16(0); - subtable.writeUInt32(12); - subtable.writeUInt16(0); - subtable.writeUInt16(262); - subtable.writeUInt16(0); - subtable.write(indexes); - return { - charMap: codeMap, - subtable: subtable.data, - maxGlyphID: id + 1 - }; - case "unicode": - startCodes = []; - endCodes = []; - nextID = 0; - map = {}; - charMap = {}; - last = diff = null; - for (_j = 0, _len1 = codes.length; _j < _len1; _j++) { - code = codes[_j]; - old = charmap[code]; - if (map[old] == null) { - map[old] = ++nextID; - } - charMap[code] = { - old: old, - new: map[old] - }; - delta = map[old] - code; - if (last == null || delta !== diff) { - if (last) { - endCodes.push(last); - } - startCodes.push(code); - diff = delta; - } - last = code; - } - if (last) { - endCodes.push(last); - } - endCodes.push(0xffff); - startCodes.push(0xffff); - segCount = startCodes.length; - segCountX2 = segCount * 2; - searchRange = 2 * Math.pow(Math.log(segCount) / Math.LN2, 2); - entrySelector = Math.log(searchRange / 2) / Math.LN2; - rangeShift = 2 * segCount - searchRange; - deltas = []; - rangeOffsets = []; - glyphIDs = []; - for (i = _k = 0, _len2 = startCodes.length; _k < _len2; i = ++_k) { - startCode = startCodes[i]; - endCode = endCodes[i]; - if (startCode === 0xffff) { - deltas.push(0); - rangeOffsets.push(0); - break; - } - startGlyph = charMap[startCode]["new"]; - if (startCode - startGlyph >= 0x8000) { - deltas.push(0); - rangeOffsets.push(2 * (glyphIDs.length + segCount - i)); - for ( - code = _l = startCode; - startCode <= endCode ? _l <= endCode : _l >= endCode; - code = startCode <= endCode ? ++_l : --_l - ) { - glyphIDs.push(charMap[code]["new"]); - } - } else { - deltas.push(startGlyph - startCode); - rangeOffsets.push(0); - } - } - subtable.writeUInt16(3); - subtable.writeUInt16(1); - subtable.writeUInt32(12); - subtable.writeUInt16(4); - subtable.writeUInt16(16 + segCount * 8 + glyphIDs.length * 2); - subtable.writeUInt16(0); - subtable.writeUInt16(segCountX2); - subtable.writeUInt16(searchRange); - subtable.writeUInt16(entrySelector); - subtable.writeUInt16(rangeShift); - for (_m = 0, _len3 = endCodes.length; _m < _len3; _m++) { - code = endCodes[_m]; - subtable.writeUInt16(code); - } - subtable.writeUInt16(0); - for (_n = 0, _len4 = startCodes.length; _n < _len4; _n++) { - code = startCodes[_n]; - subtable.writeUInt16(code); - } - for (_o = 0, _len5 = deltas.length; _o < _len5; _o++) { - delta = deltas[_o]; - subtable.writeUInt16(delta); - } - for (_p = 0, _len6 = rangeOffsets.length; _p < _len6; _p++) { - offset = rangeOffsets[_p]; - subtable.writeUInt16(offset); - } - for (_q = 0, _len7 = glyphIDs.length; _q < _len7; _q++) { - id = glyphIDs[_q]; - subtable.writeUInt16(id); - } - return { - charMap: charMap, - subtable: subtable.data, - maxGlyphID: nextID + 1 - }; - } - }; - return CmapEntry; -})(); - -var CmapTable = (function(_super) { - __extends(CmapTable, _super); - - function CmapTable() { - return CmapTable.__super__.constructor.apply(this, arguments); - } - CmapTable.prototype.tag = "cmap"; - CmapTable.prototype.parse = function(data) { - var entry, i, tableCount; - data.pos = this.offset; - this.version = data.readUInt16(); - tableCount = data.readUInt16(); - this.tables = []; - this.unicode = null; - for ( - i = 0; - 0 <= tableCount ? i < tableCount : i > tableCount; - i = 0 <= tableCount ? ++i : --i - ) { - entry = new CmapEntry(data, this.offset); - this.tables.push(entry); - if (entry.isUnicode) { - if (this.unicode == null) { - this.unicode = entry; - } - } - } - return true; - }; - /*************************************************************************/ - /* function : encode */ - /* comment : Encode the cmap table corresponding to the input character. */ - /*************************************************************************/ - CmapTable.encode = function(charmap, encoding) { - var result, table; - if (encoding == null) { - encoding = "macroman"; - } - result = CmapEntry.encode(charmap, encoding); - table = new Data(); - table.writeUInt16(0); - table.writeUInt16(1); - result.table = table.data.concat(result.subtable); - return result; - }; - return CmapTable; -})(Table); - -var HheaTable = (function(_super) { - __extends(HheaTable, _super); - - function HheaTable() { - return HheaTable.__super__.constructor.apply(this, arguments); - } - HheaTable.prototype.tag = "hhea"; - HheaTable.prototype.parse = function(data) { - data.pos = this.offset; - this.version = data.readInt(); - this.ascender = data.readShort(); - this.decender = data.readShort(); - this.lineGap = data.readShort(); - this.advanceWidthMax = data.readShort(); - this.minLeftSideBearing = data.readShort(); - this.minRightSideBearing = data.readShort(); - this.xMaxExtent = data.readShort(); - this.caretSlopeRise = data.readShort(); - this.caretSlopeRun = data.readShort(); - this.caretOffset = data.readShort(); - data.pos += 4 * 2; - this.metricDataFormat = data.readShort(); - return (this.numberOfMetrics = data.readUInt16()); - }; - /*HheaTable.prototype.encode = function (ids) { - var i, table, _i, _ref; - table = new Data; - table.writeInt(this.version); - table.writeShort(this.ascender); - table.writeShort(this.decender); - table.writeShort(this.lineGap); - table.writeShort(this.advanceWidthMax); - table.writeShort(this.minLeftSideBearing); - table.writeShort(this.minRightSideBearing); - table.writeShort(this.xMaxExtent); - table.writeShort(this.caretSlopeRise); - table.writeShort(this.caretSlopeRun); - table.writeShort(this.caretOffset); - for (i = _i = 0, _ref = 4 * 2; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { - table.writeByte(0); - } - table.writeShort(this.metricDataFormat); - table.writeUInt16(ids.length); - return table.data; - };*/ - return HheaTable; -})(Table); - -var OS2Table = (function(_super) { - __extends(OS2Table, _super); - - function OS2Table() { - return OS2Table.__super__.constructor.apply(this, arguments); - } - OS2Table.prototype.tag = "OS/2"; - OS2Table.prototype.parse = function(data) { - data.pos = this.offset; - this.version = data.readUInt16(); - this.averageCharWidth = data.readShort(); - this.weightClass = data.readUInt16(); - this.widthClass = data.readUInt16(); - this.type = data.readShort(); - this.ySubscriptXSize = data.readShort(); - this.ySubscriptYSize = data.readShort(); - this.ySubscriptXOffset = data.readShort(); - this.ySubscriptYOffset = data.readShort(); - this.ySuperscriptXSize = data.readShort(); - this.ySuperscriptYSize = data.readShort(); - this.ySuperscriptXOffset = data.readShort(); - this.ySuperscriptYOffset = data.readShort(); - this.yStrikeoutSize = data.readShort(); - this.yStrikeoutPosition = data.readShort(); - this.familyClass = data.readShort(); - this.panose = (function() { - var i, _results; - _results = []; - for (i = 0; i < 10; ++i) { - _results.push(data.readByte()); - } - return _results; - })(); - this.charRange = (function() { - var i, _results; - _results = []; - for (i = 0; i < 4; ++i) { - _results.push(data.readInt()); - } - return _results; - })(); - this.vendorID = data.readString(4); - this.selection = data.readShort(); - this.firstCharIndex = data.readShort(); - this.lastCharIndex = data.readShort(); - if (this.version > 0) { - this.ascent = data.readShort(); - this.descent = data.readShort(); - this.lineGap = data.readShort(); - this.winAscent = data.readShort(); - this.winDescent = data.readShort(); - this.codePageRange = (function() { - var i, _results; - _results = []; - for (i = 0; i < 2; i = ++i) { - _results.push(data.readInt()); - } - return _results; - })(); - if (this.version > 1) { - this.xHeight = data.readShort(); - this.capHeight = data.readShort(); - this.defaultChar = data.readShort(); - this.breakChar = data.readShort(); - return (this.maxContext = data.readShort()); - } - } - }; - /*OS2Table.prototype.encode = function () { - return this.raw(); - };*/ - return OS2Table; -})(Table); - -var PostTable = (function(_super) { - __extends(PostTable, _super); - - function PostTable() { - return PostTable.__super__.constructor.apply(this, arguments); - } - PostTable.prototype.tag = "post"; - PostTable.prototype.parse = function(data) { - var length, numberOfGlyphs, _results; - data.pos = this.offset; - this.format = data.readInt(); - this.italicAngle = data.readInt(); - this.underlinePosition = data.readShort(); - this.underlineThickness = data.readShort(); - this.isFixedPitch = data.readInt(); - this.minMemType42 = data.readInt(); - this.maxMemType42 = data.readInt(); - this.minMemType1 = data.readInt(); - this.maxMemType1 = data.readInt(); - switch (this.format) { - case 0x00010000: - break; - case 0x00020000: - numberOfGlyphs = data.readUInt16(); - this.glyphNameIndex = []; - var i; - for ( - i = 0; - 0 <= numberOfGlyphs ? i < numberOfGlyphs : i > numberOfGlyphs; - i = 0 <= numberOfGlyphs ? ++i : --i - ) { - this.glyphNameIndex.push(data.readUInt16()); - } - this.names = []; - _results = []; - while (data.pos < this.offset + this.length) { - length = data.readByte(); - _results.push(this.names.push(data.readString(length))); - } - return _results; - case 0x00025000: - numberOfGlyphs = data.readUInt16(); - return (this.offsets = data.read(numberOfGlyphs)); - case 0x00030000: - break; - case 0x00040000: - return (this.map = function() { - var _j, _ref, _results1; - _results1 = []; - for ( - i = _j = 0, _ref = this.file.maxp.numGlyphs; - 0 <= _ref ? _j < _ref : _j > _ref; - i = 0 <= _ref ? ++_j : --_j - ) { - _results1.push(data.readUInt32()); - } - return _results1; - }.call(this)); - } - }; - return PostTable; -})(Table); - -/*********************************************************************************************************/ -/* function : NameEntry */ -/* comment : Store copyright information, platformID, encodingID, and languageID in the NameEntry object.*/ -/*********************************************************************************************************/ -var NameEntry = (function() { - function NameEntry(raw, entry) { - this.raw = raw; - this.length = raw.length; - this.platformID = entry.platformID; - this.encodingID = entry.encodingID; - this.languageID = entry.languageID; - } - return NameEntry; -})(); - -var NameTable = (function(_super) { - __extends(NameTable, _super); - - function NameTable() { - return NameTable.__super__.constructor.apply(this, arguments); - } - NameTable.prototype.tag = "name"; - NameTable.prototype.parse = function(data) { - var count, - entries, - entry, - i, - name, - stringOffset, - strings, - text, - _j, - _len, - _name; - data.pos = this.offset; - data.readShort(); //format - count = data.readShort(); - stringOffset = data.readShort(); - entries = []; - for ( - i = 0; - 0 <= count ? i < count : i > count; - i = 0 <= count ? ++i : --i - ) { - entries.push({ - platformID: data.readShort(), - encodingID: data.readShort(), - languageID: data.readShort(), - nameID: data.readShort(), - length: data.readShort(), - offset: this.offset + stringOffset + data.readShort() - }); - } - strings = {}; - for (i = _j = 0, _len = entries.length; _j < _len; i = ++_j) { - entry = entries[i]; - data.pos = entry.offset; - text = data.readString(entry.length); - name = new NameEntry(text, entry); - if (strings[(_name = entry.nameID)] == null) { - strings[_name] = []; - } - strings[entry.nameID].push(name); - } - this.strings = strings; - this.copyright = strings[0]; - this.fontFamily = strings[1]; - this.fontSubfamily = strings[2]; - this.uniqueSubfamily = strings[3]; - this.fontName = strings[4]; - this.version = strings[5]; - try { - this.postscriptName = strings[6][0].raw.replace( - /[\x00-\x19\x80-\xff]/g, - "" - ); - } catch (e) { - this.postscriptName = strings[4][0].raw.replace( - /[\x00-\x19\x80-\xff]/g, - "" - ); - } - this.trademark = strings[7]; - this.manufacturer = strings[8]; - this.designer = strings[9]; - this.description = strings[10]; - this.vendorUrl = strings[11]; - this.designerUrl = strings[12]; - this.license = strings[13]; - this.licenseUrl = strings[14]; - this.preferredFamily = strings[15]; - this.preferredSubfamily = strings[17]; - this.compatibleFull = strings[18]; - return (this.sampleText = strings[19]); - }; - /*NameTable.prototype.encode = function () { - var id, list, nameID, nameTable, postscriptName, strCount, strTable, string, strings, table, val, _i, _len, _ref; - strings = {}; - _ref = this.strings; - for (id in _ref) { - val = _ref[id]; - strings[id] = val; - } - postscriptName = new NameEntry("" + subsetTag + "+" + this.postscriptName, { - platformID: 1 - , encodingID: 0 - , languageID: 0 - }); - strings[6] = [postscriptName]; - subsetTag = successorOf(subsetTag); - strCount = 0; - for (id in strings) { - list = strings[id]; - if (list != null) { - strCount += list.length; - } - } - table = new Data; - strTable = new Data; - table.writeShort(0); - table.writeShort(strCount); - table.writeShort(6 + 12 * strCount); - for (nameID in strings) { - list = strings[nameID]; - if (list != null) { - for (_i = 0, _len = list.length; _i < _len; _i++) { - string = list[_i]; - table.writeShort(string.platformID); - table.writeShort(string.encodingID); - table.writeShort(string.languageID); - table.writeShort(nameID); - table.writeShort(string.length); - table.writeShort(strTable.pos); - strTable.writeString(string.raw); - } - } - } - return nameTable = { - postscriptName: postscriptName.raw - , table: table.data.concat(strTable.data) - }; - };*/ - return NameTable; -})(Table); - -var MaxpTable = (function(_super) { - __extends(MaxpTable, _super); - - function MaxpTable() { - return MaxpTable.__super__.constructor.apply(this, arguments); - } - MaxpTable.prototype.tag = "maxp"; - MaxpTable.prototype.parse = function(data) { - data.pos = this.offset; - this.version = data.readInt(); - this.numGlyphs = data.readUInt16(); - this.maxPoints = data.readUInt16(); - this.maxContours = data.readUInt16(); - this.maxCompositePoints = data.readUInt16(); - this.maxComponentContours = data.readUInt16(); - this.maxZones = data.readUInt16(); - this.maxTwilightPoints = data.readUInt16(); - this.maxStorage = data.readUInt16(); - this.maxFunctionDefs = data.readUInt16(); - this.maxInstructionDefs = data.readUInt16(); - this.maxStackElements = data.readUInt16(); - this.maxSizeOfInstructions = data.readUInt16(); - this.maxComponentElements = data.readUInt16(); - return (this.maxComponentDepth = data.readUInt16()); - }; - /*MaxpTable.prototype.encode = function (ids) { - var table; - table = new Data; - table.writeInt(this.version); - table.writeUInt16(ids.length); - table.writeUInt16(this.maxPoints); - table.writeUInt16(this.maxContours); - table.writeUInt16(this.maxCompositePoints); - table.writeUInt16(this.maxComponentContours); - table.writeUInt16(this.maxZones); - table.writeUInt16(this.maxTwilightPoints); - table.writeUInt16(this.maxStorage); - table.writeUInt16(this.maxFunctionDefs); - table.writeUInt16(this.maxInstructionDefs); - table.writeUInt16(this.maxStackElements); - table.writeUInt16(this.maxSizeOfInstructions); - table.writeUInt16(this.maxComponentElements); - table.writeUInt16(this.maxComponentDepth); - return table.data; - };*/ - return MaxpTable; -})(Table); - -var HmtxTable = (function(_super) { - __extends(HmtxTable, _super); - - function HmtxTable() { - return HmtxTable.__super__.constructor.apply(this, arguments); - } - HmtxTable.prototype.tag = "hmtx"; - HmtxTable.prototype.parse = function(data) { - var i, last, lsbCount, m, _j, _ref, _results; - data.pos = this.offset; - this.metrics = []; - for ( - i = 0, _ref = this.file.hhea.numberOfMetrics; - 0 <= _ref ? i < _ref : i > _ref; - i = 0 <= _ref ? ++i : --i - ) { - this.metrics.push({ - advance: data.readUInt16(), - lsb: data.readInt16() - }); - } - lsbCount = this.file.maxp.numGlyphs - this.file.hhea.numberOfMetrics; - this.leftSideBearings = (function() { - var _j, _results; - _results = []; - for ( - i = _j = 0; - 0 <= lsbCount ? _j < lsbCount : _j > lsbCount; - i = 0 <= lsbCount ? ++_j : --_j - ) { - _results.push(data.readInt16()); - } - return _results; - })(); - this.widths = function() { - var _j, _len, _ref1, _results; - _ref1 = this.metrics; - _results = []; - for (_j = 0, _len = _ref1.length; _j < _len; _j++) { - m = _ref1[_j]; - _results.push(m.advance); - } - return _results; - }.call(this); - last = this.widths[this.widths.length - 1]; - _results = []; - for ( - i = _j = 0; - 0 <= lsbCount ? _j < lsbCount : _j > lsbCount; - i = 0 <= lsbCount ? ++_j : --_j - ) { - _results.push(this.widths.push(last)); - } - return _results; - }; - /***************************************************************/ - /* function : forGlyph */ - /* comment : Returns the advance width and lsb for this glyph. */ - /***************************************************************/ - HmtxTable.prototype.forGlyph = function(id) { - if (id in this.metrics) { - return this.metrics[id]; - } - return { - advance: this.metrics[this.metrics.length - 1].advance, - lsb: this.leftSideBearings[id - this.metrics.length] - }; - }; - /*HmtxTable.prototype.encode = function (mapping) { - var id, metric, table, _i, _len; - table = new Data; - for (_i = 0, _len = mapping.length; _i < _len; _i++) { - id = mapping[_i]; - metric = this.forGlyph(id); - table.writeUInt16(metric.advance); - table.writeUInt16(metric.lsb); - } - return table.data; - };*/ - return HmtxTable; -})(Table); - -var __slice = [].slice; - -var GlyfTable = (function(_super) { - __extends(GlyfTable, _super); - - function GlyfTable() { - return GlyfTable.__super__.constructor.apply(this, arguments); - } - GlyfTable.prototype.tag = "glyf"; - GlyfTable.prototype.parse = function() { - return (this.cache = {}); - }; - GlyfTable.prototype.glyphFor = function(id) { - var data, - index, - length, - loca, - numberOfContours, - raw, - xMax, - xMin, - yMax, - yMin; - if (id in this.cache) { - return this.cache[id]; - } - loca = this.file.loca; - data = this.file.contents; - index = loca.indexOf(id); - length = loca.lengthOf(id); - if (length === 0) { - return (this.cache[id] = null); - } - data.pos = this.offset + index; - raw = new Data(data.read(length)); - numberOfContours = raw.readShort(); - xMin = raw.readShort(); - yMin = raw.readShort(); - xMax = raw.readShort(); - yMax = raw.readShort(); - if (numberOfContours === -1) { - this.cache[id] = new CompoundGlyph(raw, xMin, yMin, xMax, yMax); - } else { - this.cache[id] = new SimpleGlyph( - raw, - numberOfContours, - xMin, - yMin, - xMax, - yMax - ); - } - return this.cache[id]; - }; - GlyfTable.prototype.encode = function(glyphs, mapping, old2new) { - var glyph, id, offsets, table, _i, _len; - table = []; - offsets = []; - for (_i = 0, _len = mapping.length; _i < _len; _i++) { - id = mapping[_i]; - glyph = glyphs[id]; - offsets.push(table.length); - if (glyph) { - table = table.concat(glyph.encode(old2new)); - } - } - offsets.push(table.length); - return { - table: table, - offsets: offsets - }; - }; - return GlyfTable; -})(Table); - -var SimpleGlyph = (function() { - /**************************************************************************/ - /* function : SimpleGlyph */ - /* comment : Stores raw, xMin, yMin, xMax, and yMax values for this glyph.*/ - /**************************************************************************/ - function SimpleGlyph(raw, numberOfContours, xMin, yMin, xMax, yMax) { - this.raw = raw; - this.numberOfContours = numberOfContours; - this.xMin = xMin; - this.yMin = yMin; - this.xMax = xMax; - this.yMax = yMax; - this.compound = false; - } - SimpleGlyph.prototype.encode = function() { - return this.raw.data; - }; - return SimpleGlyph; -})(); - -var CompoundGlyph = (function() { - var ARG_1_AND_2_ARE_WORDS, - MORE_COMPONENTS, - WE_HAVE_AN_X_AND_Y_SCALE, - WE_HAVE_A_SCALE, - WE_HAVE_A_TWO_BY_TWO; - ARG_1_AND_2_ARE_WORDS = 0x0001; - WE_HAVE_A_SCALE = 0x0008; - MORE_COMPONENTS = 0x0020; - WE_HAVE_AN_X_AND_Y_SCALE = 0x0040; - WE_HAVE_A_TWO_BY_TWO = 0x0080; - - /********************************************************************************************************************/ - /* function : CompoundGlypg generator */ - /* comment : It stores raw, xMin, yMin, xMax, yMax, glyph id, and glyph offset for the corresponding compound glyph.*/ - /********************************************************************************************************************/ - function CompoundGlyph(raw, xMin, yMin, xMax, yMax) { - var data, flags; - this.raw = raw; - this.xMin = xMin; - this.yMin = yMin; - this.xMax = xMax; - this.yMax = yMax; - this.compound = true; - this.glyphIDs = []; - this.glyphOffsets = []; - data = this.raw; - while (true) { - flags = data.readShort(); - this.glyphOffsets.push(data.pos); - this.glyphIDs.push(data.readUInt16()); - if (!(flags & MORE_COMPONENTS)) { - break; - } - if (flags & ARG_1_AND_2_ARE_WORDS) { - data.pos += 4; - } else { - data.pos += 2; - } - if (flags & WE_HAVE_A_TWO_BY_TWO) { - data.pos += 8; - } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) { - data.pos += 4; - } else if (flags & WE_HAVE_A_SCALE) { - data.pos += 2; - } - } - } - /****************************************************************************************************************/ - /* function : CompoundGlypg encode */ - /* comment : After creating a table for the characters you typed, you call directory.encode to encode the table.*/ - /****************************************************************************************************************/ - CompoundGlyph.prototype.encode = function() { - var i, result, _len, _ref; - result = new Data(__slice.call(this.raw.data)); - _ref = this.glyphIDs; - for (i = 0, _len = _ref.length; i < _len; ++i) { - result.pos = this.glyphOffsets[i]; - } - return result.data; - }; - return CompoundGlyph; -})(); - -var LocaTable = (function(_super) { - __extends(LocaTable, _super); - - function LocaTable() { - return LocaTable.__super__.constructor.apply(this, arguments); - } - LocaTable.prototype.tag = "loca"; - LocaTable.prototype.parse = function(data) { - var format, i; - data.pos = this.offset; - format = this.file.head.indexToLocFormat; - if (format === 0) { - return (this.offsets = function() { - var _ref, _results; - _results = []; - for (i = 0, _ref = this.length; i < _ref; i += 2) { - _results.push(data.readUInt16() * 2); - } - return _results; - }.call(this)); - } else { - return (this.offsets = function() { - var _ref, _results; - _results = []; - for (i = 0, _ref = this.length; i < _ref; i += 4) { - _results.push(data.readUInt32()); - } - return _results; - }.call(this)); - } - }; - LocaTable.prototype.indexOf = function(id) { - return this.offsets[id]; - }; - LocaTable.prototype.lengthOf = function(id) { - return this.offsets[id + 1] - this.offsets[id]; - }; - LocaTable.prototype.encode = function(offsets, activeGlyphs) { - var LocaTable = new Uint32Array(this.offsets.length); - var glyfPtr = 0; - var listGlyf = 0; - for (var k = 0; k < LocaTable.length; ++k) { - LocaTable[k] = glyfPtr; - if (listGlyf < activeGlyphs.length && activeGlyphs[listGlyf] == k) { - ++listGlyf; - LocaTable[k] = glyfPtr; - var start = this.offsets[k]; - var len = this.offsets[k + 1] - start; - if (len > 0) { - glyfPtr += len; - } - } - } - var newLocaTable = new Array(LocaTable.length * 4); - for (var j = 0; j < LocaTable.length; ++j) { - newLocaTable[4 * j + 3] = LocaTable[j] & 0x000000ff; - newLocaTable[4 * j + 2] = (LocaTable[j] & 0x0000ff00) >> 8; - newLocaTable[4 * j + 1] = (LocaTable[j] & 0x00ff0000) >> 16; - newLocaTable[4 * j] = (LocaTable[j] & 0xff000000) >> 24; - } - return newLocaTable; - }; - return LocaTable; -})(Table); - -/************************************************************************************/ -/* function : invert */ -/* comment : Change the object's (key: value) to create an object with (value: key).*/ -/************************************************************************************/ -var invert = function(object) { - var key, ret, val; - ret = {}; - for (key in object) { - val = object[key]; - ret[val] = key; - } - return ret; -}; - -/*var successorOf = function (input) { - var added, alphabet, carry, i, index, isUpperCase, last, length, next, result; - alphabet = 'abcdefghijklmnopqrstuvwxyz'; - length = alphabet.length; - result = input; - i = input.length; - while (i >= 0) { - last = input.charAt(--i); - if (isNaN(last)) { - index = alphabet.indexOf(last.toLowerCase()); - if (index === -1) { - next = last; - carry = true; - } - else { - next = alphabet.charAt((index + 1) % length); - isUpperCase = last === last.toUpperCase(); - if (isUpperCase) { - next = next.toUpperCase(); - } - carry = index + 1 >= length; - if (carry && i === 0) { - added = isUpperCase ? 'A' : 'a'; - result = added + next + result.slice(1); - break; - } - } - } - else { - next = +last + 1; - carry = next > 9; - if (carry) { - next = 0; - } - if (carry && i === 0) { - result = '1' + next + result.slice(1); - break; - } - } - result = result.slice(0, i) + next + result.slice(i + 1); - if (!carry) { - break; - } - } - return result; - };*/ - -var Subset = (function() { - function Subset(font) { - this.font = font; - this.subset = {}; - this.unicodes = {}; - this.next = 33; - } - /*Subset.prototype.use = function (character) { - var i, _i, _ref; - if (typeof character === 'string') { - for (i = _i = 0, _ref = character.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { - this.use(character.charCodeAt(i)); - } - return; - } - if (!this.unicodes[character]) { - this.subset[this.next] = character; - return this.unicodes[character] = this.next++; - } - };*/ - /*Subset.prototype.encodeText = function (text) { - var char, i, string, _i, _ref; - string = ''; - for (i = _i = 0, _ref = text.length; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { - char = this.unicodes[text.charCodeAt(i)]; - string += String.fromCharCode(char); - } - return string; - };*/ - /***************************************************************/ - /* function : generateCmap */ - /* comment : Returns the unicode cmap for this font. */ - /***************************************************************/ - Subset.prototype.generateCmap = function() { - var mapping, roman, unicode, unicodeCmap, _ref; - unicodeCmap = this.font.cmap.tables[0].codeMap; - mapping = {}; - _ref = this.subset; - for (roman in _ref) { - unicode = _ref[roman]; - mapping[roman] = unicodeCmap[unicode]; - } - return mapping; - }; - /*Subset.prototype.glyphIDs = function () { - var ret, roman, unicode, unicodeCmap, val, _ref; - unicodeCmap = this.font.cmap.tables[0].codeMap; - ret = [0]; - _ref = this.subset; - for (roman in _ref) { - unicode = _ref[roman]; - val = unicodeCmap[unicode]; - if ((val != null) && __indexOf.call(ret, val) < 0) { - ret.push(val); - } - } - return ret.sort(); - };*/ - /******************************************************************/ - /* function : glyphsFor */ - /* comment : Returns simple glyph objects for the input character.*/ - /******************************************************************/ - Subset.prototype.glyphsFor = function(glyphIDs) { - var additionalIDs, glyph, glyphs, id, _i, _len, _ref; - glyphs = {}; - for (_i = 0, _len = glyphIDs.length; _i < _len; _i++) { - id = glyphIDs[_i]; - glyphs[id] = this.font.glyf.glyphFor(id); - } - additionalIDs = []; - for (id in glyphs) { - glyph = glyphs[id]; - if (glyph != null ? glyph.compound : void 0) { - additionalIDs.push.apply(additionalIDs, glyph.glyphIDs); - } - } - if (additionalIDs.length > 0) { - _ref = this.glyphsFor(additionalIDs); - for (id in _ref) { - glyph = _ref[id]; - glyphs[id] = glyph; - } - } - return glyphs; - }; - /***************************************************************/ - /* function : encode */ - /* comment : Encode various tables for the characters you use. */ - /***************************************************************/ - Subset.prototype.encode = function(glyID, indexToLocFormat) { - var cmap, - code, - glyf, - glyphs, - id, - ids, - loca, - new2old, - newIDs, - nextGlyphID, - old2new, - oldID, - oldIDs, - tables, - _ref; - cmap = CmapTable.encode(this.generateCmap(), "unicode"); - glyphs = this.glyphsFor(glyID); - old2new = { - 0: 0 - }; - _ref = cmap.charMap; - for (code in _ref) { - ids = _ref[code]; - old2new[ids.old] = ids["new"]; - } - nextGlyphID = cmap.maxGlyphID; - for (oldID in glyphs) { - if (!(oldID in old2new)) { - old2new[oldID] = nextGlyphID++; - } - } - new2old = invert(old2new); - newIDs = Object.keys(new2old).sort(function(a, b) { - return a - b; - }); - oldIDs = (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = newIDs.length; _i < _len; _i++) { - id = newIDs[_i]; - _results.push(new2old[id]); - } - return _results; - })(); - glyf = this.font.glyf.encode(glyphs, oldIDs, old2new); - loca = this.font.loca.encode(glyf.offsets, oldIDs); - tables = { - cmap: this.font.cmap.raw(), - glyf: glyf.table, - loca: loca, - hmtx: this.font.hmtx.raw(), - hhea: this.font.hhea.raw(), - maxp: this.font.maxp.raw(), - post: this.font.post.raw(), - name: this.font.name.raw(), - head: this.font.head.encode(indexToLocFormat) - }; - if (this.font.os2.exists) { - tables["OS/2"] = this.font.os2.raw(); - } - return this.font.directory.encode(tables); - }; - return Subset; -})(); - -jsPDF.API.PDFObject = (function() { - var pad; - - function PDFObject() {} - pad = function(str, length) { - return (Array(length + 1).join("0") + str).slice(-length); - }; - /*****************************************************************************/ - /* function : convert */ - /* comment :Converts pdf tag's / FontBBox and array values in / W to strings */ - /*****************************************************************************/ - PDFObject.convert = function(object) { - var e, items, key, out, val; - if (Array.isArray(object)) { - items = (function() { - var _i, _len, _results; - _results = []; - for (_i = 0, _len = object.length; _i < _len; _i++) { - e = object[_i]; - _results.push(PDFObject.convert(e)); - } - return _results; - })().join(" "); - return "[" + items + "]"; - } else if (typeof object === "string") { - return "/" + object; - } else if (object != null ? object.isString : void 0) { - return "(" + object + ")"; - } else if (object instanceof Date) { - return ( - "(D:" + - pad(object.getUTCFullYear(), 4) + - pad(object.getUTCMonth(), 2) + - pad(object.getUTCDate(), 2) + - pad(object.getUTCHours(), 2) + - pad(object.getUTCMinutes(), 2) + - pad(object.getUTCSeconds(), 2) + - "Z)" - ); - } else if ({}.toString.call(object) === "[object Object]") { - out = ["<<"]; - for (key in object) { - val = object[key]; - out.push("/" + key + " " + PDFObject.convert(val)); - } - out.push(">>"); - return out.join("\n"); - } else { - return "" + object; - } - }; - return PDFObject; -})(); - -export { AcroForm, AcroFormAppearance, AcroFormButton, AcroFormCheckBox, AcroFormChoiceField, AcroFormComboBox, AcroFormEditBox, AcroFormListBox, AcroFormPasswordField, AcroFormPushButton, AcroFormRadioButton, AcroFormTextField, GState, ShadingPattern, TilingPattern, jsPDF as default, jsPDF }; diff --git a/js/modules/base/latex.mjs b/js/modules/base/latex.mjs deleted file mode 100644 index 2b56b6c0d80d0..0000000000000 --- a/js/modules/base/latex.mjs +++ /dev/null @@ -1,1595 +0,0 @@ -import { loadScript, settings, isNodeJs, isStr, source_dir, browser, isBatchMode } from '../core.mjs'; -import { getElementRect, _loadJSDOM, makeTranslate } from './BasePainter.mjs'; -import { FontHandler, kSymbol, kWingdings, kTimes } from './FontHandler.mjs'; - - -/* eslint-disable one-var */ - -const symbols_map = { - // greek letters from symbols.ttf - '#alpha': '\u03B1', - '#beta': '\u03B2', - '#chi': '\u03C7', - '#delta': '\u03B4', - '#varepsilon': '\u03B5', - '#phi': '\u03C6', - '#gamma': '\u03B3', - '#eta': '\u03B7', - '#iota': '\u03B9', - '#varphi': '\u03C6', - '#kappa': '\u03BA', - '#lambda': '\u03BB', - '#mu': '\u03BC', - '#nu': '\u03BD', - '#omicron': '\u03BF', - '#pi': '\u03C0', - '#theta': '\u03B8', - '#rho': '\u03C1', - '#sigma': '\u03C3', - '#tau': '\u03C4', - '#upsilon': '\u03C5', - '#varomega': '\u03D6', - '#omega': '\u03C9', - '#xi': '\u03BE', - '#psi': '\u03C8', - '#zeta': '\u03B6', - '#Alpha': '\u0391', - '#Beta': '\u0392', - '#Chi': '\u03A7', - '#Delta': '\u0394', - '#Epsilon': '\u0395', - '#Phi': '\u03A6', - '#Gamma': '\u0393', - '#Eta': '\u0397', - '#Iota': '\u0399', - '#vartheta': '\u03D1', - '#Kappa': '\u039A', - '#Lambda': '\u039B', - '#Mu': '\u039C', - '#Nu': '\u039D', - '#Omicron': '\u039F', - '#Pi': '\u03A0', - '#Theta': '\u0398', - '#Rho': '\u03A1', - '#Sigma': '\u03A3', - '#Tau': '\u03A4', - '#Upsilon': '\u03A5', - '#varsigma': '\u03C2', - '#Omega': '\u03A9', - '#Xi': '\u039E', - '#Psi': '\u03A8', - '#Zeta': '\u0396', - '#varUpsilon': '\u03D2', - '#epsilon': '\u03B5', - - // second set from symbols.ttf - '#leq': '\u2264', - '#/': '\u2044', - '#infty': '\u221E', - '#voidb': '\u0192', - '#club': '\u2663', - '#diamond': '\u2666', - '#heart': '\u2665', - '#spade': '\u2660', - '#leftrightarrow': '\u2194', - '#leftarrow': '\u2190', - '#uparrow': '\u2191', - '#rightarrow': '\u2192', - '#downarrow': '\u2193', - '#circ': '\u2E30', - '#pm': '\xB1', - '#doublequote': '\u2033', - '#geq': '\u2265', - '#times': '\xD7', - '#propto': '\u221D', - '#partial': '\u2202', - '#bullet': '\u2022', - '#divide': '\xF7', - '#neq': '\u2260', - '#equiv': '\u2261', - '#approx': '\u2248', // should be \u2245 ? - '#3dots': '\u2026', - '#cbar': '\x7C', - '#topbar': '\xAF', - '#downleftarrow': '\u21B5', - '#aleph': '\u2135', - '#Jgothic': '\u2111', - '#Rgothic': '\u211C', - '#voidn': '\u2118', - '#otimes': '\u2297', - '#oplus': '\u2295', - '#oslash': '\u2205', - '#cap': '\u2229', - '#cup': '\u222A', - '#supset': '\u2283', - '#supseteq': '\u2287', - '#notsubset': '\u2284', - '#subset': '\u2282', - '#subseteq': '\u2286', - '#in': '\u2208', - '#notin': '\u2209', - '#angle': '\u2220', - '#nabla': '\u2207', - '#oright': '\xAE', - '#ocopyright': '\xA9', - '#trademark': '\u2122', - '#prod': '\u220F', - '#surd': '\u221A', - '#upoint': '\u2027', - '#corner': '\xAC', - '#wedge': '\u2227', - '#vee': '\u2228', - '#Leftrightarrow': '\u21D4', - '#Leftarrow': '\u21D0', - '#Uparrow': '\u21D1', - '#Rightarrow': '\u21D2', - '#Downarrow': '\u21D3', - '#void2': '', // dummy, placeholder - '#LT': '\x3C', - '#void1': '\xAE', - '#copyright': '\xA9', - '#void3': '\u2122', // it is dummy placeholder, TM - '#sum': '\u2211', - '#arctop': '\u239B', - '#lbar': '\u23A2', - '#arcbottom': '\u239D', - '#void4': '', // dummy, placeholder - '#void8': '\u23A2', // same as lbar - '#bottombar': '\u230A', - '#arcbar': '\u23A7', - '#ltbar': '\u23A8', - '#AA': '\u212B', - '#aa': '\xE5', - '#void06': '', - '#GT': '\x3E', - '#int': '\u222B', - '#forall': '\u2200', - '#exists': '\u2203', - '#textendash': '\u2013', - '#textemdash': '\u2014', - // here ends second set from symbols.ttf - - // more greek symbols - '#koppa': '\u03DF', - '#sampi': '\u03E1', - '#stigma': '\u03DB', - '#san': '\u03FB', - '#sho': '\u03F8', - '#varcoppa': '\u03D9', - '#digamma': '\u03DD', - '#Digamma': '\u03DC', - '#Koppa': '\u03DE', - '#varKoppa': '\u03D8', - '#Sampi': '\u03E0', - '#Stigma': '\u03DA', - '#San': '\u03FA', - '#Sho': '\u03F7', - - '#vec': '', - '#dot': '\u22C5', - '#hat': '\xB7', - '#ddot': '', - '#acute': '', - '#grave': '', - '#check': '\u2713', - '#tilde': '\u02DC', - '#slash': '\u2044', - '#hbar': '\u0127', - '#box': '\u25FD', - '#Box': '\u2610', - '#parallel': '\u2225', - '#perp': '\u22A5', - '#odot': '\u2299', - '#left': '', - '#right': '', - '{}': '', - - '#mp': '\u2213', - - '#P': '\u00B6', // paragraph - - // only required for MathJax to provide correct replacement - '#sqrt': '\u221A', - '#bar': '', - '#overline': '', - '#underline': '', - '#strike': '' -}; - - -/** @summary Create a single regex to detect any symbol to replace, apply longer symbols first - * @private */ -const symbolsRegexCache = new RegExp(Object.keys(symbols_map).sort((a, b) => (a.length < b.length ? 1 : (a.length > b.length ? -1 : 0))).join('|'), 'g'); - -/** @summary Simple replacement of latex letters - * @private */ -function translateLaTeX(str) { - while ((str.length > 2) && (str.at(0) === '{') && (str.at(-1) === '}')) - str = str.slice(1, str.length - 1); - - return str.replace(symbolsRegexCache, ch => symbols_map[ch]).replace(/\{\}/g, ''); -} - -// array with relative width of base symbols from range 32..126 -// eslint-disable-next-line -const base_symbols_width = [453,535,661,973,955,1448,1242,324,593,596,778,1011,200,570,200,492,947,885,947,947,947,947,947,947,947,947,511,495,980,1010,987,893,1624,1185,1147,1193,1216,1080,1028,1270,1274,531,910,1177,1004,1521,1252,1276,1111,1276,1164,1056,1073,1215,1159,1596,1150,1124,1065,540,591,540,837,874,572,929,972,879,973,901,569,967,973,453,458,903,453,1477,973,970,972,976,638,846,548,973,870,1285,884,864,835,656,430,656,1069]; - -// eslint-disable-next-line -const extra_symbols_width = {945:1002,946:996,967:917,948:953,949:834,966:1149,947:847,951:989,953:516,954:951,955:913,956:1003,957:862,959:967,960:1070,952:954,961:973,963:1017,964:797,965:944,982:1354,969:1359,958:803,968:1232,950:825,913:1194,914:1153,935:1162,916:1178,917:1086,934:1358,915:1016,919:1275,921:539,977:995,922:1189,923:1170,924:1523,925:1253,927:1281,928:1281,920:1285,929:1102,931:1041,932:1069,933:1135,962:848,937:1279,926:1092,936:1334,918:1067,978:1154,8730:986,8804:940,8260:476,8734:1453,402:811,9827:1170,9830:931,9829:1067,9824:965,8596:1768,8592:1761,8593:895,8594:1761,8595:895,710:695,177:955,8243:680,8805:947,215:995,8733:1124,8706:916,8226:626,247:977,8800:969,8801:1031,8776:976,8230:1552,175:883,8629:1454,8501:1095,8465:1002,8476:1490,8472:1493,8855:1417,8853:1417,8709:1205,8745:1276,8746:1404,8839:1426,8835:1426,8836:1426,8838:1426,8834:1426,8747:480,8712:1426,8713:1426,8736:1608,8711:1551,174:1339,169:1339,8482:1469,8719:1364,729:522,172:1033,8743:1383,8744:1383,8660:1768,8656:1496,8657:1447,8658:1496,8659:1447,8721:1182,9115:882,9144:1000,9117:882,8970:749,9127:1322,9128:1322,8491:1150,229:929,8704:1397,8707:1170,8901:524,183:519,10003:1477,732:692,295:984,9725:1780,9744:1581,8741:737,8869:1390,8857:1421}; - -/** @summary Calculate approximate labels width - * @private */ -function approximateLabelWidth(label, font, fsize) { - if (Number.isInteger(font)) - font = new FontHandler(font, fsize); - - const len = label.length, - symbol_width = (fsize || font.size) * font.aver_width; - if (font.isMonospace()) - return len * symbol_width; - - let sum = 0; - for (let i = 0; i < len; ++i) { - const code = label.charCodeAt(i); - if ((code >= 32) && (code < 127)) - sum += base_symbols_width[code - 32]; - else - sum += extra_symbols_width[code] || 1000; - } - - return sum / 1000 * symbol_width; -} - -/** @summary array defines features supported by latex parser, used by both old and new parsers - * @private */ -const latex_features = [ - { name: '#it{', bi: 'italic' }, // italic - { name: '#bf{', bi: 'bold' }, // bold - { name: '#underline{', deco: 'underline' }, // underline - { name: '#overline{', deco: 'overline' }, // overline - { name: '#strike{', deco: 'line-through' }, // line through - { name: '#kern[', arg: 'float', shift: 'x' }, // horizontal shift - { name: '#lower[', arg: 'float', shift: 'y' }, // vertical shift - { name: '#scale[', arg: 'float' }, // font scale - { name: '#color[', arg: 'int' }, // font color - { name: '#font[', arg: 'int' }, // font face - { name: '#url[', arg: 'string' }, // url link - { name: '_{', low_up: 'low' }, // subscript - { name: '^{', low_up: 'up' }, // superscript - { name: '#bar{', deco: 'overline' /* accent: '\u02C9' */ }, // '\u0305' - { name: '#hat{', accent: '\u02C6', hasw: true }, // '\u0302' - { name: '#check{', accent: '\u02C7', hasw: true }, // '\u030C' - { name: '#acute{', accent: '\u02CA' }, // '\u0301' - { name: '#grave{', accent: '\u02CB' }, // '\u0300' - { name: '#dot{', accent: '\u02D9' }, // '\u0307' - { name: '#ddot{', accent: '\u02BA', hasw: true }, // '\u0308' - { name: '#tilde{', accent: '\u02DC', hasw: true }, // '\u0303' - { name: '#slash{', accent: '\u2215' }, // '\u0337' - { name: '#vec{', accent: '\u02ED', hasw: true }, // '\u0350' arrowhead - { name: '#frac{', twolines: 'line', middle: true }, - { name: '#splitmline{', twolines: true, middle: true }, - { name: '#splitline{', twolines: true }, - { name: '#sqrt[', arg: 'int', sqrt: true }, // root with arbitrary power - { name: '#sqrt{', sqrt: true }, // square root - { name: '#sum', special: '\u2211', w: 0.8, h: 0.9 }, - { name: '#int', special: '\u222B', w: 0.3, h: 1.0 }, - { name: '#left[', right: '#right]', braces: '[]' }, - { name: '#left(', right: '#right)', braces: '()' }, - { name: '#left{', right: '#right}', braces: '{}' }, - { name: '#left|', right: '#right|', braces: '||' }, - { name: '#[]{', braces: '[]' }, - { name: '#(){', braces: '()' }, - { name: '#{}{', braces: '{}' }, - { name: '#||{', braces: '||' } -]; - -// taken from: https://sites.math.washington.edu/~marshall/cxseminar/symbol.htm, starts from 33 -// eslint-disable-next-line -const symbolsMap = [0,8704,0,8707,0,0,8717,0,0,8727,0,0,8722,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8773,913,914,935,916,917,934,915,919,921,977,922,923,924,925,927,928,920,929,931,932,933,962,937,926,936,918,0,8756,0,8869,0,0,945,946,967,948,949,966,947,951,953,981,954,955,956,957,959,960,952,961,963,964,965,982,969,958,968,950,0,402,0,8764,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,978,8242,8804,8260,8734,0,9827,9830,9829,9824,8596,8592,8593,8594,8595,0,0,8243,8805,0,8733,8706,8729,0,8800,8801,8776,8230,0,0,8629,8501,8465,8476,8472,8855,8853,8709,8745,8746,8835,8839,8836,8834,8838,8712,8713,8736,8711,0,0,8482,8719,8730,8901,0,8743,8744,8660,8656,8657,8658,8659,9674,9001,0,0,8482,8721,0,0,0,0,0,0,0,0,0,0,8364,9002,8747,8992,0,8993]; - -// taken from http://www.alanwood.net/demos/wingdings.html, starts from 33 -// eslint-disable-next-line -const wingdingsMap = [128393,9986,9985,128083,128365,128366,128367,128383,9990,128386,128387,128234,128235,128236,128237,128193,128194,128196,128463,128464,128452,8987,128430,128432,128434,128435,128436,128427,128428,9991,9997,128398,9996,128076,128077,128078,9756,9758,9757,9759,128400,9786,128528,9785,128163,9760,127987,127985,9992,9788,128167,10052,128326,10014,128328,10016,10017,9770,9775,2384,9784,9800,9801,9802,9803,9804,9805,9806,9807,9808,9809,9810,9811,128624,128629,9679,128318,9632,9633,128912,10065,10066,11047,10731,9670,10070,11045,8999,11193,8984,127989,127990,128630,128631,0,9450,9312,9313,9314,9315,9316,9317,9318,9319,9320,9321,9471,10102,10103,10104,10105,10106,10107,10108,10109,10110,10111,128610,128608,128609,128611,128606,128604,128605,128607,183,8226,9642,9898,128902,128904,9673,9678,128319,9642,9723,128962,10022,9733,10038,10036,10041,10037,11216,8982,10209,8977,11217,10026,10032,128336,128337,128338,128339,128340,128341,128342,128343,128344,128345,128346,128347,11184,11185,11186,11187,11188,11189,11190,11191,128618,128619,128597,128596,128599,128598,128592,128593,128594,128595,9003,8998,11160,11162,11161,11163,11144,11146,11145,11147,129128,129130,129129,129131,129132,129133,129135,129134,129144,129146,129145,129147,129148,129149,129151,129150,8678,8680,8679,8681,11012,8691,11008,11009,11011,11010,129196,129197,128502,10004,128503,128505]; - -const symbolsPdfMap = {}; - -/** @summary Return code for symbols from symbols.ttf - * @desc Used in PDF generation - * @private */ -function remapSymbolTtfCode(code) { - if (!symbolsPdfMap[0x3B1]) { - let cnt = 0; - for (const key in symbols_map) { - const symbol = symbols_map[key]; - if (symbol.length === 1) { - let letter; - if (cnt < 54) { - const opGreek = cnt; - // see code in TLatex.cxx, line 1302 - letter = 97 + opGreek; - if (opGreek > 25) - letter -= 58; - if (opGreek === 52) - letter = 0o241; // varUpsilon - if (opGreek === 53) - letter = 0o316; // epsilon - } else { - // see code in TLatex.cxx, line 1323 - const opSpec = cnt - 54; - letter = 0o243 + opSpec; - switch (opSpec) { - case 75: letter = 0o305; break; // AA Angstroem - case 76: letter = 0o345; break; // aa Angstroem - case 80: letter = 0o42; break; // #forall - case 81: letter = 0o44; break; // #exists - case 82: letter = 0o55; break; // #textendash - case 83: letter = 0o276; break; // #textemdash - } - } - const scode = symbol.charCodeAt(0); - if (scode > 0x80) - symbolsPdfMap[scode] = letter; - } - if (++cnt > 54 + 84) - break; - } - for (let k = 0; k < symbolsMap.length; ++k) { - const scode2 = symbolsMap[k]; - if (scode2) - symbolsPdfMap[scode2] = k + 33; - } - } - return symbolsPdfMap[code] ?? code; -} - - -/** @summary Reformat text node if it includes greek or special symbols - * @desc Used in PDF generation where greek symbols are not available - * @private */ -function replaceSymbolsInTextNode(node) { - if (node.$text && node.$font) { - node.$originalHTML = node.innerHTML; - node.$originalFont = node.getAttribute('font-family'); - - node.innerHTML = node.$text; - if (settings.LoadSymbolTtf) - node.setAttribute('font-family', node.$font.isSymbol); - else - node.setAttribute('font-family', (node.$font.isSymbol === kWingdings) ? 'zapfdingbats' : 'symbol'); - return node.$font.isSymbol; - } - - if (node.childNodes.length !== 1) - return false; - const txt = node.textContent; - if (!txt) - return false; - let new_html = '', lasti = -1; - for (let i = 0; i < txt.length; i++) { - const code = txt.charCodeAt(i), - newcode = remapSymbolTtfCode(code); - if (code !== newcode) { - new_html += txt.slice(lasti + 1, i) + `${String.fromCharCode(newcode)} `; - lasti = i; - } - } - - if (lasti < 0) - return false; - - if (lasti < txt.length - 1) - new_html += txt.slice(lasti + 1, txt.length); - - node.$originalHTML = node.innerHTML; - node.$originalFont = node.getAttribute('font-family'); - node.innerHTML = new_html; - return kSymbol; -} - - -/** @summary Replace codes from symbols.ttf into normal font - when symbols.ttf cannot be used - * @private */ -function replaceSymbols(s, name) { - const m = name === kWingdings ? wingdingsMap : symbolsMap; - let res = ''; - for (let k = 0; k < s.length; ++k) { - const code = s.charCodeAt(k), - new_code = (code > 32) ? m[code - 33] : 0; - res += String.fromCodePoint(new_code || code); - } - return res; -} - -/** @summary Just add plain text to the SVG text elements - * @private */ -function producePlainText(painter, txt_node, arg) { - arg.plain = true; - if (arg.simple_latex) - arg.text = translateLaTeX(arg.text); // replace latex symbols - if (arg.font?.isSymbol) { - txt_node.text(replaceSymbols(arg.text, arg.font.isSymbol)); - txt_node.property('$text', arg.text); - txt_node.property('$font', arg.font); - } else - txt_node.text(arg.text); -} - -/** @summary Check if plain text - * @private */ -function isPlainText(txt) { - return !txt || ((txt.indexOf('#') < 0) && (txt.indexOf('{') < 0)); -} - -/** @summary translate TLatex and draw inside provided g element - * @desc use together with normal elements - * @private */ -function parseLatex(node, arg, label, curr) { - let nelements = 0; - - const currG = () => { - if (!curr.g) - curr.g = node.append('svg:g'); - return curr.g; - }; - - const shiftX = dx => { curr.x += Math.round(dx); }; - - const extendPosition = (x1, y1, x2, y2) => { - if (!curr.rect) - curr.rect = { x1, y1, x2, y2 }; - else { - curr.rect.x1 = Math.min(curr.rect.x1, x1); - curr.rect.y1 = Math.min(curr.rect.y1, y1); - curr.rect.x2 = Math.max(curr.rect.x2, x2); - curr.rect.y2 = Math.max(curr.rect.y2, y2); - } - - curr.rect.last_y1 = y1; // upper position of last symbols - - curr.rect.width = curr.rect.x2 - curr.rect.x1; - curr.rect.height = curr.rect.y2 - curr.rect.y1; - - if (!curr.parent) - arg.text_rect = curr.rect; - }; - - const addSpaces = nspaces => { - extendPosition(curr.x, curr.y, curr.x + nspaces * curr.fsize * 0.4, curr.y); - shiftX(nspaces * curr.fsize * 0.4); - }; - - /** Position pos.g node which directly attached to curr.g and uses curr.g coordinates */ - const positionGNode = (pos, x, y, inside_gg) => { - x = Math.round(x); - y = Math.round(y); - - makeTranslate(pos.g, x, y); - pos.rect.x1 += x; - pos.rect.x2 += x; - pos.rect.y1 += y; - pos.rect.y2 += y; - - if (inside_gg) - extendPosition(curr.x + pos.rect.x1, curr.y + pos.rect.y1, curr.x + pos.rect.x2, curr.y + pos.rect.y2); - else - extendPosition(pos.rect.x1, pos.rect.y1, pos.rect.x2, pos.rect.y2); - }; - - /** Create special sub-container for elements like sqrt or braces */ - const createGG = is_a => { - const gg = currG(); - - // this is indicator that gg element will be the only one, one can use directly main container - if ((nelements === 1) && !label && !curr.x && !curr.y && !is_a) - return gg; - - return makeTranslate(gg.append(is_a ? 'svg:a' : 'svg:g'), curr.x, curr.y); - }; - - const extractSubLabel = (check_first, lbrace, rbrace) => { - let pos = 0, n = 1, extra_braces = false; - if (!lbrace) - lbrace = '{'; - if (!rbrace) - rbrace = '}'; - - const match = br => (pos + br.length <= label.length) && (label.slice(pos, pos + br.length) === br); - - if (check_first) { - if (!match(lbrace)) { - console.log(`not starting with ${lbrace} in ${label}`); - return -1; - } - label = label.slice(lbrace.length); - } - - while (n && (pos < label.length)) { - if (match(lbrace)) { - n++; - pos += lbrace.length; - } else if (match(rbrace)) { - n--; - pos += rbrace.length; - if ((n === 0) && (typeof check_first === 'string') && match(check_first + lbrace)) { - // handle special case like a^{b}^{2} should mean a^{b^{2}} - n++; - pos += lbrace.length + check_first.length; - check_first = true; - extra_braces = true; - } - } else pos++; - } - if (n) { - console.log(`mismatch with open ${lbrace} and closing ${rbrace} in ${label}`); - return -1; - } - - let sublabel = label.slice(0, pos - rbrace.length); - - if (extra_braces) - sublabel = lbrace + sublabel + rbrace; - - label = label.slice(pos); - - return sublabel; - }; - - const createPath = (gg, d, dofill) => { - return gg.append('svg:path') - .attr('d', d || 'M0,0') // provide dummy d value as placeholder, preserve order of attributes - .style('stroke', dofill ? 'none' : (curr.color || arg.color)) - .style('stroke-width', dofill ? null : Math.max(1, Math.round(curr.fsize * (curr.font.weight ? 0.1 : 0.07)))) - .style('fill', dofill ? (curr.color || arg.color) : 'none'); - }; - - const createSubPos = fscale => { - return { - lvl: curr.lvl + 1, x: 0, y: 0, fsize: curr.fsize * (fscale || 1), - color: curr.color, font: curr.font, parent: curr, - painter: curr.painter, italic: curr.italic, bold: curr.bold - }; - }; - - while (label) { - let best = label.length, found = null; - - for (let n = 0; n < latex_features.length; ++n) { - const pos = label.indexOf(latex_features[n].name); - if ((pos >= 0) && (pos < best)) { - best = pos; - found = latex_features[n]; - } - } - - if (best > 0) { - const alone = (best === label.length) && (nelements === 0) && !found; - - nelements++; - - let s = translateLaTeX(label.slice(0, best)), - nbeginspaces = 0, nendspaces = 0; - - while ((nbeginspaces < s.length) && (s[nbeginspaces] === ' ')) - nbeginspaces++; - - if (nbeginspaces > 0) { - addSpaces(nbeginspaces); - s = s.slice(nbeginspaces); - } - - while ((nendspaces < s.length) && (s[s.length - 1 - nendspaces] === ' ')) - nendspaces++; - - if (nendspaces > 0) - s = s.slice(0, s.length - nendspaces); - - if (s || alone) { - // if single text element created, place it directly in the node - const g = curr.g || (alone ? node : currG()), - elem = g.append('svg:text'); - - if (alone && !curr.g) - curr.g = elem; - - // apply font attributes only once, inherited by all other elements - if (curr.ufont) { - curr.font.setPainter(arg.painter); - curr.font.setFont(curr.g); - } - - if (curr.bold !== undefined) - curr.g.attr('font-weight', curr.bold ? 'bold' : 'normal'); - - if (curr.italic !== undefined) - curr.g.attr('font-style', curr.italic ? 'italic' : 'normal'); - - // set fill color directly to element - elem.attr('fill', curr.color || arg.color || null); - - // set font size directly to element to avoid complex control - elem.attr('font-size', Math.max(1, Math.round(curr.fsize))); - - if (curr.font?.isSymbol) { - elem.text(replaceSymbols(s, curr.font.isSymbol)); - elem.property('$text', s); - elem.property('$font', curr.font); - } else - elem.text(s); - - const rect = !isNodeJs() && !settings.ApproxTextSize && !arg.fast - ? getElementRect(elem, 'nopadding') - : { height: curr.fsize * 1.2, width: approximateLabelWidth(s, curr.font, curr.fsize) }; - - if (curr.x) - elem.attr('x', curr.x); - if (curr.y) - elem.attr('y', curr.y); - - // for single symbols like f,l,i,j one gets wrong estimation of total width, use it in sup/sub-scripts - const xgap = (s.length === 1) && !curr.font.isMonospace() && ('lfij'.indexOf(s) >= 0) ? 0.1 * curr.fsize : 0; - - extendPosition(curr.x, curr.y - rect.height * 0.8, curr.x + rect.width, curr.y + rect.height * 0.2); - - if (!alone) { - shiftX(rect.width + xgap); - addSpaces(nendspaces); - curr.xgap = 0; - } else if (curr.deco) { - elem.attr('text-decoration', curr.deco); - curr.deco = ''; // inform that decoration was applied - } else - curr.xgap = xgap; // may be used in accent or somewhere else - } else - addSpaces(nendspaces); - } - - if (!found) - return true; - - // remove preceding block and tag itself - label = label.slice(best + found.name.length); - - nelements++; - - if (found.accent) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const gg = createGG(), - subpos = createSubPos(), - reduce = (sublabel.length !== 1) ? 1 : (((sublabel >= 'a') && (sublabel <= 'z') && ('tdbfhkli'.indexOf(sublabel) < 0)) ? 0.75 : 0.9); - - parseLatex(gg, arg, sublabel, subpos); - - const minw = curr.fsize * 0.6, - y1 = Math.round(subpos.rect.y1 * reduce), - dy2 = Math.round(curr.fsize * 0.1), dy = dy2 * 2, - dot = `a${dy2},${dy2},0,0,1,${dy},0a${dy2},${dy2},0,0,1,${-dy},0z`; - let xpos = 0, w = subpos.rect.width; - - // shift symbol when it is too small - if (found.hasw && (w < minw)) { - w = minw; - xpos = (minw - subpos.rect.width) / 2; - } - - const w5 = Math.round(w * 0.5), w3 = Math.round(w * 0.3), w2 = w5 - w3, w8 = w5 + w3; - w = w5 * 2; - - positionGNode(subpos, xpos, 0, true); - - switch (found.name) { - case '#check{': createPath(gg, `M${w2},${y1 - dy}L${w5},${y1}L${w8},${y1 - dy}`); break; - case '#acute{': createPath(gg, `M${w5},${y1}l${dy},${-dy}`); break; - case '#grave{': createPath(gg, `M${w5},${y1}l${-dy},${-dy}`); break; - case '#dot{': createPath(gg, `M${w5 - dy2},${y1}${dot}`, true); break; - case '#ddot{': createPath(gg, `M${w5 - 3 * dy2},${y1}${dot} M${w5 + dy2},${y1}${dot}`, true); break; - case '#tilde{': createPath(gg, `M${w2},${y1} a${w3},${dy},0,0,1,${w3},0 a${w3},${dy},0,0,0,${w3},0`); break; - case '#slash{': createPath(gg, `M${w},${y1}L0,${Math.round(subpos.rect.y2)}`); break; - case '#vec{': createPath(gg, `M${w2},${y1}H${w8}M${w8 - dy},${y1 - dy}l${dy},${dy}l${-dy},${dy}`); break; - default: createPath(gg, `M${w2},${y1}L${w5},${y1 - dy}L${w8},${y1}`); // #hat{ - } - - shiftX(subpos.rect.width + (subpos.xgap ?? 0)); - - continue; - } - - if (found.twolines) { - curr.twolines = true; - - const line1 = extractSubLabel(), - line2 = extractSubLabel(true); - if ((line1 === -1) || (line2 === -1)) - return false; - - const gg = createGG(), - fscale = curr.parent?.twolines ? 0.7 : 1, - subpos1 = createSubPos(fscale); - - parseLatex(gg, arg, line1, subpos1); - - const path = found.twolines === 'line' ? createPath(gg) : null, - subpos2 = createSubPos(fscale); - - parseLatex(gg, arg, line2, subpos2); - - const w = Math.max(subpos1.rect.width, subpos2.rect.width), - dw = subpos1.rect.width - subpos2.rect.width, - dy = -curr.fsize * 0.35; // approximate position of middle line - - positionGNode(subpos1, found.middle && (dw < 0) ? -dw / 2 : 0, dy - subpos1.rect.y2, true); - - positionGNode(subpos2, found.middle && (dw > 0) ? dw / 2 : 0, dy - subpos2.rect.y1, true); - - path?.attr('d', `M0,${Math.round(dy)}h${Math.round(w - curr.fsize * 0.1)}`); - - shiftX(w); - - delete curr.twolines; - - continue; - } - - const extractLowUp = name => { - const res = {}; - - if (name) { - label = '{' + label; - res[name] = extractSubLabel(name === 'low' ? '_' : '^'); - if (res[name] === -1) - return false; - } - - while (label) { - if ((label[0] === '_') && !res.low) { - label = label.slice(1); - res.low = extractSubLabel('_'); - if (res.low === -1) { - console.log(`error with ${found.name} low limit ${label}`); - return false; - } - } else if ((label[0] === '^') && !res.up) { - label = label.slice(1); - res.up = extractSubLabel('^'); - if (res.up === -1) { - console.log(`error with ${found.name} upper limit ${label}`); - return false; - } - } else break; - } - return res; - }; - - if (found.low_up) { - const subs = extractLowUp(found.low_up); - if (!subs) - return false; - - const x = curr.x, dx = 0.03 * curr.fsize, ylow = 0.25 * curr.fsize; - let pos_up, pos_low, w1 = 0, w2 = 0, yup = -curr.fsize; - - if (subs.up) { - pos_up = createSubPos(0.6); - parseLatex(currG(), arg, subs.up, pos_up); - } - - if (subs.low) { - pos_low = createSubPos(0.6); - parseLatex(currG(), arg, subs.low, pos_low); - } - - if (pos_up) { - if (!pos_low && curr.rect) - yup = Math.min(yup, curr.rect.last_y1); - positionGNode(pos_up, x + dx, yup - pos_up.rect.y1 - curr.fsize * 0.1); - w1 = pos_up.rect.width; - } - - if (pos_low) { - positionGNode(pos_low, x + dx, ylow - pos_low.rect.y2 + curr.fsize * 0.1); - w2 = pos_low.rect.width; - } - - shiftX(dx + Math.max(w1, w2)); - - continue; - } - - if (found.special) { - // this is sum and integral, now make fix height, later can adjust to right-content size - - const subs = extractLowUp() || {}, - gg = createGG(), path = createPath(gg), - h = Math.round(curr.fsize * 1.7), w = Math.round(curr.fsize), r = Math.round(h * 0.1); - let x_up, x_low; - - if (found.name === '#sum') { - x_up = x_low = w / 2; - path.attr('d', `M${w},${Math.round(-0.75 * h)}h${-w}l${Math.round(0.4 * w)},${Math.round(0.3 * h)}l${Math.round(-0.4 * w)},${Math.round(0.7 * h)}h${w}`); - } else { - x_up = 3 * r; - x_low = r; - path.attr('d', `M0,${Math.round(0.25 * h - r)}a${r},${r},0,0,0,${2 * r},0v${2 * r - h}a${r},${r},0,1,1,${2 * r},0`); - // path.attr('transform','skewX(-3)'); could use skewX for italic-like style - } - - extendPosition(curr.x, curr.y - 0.6 * h, curr.x + w, curr.y + 0.4 * h); - - if (subs.low) { - const subpos1 = createSubPos(0.6); - parseLatex(gg, arg, subs.low, subpos1); - positionGNode(subpos1, (x_low - subpos1.rect.width / 2), 0.25 * h - subpos1.rect.y1, true); - } - - if (subs.up) { - const subpos2 = createSubPos(0.6); - parseLatex(gg, arg, subs.up, subpos2); - positionGNode(subpos2, (x_up - subpos2.rect.width / 2), -0.75 * h - subpos2.rect.y2, true); - } - - shiftX(w); - - continue; - } - - if (found.braces) { - const rbrace = found.right, - lbrace = rbrace ? found.name : '{', - sublabel = extractSubLabel(false, lbrace, rbrace), - gg = createGG(), - subpos = createSubPos(), - path1 = createPath(gg); - - parseLatex(gg, arg, sublabel, subpos); - - const path2 = createPath(gg), - w = Math.max(2, Math.round(curr.fsize * 0.2)), - r = subpos.rect, dy = Math.round(r.y2 - r.y1), - r_y1 = Math.round(r.y1), r_width = Math.round(r.width); - - switch (found.braces) { - case '||': - path1.attr('d', `M${w},${r_y1}v${dy}`); - path2.attr('d', `M${3 * w + r_width},${r_y1}v${dy}`); - break; - case '[]': - path1.attr('d', `M${2 * w},${r_y1}h${-w}v${dy}h${w}`); - path2.attr('d', `M${2 * w + r_width},${r_y1}h${w}v${dy}h${-w}`); - break; - case '{}': - path1.attr('d', `M${2 * w},${r_y1}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}`); - path2.attr('d', `M${2 * w + r_width},${r_y1}a${w},${w},0,0,1,${w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,0,${w},${w}a${w},${w},0,0,0,${-w},${w}v${dy / 2 - 2 * w}a${w},${w},0,0,1,${-w},${w}`); - break; - default: // () - path1.attr('d', `M${w},${r_y1}a${4 * dy},${4 * dy},0,0,0,0,${dy}`); - path2.attr('d', `M${3 * w + r_width},${r_y1}a${4 * dy},${4 * dy},0,0,1,0,${dy}`); - } - - positionGNode(subpos, 2 * w, 0, true); - - extendPosition(curr.x, curr.y + r.y1, curr.x + 4 * w + r.width, curr.y + r.y2); - - shiftX(4 * w + r.width); - - continue; - } - - if (found.deco) { - const sublabel = extractSubLabel(), - gg = createGG(), - subpos = createSubPos(); - - subpos.deco = found.deco; - - parseLatex(gg, arg, sublabel, subpos); - - const r = subpos.rect; - switch (subpos.deco) { - case 'underline': - createPath(gg, `M0,${Math.round(r.y2)}h${Math.round(r.width)}`); - break; - case 'overline': - createPath(gg, `M0,${Math.round(r.y1)}h${Math.round(r.width)}`); - break; - case 'line-through': - createPath(gg, `M0,${Math.round(0.45 * r.y1 + 0.55 * r.y2)}h${Math.round(r.width)}`); - break; - } - - positionGNode(subpos, 0, 0, true); - shiftX(r.width); - continue; - } - - if (found.bi) { // bold or italic - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const subpos = createSubPos(); - - subpos[found.bi] = !subpos[found.bi]; - - parseLatex(currG(), arg, sublabel, subpos); - - positionGNode(subpos, curr.x, curr.y); - - shiftX(subpos.rect.width); - - continue; - } - - let foundarg = 0; - - if (found.arg) { - const pos = label.indexOf(']{'); - if (pos < 0) { - console.log('missing argument for ', found.name); - return false; - } - foundarg = label.slice(0, pos); - if (found.arg === 'int') { - foundarg = parseInt(foundarg); - if (!Number.isInteger(foundarg)) { - console.log('wrong int argument', label.slice(0, pos)); - return false; - } - } else if (found.arg === 'float') { - foundarg = parseFloat(foundarg); - if (!Number.isFinite(foundarg)) { - console.log('wrong float argument', label.slice(0, pos)); - return false; - } - } - label = label.slice(pos + 2); - } - - if (found.shift) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const subpos = createSubPos(); - - parseLatex(currG(), arg, sublabel, subpos); - - let shiftx = 0, shifty = 0; - if (found.shift === 'x') - shiftx = foundarg * subpos.rect.width; - else - shifty = foundarg * subpos.rect.height; - - positionGNode(subpos, curr.x + shiftx, curr.y + shifty); - - shiftX(subpos.rect.width * (shiftx > 0 ? 1 + foundarg : 1)); - - continue; - } - - if (found.name === '#url[') { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const gg = createGG(true), - subpos = createSubPos(); - - gg.attr('href', foundarg); - if (!isBatchMode() && !curr.painter?.isBatchMode()) { - gg.on('mouseenter', () => gg.style('text-decoration', 'underline')) - .on('mouseleave', () => gg.style('text-decoration', null)) - .append('svg:title').text(`link on ${foundarg}`); - } - - parseLatex(gg, arg, sublabel, subpos); - - positionGNode(subpos, 0, 0, true); - shiftX(subpos.rect.width); - continue; - } - - if ((found.name === '#color[') || (found.name === '#scale[') || (found.name === '#font[')) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const subpos = createSubPos(); - - if (found.name === '#color[') - subpos.color = curr.painter.getColor(foundarg); - else if (found.name === '#font[') { - subpos.font = new FontHandler(foundarg, subpos.fsize); - // here symbols embedding not works, use replacement - if ((subpos.font.name === kSymbol) && !subpos.font.isSymbol) { - subpos.font.isSymbol = kSymbol; - subpos.font.name = kTimes; - } - subpos.font.setUseFullStyle(true); // while embedding - need to enforce full style - subpos.ufont = true; // mark that custom font is applied - } else - subpos.fsize *= foundarg; - - parseLatex(currG(), arg, sublabel, subpos); - - positionGNode(subpos, curr.x, curr.y); - - shiftX(subpos.rect.width); - - continue; - } - - if (found.sqrt) { - const sublabel = extractSubLabel(); - if (sublabel === -1) - return false; - - const gg = createGG(), subpos = createSubPos(); - let subpos0; - - if (found.arg) { - subpos0 = createSubPos(0.7); - parseLatex(gg, arg, foundarg.toString(), subpos0); - } - - // placeholder for the sqrt sign - const path = createPath(gg); - - parseLatex(gg, arg, sublabel, subpos); - - const r = subpos.rect, - h = Math.round(r.height), - h1 = Math.round(r.height * 0.1), - w = Math.round(r.width), midy = Math.round((r.y1 + r.y2) / 2), - f2 = Math.round(curr.fsize * 0.2), r_y2 = Math.round(r.y2); - - if (subpos0) - positionGNode(subpos0, 0, midy - subpos0.fsize * 0.3, true); - - path.attr('d', `M0,${midy}h${h1}l${h1},${r_y2 - midy - f2}l${h1},${f2 - h}h${Math.round(h * 0.2 + w)}v${h1}`); - - positionGNode(subpos, h * 0.4, 0, true); - - extendPosition(curr.x, curr.y + r.y1 - curr.fsize * 0.1, curr.x + w + h * 0.6, curr.y + r.y2); - - shiftX(w + h * 0.6); - - continue; - } - } - - return true; -} - -/** @summary translate TLatex and draw inside provided g element - * @desc use together with normal elements - * @private */ -function produceLatex(painter, node, arg) { - const pos = { lvl: 0, g: node, x: 0, y: 0, dx: 0, dy: -0.1, fsize: arg.font_size, font: arg.font, parent: null, painter }; - return parseLatex(node, arg, arg.text, pos); -} - -let _mj_loading; - -/** @summary Load MathJax functionality, - * @desc one need not only to load script but wait for initialization - * @private */ -async function loadMathjax() { - const loading = _mj_loading !== undefined; - - if (!loading && (typeof globalThis.MathJax !== 'undefined')) - return globalThis.MathJax; - - if (!loading) - _mj_loading = []; - - const promise = new Promise(resolve => { - if (_mj_loading) - _mj_loading.push(resolve); - else - resolve(globalThis.MathJax); - }); - - if (loading) - return promise; - - const svg = { - scale: 1, // global scaling factor for all expressions - minScale: 0.5, // smallest scaling factor to use - mtextInheritFont: false, // true to make mtext elements use surrounding font - merrorInheritFont: true, // true to make merror text use surrounding font - mathmlSpacing: false, // true for MathML spacing rules, false for TeX rules - skipAttributes: {}, // RFDa and other attributes NOT to copy to the output - exFactor: 0.5, // default size of ex in em units - displayAlign: 'center', // default for indentalign when set to 'auto' - displayIndent: '0', // default for indentshift when set to 'auto' - fontCache: 'local', // or 'global' or 'none' - localID: null, // ID to use for local font cache (for single equation processing) - internalSpeechTitles: true, // insert tags with speech content - titleID: 0 // initial id number to use for aria-labeledby titles - }; - - if (!isNodeJs()) { - window.MathJax = { - options: { - enableMenu: false - }, - loader: { - load: ['[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] - }, - tex: { - packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } - }, - svg, - startup: { - ready() { - MathJax.startup.defaultReady(); - const arr = _mj_loading; - _mj_loading = undefined; - arr.forEach(func => func(globalThis.MathJax)); - } - } - }; - - let mj_dir = '../mathjax/3.2.0'; - if (browser.webwindow && source_dir.indexOf('https://root.cern/js') < 0 && source_dir.indexOf('https://jsroot.gsi.de') < 0) - mj_dir = 'mathjax'; - - return loadScript(source_dir + mj_dir + '/es5/tex-svg.js') - .catch(() => loadScript('https://cdn.jsdelivr.net/npm/mathjax@3.2.0/es5/tex-svg.js')) - .then(() => promise); - } - - let JSDOM; - - return _loadJSDOM().then(handle => { - JSDOM = handle.JSDOM; - return import('mathjax'); - }).then(mj0 => { - // return Promise with mathjax loading - mj0.init({ - loader: { - load: ['input/tex', 'output/svg', '[tex]/color', '[tex]/upgreek', '[tex]/mathtools', '[tex]/physics'] - }, - tex: { - packages: { '[+]': ['color', 'upgreek', 'mathtools', 'physics'] } - }, - svg, - config: { - JSDOM - }, - startup: { - typeset: false, - ready() { - const mj = MathJax; - - mj.startup.registerConstructor('jsdomAdaptor', () => { - return new mj._.adaptors.HTMLAdaptor.HTMLAdaptor(new mj.config.config.JSDOM().window); - }); - mj.startup.useAdaptor('jsdomAdaptor', true); - mj.startup.defaultReady(); - const arr = _mj_loading; - _mj_loading = undefined; - arr.forEach(func => func(mj)); - } - } - }); - - return promise; - }); -} - -const math_symbols_map = { - '#LT': '\\langle', - '#GT': '\\rangle', - '#club': '\\clubsuit', - '#spade': '\\spadesuit', - '#heart': '\\heartsuit', - '#diamond': '\\diamondsuit', - '#voidn': '\\wp', - '#voidb': 'f', - '#copyright': '(c)', - '#ocopyright': '(c)', - '#trademark': 'TM', - '#void3': 'TM', - '#oright': 'R', - '#void1': 'R', - '#3dots': '\\ldots', - '#lbar': '\\mid', - '#void8': '\\mid', - '#divide': '\\div', - '#Jgothic': '\\Im', - '#Rgothic': '\\Re', - '#doublequote': '"', - '#plus': '+', - '#minus': '-', - '#/': '/', - '#upoint': '.', - '#aa': '\\mathring{a}', - '#AA': '\\mathring{A}', - '#omicron': 'o', - '#Alpha': 'A', - '#Beta': 'B', - '#Epsilon': 'E', - '#Zeta': 'Z', - '#Eta': 'H', - '#Iota': 'I', - '#Kappa': 'K', - '#Mu': 'M', - '#Nu': 'N', - '#Omicron': 'O', - '#Rho': 'P', - '#Tau': 'T', - '#Chi': 'X', - '#varomega': '\\varpi', - '#corner': '?', - '#ltbar': '?', - '#bottombar': '?', - '#notsubset': '?', - '#arcbottom': '?', - '#cbar': '?', - '#arctop': '?', - '#topbar': '?', - '#arcbar': '?', - '#downleftarrow': '?', - '#splitline': '\\genfrac{}{}{0pt}{}', - '#it': '\\textit', - '#bf': '\\textbf', - '#frac': '\\frac', - '#left{': '\\lbrace', - '#right}': '\\rbrace', - '#left\\[': '\\lbrack', - '#right\\]': '\\rbrack', - '#\\[\\]{': '\\lbrack', - ' } ': '\\rbrack', - '#\\[': '\\lbrack', - '#\\]': '\\rbrack', - '#{': '\\lbrace', - '#}': '\\rbrace', - ' ': '\\;' -}; - -const mathjax_remap = { - upDelta: 'Updelta', - upGamma: 'Upgamma', - upLambda: 'Uplambda', - upOmega: 'Upomega', - upPhi: 'Upphi', - upPi: 'Uppi', - upPsi: 'Uppsi', - upSigma: 'Upsigma', - upTheta: 'Uptheta', - upUpsilon: 'Upupsilon', - upXi: 'Upxi', - notcong: 'ncong', - notgeq: 'ngeq', - notgr: 'ngtr', - notless: 'nless', - notleq: 'nleq', - notsucc: 'nsucc', - notprec: 'nprec', - notsubseteq: 'nsubseteq', - notsupseteq: 'nsupseteq', - openclubsuit: 'clubsuit', - openspadesuit: 'spadesuit', - dasharrow: 'dashrightarrow', - comp: 'circ', - iiintop: 'iiint', - iintop: 'iint', - ointop: 'oint' -}; - -const mathjax_unicode = { - Digamma: 0x3DC, - upDigamma: 0x3DC, - digamma: 0x3DD, - updigamma: 0x3DD, - Koppa: 0x3DE, - koppa: 0x3DF, - upkoppa: 0x3DF, - upKoppa: 0x3DE, - VarKoppa: 0x3D8, - upVarKoppa: 0x3D8, - varkoppa: 0x3D9, - upvarkoppa: 0x3D9, - varkappa: 0x3BA, // not found archaic kappa - use normal - upvarkappa: 0x3BA, - varbeta: 0x3D0, // not found archaic beta - use normal - upvarbeta: 0x3D0, - Sampi: 0x3E0, - upSampi: 0x3E0, - sampi: 0x3E1, - upsampi: 0x3E1, - Stigma: 0x3DA, - upStigma: 0x3DA, - stigma: 0x3DB, - upstigma: 0x3DB, - San: 0x3FA, - upSan: 0x3FA, - san: 0x3FB, - upsan: 0x3FB, - Sho: 0x3F7, - upSho: 0x3F7, - sho: 0x3F8, - upsho: 0x3F8, - P: 0xB6, - aa: 0xB0, - bulletdashcirc: 0x22B7, - circdashbullet: 0x22B6, - downuparrows: 0x21F5, - updownarrows: 0x21C5, - dashdownarrow: 0x21E3, - dashuparrow: 0x21E1, - complement: 0x2201, - dbar: 0x18C, - ddddot: 0x22EF, - dddot: 0x22EF, - ddots: 0x22F1, - defineequal: 0x225D, - defineeq: 0x225D, - downdownharpoons: 0x2965, - downupharpoons: 0x296F, - updownharpoons: 0x296E, - upupharpoons: 0x2963, - hateq: 0x2259, - ldbrack: 0x27E6, - rdbrack: 0x27E7, - leadsfrom: 0x219C, - leftsquigarrow: 0x21DC, - lightning: 0x2607, - napprox: 0x2249, - nasymp: 0x226D, - nequiv: 0x2262, - nsimeq: 0x2244, - nsubseteq: 0x2288, - nsubset: 0x2284, - notapprox: 0x2249, - notasymp: 0x226D, - notequiv: 0x2262, - notni: 0x220C, - notsimeq: 0x2244, - notsubseteq: 0x2288, - notsubset: 0x2284, - notsupseteq: 0x2289, - notsupset: 0x2285, - nsupset: 0x2285, - setdif: 0x2216, - simarrow: 0x2972, - t: 0x2040, - u: 0x2C7, - v: 0x2C7, - undercurvearrowright: 0x293B, - updbar: 0x18C, - wwbar: 0x2015, - awointop: 0x2232, - awoint: 0x2233, - barintop: 0x2A1C, - barint: 0x2A1B, - cwintop: 0x2231, // no opposite direction, use same - cwint: 0x2231, - cwointop: 0x2233, - cwoint: 0x2232, - oiiintop: 0x2230, - oiiint: 0x2230, - oiintop: 0x222F, - oiint: 0x222F, - slashintop: 0x2A0F, - slashint: 0x2A0F -}; - -const mathjax_asis = ['"', '\'', '`', '=', '~']; - -/** @summary Function translates ROOT TLatex into MathJax format - * @private */ -function translateMath(str, kind, color, painter) { - if (kind !== 2) { - for (const x in math_symbols_map) - str = str.replace(new RegExp(x, 'g'), math_symbols_map[x]); - - for (const x in symbols_map) { - if (x.length > 2) - str = str.replace(new RegExp(x, 'g'), '\\' + x.slice(1)); - } - - // replace all #color[]{} occurrences - let clean = '', first = true; - while (str) { - let p = str.indexOf('#color['); - if ((p < 0) && first) { - clean = str; - break; - } - first = false; - if (p) { - const norm = (p < 0) ? str : str.slice(0, p); - clean += norm; - if (p < 0) - break; - } - - str = str.slice(p + 7); - p = str.indexOf(']{'); - if (p <= 0) - break; - const colindx = parseInt(str.slice(0, p)); - if (!Number.isInteger(colindx)) - break; - const col = painter.getColor(colindx); - let cnt = 1; - str = str.slice(p + 2); - p = -1; - while (cnt && (++p < str.length)) { - if (str[p] === '{') - cnt++; - else if (str[p] === '}') - cnt--; - } - if (cnt) - break; - - const part = str.slice(0, p); - str = str.slice(p + 1); - if (part) - clean += `\\color{${col}}{${part}}`; - } - - str = clean; - } else { - if (str === '\\^') - str = '\\unicode{0x5E}'; - if (str === '\\vec') - str = '\\unicode{0x2192}'; - str = str.replace(/\\\./g, '\\unicode{0x2E}').replace(/\\\^/g, '\\hat'); - for (const x in mathjax_unicode) - str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\unicode{0x${mathjax_unicode[x].toString(16)}}`); - mathjax_asis.forEach(symbol => { - str = str.replace(new RegExp(`(\\\\${symbol})`, 'g'), `\\unicode{0x${symbol.charCodeAt(0).toString(16)}}`); - }); - for (const x in mathjax_remap) - str = str.replace(new RegExp(`\\\\\\b${x}\\b`, 'g'), `\\${mathjax_remap[x]}`); - } - - if (!isStr(color)) - return str; - - // MathJax SVG converter use colors in normal form - // if (color.indexOf('rgb(') >= 0) - // color = color.replace(/rgb/g, '[RGB]') - // .replace(/\(/g, '{') - // .replace(/\)/g, '}'); - return `\\color{${color}}{${str}}`; -} - -/** @summary Workaround to fix size attributes in MathJax SVG - * @private */ -function repairMathJaxSvgSize(painter, mj_node, svg, arg) { - const transform = value => { - if (!value || !isStr(value) || (value.length < 3)) - return null; - const p = value.indexOf('ex'); - if ((p < 0) || (p !== value.length - 2)) - return null; - value = parseFloat(value.slice(0, p)); - return Number.isFinite(value) ? value * arg.font.size * 0.5 : null; - }; - - let width = transform(svg.getAttribute('width')), - height = transform(svg.getAttribute('height')), - valign = svg.getAttribute('style'); - - if (valign && (valign.length > 18) && valign.indexOf('vertical-align:') === 0) { - const p = valign.indexOf('ex;'); - valign = ((p > 0) && (p === valign.length - 3)) ? transform(valign.slice(15, valign.length - 1)) : null; - } else - valign = null; - - width = (!width || (width <= 0.5)) ? 1 : Math.round(width); - height = (!height || (height <= 0.5)) ? 1 : Math.round(height); - - svg.setAttribute('width', width); - svg.setAttribute('height', height); - svg.removeAttribute('style'); - - if (!isNodeJs()) { - const box = getElementRect(mj_node, 'bbox'); - width = 1.05 * box.width; height = 1.05 * box.height; - } - - arg.valign = valign; - - if (arg.scale) - painter.scaleTextDrawing(Math.max(width / arg.width, height / arg.height), arg.draw_g); -} - -/** @summary Apply attributes to mathjax drawing - * @private */ -function applyAttributesToMathJax(painter, mj_node, svg, arg, font_size, svg_factor) { - let mw = parseInt(svg.attr('width')), - mh = parseInt(svg.attr('height')); - - if (isNodeJs()) { - // workaround for NaN in viewBox produced by MathJax - const vb = svg.attr('viewBox'); - if (isStr(vb) && vb.indexOf('NaN') > 0) - svg.attr('viewBox', vb.replaceAll('NaN', '600')); - // console.log('Problematic viewBox', vb, svg.select('text').node()?.innerHTML); - } - - if (Number.isInteger(mh) && Number.isInteger(mw)) { - if (svg_factor > 0) { - mw /= svg_factor; - mh /= svg_factor; - svg.attr('width', Math.round(mw)).attr('height', Math.round(mh)); - } - } else { - const box = getElementRect(mj_node, 'bbox'); // sizes before rotation - mw = box.width || mw || 100; - mh = box.height || mh || 10; - } - - if ((svg_factor > 0) && arg.valign) - arg.valign /= svg_factor; - - if (arg.valign === null) - arg.valign = (font_size - mh) / 2; - - const sign = { x: 1, y: 1 }; - let nx = 'x', ny = 'y'; - if (arg.rotate === 180) - sign.x = sign.y = -1; - else if ((arg.rotate === 270) || (arg.rotate === 90)) { - sign.x = (arg.rotate === 270) ? -1 : 1; - sign.y = -sign.x; - nx = 'y'; - ny = 'x'; // replace names to which align applied - } - - if (arg.align[0] === 'middle') - arg[nx] += sign.x * (arg.width - mw) / 2; - else if (arg.align[0] === 'end') - arg[nx] += sign.x * (arg.width - mw); - - if (arg.align[1] === 'middle') - arg[ny] += sign.y * (arg.height - mh) / 2; - else if (arg.align[1] === 'bottom') - arg[ny] += sign.y * (arg.height - mh); - else if (arg.align[1] === 'bottom-base') - arg[ny] += sign.y * (arg.height - mh - arg.valign); - - let trans = makeTranslate(arg.x, arg.y) || ''; - if (arg.rotate) - trans += `${trans ? ' ' : ''}rotate(${arg.rotate})`; - - mj_node.attr('transform', trans || null).attr('visibility', null); -} - -/** @summary Produce text with MathJax - * @private */ -async function produceMathjax(painter, mj_node, arg) { - const mtext = translateMath(arg.text, arg.latex, arg.color, painter), - options = { em: arg.font.size, ex: arg.font.size / 2, family: arg.font.name, scale: 1, containerWidth: -1, lineWidth: 100000 }; - - return loadMathjax() - .then(mj => mj.tex2svgPromise(mtext, options)) - .then(elem => { - // when adding element to new node, it will be removed from original parent - const svg = elem.querySelector('svg'); - - mj_node.append(() => svg); - - repairMathJaxSvgSize(painter, mj_node, svg, arg); - - arg.mj_func = applyAttributesToMathJax; - return true; - }); -} - -/** @summary Just typeset HTML node with MathJax - * @private */ -async function typesetMathjax(node) { - return loadMathjax().then(mj => mj.typesetPromise(node ? [node] : undefined)); -} - -export { symbols_map, translateLaTeX, producePlainText, isPlainText, produceLatex, loadMathjax, - produceMathjax, typesetMathjax, approximateLabelWidth, replaceSymbolsInTextNode }; diff --git a/js/modules/base/latex3d.mjs b/js/modules/base/latex3d.mjs deleted file mode 100644 index 4ef61cfcfe47e..0000000000000 --- a/js/modules/base/latex3d.mjs +++ /dev/null @@ -1,277 +0,0 @@ -import { isStr, clTLatex } from '../core.mjs'; -import { THREE, getMaterialArgs, getHelveticaFont } from './base3d.mjs'; -import { isPlainText, translateLaTeX, produceLatex } from './latex.mjs'; -import { ObjectPainter } from './ObjectPainter.mjs'; - -class TextParseWrapper { - - constructor(kind, parent, font_size) { - this.kind = kind ?? 'g'; - this.childs = []; - this.x = 0; - this.y = 0; - this.font_size = parent?.font_size ?? font_size; - this.stroke_width = parent?.stroke_width ?? 5; - parent?.childs.push(this); - } - - append(kind) { - if (kind === 'svg:g') - return new TextParseWrapper('g', this); - if (kind === 'svg:text') - return new TextParseWrapper('text', this); - if (kind === 'svg:path') - return new TextParseWrapper('path', this); - console.warn('missing handle for svg', kind); - } - - style(name, value) { - if ((name === 'stroke-width') && value) - this.stroke_width = Number.parseInt(value); - return this; - } - - property(name, value) { - if (value === undefined) - return this[name]; - this[name] = value; - return this; - } - - attr(name, value) { - const get = () => { - if (!value) - return ''; - const res = value[0]; - value = value.slice(1); - return res; - }, getN = skip => { - let p = 0; - while (((value[p] >= '0') && (value[p] <= '9')) || (value[p] === '-')) - p++; - const res = Number.parseInt(value.slice(0, p)); - value = value.slice(p); - if (skip) - get(); - return res; - }; - - if ((name === 'font-size') && value) - this.font_size = Number.parseInt(value); - else if ((name === 'transform') && isStr(value) && (value.indexOf('translate') === 0)) { - const arr = value.slice(value.indexOf('(') + 1, value.lastIndexOf(')')).split(','); - this.x += arr[0] ? Number.parseInt(arr[0]) * 0.01 : 0; - this.y -= arr[1] ? Number.parseInt(arr[1]) * 0.01 : 0; - } else if ((name === 'x') && (this.kind === 'text')) - this.x += Number.parseInt(value) * 0.01; - else if ((name === 'y') && (this.kind === 'text')) - this.y -= Number.parseInt(value) * 0.01; - else if ((name === 'fill') && (this.kind === 'text')) - this.fill = value; - else if ((name === 'd') && (this.kind === 'path') && (value !== 'M0,0')) { - if (get() !== 'M') - return console.error('Not starts with M'); - let x1 = getN(true), y1 = getN(), next; - const pnts = [], add_line = (x2, y2) => { - const angle = Math.atan2(y2 - y1, x2 - x1), - dx = 0.5 * this.stroke_width * Math.sin(angle), - dy = -0.5 * this.stroke_width * Math.cos(angle); - // front side - pnts.push(x1 - dx, y1 - dy, 0, x2 - dx, y2 - dy, 0, x2 + dx, y2 + dy, 0, x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x1 + dx, y1 + dy, 0); - // back side - pnts.push(x1 - dx, y1 - dy, 0, x2 + dx, y2 + dy, 0, x2 - dx, y2 - dy, 0, x1 - dx, y1 - dy, 0, x1 + dx, y1 + dy, 0, x2 + dx, y2 + dy, 0); - x1 = x2; - y1 = y2; - }; - - while ((next = get())) { - switch (next) { - case 'L': - add_line(getN(true), getN()); - continue; - case 'l': - add_line(x1 + getN(true), y1 + getN()); - continue; - case 'H': - add_line(getN(), y1); - continue; - case 'h': - add_line(x1 + getN(), y1); - continue; - case 'V': - add_line(x1, getN()); - continue; - case 'v': - add_line(x1, y1 + getN()); - continue; - case 'a': { - const rx = getN(true), ry = getN(true), - angle = getN(true) / 180 * Math.PI, flag1 = getN(true); - getN(true); // skip unused flag2 - const x2 = x1 + getN(true), - y2 = y1 + getN(), - x0 = x1 + rx * Math.cos(angle), - y0 = y1 + ry * Math.sin(angle); - let angle2 = Math.atan2(y0 - y2, x0 - x2); - if (flag1 && (angle2 < angle)) - angle2 += 2 * Math.PI; - else if (!flag1 && (angle2 > angle)) - angle2 -= 2 * Math.PI; - - for (let cnt = 0; cnt < 10; ++cnt) { - const a = angle + (angle2 - angle) / 10 * (cnt + 1); - add_line(x0 - rx * Math.cos(a), y0 - ry * Math.sin(a)); - } - continue; - } - default: - console.log('not supported path operator', next); - } - } - - if (pnts.length) { - const pos = new Float32Array(pnts); - this.geom = new THREE.BufferGeometry(); - this.geom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); - this.geom.scale(0.01, -0.01, 0.01); - this.geom.computeVertexNormals(); - } - } - return this; - } - - text(v) { - if (this.kind === 'text') - this._text = v; - } - - collect(geoms, geom_args, as_array) { - if (this._text) { - geom_args.size = Math.round(0.01 * this.font_size); - const geom = new THREE.TextGeometry(this._text, geom_args); - if (as_array) { - // this is latex parsing - // while three.js uses full height, make it more like normal fonts - geom.scale(1, 0.9, 1); - geom.translate(0, 0.0005 * this.font_size, 0); - } - geom.translate(this.x, this.y, 0); - geom._fill = this.fill; - geoms.push(geom); - } - if (this.geom) { - this.geom.translate(this.x, this.y, 0); - this.geom._fill = this.fill; - geoms.push(this.geom); - } - - this.childs.forEach(chld => { - chld.x += this.x; - chld.y += this.y; - chld.collect(geoms, geom_args, as_array); - }); - } - -} // class TextParseWrapper - - -function createLatexGeometry(painter, lbl, size, as_array, use_latex = true) { - const geom_args = { font: getHelveticaFont(), size, height: 0, curveSegments: 5 }, - font_size = size * 100, - node = new TextParseWrapper('g', null, font_size), - arg = { font_size, latex: use_latex ? 1 : 0, x: 0, y: 0, text: lbl, align: ['start', 'top'], fast: true, font: { size: font_size, isMonospace: () => false, aver_width: 0.9 } }, - geoms = []; - - if (THREE.REVISION > 162) - geom_args.depth = 0; - else - geom_args.height = 0; - - if (!isPlainText(lbl)) { - produceLatex(painter, node, arg); - node.collect(geoms, geom_args, as_array); - } - - if (!geoms.length) { - geom_args.size = size; - const res = new THREE.TextGeometry(translateLaTeX(lbl), geom_args); - return as_array ? [res] : res; - } - - if (as_array) - return geoms; - - if (geoms.length === 1) - return geoms[0]; - - let total_size = 0; - geoms.forEach(geom => { total_size += geom.getAttribute('position').array.length; }); - - const pos = new Float32Array(total_size), - norm = new Float32Array(total_size); - let indx = 0; - - geoms.forEach(geom => { - const p1 = geom.getAttribute('position').array, - n1 = geom.getAttribute('normal').array; - for (let i = 0; i < p1.length; ++i, ++indx) { - pos[indx] = p1[i]; - norm[indx] = n1[i]; - } - }); - - const fullgeom = new THREE.BufferGeometry(); - fullgeom.setAttribute('position', new THREE.BufferAttribute(pos, 3)); - fullgeom.setAttribute('normal', new THREE.BufferAttribute(norm, 3)); - return fullgeom; -} - - -/** @summary Build three.js object for the TLatex - * @private */ -function build3dlatex(obj, opt, painter, fp) { - if (!painter) - painter = new ObjectPainter(null, obj, opt); - const handle = painter.createAttText({ attr: obj }), - valign = handle.align % 10, - halign = (handle.align - valign) / 10, - text_size = handle.size > 1 ? handle.size : 2 * handle.size * (fp?.size_z3d || 100), - arr3d = createLatexGeometry(painter, obj.fTitle, text_size || 10, true, fp || (obj._typename === clTLatex)), - bb = new THREE.Box3().makeEmpty(); - - arr3d.forEach(geom => { - geom.computeBoundingBox(); - bb.expandByPoint(geom.boundingBox.max); - bb.expandByPoint(geom.boundingBox.min); - }); - - let dx = 0, dy = 0; - if (halign === 2) - dx = 0.5 * (bb.max.x + bb.min.x); - else if (halign === 3) - dx = bb.max.x; - - if (valign === 2) - dy = 0.5 * (bb.max.y + bb.min.y); - else if (valign === 3) - dy = bb.max.y; - - const obj3d = new THREE.Object3D(), - materials = [], - getMaterial = color => { - if (!color) - color = 'black'; - if (!materials[color]) - materials[color] = new THREE.MeshBasicMaterial(getMaterialArgs(color, { vertexColors: false })); - return materials[color]; - }; - - arr3d.forEach(geom => { - geom.translate(-dx, -dy, 0); - obj3d.add(new THREE.Mesh(geom, getMaterial(geom._fill || handle.color))); - }); - - return arr3d.length === 1 ? obj3d.children[0] : obj3d; -} - -export { createLatexGeometry, build3dlatex }; diff --git a/js/modules/base/lzma.mjs b/js/modules/base/lzma.mjs deleted file mode 100644 index 4fd1488ae9e0f..0000000000000 --- a/js/modules/base/lzma.mjs +++ /dev/null @@ -1,614 +0,0 @@ -/** © 2016 Nathan Rugg <nmrugg@gmail.com> - * Code extracted from https://github.com/LZMA-JS/LZMA-JS */ - -const __4294967296 = 4294967296, - P0_longLit = [0, 0], - P1_longLit = [1, 0]; - -function initDim(len) { - // This is MUCH faster than "new Array(len)" in newer versions of v8 (starting with Node.js 0.11.15, which uses v8 3.28.73). - const a = []; - a[len - 1] = undefined; - return a; -} - -function add(a, b) { - return create(a[0] + b[0], a[1] + b[1]); -} - -function compare(a, b) { - if (a[0] === b[0] && a[1] === b[1]) - return 0; - const nega = a[1] < 0, - negb = b[1] < 0; - if (nega && !negb) - return -1; - if (!nega && negb) - return 1; - if (sub(a, b)[1] < 0) - return -1; - return 1; -} - -function create(valueLow, valueHigh) { - valueHigh %= 1.8446744073709552E19; - valueLow %= 1.8446744073709552E19; - const diffHigh = valueHigh % __4294967296, - diffLow = Math.floor(valueLow / __4294967296) * __4294967296; - valueHigh = valueHigh - diffHigh + diffLow; - valueLow = valueLow - diffLow + diffHigh; - while (valueLow < 0) { - valueLow += __4294967296; - valueHigh -= __4294967296; - } - while (valueLow > 4294967295) { - valueLow -= __4294967296; - valueHigh += __4294967296; - } - valueHigh = valueHigh % 1.8446744073709552E19; - while (valueHigh > 9223372032559808512) - valueHigh -= 1.8446744073709552E19; - while (valueHigh < -9223372036854775808) - valueHigh += 1.8446744073709552E19; - return [valueLow, valueHigh]; -} - -function fromInt(value) { - if (value >= 0) - return [value, 0]; - else - return [value + __4294967296, -__4294967296]; -} - -function lowBits_0(a) { - if (a[0] >= 2147483648) - return ~~Math.max(Math.min(a[0] - __4294967296, 2147483647), -2147483648); - else - return ~~Math.max(Math.min(a[0], 2147483647), -2147483648); -} - -function sub(a, b) { - return create(a[0] - b[0], a[1] - b[1]); -} - -function $ByteArrayInputStream(this$static, buf, offset) { - this$static.buf = buf; - this$static.pos = offset ?? 0; - this$static.count = buf.length; - return this$static; -} - -function $read(this$static) { - if (this$static.pos >= this$static.count) - return -1; - return this$static.buf[this$static.pos++]; // & 255; not needed, input always uint8 -} - -function $ByteArrayOutputStream(this$static, buf) { - this$static.buf = buf; - this$static.count = 0; - return this$static; -} - -function $write_0(this$static, buf, off, len) { - arraycopy(buf, off, this$static.buf, this$static.count, len); - this$static.count += len; -} - -function arraycopy(src, srcOfs, dest, destOfs, len) { - dest.set(src.subarray(srcOfs, srcOfs + len), destOfs); -} - -function $CopyBlock(this$static, distance, len) { - let pos = this$static._pos - distance - 1; - if (pos < 0) - pos += this$static._windowSize; - - for (; len; --len) { - if (pos >= this$static._windowSize) - pos = 0; - - this$static._buffer[this$static._pos++] = this$static._buffer[pos++]; - if (this$static._pos >= this$static._windowSize) - $Flush_0(this$static); - } -} - -function $Create_5(this$static, windowSize) { - if (!this$static._buffer || this$static._windowSize !== windowSize) - this$static._buffer = new Uint8Array(windowSize); - - this$static._windowSize = windowSize; - this$static._pos = 0; - this$static._streamPos = 0; -} - -function $Flush_0(this$static) { - const size = this$static._pos - this$static._streamPos; - if (!size) - return; - - $write_0(this$static._stream, this$static._buffer, this$static._streamPos, size); - if (this$static._pos >= this$static._windowSize) - this$static._pos = 0; - - this$static._streamPos = this$static._pos; -} - -function $GetByte(this$static, distance) { - let pos = this$static._pos - distance - 1; - if (pos < 0) - pos += this$static._windowSize; - - return this$static._buffer[pos]; -} - -function $PutByte(this$static, b) { - this$static._buffer[this$static._pos++] = b; - if (this$static._pos >= this$static._windowSize) - $Flush_0(this$static); -} - -function $ReleaseStream(this$static) { - $Flush_0(this$static); - this$static._stream = null; -} - -function GetLenToPosState(len) { - len -= 2; - return (len < 4) ? len : 3; -} - -function StateUpdateChar(index) { - if (index < 4) - return 0; - - return index < 10 ? index - 3 : index - 6; -} - -/** @summary Decode LZMA2 chunks, returns size of unpacked data */ -function $DecodeLZMA2(this$static, input, output_size) { - const rc = this$static.m_RangeDecoder, - window = this$static.m_OutWindow; - let produced = 0, needProps = true, needDictReset = true, control; - - rc.Stream = input; - this$static.state = 0; - this$static.rep0 = 0; - this$static.rep1 = 0; - this$static.rep2 = 0; - this$static.rep3 = 0; - this$static.nowPos64 = P0_longLit; - this$static.prevByte = 0; - - while ((control = $read(input)) !== 0) { - if (control < 0) - throw new Error('LZMA: unexpected end of input'); - - if (control === 1 || control >= 0xE0) { - needDictReset = false; - needProps = true; - this$static.nowPos64 = P0_longLit; - this$static.prevByte = 0; - } else if (needDictReset) - throw new Error('corrupted input'); - - let size, packed, prevByte; - - if (control < 0x80) { - if (control > 2) - throw new Error('corrupted input'); - size = ($read(input) << 8 | $read(input)) + 1; - if (produced + size > output_size) - throw new Error('corrupted input'); - for (let i = 0; i < size; ++i) { - prevByte = $read(input); - if (prevByte < 0) - throw new Error('LZMA: unexpected end of input'); - $PutByte(window, prevByte); - } - this$static.prevByte = prevByte; - this$static.nowPos64 = add(this$static.nowPos64, fromInt(size)); - produced += size; - continue; - } - - size = ((control & 0x1F) << 16 | $read(input) << 8 | $read(input)) + 1; - packed = ($read(input) << 8 | $read(input)) + 1; - - const mode = control >> 5 & 3; - if (mode >= 2) { - const val = $read(input); - if (val < 0 || val >= 9 * 5 * 5) - throw new Error('corrupted input'); - const lc = val % 9, lp = ~~(val / 9) % 5, pb = ~~(val / 45); - if (lc + lp > 4 || !$SetLcLpPb(this$static, lc, lp, pb)) - throw new Error('corrupted input'); - needProps = false; - } else if (needProps) - throw new Error('corrupted input'); - - if (produced + size > output_size || input.pos + packed > input.count) - throw new Error('corrupted input'); - - if (mode >= 1) { - $Init_1(this$static); - this$static.state = 0; - this$static.rep0 = 0; - this$static.rep1 = 0; - this$static.rep2 = 0; - this$static.rep3 = 0; - } - - const chunkEnd = input.pos + packed; - this$static.outSize = add(this$static.nowPos64, fromInt(size)); - $Init_8(rc); - - while (compare(this$static.nowPos64, this$static.outSize) < 0) { - if ($CodeOneChunk(this$static)) - throw new Error('corrupted input'); - } - - if (compare(this$static.nowPos64, this$static.outSize) || input.pos !== chunkEnd) - throw new Error('corrupted input'); - - produced += size; - } - - $ReleaseStream(window); - return produced; -} - -function $CodeOneChunk(this$static) { - let decoder2, distance, len, numDirectBits, posSlot; - const posState = lowBits_0(this$static.nowPos64) & this$static.m_PosStateMask; - if (!$DecodeBit(this$static.m_RangeDecoder, this$static.m_IsMatchDecoders, (this$static.state << 4) + posState)) { - decoder2 = $GetDecoder(this$static.m_LiteralDecoder, lowBits_0(this$static.nowPos64), this$static.prevByte); - if (this$static.state < 7) - this$static.prevByte = $DecodeNormal(decoder2, this$static.m_RangeDecoder); - else - this$static.prevByte = $DecodeWithMatchByte(decoder2, this$static.m_RangeDecoder, $GetByte(this$static.m_OutWindow, this$static.rep0)); - - $PutByte(this$static.m_OutWindow, this$static.prevByte); - this$static.state = StateUpdateChar(this$static.state); - this$static.nowPos64 = add(this$static.nowPos64, P1_longLit); - } else { - if ($DecodeBit(this$static.m_RangeDecoder, this$static.m_IsRepDecoders, this$static.state)) { - len = 0; - if (!$DecodeBit(this$static.m_RangeDecoder, this$static.m_IsRepG0Decoders, this$static.state)) { - if (!$DecodeBit(this$static.m_RangeDecoder, this$static.m_IsRep0LongDecoders, (this$static.state << 4) + posState)) { - this$static.state = this$static.state < 7 ? 9 : 11; - len = 1; - } - } else { - if (!$DecodeBit(this$static.m_RangeDecoder, this$static.m_IsRepG1Decoders, this$static.state)) - distance = this$static.rep1; - else { - if (!$DecodeBit(this$static.m_RangeDecoder, this$static.m_IsRepG2Decoders, this$static.state)) - distance = this$static.rep2; - else { - distance = this$static.rep3; - this$static.rep3 = this$static.rep2; - } - this$static.rep2 = this$static.rep1; - } - this$static.rep1 = this$static.rep0; - this$static.rep0 = distance; - } - if (!len) { - len = $Decode(this$static.m_RepLenDecoder, this$static.m_RangeDecoder, posState) + 2; - this$static.state = this$static.state < 7 ? 8 : 11; - } - } else { - this$static.rep3 = this$static.rep2; - this$static.rep2 = this$static.rep1; - this$static.rep1 = this$static.rep0; - len = 2 + $Decode(this$static.m_LenDecoder, this$static.m_RangeDecoder, posState); - this$static.state = this$static.state < 7 ? 7 : 10; - posSlot = $Decode_0(this$static.m_PosSlotDecoder[GetLenToPosState(len)], this$static.m_RangeDecoder); - if (posSlot >= 4) { - numDirectBits = (posSlot >> 1) - 1; - this$static.rep0 = (2 | posSlot & 1) << numDirectBits; - if (posSlot < 14) - this$static.rep0 += ReverseDecode(this$static.m_PosDecoders, this$static.rep0 - posSlot - 1, this$static.m_RangeDecoder, numDirectBits); - else { - this$static.rep0 += $DecodeDirectBits(this$static.m_RangeDecoder, numDirectBits - 4) << 4; - this$static.rep0 += $ReverseDecode(this$static.m_PosAlignDecoder, this$static.m_RangeDecoder); - if (this$static.rep0 < 0) { - if (this$static.rep0 === -1) - return 1; - - return -1; - } - } - } else - this$static.rep0 = posSlot; - } - if (compare(fromInt(this$static.rep0), this$static.nowPos64) >= 0 || this$static.rep0 >= this$static.m_DictionarySizeCheck) - return -1; - - $CopyBlock(this$static.m_OutWindow, this$static.rep0, len); - this$static.nowPos64 = add(this$static.nowPos64, fromInt(len)); - this$static.prevByte = $GetByte(this$static.m_OutWindow, 0); - } - return 0; -} - -function $Decoder(this$static) { - this$static.m_OutWindow = {}; - this$static.m_RangeDecoder = {}; - this$static.m_IsMatchDecoders = initDim(192); - this$static.m_IsRepDecoders = initDim(12); - this$static.m_IsRepG0Decoders = initDim(12); - this$static.m_IsRepG1Decoders = initDim(12); - this$static.m_IsRepG2Decoders = initDim(12); - this$static.m_IsRep0LongDecoders = initDim(192); - this$static.m_PosSlotDecoder = initDim(4); - this$static.m_PosDecoders = initDim(114); - this$static.m_PosAlignDecoder = $BitTreeDecoder({}, 4); - this$static.m_LenDecoder = $Decoder$LenDecoder({}); - this$static.m_RepLenDecoder = $Decoder$LenDecoder({}); - this$static.m_LiteralDecoder = {}; - for (let i = 0; i < 4; ++i) - this$static.m_PosSlotDecoder[i] = $BitTreeDecoder({}, 6); - - return this$static; -} - -function $Init_1(this$static) { - InitBitModels(this$static.m_IsMatchDecoders); - InitBitModels(this$static.m_IsRep0LongDecoders); - InitBitModels(this$static.m_IsRepDecoders); - InitBitModels(this$static.m_IsRepG0Decoders); - InitBitModels(this$static.m_IsRepG1Decoders); - InitBitModels(this$static.m_IsRepG2Decoders); - InitBitModels(this$static.m_PosDecoders); - $Init_0(this$static.m_LiteralDecoder); - for (let i = 0; i < 4; ++i) - InitBitModels(this$static.m_PosSlotDecoder[i].Models); - - $Init(this$static.m_LenDecoder); - $Init(this$static.m_RepLenDecoder); - InitBitModels(this$static.m_PosAlignDecoder.Models); -} - -function $SetDictionarySize(this$static, dictionarySize, outputSize) { - this$static.m_DictionarySizeCheck = Math.max(dictionarySize, 1); - $Create_5(this$static.m_OutWindow, Math.max(Math.min(this$static.m_DictionarySizeCheck, outputSize), 4096)); -} - -function $SetLcLpPb(this$static, lc, lp, pb) { - if (lc > 8 || lp > 4 || pb > 4) - return 0; - - $Create_0(this$static.m_LiteralDecoder, lp, lc); - const numPosStates = 1 << pb; - $Create(this$static.m_LenDecoder, numPosStates); - $Create(this$static.m_RepLenDecoder, numPosStates); - this$static.m_PosStateMask = numPosStates - 1; - return 1; -} - -function $Create(this$static, numPosStates) { - for (; this$static.m_NumPosStates < numPosStates; ++this$static.m_NumPosStates) { - this$static.m_LowCoder[this$static.m_NumPosStates] = $BitTreeDecoder({}, 3); - this$static.m_MidCoder[this$static.m_NumPosStates] = $BitTreeDecoder({}, 3); - } -} - -function $Decode(this$static, rangeDecoder, posState) { - if (!$DecodeBit(rangeDecoder, this$static.m_Choice, 0)) - return $Decode_0(this$static.m_LowCoder[posState], rangeDecoder); - - let symbol = 8; - if (!$DecodeBit(rangeDecoder, this$static.m_Choice, 1)) - symbol += $Decode_0(this$static.m_MidCoder[posState], rangeDecoder); - else - symbol += 8 + $Decode_0(this$static.m_HighCoder, rangeDecoder); - - return symbol; -} - -function $Decoder$LenDecoder(this$static) { - this$static.m_Choice = initDim(2); - this$static.m_LowCoder = initDim(16); - this$static.m_MidCoder = initDim(16); - this$static.m_HighCoder = $BitTreeDecoder({}, 8); - this$static.m_NumPosStates = 0; - return this$static; -} - -function $Init(this$static) { - InitBitModels(this$static.m_Choice); - for (let posState = 0; posState < this$static.m_NumPosStates; ++posState) { - InitBitModels(this$static.m_LowCoder[posState].Models); - InitBitModels(this$static.m_MidCoder[posState].Models); - } - InitBitModels(this$static.m_HighCoder.Models); -} - - -function $Create_0(this$static, numPosBits, numPrevBits) { - if (this$static.m_Coders != null && this$static.m_NumPrevBits === numPrevBits && this$static.m_NumPosBits === numPosBits) - return; - this$static.m_NumPosBits = numPosBits; - this$static.m_PosMask = (1 << numPosBits) - 1; - this$static.m_NumPrevBits = numPrevBits; - const numStates = 1 << this$static.m_NumPrevBits + this$static.m_NumPosBits; - this$static.m_Coders = initDim(numStates); - for (let i = 0; i < numStates; ++i) - this$static.m_Coders[i] = $Decoder$LiteralDecoder$Decoder2({}); -} - -function $GetDecoder(this$static, pos, prevByte) { - return this$static.m_Coders[((pos & this$static.m_PosMask) << this$static.m_NumPrevBits) + ((prevByte & 255) >>> 8 - this$static.m_NumPrevBits)]; -} - -function $Init_0(this$static) { - const numStates = 1 << this$static.m_NumPrevBits + this$static.m_NumPosBits; - for (let i = 0; i < numStates; ++i) - InitBitModels(this$static.m_Coders[i].m_Decoders); -} - -function $DecodeNormal(this$static, rangeDecoder) { - let symbol = 1; - do - symbol = symbol << 1 | $DecodeBit(rangeDecoder, this$static.m_Decoders, symbol); - while (symbol < 256); - return symbol << 24 >> 24; -} - -function $DecodeWithMatchByte(this$static, rangeDecoder, matchByte) { - let bit, matchBit, symbol = 1; - do { - matchBit = matchByte >> 7 & 1; - matchByte <<= 1; - bit = $DecodeBit(rangeDecoder, this$static.m_Decoders, (1 + matchBit << 8) + symbol); - symbol = symbol << 1 | bit; - if (matchBit !== bit) { - while (symbol < 256) - symbol = symbol << 1 | $DecodeBit(rangeDecoder, this$static.m_Decoders, symbol); - - break; - } - } while (symbol < 256); - return symbol << 24 >> 24; -} - -function $Decoder$LiteralDecoder$Decoder2(this$static) { - this$static.m_Decoders = initDim(768); - return this$static; -} - -function $BitTreeDecoder(this$static, numBitLevels) { - this$static.NumBitLevels = numBitLevels; - this$static.Models = initDim(1 << numBitLevels); - return this$static; -} - -function $Decode_0(this$static, rangeDecoder) { - let m = 1; - for (let bitIndex = this$static.NumBitLevels; bitIndex; --bitIndex) - m = (m << 1) + $DecodeBit(rangeDecoder, this$static.Models, m); - - return m - (1 << this$static.NumBitLevels); -} - -function $ReverseDecode(this$static, rangeDecoder) { - let bit, bitIndex, m = 1, symbol = 0; - for (bitIndex = 0; bitIndex < this$static.NumBitLevels; ++bitIndex) { - bit = $DecodeBit(rangeDecoder, this$static.Models, m); - m <<= 1; - m += bit; - symbol |= bit << bitIndex; - } - return symbol; -} - -function ReverseDecode(Models, startIndex, rangeDecoder, NumBitLevels) { - let bit, bitIndex, m = 1, symbol = 0; - for (bitIndex = 0; bitIndex < NumBitLevels; ++bitIndex) { - bit = $DecodeBit(rangeDecoder, Models, startIndex + m); - m <<= 1; - m += bit; - symbol |= bit << bitIndex; - } - return symbol; -} - -function $DecodeBit(this$static, probs, index) { - const prob = probs[index], - newBound = (this$static.Range >>> 11) * prob; - if ((this$static.Code ^ -2147483648) < (newBound ^ -2147483648)) { - this$static.Range = newBound; - probs[index] = prob + (2048 - prob >>> 5) << 16 >> 16; - if (!(this$static.Range & -16777216)) { - this$static.Code = this$static.Code << 8 | $read(this$static.Stream); - this$static.Range <<= 8; - } - return 0; - } else { - this$static.Range -= newBound; - this$static.Code -= newBound; - probs[index] = prob - (prob >>> 5) << 16 >> 16; - if (!(this$static.Range & -16777216)) { - this$static.Code = this$static.Code << 8 | $read(this$static.Stream); - this$static.Range <<= 8; - } - return 1; - } -} - -function $DecodeDirectBits(this$static, numTotalBits) { - let i, t, result = 0; - for (i = numTotalBits; i; --i) { - this$static.Range >>>= 1; - t = this$static.Code - this$static.Range >>> 31; - this$static.Code -= this$static.Range & t - 1; - result = result << 1 | 1 - t; - if (!(this$static.Range & -16777216)) { - this$static.Code = this$static.Code << 8 | $read(this$static.Stream); - this$static.Range <<= 8; - } - } - return result; -} - -function $Init_8(this$static) { - this$static.Code = 0; - this$static.Range = -1; - for (let i = 0; i < 5; ++i) - this$static.Code = this$static.Code << 8 | $read(this$static.Stream); -} - -function InitBitModels(probs) { - for (let i = probs.length - 1; i >= 0; --i) - probs[i] = 1024; -} - -/** @summary decompress ROOT LZMA buffer - * @desc ROOT buffer is XZ stream with single block of LZMA2 data */ -function decompress(uint8arr, tgt8arr, expected_size) { - let pos = 14; - - const readVarInt = () => { - let res = 0, mult = 1, b; - do { - b = uint8arr[pos++]; - res += (b & 0x7F) * mult; - mult *= 128; - } while ((b & 0x80) && (mult < 2 ** 49)); - return res; - }; - - if ((uint8arr.length < 32) || [0xFD, 0x37, 0x7A, 0x58, 0x5A, 0].some((v, i) => uint8arr[i] !== v)) - throw new Error('LZMA: wrong XZ stream header'); - - const hdrend = 12 + (uint8arr[12] + 1) * 4, flags = uint8arr[13]; - - if (flags & 0x40) readVarInt(); - if (flags & 0x80) readVarInt(); - - // only single LZMA2 filter is supported - if ((flags & 3) || (readVarInt() !== 0x21) || (readVarInt() !== 1) || (hdrend > uint8arr.length) || (pos >= hdrend)) - throw new Error('LZMA: unsupported XZ block'); - - const dictprop = uint8arr[pos]; - if (dictprop > 40) - throw new Error('LZMA: wrong dictionary size'); - const dictsize = dictprop === 40 ? 0xFFFFFFFF : (2 | (dictprop & 1)) * 2 ** ((dictprop >> 1) + 11), - decoder = $Decoder({}), - output = $ByteArrayOutputStream({}, tgt8arr); - - $SetDictionarySize(decoder, dictsize, expected_size); - decoder.m_OutWindow._stream = output; - - const res_length = $DecodeLZMA2(decoder, $ByteArrayInputStream({}, uint8arr, hdrend), expected_size); - - if (res_length !== expected_size || output.count !== expected_size) - throw Error(`LZMA: mismatch unpacked buffer size ${res_length} != ${expected_size}`); - - return res_length; -} - -export { decompress }; diff --git a/js/modules/base/makepdf.mjs b/js/modules/base/makepdf.mjs deleted file mode 100644 index e1700034c340b..0000000000000 --- a/js/modules/base/makepdf.mjs +++ /dev/null @@ -1,180 +0,0 @@ -import { select as d3_select } from '../d3.mjs'; -import { jsPDF } from './jspdf.mjs'; -import { svg2pdf } from './svg2pdf.mjs'; -import { isNodeJs, internals, settings } from '../core.mjs'; -import { FontHandler, detectPdfFont, kArial, kCourier, kSymbol, kWingdings } from './FontHandler.mjs'; -import { approximateLabelWidth, replaceSymbolsInTextNode } from './latex.mjs'; - - -/** @summary Create pdf for existing SVG element - * @return {Promise} with produced PDF file as url string - * @private */ -async function makePDF(svg, args) { - const nodejs = isNodeJs(); - let need_symbols = false; - - const restore_fonts = [], restore_symb = [], restore_wing = [], restore_dominant = [], restore_oblique = [], restore_text = [], - node_transform = svg.node.getAttribute('transform'), custom_fonts = {}; - - if (svg.reset_tranform) - svg.node.removeAttribute('transform'); - - d3_select(svg.node).selectAll('g').each(function() { - if (this.hasAttribute('font-family')) { - const name = this.getAttribute('font-family'); - if (name === kCourier) { - this.setAttribute('font-family', 'courier'); - if (!svg.can_modify) - restore_fonts.push(this); // keep to restore it - } - if (name === kSymbol) { - this.setAttribute('font-family', 'symbol'); - if (!svg.can_modify) - restore_symb.push(this); // keep to restore it - } - if (name === kWingdings) { - this.setAttribute('font-family', 'zapfdingbats'); - if (!svg.can_modify) - restore_wing.push(this); // keep to restore it - } - - if (((name === kArial) || (name === kCourier)) && (this.getAttribute('font-weight') === 'bold') && (this.getAttribute('font-style') === 'oblique')) { - this.setAttribute('font-style', 'italic'); - if (!svg.can_modify) - restore_oblique.push(this); // keep to restore it - } else if ((name === kCourier) && (this.getAttribute('font-style') === 'oblique')) { - this.setAttribute('font-style', 'italic'); - if (!svg.can_modify) - restore_oblique.push(this); // keep to restore it - } - } - }); - - d3_select(svg.node).selectAll('text').each(function() { - if (this.hasAttribute('dominant-baseline')) { - this.setAttribute('dy', '.2em'); // slightly different as in plain text - this.removeAttribute('dominant-baseline'); - if (!svg.can_modify) - restore_dominant.push(this); // keep to restore it - } else if (svg.can_modify && nodejs && this.getAttribute('dy') === '.4em') - this.setAttribute('dy', '.2em'); // better alignment in PDF - - if (replaceSymbolsInTextNode(this)) { - need_symbols = true; - if (!svg.can_modify) - restore_text.push(this); // keep to restore it - } - }); - - let pr = Promise.resolve(); - - if (nodejs) { - const doc = internals.nodejs_document; - doc.originalCreateElementNS = doc.createElementNS; - globalThis.document = doc; - globalThis.CSSStyleSheet = internals.nodejs_window.CSSStyleSheet; - globalThis.CSSStyleRule = internals.nodejs_window.CSSStyleRule; - doc.createElementNS = function(ns, kind) { - const res = doc.originalCreateElementNS(ns, kind); - res.getBBox = function() { - let width = 50, height = 10; - if (this.tagName === 'text') { - // TODO: use jsDOC fonts for label width estimation - const font = detectPdfFont(this); - width = approximateLabelWidth(this.textContent, font); - height = font.size * 1.2; - } - - return { x: 0, y: 0, width, height }; - }; - return res; - }; - - pr = import('canvas').then(handle => { - globalThis.Image = handle.Image; - }); - } - - const orientation = (svg.width < svg.height) ? 'portrait' : 'landscape'; - - let doc = args?.as_doc ? args.doc : null; - - if (doc) { - doc.addPage({ - orientation, - unit: 'px', - format: [svg.width + 10, svg.height + 10] - }); - } else { - doc = new jsPDF({ - orientation, - unit: 'px', - format: [svg.width + 10, svg.height + 10] - }); - if (args?.as_doc) - args.doc = doc; - } - - // add custom fonts to PDF document, only TTF format supported - d3_select(svg.node).selectAll('style').each(function() { - const fcfg = this.$fontcfg; - if (!fcfg?.n || !fcfg?.base64) - return; - const name = fcfg.n; - if ((name === kSymbol) || (name === kWingdings)) - return; - if (custom_fonts[name]) - return; - custom_fonts[name] = true; - - const filename = name.toLowerCase().replace(/\s/g, '') + '.ttf'; - doc.addFileToVFS(filename, fcfg.base64); - doc.addFont(filename, fcfg.n, fcfg.s || 'normal'); - }); - - if (need_symbols && !custom_fonts[kSymbol] && settings.LoadSymbolTtf) { - const handler = new FontHandler(122, 10); - pr = pr.then(() => handler.load()).then(() => { - handler.addCustomFontToSvg(d3_select(svg.node)); - doc.addFileToVFS(kSymbol + '.ttf', handler.base64); - doc.addFont(kSymbol + '.ttf', kSymbol, 'normal'); - }); - } - - return pr.then(() => svg2pdf(svg.node, doc, { x: 5, y: 5, width: svg.width, height: svg.height })).then(() => { - if (svg.reset_tranform && !svg.can_modify && node_transform) - svg.node.setAttribute('transform', node_transform); - - restore_fonts.forEach(node => node.setAttribute('font-family', kCourier)); - restore_symb.forEach(node => node.setAttribute('font-family', kSymbol)); - restore_wing.forEach(node => node.setAttribute('font-family', kWingdings)); - restore_oblique.forEach(node => node.setAttribute('font-style', 'oblique')); - restore_dominant.forEach(node => { - node.setAttribute('dominant-baseline', 'middle'); - node.removeAttribute('dy'); - }); - - restore_text.forEach(node => { - node.innerHTML = node.$originalHTML; - if (node.$originalFont) - node.setAttribute('font-family', node.$originalFont); - else - node.removeAttribute('font-family'); - }); - - const res = args?.as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring'); - if (nodejs) { - globalThis.document = undefined; - globalThis.CSSStyleSheet = undefined; - globalThis.CSSStyleRule = undefined; - globalThis.Image = undefined; - internals.nodejs_document.createElementNS = internals.nodejs_document.originalCreateElementNS; - if (args?.as_buffer) - return Buffer.from(res); - } - - return res; - }); -} - -export { makePDF }; diff --git a/js/modules/base/math.mjs b/js/modules/base/math.mjs deleted file mode 100644 index b2db8e971658f..0000000000000 --- a/js/modules/base/math.mjs +++ /dev/null @@ -1,2157 +0,0 @@ -/** - * A math namespace - all functions can be exported from base/math.mjs. - * Also all these functions can be used with TFormula calculations - * @namespace Math - */ - -/* eslint-disable curly */ -/* eslint-disable no-useless-assignment */ -/* eslint-disable no-use-before-define */ -/* eslint-disable no-else-return */ -/* eslint-disable no-shadow */ -/* eslint-disable operator-assignment */ -/* eslint-disable @stylistic/js/comma-spacing */ -/* eslint-disable @stylistic/js/no-floating-decimal */ -/* eslint-disable @stylistic/js/space-in-parens */ - -// this can be improved later - -/* eslint-disable eqeqeq */ - -const kMACHEP = 1.11022302462515654042363166809e-16, - kMINLOG = -708.396418532264078748994506896, - kMAXLOG = 709.782712893383973096206318587, - kMAXSTIR = 108.116855767857671821730036754, - kBig = 4.503599627370496e15, - kBiginv = 2.22044604925031308085e-16, - kSqrt2 = 1.41421356237309515, - M_PI = 3.14159265358979323846264338328; - -/** @summary Polynomialeval function - * @desc calculates a value of a polynomial of the form: - * a[0]x^N+a[1]x^(N-1) + ... + a[N] - * @memberof Math */ -function Polynomialeval(x, a, N) { - if (!N) - return a[0]; - - let pom = a[0]; - for (let i = 1; i <= N; ++i) - pom = pom * x + a[i]; - return pom; -} - -/** @summary Polynomial1eval function - * @desc calculates a value of a polynomial of the form: - * x^N+a[0]x^(N-1) + ... + a[N-1] - * @memberof Math */ -function Polynomial1eval(x, a, N) { - if (!N) - return a[0]; - - let pom = x + a[0]; - for (let i = 1; i < N; ++i) - pom = pom * x + a[i]; - return pom; -} - -/** @summary lgam function, logarithm from gamma - * @memberof Math */ -function lgam(x) { - let p, q, u, w, z; - const kMAXLGM = 2.556348e305, - LS2PI = 0.91893853320467274178, - A = [ - 8.11614167470508450300E-4, - -5.95061904284301438324E-4, - 7.93650340457716943945E-4, - -2.77777777730099687205E-3, - 8.33333333333331927722E-2 - ], B = [ - -1.37825152569120859100E3, - -3.88016315134637840924E4, - -3.31612992738871184744E5, - -1.16237097492762307383E6, - -1.72173700820839662146E6, - -8.53555664245765465627E5 - ], C = [ - /* 1.00000000000000000000E0, */ - -3.51815701436523470549E2, - -1.70642106651881159223E4, - -2.20528590553854454839E5, - -1.13933444367982507207E6, - -2.53252307177582951285E6, - -2.01889141433532773231E6 - ]; - - if ((x >= Number.MAX_VALUE) || (x == Number.POSITIVE_INFINITY)) - return Number.POSITIVE_INFINITY; - - if ( x < -34.0 ) { - q = -x; - w = lgam(q); - p = Math.floor(q); - if ( p === q ) // _unur_FP_same(p,q) - return Number.POSITIVE_INFINITY; - z = q - p; - if ( z > 0.5 ) { - p += 1.0; - z = p - q; - } - z = q * Math.sin( Math.PI * z ); - if ( z < 1e-300 ) - return Number.POSITIVE_INFINITY; - z = Math.log(Math.PI) - Math.log( z ) - w; - return z; - } - if ( x < 13.0 ) { - z = 1.0; - p = 0.0; - u = x; - while ( u >= 3.0 ) { - p -= 1.0; - u = x + p; - z *= u; - } - while ( u < 2.0 ) { - if ( u < 1e-300 ) - return Number.POSITIVE_INFINITY; - z /= u; - p += 1.0; - u = x + p; - } - if ( z < 0.0 ) { - z = -z; - } - if ( u === 2.0 ) - return Math.log(z); - p -= 2.0; - x = x + p; - p = x * Polynomialeval(x, B, 5 ) / Polynomial1eval( x, C, 6); - return Math.log(z) + p; - } - if ( x > kMAXLGM ) - return Number.POSITIVE_INFINITY; - - q = ( x - 0.5 ) * Math.log(x) - x + LS2PI; - if ( x > 1.0e8 ) - return q; - - p = 1.0 / (x * x); - if ( x >= 1000.0 ) - q += ((7.9365079365079365079365e-4 * p - - 2.7777777777777777777778e-3) * p - + 0.0833333333333333333333) / x; - else - q += Polynomialeval( p, A, 4 ) / x; - return q; -} - -/** @summary Stirling formula for the gamma function - * @memberof Math */ -function stirf(x) { - let y, w, v; - - const STIR = [ - 7.87311395793093628397E-4, - -2.29549961613378126380E-4, - -2.68132617805781232825E-3, - 3.47222221605458667310E-3, - 8.33333333333482257126E-2 - ], SQTPI = Math.sqrt(2 * Math.PI); - - w = 1.0 / x; - w = 1.0 + w * Polynomialeval( w, STIR, 4 ); - y = Math.exp(x); - - /* #define kMAXSTIR kMAXLOG/log(kMAXLOG) */ - - if ( x > kMAXSTIR ) - { /* Avoid overflow in pow() */ - v = Math.pow( x, 0.5 * x - 0.25 ); - y = v * (v / y); - } - else - { - y = Math.pow( x, x - 0.5 ) / y; - } - y = SQTPI * y * w; - return y; -} - -/** @summary complementary error function - * @memberof Math */ -function erfc(a) { - const erfP = [ - 2.46196981473530512524E-10, - 5.64189564831068821977E-1, - 7.46321056442269912687E0, - 4.86371970985681366614E1, - 1.96520832956077098242E2, - 5.26445194995477358631E2, - 9.34528527171957607540E2, - 1.02755188689515710272E3, - 5.57535335369399327526E2 - ], erfQ = [ - 1.32281951154744992508E1, - 8.67072140885989742329E1, - 3.54937778887819891062E2, - 9.75708501743205489753E2, - 1.82390916687909736289E3, - 2.24633760818710981792E3, - 1.65666309194161350182E3, - 5.57535340817727675546E2 - ], erfR = [ - 5.64189583547755073984E-1, - 1.27536670759978104416E0, - 5.01905042251180477414E0, - 6.16021097993053585195E0, - 7.40974269950448939160E0, - 2.97886665372100240670E0 - ], erfS = [ - 2.26052863220117276590E0, - 9.39603524938001434673E0, - 1.20489539808096656605E1, - 1.70814450747565897222E1, - 9.60896809063285878198E0, - 3.36907645100081516050E0 - ]; - - let p,q,x,y,z; - - if ( a < 0.0 ) - x = -a; - else - x = a; - - if ( x < 1.0 ) - return 1.0 - erf(a); - - z = -a * a; - - if (z < -kMAXLOG) - return (a < 0) ? 2.0 : 0.0; - - z = Math.exp(z); - - if ( x < 8.0 ) { - p = Polynomialeval( x, erfP, 8 ); - q = Polynomial1eval( x, erfQ, 8 ); - } else { - p = Polynomialeval( x, erfR, 5 ); - q = Polynomial1eval( x, erfS, 6 ); - } - y = (z * p) / q; - - if (a < 0) - y = 2.0 - y; - - if (y == 0) - return (a < 0) ? 2.0 : 0.0; - - return y; -} - -/** @summary error function - * @memberof Math */ -function erf(x) { - if (Math.abs(x) > 1.0) - return 1.0 - erfc(x); - - const erfT = [ - 9.60497373987051638749E0, - 9.00260197203842689217E1, - 2.23200534594684319226E3, - 7.00332514112805075473E3, - 5.55923013010394962768E4 - ], erfU = [ - 3.35617141647503099647E1, - 5.21357949780152679795E2, - 4.59432382970980127987E3, - 2.26290000613890934246E4, - 4.92673942608635921086E4 - ], z = x * x; - - return x * Polynomialeval(z, erfT, 4) / Polynomial1eval(z, erfU, 5); -} - -/** @summary lognormal_cdf_c function - * @memberof Math */ -function lognormal_cdf_c(x, m, s, x0) { - if (x0 === undefined) - x0 = 0; - const z = (Math.log((x - x0)) - m) / (s * kSqrt2); - if (z > 1.) - return 0.5 * erfc(z); - else - return 0.5 * (1.0 - erf(z)); -} - -/** @summary lognormal_cdf_c function - * @memberof Math */ -function lognormal_cdf(x, m, s, x0 = 0) { - const z = (Math.log((x - x0)) - m) / (s * kSqrt2); - if (z < -1.) - return 0.5 * erfc(-z); - else - return 0.5 * (1.0 + erf(z)); -} - -/** @summary normal_cdf_c function - * @memberof Math */ -function normal_cdf_c(x, sigma, x0 = 0) { - const z = (x - x0) / (sigma * kSqrt2); - if (z > 1.) - return 0.5 * erfc(z); - else - return 0.5 * (1. - erf(z)); -} - -/** @summary normal_cdf function - * @memberof Math */ -function normal_cdf(x, sigma, x0 = 0) { - const z = (x - x0) / (sigma * kSqrt2); - if (z < -1.) - return 0.5 * erfc(-z); - else - return 0.5 * (1.0 + erf(z)); -} - -/** @summary log normal pdf - * @memberof Math */ -function lognormal_pdf(x, m, s, x0 = 0) { - if ((x - x0) <= 0) - return 0.0; - const tmp = (Math.log((x - x0)) - m) / s; - return 1.0 / ((x - x0) * Math.abs(s) * Math.sqrt(2 * M_PI)) * Math.exp(-(tmp * tmp) / 2); -} - -/** @summary normal pdf - * @memberof Math */ -function normal_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x - x0) / sigma; - return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); -} - -/** @summary gamma calculation - * @memberof Math */ -function gamma(x) { - let p, q, z, i, sgngam = 1; - - if (x >= Number.MAX_VALUE) - return x; - - q = Math.abs(x); - - if ( q > 33.0 ) - { - if ( x < 0.0 ) - { - p = Math.floor(q); - if ( p == q ) - return Number.POSITIVE_INFINITY; - i = Math.round(p); - if ( (i & 1) == 0 ) - sgngam = -1; - z = q - p; - if ( z > 0.5 ) - { - p += 1.0; - z = q - p; - } - z = q * Math.sin(Math.PI * z); - if ( z == 0 ) - { - return sgngam > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; - } - z = Math.abs(z); - z = Math.PI / (z * stirf(q)); - } - else - { - z = stirf(x); - } - return sgngam * z; - } - - z = 1.0; - while ( x >= 3.0 ) { - x -= 1.0; - z *= x; - } - - let small = false; - - while (( x < 0.0 ) && !small) { - if ( x > -1.E-9 ) - small = true; - else { - z /= x; - x += 1.0; - } - } - - while (( x < 2.0 ) && !small) { - if ( x < 1.e-9 ) - small = true; - else { - z /= x; - x += 1.0; - } - } - - if (small) { - if ( x == 0 ) - return Number.POSITIVE_INFINITY; - else - return z / ((1.0 + 0.5772156649015329 * x) * x); - } - - if ( x == 2.0 ) - return z; - - const P = [ - 1.60119522476751861407E-4, - 1.19135147006586384913E-3, - 1.04213797561761569935E-2, - 4.76367800457137231464E-2, - 2.07448227648435975150E-1, - 4.94214826801497100753E-1, - 9.99999999999999996796E-1 - ], Q = [ - -2.31581873324120129819E-5, - 5.39605580493303397842E-4, - -4.45641913851797240494E-3, - 1.18139785222060435552E-2, - 3.58236398605498653373E-2, - -2.34591795718243348568E-1, - 7.14304917030273074085E-2, - 1.00000000000000000320E0]; - - x -= 2.0; - p = Polynomialeval( x, P, 6 ); - q = Polynomialeval( x, Q, 7 ); - return z * p / q; -} - -/** @summary ndtri function - * @memberof Math */ -function ndtri(y0) { - if ( y0 <= 0.0 ) - return Number.NEGATIVE_INFINITY; - if ( y0 >= 1.0 ) - return Number.POSITIVE_INFINITY; - - const P0 = [ - -5.99633501014107895267E1, - 9.80010754185999661536E1, - -5.66762857469070293439E1, - 1.39312609387279679503E1, - -1.23916583867381258016E0 - ], Q0 = [ - 1.95448858338141759834E0, - 4.67627912898881538453E0, - 8.63602421390890590575E1, - -2.25462687854119370527E2, - 2.00260212380060660359E2, - -8.20372256168333339912E1, - 1.59056225126211695515E1, - -1.18331621121330003142E0 - ], P1 = [ - 4.05544892305962419923E0, - 3.15251094599893866154E1, - 5.71628192246421288162E1, - 4.40805073893200834700E1, - 1.46849561928858024014E1, - 2.18663306850790267539E0, - -1.40256079171354495875E-1, - -3.50424626827848203418E-2, - -8.57456785154685413611E-4 - ], Q1 = [ - 1.57799883256466749731E1, - 4.53907635128879210584E1, - 4.13172038254672030440E1, - 1.50425385692907503408E1, - 2.50464946208309415979E0, - -1.42182922854787788574E-1, - -3.80806407691578277194E-2, - -9.33259480895457427372E-4 - ], P2 = [ - 3.23774891776946035970E0, - 6.91522889068984211695E0, - 3.93881025292474443415E0, - 1.33303460815807542389E0, - 2.01485389549179081538E-1, - 1.23716634817820021358E-2, - 3.01581553508235416007E-4, - 2.65806974686737550832E-6, - 6.23974539184983293730E-9 - ], Q2 = [ - 6.02427039364742014255E0, - 3.67983563856160859403E0, - 1.37702099489081330271E0, - 2.16236993594496635890E-1, - 1.34204006088543189037E-2, - 3.28014464682127739104E-4, - 2.89247864745380683936E-6, - 6.79019408009981274425E-9 - ], s2pi = 2.50662827463100050242e0, dd = 0.13533528323661269189; - - let code = 1, y = y0, x, y2, x1; - - if (y > (1.0 - dd)) { - y = 1.0 - y; - code = 0; - } - if ( y > dd ) { - y = y - 0.5; - y2 = y * y; - x = y + y * (y2 * Polynomialeval( y2, P0, 4) / Polynomial1eval( y2, Q0, 8 )); - x = x * s2pi; - return x; - } - x = Math.sqrt(-2.0 * Math.log(y)); - const x0 = x - Math.log(x) / x, - z = 1.0 / x; - if ( x < 8.0 ) - x1 = z * Polynomialeval( z, P1, 8 ) / Polynomial1eval( z, Q1, 8 ); - else - x1 = z * Polynomialeval( z, P2, 8 ) / Polynomial1eval( z, Q2, 8 ); - x = x0 - x1; - if ( code != 0 ) - x = -x; - return x; -} - -/** @summary normal_quantile function - * @memberof Math */ -function normal_quantile(z, sigma) { - return sigma * ndtri(z); -} - -/** @summary normal_quantile_c function - * @memberof Math */ -function normal_quantile_c(z, sigma) { - return -sigma * ndtri(z); -} - -/** @summary igamc function - * @memberof Math */ -function igamc(a,x) { - // LM: for negative values returns 0.0 - // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) - return 0.0; - - if (x <= 0) - return 1.0; - - if ((x < 1.0) || (x < a)) - return (1.0 - igam(a,x)); - - let ax = a * Math.log(x) - x - lgam(a); - if ( ax < -kMAXLOG ) - return 0.0; - - ax = Math.exp(ax); - - /* continued fraction */ - let y = 1.0 - a, - z = x + y + 1.0, - c = 0.0, - pkm2 = 1.0, - qkm2 = x, - pkm1 = x + 1.0, - qkm1 = z * x, - ans = pkm1 / qkm1, - yc, r, t, pk, qk; - - do { - c += 1.0; - y += 1.0; - z += 2.0; - yc = y * c; - pk = pkm1 * z - pkm2 * yc; - qk = qkm1 * z - qkm2 * yc; - if (qk) - { - r = pk / qk; - t = Math.abs( (ans - r) / r ); - ans = r; - } - else - t = 1.0; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - if ( Math.abs(pk) > kBig ) - { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - } while ( t > kMACHEP ); - - return ans * ax; -} - -/** @summary igam function - * @memberof Math */ -function igam(a, x) { - // LM: for negative values returns 1.0 instead of zero - // This is correct if a is a negative integer since Gamma(-n) = +/- inf - if (a <= 0) - return 1.0; - - if (x <= 0) - return 0.0; - - if ((x > 1.0) && (x > a)) - return 1.0 - igamc(a,x); - - /* Compute x**a * exp(-x) / gamma(a) */ - let ax = a * Math.log(x) - x - lgam(a); - if ( ax < -kMAXLOG ) - return 0.0; - - ax = Math.exp(ax); - - /* power series */ - let r = a, c = 1.0, ans = 1.0; - - do { - r += 1.0; - c *= x / r; - ans += c; - } while ( c / ans > kMACHEP ); - - return ans * ax / a; -} - - -/** @summary igami function - * @memberof Math */ -function igami(a, y0) { - // check the domain - if (a <= 0) { - console.error(`igami : Wrong domain for parameter a = ${a} (must be > 0)`); - return 0; - } - if (y0 <= 0) { - return Number.POSITIVE_INFINITY; - } - if (y0 >= 1) { - return 0; - } - const kMAXNUM = Number.MAX_VALUE, dithresh = 5.0 * kMACHEP; - let x0 = kMAXNUM, x1 = 0, x, yl = 0, yh = 1, y, d, lgm, i, dir; - - /* approximation to inverse function */ - d = 1.0 / (9.0 * a); - y = 1.0 - d - ndtri(y0) * Math.sqrt(d); - x = a * y * y * y; - - lgm = lgam(a); - - for ( i = 0; i < 10; ++i ) { - if ( x > x0 || x < x1 ) - break; - y = igamc(a,x); - if ( y < yl || y > yh ) - break; - if ( y < y0 ) { - x0 = x; - yl = y; - } - else { - x1 = x; - yh = y; - } - /* compute the derivative of the function at this point */ - d = (a - 1.0) * Math.log(x) - x - lgm; - if ( d < -kMAXLOG ) - break; - d = -Math.exp(d); - /* compute the step to the next approximation of x */ - d = (y - y0) / d; - if ( Math.abs(d / x) < kMACHEP ) - return x; - x = x - d; - } - /* Resort to interval halving if Newton iteration did not converge. */ - d = 0.0625; - if ( x0 == kMAXNUM ) { - if ( x <= 0.0 ) - x = 1.0; - while ( x0 == kMAXNUM ) { - x = (1.0 + d) * x; - y = igamc( a, x ); - if ( y < y0 ) { - x0 = x; - yl = y; - break; - } - d = d + d; - } - } - d = 0.5; - dir = 0; - - for ( i = 0; i < 400; ++i ) { - x = x1 + d * (x0 - x1); - y = igamc( a, x ); - lgm = (x0 - x1) / (x1 + x0); - if ( Math.abs(lgm) < dithresh ) - break; - lgm = (y - y0) / y0; - if ( Math.abs(lgm) < dithresh ) - break; - if ( x <= 0.0 ) - break; - if ( y >= y0 ) { - x1 = x; - yh = y; - if ( dir < 0 ) { - dir = 0; - d = 0.5; - } - else if ( dir > 1 ) - d = 0.5 * d + 0.5; - else - d = (y0 - yl) / (yh - yl); - dir += 1; - } - else { - x0 = x; - yl = y; - if ( dir > 0 ) { - dir = 0; - d = 0.5; - } - else if ( dir < -1 ) - d = 0.5 * d; - else - d = (y0 - yl) / (yh - yl); - dir -= 1; - } - } - return x; -} - -/** @summary landau_pdf function - * @desc LANDAU pdf : algorithm from CERNLIB G110 denlan - * same algorithm is used in GSL - * @memberof Math */ -function landau_pdf(x, xi, x0 = 0) { - if (xi <= 0) - return 0; - const v = (x - x0) / xi; - let u, ue, us, denlan; - const p1 = [0.4259894875,-0.1249762550, 0.03984243700, -0.006298287635, 0.001511162253], - q1 = [1.0 ,-0.3388260629, 0.09594393323, -0.01608042283, 0.003778942063], - p2 = [0.1788541609, 0.1173957403, 0.01488850518, -0.001394989411, 0.0001283617211], - q2 = [1.0 , 0.7428795082, 0.3153932961, 0.06694219548, 0.008790609714], - p3 = [0.1788544503, 0.09359161662,0.006325387654, 0.00006611667319,-0.000002031049101], - q3 = [1.0 , 0.6097809921, 0.2560616665, 0.04746722384, 0.006957301675], - p4 = [0.9874054407, 118.6723273, 849.2794360, -743.7792444, 427.0262186], - q4 = [1.0 , 106.8615961, 337.6496214, 2016.712389, 1597.063511], - p5 = [1.003675074, 167.5702434, 4789.711289, 21217.86767, -22324.94910], - q5 = [1.0 , 156.9424537, 3745.310488, 9834.698876, 66924.28357], - p6 = [1.000827619, 664.9143136, 62972.92665, 475554.6998, -5743609.109], - q6 = [1.0 , 651.4101098, 56974.73333, 165917.4725, -2815759.939], - a1 = [0.04166666667,-0.01996527778, 0.02709538966], - a2 = [-1.845568670,-4.284640743]; - - if (v < -5.5) { - u = Math.exp(v + 1.0); - if (u < 1e-10) - return 0.0; - ue = Math.exp(-1 / u); - us = Math.sqrt(u); - denlan = 0.3989422803 * (ue / us) * (1 + (a1[0] + (a1[1] + a1[2] * u) * u) * u); - } else if (v < -1) { - u = Math.exp(-v - 1); - denlan = Math.exp(-u) * Math.sqrt(u) * - (p1[0] + (p1[1] + (p1[2] + (p1[3] + p1[4] * v) * v) * v) * v) / - (q1[0] + (q1[1] + (q1[2] + (q1[3] + q1[4] * v) * v) * v) * v); - } else if (v < 1) { - denlan = (p2[0] + (p2[1] + (p2[2] + (p2[3] + p2[4] * v) * v) * v) * v) / - (q2[0] + (q2[1] + (q2[2] + (q2[3] + q2[4] * v) * v) * v) * v); - } else if (v < 5) { - denlan = (p3[0] + (p3[1] + (p3[2] + (p3[3] + p3[4] * v) * v) * v) * v) / - (q3[0] + (q3[1] + (q3[2] + (q3[3] + q3[4] * v) * v) * v) * v); - } else if (v < 12) { - u = 1 / v; - denlan = u * u * (p4[0] + (p4[1] + (p4[2] + (p4[3] + p4[4] * u) * u) * u) * u) / - (q4[0] + (q4[1] + (q4[2] + (q4[3] + q4[4] * u) * u) * u) * u); - } else if (v < 50) { - u = 1 / v; - denlan = u * u * (p5[0] + (p5[1] + (p5[2] + (p5[3] + p5[4] * u) * u) * u) * u) / - (q5[0] + (q5[1] + (q5[2] + (q5[3] + q5[4] * u) * u) * u) * u); - } else if (v < 300) { - u = 1 / v; - denlan = u * u * (p6[0] + (p6[1] + (p6[2] + (p6[3] + p6[4] * u) * u) * u) * u) / - (q6[0] + (q6[1] + (q6[2] + (q6[3] + q6[4] * u) * u) * u) * u); - } else { - u = 1 / (v - v * Math.log(v) / (v + 1)); - denlan = u * u * (1 + (a2[0] + a2[1] * u) * u); - } - return denlan / xi; -} - -/** @summary Landau function - * @memberof Math */ -function Landau(x, mpv, sigma, norm) { - if (sigma <= 0) - return 0; - const den = landau_pdf((x - mpv) / sigma, 1, 0); - if (!norm) - return den; - return den / sigma; -} - -/** @summary inc_gamma_c - * @memberof Math */ -function inc_gamma_c(a,x) { - return igamc(a,x); -} - -/** @summary inc_gamma - * @memberof Math */ -function inc_gamma(a,x) { - return igam(a,x); -} - -/** @summary lgamma - * @memberof Math */ -function lgamma(z) { - return lgam(z); -} - -/** @summary Probability density function of the beta distribution. - * @memberof Math */ -function beta_pdf(x, a, b) { - if (x < 0 || x > 1.0) - return 0; - if (x == 0 ) { - if (a < 1) - return Number.POSITIVE_INFINITY; - else if (a > 1) - return 0; - else if ( a == 1) - return b; // to avoid a nan from log(0)*0 - } - if (x == 1 ) { - if (b < 1) - return Number.POSITIVE_INFINITY; - else if (b > 1) - return 0; - else if ( b == 1) - return a; // to avoid a nan from log(0)*0 - } - return Math.exp(lgamma(a + b) - lgamma(a) - lgamma(b) + - Math.log(x) * (a - 1.) + Math.log1p(-x) * (b - 1.)); -} - -/** @summary beta - * @memberof Math */ -function beta(x,y) { - return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); -} - -/** @summary chisquared_cdf_c - * @memberof Math */ -function chisquared_cdf_c(x,r,x0 = 0) { - return inc_gamma_c(0.5 * r, 0.5 * (x - x0)); -} - -/** @summary Continued fraction expansion #1 for incomplete beta integral - * @memberof Math */ -function incbcf(a,b,x) { - let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, - k1, k2, k3, k4, k5, k6, k7, k8, - r, t, ans, n; - const thresh = 3.0 * kMACHEP; - - k1 = a; - k2 = a + b; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = b - 1.0; - k7 = k4; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; - - do { - xk = -( x * k1 * k2 ) / ( k3 * k4 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = ( x * k5 * k6 ) / ( k7 * k8 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if ( qk != 0 ) - r = pk / qk; - if ( r != 0 ) - { - t = Math.abs( (ans - r) / r ); - ans = r; - } - else - t = 1.0; - - if ( t < thresh ) - break; // goto cdone; - - k1 += 1.0; - k2 += 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 -= 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((Math.abs(qk) + Math.abs(pk)) > kBig) { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { - pkm2 *= kBig; - pkm1 *= kBig; - qkm2 *= kBig; - qkm1 *= kBig; - } - } - while ( ++n < 300 ); - - // cdone: - return ans; -} - -/** @summary Continued fraction expansion #2 for incomplete beta integral - * @memberof Math */ -function incbd(a,b,x) { - const z = x / (1.0 - x), - thresh = 3.0 * kMACHEP; - - let xk, pk, pkm1, pkm2, qk, qkm1, qkm2, - k1, k2, k3, k4, k5, k6, k7, k8, - r, t, ans, n; - - k1 = a; - k2 = b - 1.0; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = a + b; - k7 = a + 1.0; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; - do { - xk = -( z * k1 * k2 ) / ( k3 * k4 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = ( z * k5 * k6 ) / ( k7 * k8 ); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if ( qk != 0 ) - r = pk / qk; - if ( r != 0 ) - { - t = Math.abs( (ans - r) / r ); - ans = r; - } - else - t = 1.0; - - if ( t < thresh ) - break; // goto cdone; - - k1 += 1.0; - k2 -= 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 += 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((Math.abs(qk) + Math.abs(pk)) > kBig) { - pkm2 *= kBiginv; - pkm1 *= kBiginv; - qkm2 *= kBiginv; - qkm1 *= kBiginv; - } - if ((Math.abs(qk) < kBiginv) || (Math.abs(pk) < kBiginv)) { - pkm2 *= kBig; - pkm1 *= kBig; - qkm2 *= kBig; - qkm1 *= kBig; - } - } - while ( ++n < 300 ); - // cdone: - return ans; -} - -/** @summary ROOT::Math::Cephes::pseries - * @memberof Math */ -function pseries(a,b,x) { - let s, t, u, v, n; - - const ai = 1.0 / a; - u = (1.0 - b) * x; - v = u / (a + 1.0); - const t1 = v; - t = u; - n = 2.0; - s = 0.0; - const z = kMACHEP * ai; - while ( Math.abs(v) > z ) { - u = (n - b) * x / n; - t *= u; - v = t / (a + n); - s += v; - n += 1.0; - } - s += t1; - s += ai; - - u = a * Math.log(x); - if ( (a + b) < kMAXSTIR && Math.abs(u) < kMAXLOG ) - { - t = gamma(a + b) / (gamma(a) * gamma(b)); - s = s * t * Math.pow(x,a); - } - else - { - t = lgam(a + b) - lgam(a) - lgam(b) + u + Math.log(s); - if ( t < kMINLOG ) - s = 0.0; - else - s = Math.exp(t); - } - return s; -} - -/** @summary ROOT::Math::Cephes::incbet - * @memberof Math */ -function incbet(aa,bb,xx) { - let a, b, t, x, xc, w, y, flag; - - if ( aa <= 0.0 || bb <= 0.0 ) - return 0.0; - - // LM: changed: for X > 1 return 1. - if (xx <= 0.0) - return 0.0; - if (xx >= 1.0) - return 1.0; - - flag = 0; - - /* - to test if that way is better for large b/ (comment out from Cephes version) - if ( (bb * xx) <= 1.0 && xx <= 0.95) - { - t = pseries(aa, bb, xx); - goto done; - } - -**/ - w = 1.0 - xx; - - /* Reverse a and b if x is greater than the mean. */ - /* aa,bb > 1 -> sharp rise at x=aa/(aa+bb) */ - if (xx > (aa / (aa + bb))) - { - flag = 1; - a = bb; - b = aa; - xc = xx; - x = w; - } - else - { - a = aa; - b = bb; - xc = w; - x = xx; - } - - if ( flag == 1 && (b * x) <= 1.0 && x <= 0.95) { - t = pseries(a, b, x); - // goto done; - } else { - /* Choose expansion for better convergence. */ - y = x * (a + b - 2.0) - (a - 1.0); - if ( y < 0.0 ) - w = incbcf( a, b, x ); - else - w = incbd( a, b, x ) / xc; - - /* Multiply w by the factor - a b _ _ _ - x (1-x) | (a+b) / (a | (a) | (b)) . */ - - y = a * Math.log(x); - t = b * Math.log(xc); - if ( (a + b) < kMAXSTIR && Math.abs(y) < kMAXLOG && Math.abs(t) < kMAXLOG ) - { - t = Math.pow(xc,b); - t *= Math.pow(x,a); - t /= a; - t *= w; - t *= gamma(a + b) / (gamma(a) * gamma(b)); - // goto done; - } else { - /* Resort to logarithms. */ - y += t + lgam(a + b) - lgam(a) - lgam(b); - y += Math.log(w / a); - if ( y < kMINLOG ) - t = 0.0; - else - t = Math.exp(y); - } - } - - // done: - - if (flag == 1) { - if ( t <= kMACHEP ) - t = 1.0 - kMACHEP; - else - t = 1.0 - t; - } - return t; -} - -/** @summary copy of ROOT::Math::Cephes::incbi - * @memberof Math */ -function incbi(aa,bb,yy0) { - let a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt, - i, rflg, dir, nflg, ihalve = true; - - // check the domain - if (aa <= 0) { - // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter a (must be > 0)'); - return 0; - } - if (bb <= 0) { - // MATH_ERROR_MSG('Cephes::incbi','Wrong domain for parameter b (must be > 0)'); - return 0; - } - - const process_done = () => { - if ( rflg ) { - if ( x <= kMACHEP ) - x = 1.0 - kMACHEP; - else - x = 1.0 - x; - } - return x; - }; - - i = 0; - if ( yy0 <= 0 ) - return 0.0; - if ( yy0 >= 1.0 ) - return 1.0; - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - nflg = 0; - - if ( aa <= 1.0 || bb <= 1.0 ) - { - dithresh = 1.0e-6; - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - x = a / (a + b); - y = incbet( a, b, x ); - // goto ihalve; // will start - } - else - { - dithresh = 1.0e-4; - /* approximation to inverse function */ - - yp = -ndtri(yy0); - - if ( yy0 > 0.5 ) - { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - yp = -yp; - } - else - { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } - - lgm = (yp * yp - 3.0) / 6.0; - x = 2.0 / (1.0 / (2.0 * a - 1.0) + 1.0 / (2.0 * b - 1.0)); - d = yp * Math.sqrt( x + lgm ) / x - - (1.0 / (2.0 * b - 1.0) - 1.0 / (2.0 * a - 1.0)) - * (lgm + 5.0 / 6.0 - 2.0 / (3.0 * x)); - d = 2.0 * d; - if ( d < kMINLOG ) { - // x = 1.0; - // goto under; - x = 0.0; - return process_done(); - } - x = a / (a + b * Math.exp(d)); - y = incbet( a, b, x ); - yp = (y - y0) / y0; - if ( Math.abs(yp) < 0.2 ) - ihalve = false; // instead goto newt; exclude ihalve for the first time - } - - let mainloop = 1000; - - // endless loop until coverage - while (mainloop-- > 0) { - /* Resort to interval halving if not close enough. */ - // ihalve: - if (ihalve) { - dir = 0; - di = 0.5; - for ( i = 0; i < 100; i++ ) - { - if ( i != 0 ) - { - x = x0 + di * (x1 - x0); - if ( x == 1.0 ) - x = 1.0 - kMACHEP; - if ( x == 0.0 ) - { - di = 0.5; - x = x0 + di * (x1 - x0); - if ( x == 0.0 ) - return process_done(); // goto under; - } - y = incbet( a, b, x ); - yp = (x1 - x0) / (x1 + x0); - if ( Math.abs(yp) < dithresh ) - break; // goto newt; - yp = (y - y0) / y0; - if ( Math.abs(yp) < dithresh ) - break; // goto newt; - } - if ( y < y0 ) - { - x0 = x; - yl = y; - if ( dir < 0 ) - { - dir = 0; - di = 0.5; - } - else if ( dir > 3 ) - di = 1.0 - (1.0 - di) * (1.0 - di); - else if ( dir > 1 ) - di = 0.5 * di + 0.5; - else - di = (y0 - y) / (yh - yl); - dir += 1; - if ( x0 > 0.75 ) - { - if ( rflg == 1 ) - { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } - else - { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - } - x = 1.0 - x; - y = incbet( a, b, x ); - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - continue; // goto ihalve; - } - } - else - { - x1 = x; - if ( rflg == 1 && x1 < kMACHEP ) - { - x = 0.0; - return process_done(); // goto done; - } - yh = y; - if ( dir > 0 ) - { - dir = 0; - di = 0.5; - } - else if ( dir < -3 ) - di = di * di; - else if ( dir < -1 ) - di = 0.5 * di; - else - di = (y - y0) / (yh - yl); - dir -= 1; - } - } - // math_error( 'incbi', PLOSS ); - if ( x0 >= 1.0 ) { - x = 1.0 - kMACHEP; - return process_done(); // goto done; - } - if ( x <= 0.0 ) { - // math_error( 'incbi', UNDERFLOW ); - x = 0.0; - return process_done(); // goto done; - } - break; // if here, break ihalve - } // end of ihalve - - ihalve = true; // enter loop next time - - // newt: - - if ( nflg ) - return process_done(); // goto done; - nflg = 1; - lgm = lgam(a + b) - lgam(a) - lgam(b); - - for ( i = 0; i < 8; i++ ) - { - /* Compute the function at this point. */ - if ( i != 0 ) - y = incbet(a,b,x); - if ( y < yl ) - { - x = x0; - y = yl; - } - else if ( y > yh ) - { - x = x1; - y = yh; - } - else if ( y < y0 ) - { - x0 = x; - yl = y; - } - else - { - x1 = x; - yh = y; - } - if ( x == 1.0 || x == 0.0 ) - break; - /* Compute the derivative of the function at this point. */ - d = (a - 1.0) * Math.log(x) + (b - 1.0) * Math.log(1.0 - x) + lgm; - if ( d < kMINLOG ) - return process_done(); // goto done; - if ( d > kMAXLOG ) - break; - d = Math.exp(d); - /* Compute the step to the next approximation of x. */ - d = (y - y0) / d; - xt = x - d; - if ( xt <= x0 ) - { - y = (x - x0) / (x1 - x0); - xt = x0 + 0.5 * y * (x - x0); - if ( xt <= 0.0 ) - break; - } - if ( xt >= x1 ) - { - y = (x1 - x) / (x1 - x0); - xt = x1 - 0.5 * y * (x1 - x); - if ( xt >= 1.0 ) - break; - } - x = xt; - if ( Math.abs(d / x) < 128.0 * kMACHEP ) - return process_done(); // goto done; - } - /* Did not converge. */ - dithresh = 256.0 * kMACHEP; - } // endless loop instead of // goto ihalve; - - // done: - - return process_done(); -} - -/** @summary Calculates the normalized (regularized) incomplete beta function. - * @memberof Math */ -function inc_beta(x,a,b) { - return incbet(a,b,x); -} - -const BetaIncomplete = inc_beta; - -/** @summary ROOT::Math::beta_quantile - * @memberof Math */ -function beta_quantile(z,a,b) { - return incbi(a,b,z); -} - -/** @summary Complement of the cumulative distribution function of the beta distribution. - * @memberof Math */ -function beta_cdf_c(x,a,b) { - return inc_beta(1 - x, b, a); -} - -/** @summary chisquared_cdf - * @memberof Math */ -function chisquared_cdf(x,r,x0 = 0) { - return inc_gamma(0.5 * r, 0.5 * (x - x0)); -} - -/** @summary gamma_quantile_c function - * @memberof Math */ -function gamma_quantile_c(z, alpha, theta) { - return theta * igami( alpha, z); -} - -/** @summary gamma_quantile function - * @memberof Math */ -function gamma_quantile(z, alpha, theta) { - return theta * igami( alpha, 1. - z); -} - -/** @summary breitwigner_cdf_c function - * @memberof Math */ -function breitwigner_cdf_c(x,gamma, x0 = 0) { - return 0.5 - Math.atan(2.0 * (x - x0) / gamma) / M_PI; -} - -/** @summary breitwigner_cdf function - * @memberof Math */ -function breitwigner_cdf(x, gamma, x0 = 0) { - return 0.5 + Math.atan(2.0 * (x - x0) / gamma) / M_PI; -} - -/** @summary cauchy_cdf_c function - * @memberof Math */ -function cauchy_cdf_c(x, b, x0 = 0) { - return 0.5 - Math.atan( (x - x0) / b) / M_PI; -} - -/** @summary cauchy_cdf function - * @memberof Math */ -function cauchy_cdf(x, b, x0 = 0) { - return 0.5 + Math.atan( (x - x0) / b) / M_PI; -} - -/** @summary cauchy_pdf function - * @memberof Math */ -function cauchy_pdf(x, b = 1, x0 = 0) { - return b / (M_PI * ((x - x0) * (x - x0) + b * b)); -} - -/** @summary gaussian_pdf function - * @memberof Math */ -function gaussian_pdf(x, sigma = 1, x0 = 0) { - const tmp = (x - x0) / sigma; - return (1.0 / (Math.sqrt(2 * M_PI) * Math.abs(sigma))) * Math.exp(-tmp * tmp / 2); -} - -/** @summary gamma_pdf function - * @memberof Math */ -function gamma_pdf(x, alpha, theta, x0 = 0) { - if ((x - x0) < 0) { - return 0.0; - } else if ((x - x0) == 0) { - return (alpha == 1) ? 1.0 / theta : 0; - } else if (alpha == 1) { - return Math.exp(-(x - x0) / theta) / theta; - } - return Math.exp((alpha - 1) * Math.log((x - x0) / theta) - (x - x0) / theta - lgamma(alpha)) / theta; -} - -/** @summary tdistribution_cdf_c function - * @memberof Math */ -function tdistribution_cdf_c(x, r, x0 = 0) { - const p = x - x0, - sign = (p > 0) ? 1. : -1; - return .5 - .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; -} - -/** @summary tdistribution_cdf function - * @memberof Math */ -function tdistribution_cdf(x, r, x0 = 0) { - const p = x - x0, - sign = (p > 0) ? 1. : -1; - return .5 + .5 * inc_beta(p * p / (r + p * p), .5, .5 * r) * sign; -} - -/** @summary tdistribution_pdf function - * @memberof Math */ -function tdistribution_pdf(x, r, x0 = 0) { - return (Math.exp(lgamma((r + 1.0) / 2.0) - lgamma(r / 2.0)) / Math.sqrt(M_PI * r)) - * Math.pow((1.0 + (x - x0) * (x - x0) / r), -(r + 1.0) / 2.0); -} - -/** @summary exponential_cdf_c function - * @memberof Math */ -function exponential_cdf_c(x, lambda, x0 = 0) { - return ((x - x0) < 0) ? 1.0 : Math.exp(-lambda * (x - x0)); -} - -/** @summary exponential_cdf function - * @memberof Math */ -function exponential_cdf(x, lambda, x0 = 0) { - return ((x - x0) < 0) ? 0.0 : -Math.expm1(-lambda * (x - x0)); -} - -/** @summary chisquared_pdf - * @memberof Math */ -function chisquared_pdf(x, r, x0 = 0) { - if ((x - x0) < 0) - return 0.0; - const a = r / 2 - 1.; - // let return inf for case x = x0 and treat special case of r = 2 otherwise will return nan - if (x == x0 && a == 0) - return 0.5; - - return Math.exp((r / 2 - 1) * Math.log((x - x0) / 2) - (x - x0) / 2 - lgamma(r / 2)) / 2; -} - -/** @summary Probability density function of the F-distribution. - * @memberof Math */ -function fdistribution_pdf(x, n, m, x0 = 0) { - if (n < 0 || m < 0) - return Number.NaN; - if ((x - x0) < 0) - return 0.0; - - return Math.exp((n / 2) * Math.log(n) + (m / 2) * Math.log(m) + lgamma((n + m) / 2) - lgamma(n / 2) - lgamma(m / 2) - + (n / 2 - 1) * Math.log(x - x0) - ((n + m) / 2) * Math.log(m + n * (x - x0))); -} - -/** @summary fdistribution_cdf_c function - * @memberof Math */ -function fdistribution_cdf_c(x, n, m, x0 = 0) { - if (n < 0 || m < 0) - return Number.NaN; - - const z = m / (m + n * (x - x0)); - // fox z->1 and large a and b IB looses precision use complement function - if (z > 0.9 && n > 1 && m > 1) - return 1. - fdistribution_cdf(x, n, m, x0); - - // for the complement use the fact that IB(x,a,b) = 1. - IB(1-x,b,a) - return inc_beta(m / (m + n * (x - x0)), .5 * m, .5 * n); -} - -/** @summary fdistribution_cdf function - * @memberof Math */ -function fdistribution_cdf(x, n, m, x0 = 0) { - if (n < 0 || m < 0) - return Number.NaN; - - const z = n * (x - x0) / (m + n * (x - x0)); - // fox z->1 and large a and b IB looses precision use complement function - if (z > 0.9 && n > 1 && m > 1) - return 1. - fdistribution_cdf_c(x, n, m, x0); - - return inc_beta(z, .5 * n, .5 * m); -} - -/** @summary Prob function - * @memberof Math */ -function Prob(chi2, ndf) { - if (ndf <= 0) - return 0; // Set CL to zero in case ndf <= 0 - - if (chi2 <= 0) { - if (chi2 < 0) - return 0; - else - return 1; - } - - return chisquared_cdf_c(chi2,ndf,0); -} - -/** @summary Gaus function - * @memberof Math */ -function Gaus(x, mean, sigma, norm) { - if (!sigma) - return 1e30; - const arg = (x - mean) / sigma; - if (arg < -39 || arg > 39) - return 0; - const res = Math.exp(-0.5 * arg * arg); - return norm ? res / (2.50662827463100024 * sigma) : res; // sqrt(2*Pi)=2.50662827463100024 -} - -/** @summary BreitWigner function - * @memberof Math */ -function BreitWigner(x, mean, gamma) { - return gamma / ((x - mean) * (x - mean) + gamma * gamma / 4) / 2 / Math.PI; -} - -/** @summary Calculates Beta-function Gamma(p)*Gamma(q)/Gamma(p+q). - * @memberof Math */ -function Beta(x,y) { - return Math.exp(lgamma(x) + lgamma(y) - lgamma(x + y)); -} - -/** @summary GammaDist function - * @memberof Math */ -function GammaDist(x, gamma, mu = 0, beta = 1) { - if ((x < mu) || (gamma <= 0) || (beta <= 0)) - return 0; - return gamma_pdf(x, gamma, beta, mu); -} - -/** @summary probability density function of Laplace distribution - * @memberof Math */ -function LaplaceDist(x, alpha = 0, beta = 1) { - return Math.exp(-Math.abs((x - alpha) / beta)) / (2. * beta); -} - -/** @summary distribution function of Laplace distribution - * @memberof Math */ -function LaplaceDistI(x, alpha = 0, beta = 1) { - return (x <= alpha) ? 0.5 * Math.exp(-Math.abs((x - alpha) / beta)) : 1 - 0.5 * Math.exp(-Math.abs((x - alpha) / beta)); -} - -/** @summary density function for Student's t- distribution - * @memberof Math */ -function Student(T, ndf) { - if (ndf < 1) - return 0; - - const r = ndf, - rh = 0.5 * r, - rh1 = rh + 0.5, - denom = Math.sqrt(r * Math.PI) * gamma(rh) * Math.pow(1 + T * T / r, rh1); - return gamma(rh1) / denom; -} - -/** @summary cumulative distribution function of Student's - * @memberof Math */ -function StudentI(T, ndf) { - const r = ndf; - - return (T > 0) - ? (1 - 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5)) - : 0.5 * BetaIncomplete((r / (r + T * T)), r * 0.5, 0.5); -} - -/** @summary LogNormal function - * @memberof Math */ -function LogNormal(x, sigma, theta = 0, m = 1) { - if ((x < theta) || (sigma <= 0) || (m <= 0)) - return 0; - return lognormal_pdf(x, Math.log(m), sigma, theta); -} - -/** @summary Computes the probability density function of the Beta distribution - * @memberof Math */ -function BetaDist(x, p, q) { - if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) - return 0; - const beta = Beta(p, q); - return Math.pow(x, p - 1) * Math.pow(1 - x, q - 1) / beta; -} - -/** @summary Computes the distribution function of the Beta distribution. - * @memberof Math */ -function BetaDistI(x, p, q) { - if ((x < 0) || (x > 1) || (p <= 0) || (q <= 0)) - return 0; - return BetaIncomplete(x, p, q); -} - -/** @summary gaus function for TFormula - * @memberof Math */ -function gaus(f, x, i) { - return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)); -} - -/** @summary gausn function for TFormula - * @memberof Math */ -function gausn(f, x, i) { - return gaus(f, x, i) / (Math.sqrt(2 * Math.PI) * f.GetParValue(i + 2)); -} - -/** @summary gausxy function for TFormula - * @memberof Math */ -function gausxy(f, x, y, i) { - return f.GetParValue(i + 0) * Math.exp(-0.5 * Math.pow((x - f.GetParValue(i + 1)) / f.GetParValue(i + 2), 2)) - * Math.exp(-0.5 * Math.pow((y - f.GetParValue(i + 3)) / f.GetParValue(i + 4), 2)); -} - -/** @summary expo function for TFormula - * @memberof Math */ -function expo(f, x, i) { - return Math.exp(f.GetParValue(i + 0) + f.GetParValue(i + 1) * x); -} - -/** @summary landau function for TFormula - * @memberof Math */ -function landau(f, x, i) { - return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), false); -} - -/** @summary landaun function for TFormula - * @memberof Math */ -function landaun(f, x, i) { - return Landau(x, f.GetParValue(i + 1),f.GetParValue(i + 2), true); -} - -/** @summary Crystal ball function - * @memberof Math */ -function crystalball_function(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) - return 0.; - let z = (x - mean) / sigma; - if (alpha < 0) - z = -z; - const abs_alpha = Math.abs(alpha); - if (z > -abs_alpha) - return Math.exp(-0.5 * z * z); - const nDivAlpha = n / abs_alpha, - AA = Math.exp(-0.5 * abs_alpha * abs_alpha), - B = nDivAlpha - abs_alpha, - arg = nDivAlpha / (B - z); - return AA * Math.pow(arg,n); -} - -/** @summary pdf definition of the crystal_ball which is defined only for n > 1 otherwise integral is diverging - * @memberof Math */ -function crystalball_pdf(x, alpha, n, sigma, mean = 0) { - if (sigma < 0.) - return 0.; - if (n <= 1) - return Number.NaN; // pdf is not normalized for n <=1 - const abs_alpha = Math.abs(alpha), - C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), - D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), - N = 1. / (sigma * (C + D)); - return N * crystalball_function(x,alpha,n,sigma,mean); -} - -/** @summary compute the integral of the crystal ball function - * @memberof Math */ -function crystalball_integral(x, alpha, n, sigma, mean = 0) { - if (sigma == 0) - return 0; - if (alpha == 0) - return 0.; - const useLog = (n == 1.0), - abs_alpha = Math.abs(alpha); - - let z = (x - mean) / sigma, intgaus = 0., intpow = 0.; - if (alpha < 0 ) - z = -z; - - const sqrtpiover2 = Math.sqrt(M_PI / 2.), - sqrt2pi = Math.sqrt( 2. * M_PI), - oneoversqrt2 = 1. / Math.sqrt(2.); - if (z <= -abs_alpha) { - const A = Math.pow(n / abs_alpha,n) * Math.exp(-0.5 * alpha * alpha), - B = n / abs_alpha - abs_alpha; - - if (!useLog) { - const C = (n / abs_alpha) * (1. / (n - 1)) * Math.exp(-alpha * alpha / 2.); - intpow = C - A / (n - 1.) * Math.pow(B - z,-n + 1); - } - else { - // for n=1 the primitive of 1/x is log(x) - intpow = -A * Math.log( n / abs_alpha ) + A * Math.log(B - z); - } - intgaus = sqrtpiover2 * (1. + erf(abs_alpha * oneoversqrt2)); - } else { - intgaus = normal_cdf_c(z, 1); - intgaus *= sqrt2pi; - intpow = 0; - } - return sigma * (intgaus + intpow); -} - -/** @summary crystalball_cdf function - * @memberof Math */ -function crystalball_cdf(x, alpha, n, sigma, mean = 0) { - if (n <= 1.) - return Number.NaN; - - const abs_alpha = Math.abs(alpha), - C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), - D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), - totIntegral = sigma * (C + D), - integral = crystalball_integral(x,alpha,n,sigma,mean); - - return (alpha > 0) ? 1. - integral / totIntegral : integral / totIntegral; -} - -/** @summary crystalball_cdf_c function - * @memberof Math */ -function crystalball_cdf_c(x, alpha, n, sigma, mean = 0) { - if (n <= 1.) - return Number.NaN; - - const abs_alpha = Math.abs(alpha), - C = n / abs_alpha * 1. / (n - 1.) * Math.exp(-alpha * alpha / 2.), - D = Math.sqrt(M_PI / 2.) * (1. + erf(abs_alpha / Math.sqrt(2.))), - totIntegral = sigma * (C + D), - integral = crystalball_integral(x,alpha,n,sigma,mean); - - return (alpha > 0) ? integral / totIntegral : 1. - (integral / totIntegral); -} - -/** @summary ChebyshevN function - * @memberof Math */ -function ChebyshevN(n, x, c) { - let d1 = 0.0, d2 = 0.0; - const y2 = 2.0 * x; - - for (let i = n; i >= 1; i--) { - const temp = d1; - d1 = y2 * d1 - d2 + c[i]; - d2 = temp; - } - - return x * d1 - d2 + c[0]; -} - -/** @summary Chebyshev0 function - * @memberof Math */ -function Chebyshev0(_x, c0) { - return c0; -} - -/** @summary Chebyshev1 function - * @memberof Math */ -function Chebyshev1(x, c0, c1) { - return c0 + c1 * x; -} - -/** @summary Chebyshev2 function - * @memberof Math */ -function Chebyshev2(x, c0, c1, c2) { - return c0 + c1 * x + c2 * (2.0 * x * x - 1.0); -} - -/** @summary Chebyshev3 function - * @memberof Math */ -function Chebyshev3(x, ...args) { - return ChebyshevN(3, x, args); -} - -/** @summary Chebyshev4 function - * @memberof Math */ -function Chebyshev4(x, ...args) { - return ChebyshevN(4, x, args); -} - -/** @summary Chebyshev5 function - * @memberof Math */ -function Chebyshev5(x, ...args) { - return ChebyshevN(5, x, args); -} - -/** @summary Chebyshev6 function - * @memberof Math */ -function Chebyshev6(x, ...args) { - return ChebyshevN(6, x, args); -} - -/** @summary Chebyshev7 function - * @memberof Math */ -function Chebyshev7(x, ...args) { - return ChebyshevN(7, x, args); -} - -/** @summary Chebyshev8 function - * @memberof Math */ -function Chebyshev8(x, ...args) { - return ChebyshevN(8, x, args); -} - -/** @summary Chebyshev9 function - * @memberof Math */ -function Chebyshev9(x, ...args) { - return ChebyshevN(9, x, args); -} - -/** @summary Chebyshev10 function - * @memberof Math */ -function Chebyshev10(x, ...args) { - return ChebyshevN(10, x, args); -} - -// ========================================================================= - -/** @summary Calculate ClopperPearson - * @memberof Math */ -function eff_ClopperPearson(total,passed,level,bUpper) { - const alpha = (1.0 - level) / 2; - if (bUpper) - return ((passed == total) ? 1.0 : beta_quantile(1 - alpha,passed + 1,total - passed)); - - return ((passed == 0) ? 0.0 : beta_quantile(alpha,passed,total - passed + 1.0)); -} - -/** @summary Calculate normal - * @memberof Math */ -function eff_Normal(total,passed,level,bUpper) { - if (total == 0) - return bUpper ? 1 : 0; - - const alpha = (1.0 - level) / 2, - average = passed / total, - sigma = Math.sqrt(average * (1 - average) / total), - delta = normal_quantile(1 - alpha, sigma); - - if (bUpper) - return ((average + delta) > 1) ? 1.0 : (average + delta); - - return ((average - delta) < 0) ? 0.0 : (average - delta); -} - -/** @summary Calculates the boundaries for the frequentist Wilson interval - * @memberof Math */ -function eff_Wilson(total,passed,level,bUpper) { - const alpha = (1.0 - level) / 2; - if (total == 0) - return bUpper ? 1 : 0; - const average = passed / total, - kappa = normal_quantile(1 - alpha,1), - mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), - delta = kappa / (total + kappa * kappa) * Math.sqrt(total * average * (1 - average) + kappa * kappa / 4); - - if (bUpper) - return ((mode + delta) > 1) ? 1.0 : (mode + delta); - - return ((mode - delta) < 0) ? 0.0 : (mode - delta); -} - -/** @summary Calculates the boundaries for the frequentist Agresti-Coull interval - * @memberof Math */ -function eff_AgrestiCoull(total,passed,level,bUpper) { - const alpha = (1.0 - level) / 2, - kappa = normal_quantile(1 - alpha,1), - mode = (passed + 0.5 * kappa * kappa) / (total + kappa * kappa), - delta = kappa * Math.sqrt(mode * (1 - mode) / (total + kappa * kappa)); - - if (bUpper) - return ((mode + delta) > 1) ? 1.0 : (mode + delta); - - return ((mode - delta) < 0) ? 0.0 : (mode - delta); -} - -/** @summary Calculates the boundaries using the mid-P binomial - * @memberof Math */ -function eff_MidPInterval(total,passed,level,bUpper) { - const alpha = 1. - level, equal_tailed = true, alpha_min = equal_tailed ? alpha / 2 : alpha, tol = 1e-9; // tolerance - let pmin = 0, pmax = 1, p = 0; - - // treat special case for 0<passed<1 - // do a linear interpolation of the upper limit values - if (passed > 0 && passed < 1) { - const p0 = eff_MidPInterval(total, 0.0, level, bUpper), - p1 = eff_MidPInterval(total, 1.0, level, bUpper); - p = (p1 - p0) * passed + p0; - return p; - } - - while (Math.abs(pmax - pmin) > tol) { - p = (pmin + pmax) / 2; - // double v = 0.5 * ROOT::Math::binomial_pdf(int(passed), p, int(total)); - // make it work for non integer using the binomial - beta relationship - let v = 0.5 * beta_pdf(p, passed + 1., total - passed + 1) / (total + 1); - // if (passed > 0) - // v += ROOT::Math::binomial_cdf(int(passed - 1), p, int(total)); - // compute the binomial cdf at passed -1 - if ((passed - 1) >= 0) - v += beta_cdf_c(p, passed, total - passed + 1); - - const vmin = bUpper ? alpha_min : 1. - alpha_min; - if (v > vmin) - pmin = p; - else - pmax = p; - } - - return p; -} - -/** @summary for a central confidence interval for a Beta distribution - * @memberof Math */ -function eff_Bayesian(total,passed,level,bUpper,alpha,beta) { - const a = passed + alpha, - b = total - passed + beta; - if (bUpper) { - if ((a > 0) && (b > 0)) - return beta_quantile((1 + level) / 2,a,b); - else - return 1; - } else if ((a > 0) && (b > 0)) - return beta_quantile((1 - level) / 2,a,b); - - return 0; -} - -/** @summary Return function to calculate boundary of TEfficiency - * @memberof Math */ -function getTEfficiencyBoundaryFunc(option, isbayessian) { - const kFCP = 0, // Clopper-Pearson interval (recommended by PDG) - kFNormal = 1, // Normal approximation - kFWilson = 2, // Wilson interval - kFAC = 3, // Agresti-Coull interval - kFFC = 4, // Feldman-Cousins interval, too complicated for JavaScript - // kBJeffrey = 5, // Jeffrey interval (Prior ~ Beta(0.5,0.5) - // kBUniform = 6, // Prior ~ Uniform = Beta(1,1) - // kBBayesian = 7, // User specified Prior ~ Beta(fBeta_alpha,fBeta_beta) - kMidP = 8; // Mid-P Lancaster interval - - if (isbayessian) - return eff_Bayesian; - - switch (option) { - case kFCP: - return eff_ClopperPearson; - case kFNormal: - return eff_Normal; - case kFWilson: - return eff_Wilson; - case kFAC: - return eff_AgrestiCoull; - case kFFC: - console.log('Feldman-Cousins interval kFFC not supported; using kFCP'); - return eff_ClopperPearson; - case kMidP: - return eff_MidPInterval; - // case kBJeffrey: - // case kBUniform: - // case kBBayesian: return eff_ClopperPearson; - } - console.log(`Not recognized stat option ${option}, using kFCP`); - return eff_ClopperPearson; -} - -/** @summary Square function - * @memberof Math */ -function Sq(x) { - return x * x; -} - -/** @summary Pi function - * @memberof Math */ -function Pi() { - return Math.PI; -} - -/** @summary TwoPi function - * @memberof Math */ -function TwoPi() { - return 2 * Math.PI; -} - -/** @summary PiOver2 function - * @memberof Math */ -function PiOver2() -{ - return Math.PI / 2; -} - -/** @summary PiOver4 function - * @memberof Math */ -function PiOver4() -{ - return Math.PI / 4; -} - -/** @summary InvPi function - * @memberof Math */ -function InvPi() -{ - return 1 / Math.PI; -} - - -export { gamma, gamma as tgamma, gamma as Gamma, - Polynomialeval, Polynomial1eval, stirf, - gamma_pdf, ndtri, normal_quantile, normal_quantile_c, lognormal_cdf_c, lognormal_cdf, - igami, igamc, igam, lgam, lgamma, erfc, erf, - beta_pdf, inc_beta, BetaIncomplete, - pseries, incbet, incbi, beta_quantile, chisquared_cdf_c, - beta, inc_gamma, inc_gamma_c, landau_pdf, beta_cdf_c, Landau, - fdistribution_pdf, fdistribution_pdf as FDist, - fdistribution_cdf, fdistribution_cdf as FDistI, - fdistribution_cdf_c, - normal_cdf_c, normal_cdf_c as gaussian_cdf_c, - normal_cdf, normal_cdf as gaussian_cdf, - lognormal_pdf, normal_pdf, crystalball_function, crystalball_pdf, crystalball_cdf, crystalball_cdf_c, - - chisquared_cdf, gamma_quantile_c, gamma_quantile, breitwigner_cdf_c, breitwigner_cdf, - cauchy_cdf_c, cauchy_cdf, cauchy_pdf, gaussian_pdf, - tdistribution_cdf_c, tdistribution_cdf, tdistribution_pdf, exponential_cdf_c, exponential_cdf, chisquared_pdf, - Beta, GammaDist, LaplaceDist, LaplaceDistI, LogNormal, Student, StudentI, - gaus, gausn, gausxy, expo, - Prob, Gaus, BreitWigner, BetaDist, BetaDistI, landau, landaun, - - ChebyshevN, Chebyshev0, Chebyshev1, Chebyshev2, Chebyshev3, Chebyshev4, - Chebyshev5, Chebyshev6, Chebyshev7, Chebyshev8, Chebyshev9, Chebyshev10, - - getTEfficiencyBoundaryFunc, - - Sq, Pi, TwoPi, PiOver2, PiOver4, InvPi }; diff --git a/js/modules/base/sha256.mjs b/js/modules/base/sha256.mjs deleted file mode 100644 index 8c6adc6e8e0df..0000000000000 --- a/js/modules/base/sha256.mjs +++ /dev/null @@ -1,265 +0,0 @@ -/** - * [js-sha256]{@link https://github.com/emn178/js-sha256} - * - * @version 0.10.1 - * @author Chen, Yi-Cyuan [emn178@gmail.com] - * @copyright Chen, Yi-Cyuan 2014-2023 - * @license MIT - */ - -const HEX_CHARS = '0123456789abcdef'.split(''), - EXTRA = [-2147483648, 8388608, 32768, 128], - SHIFT = [24, 16, 8, 0], - K = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]; - - -class Sha256 { - - constructor(is224) { - this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - - if (is224) { - this.h0 = 0xc1059ed8; - this.h1 = 0x367cd507; - this.h2 = 0x3070dd17; - this.h3 = 0xf70e5939; - this.h4 = 0xffc00b31; - this.h5 = 0x68581511; - this.h6 = 0x64f98fa7; - this.h7 = 0xbefa4fa4; - } else { // 256 - this.h0 = 0x6a09e667; - this.h1 = 0xbb67ae85; - this.h2 = 0x3c6ef372; - this.h3 = 0xa54ff53a; - this.h4 = 0x510e527f; - this.h5 = 0x9b05688c; - this.h6 = 0x1f83d9ab; - this.h7 = 0x5be0cd19; - } - - this.block = this.start = this.bytes = this.hBytes = 0; - this.finalized = this.hashed = false; - this.first = true; - this.is224 = is224; - } - - /** One can use only string or Uint8Array */ - update(message) { - if (this.finalized) - return; - - const notString = (typeof message !== 'string'), - length = message.length, blocks = this.blocks; - - let code, index = 0, i; - - while (index < length) { - if (this.hashed) { - this.hashed = false; - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - } - - if (notString) { - for (i = this.start; index < length && i < 64; ++index) - blocks[i >> 2] |= message[index] << SHIFT[i++ & 3]; - } else { - for (i = this.start; index < length && i < 64; ++index) { - code = message.charCodeAt(index); - if (code < 0x80) - blocks[i >> 2] |= code << SHIFT[i++ & 3]; - else if (code < 0x800) { - blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else if (code < 0xd800 || code >= 0xe000) { - blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } else { - code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff)); - blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; - blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3]; - } - } - } - - this.lastByteIndex = i; - this.bytes += i - this.start; - if (i >= 64) { - this.block = blocks[16]; - this.start = i - 64; - this.hash(); - this.hashed = true; - } else - this.start = i; - } - if (this.bytes > 4294967295) { - this.hBytes += this.bytes / 4294967296 << 0; - this.bytes %= 4294967296; - } - return this; - } - - finalize() { - if (this.finalized) - return; - this.finalized = true; - const blocks = this.blocks, - i = this.lastByteIndex; - blocks[16] = this.block; - blocks[i >> 2] |= EXTRA[i & 3]; - this.block = blocks[16]; - if (i >= 56) { - if (!this.hashed) - this.hash(); - blocks[0] = this.block; - blocks[16] = blocks[1] = blocks[2] = blocks[3] = - blocks[4] = blocks[5] = blocks[6] = blocks[7] = - blocks[8] = blocks[9] = blocks[10] = blocks[11] = - blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; - } - blocks[14] = this.hBytes << 3 | this.bytes >>> 29; - blocks[15] = this.bytes << 3; - this.hash(); - } - - hash() { - const blocks = this.blocks; - let a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6, - h = this.h7, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc; - - for (j = 16; j < 64; ++j) { - // right rotate - t1 = blocks[j - 15]; - s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3); - t1 = blocks[j - 2]; - s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10); - blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0; - } - - bc = b & c; - for (j = 0; j < 64; j += 4) { - if (this.first) { - if (this.is224) { - ab = 300032; - t1 = blocks[0] - 1413257819; - h = t1 - 150054599 << 0; - d = t1 + 24177077 << 0; - } else { - ab = 704751109; - t1 = blocks[0] - 210244248; - h = t1 - 1521486534 << 0; - d = t1 + 143694565 << 0; - } - this.first = false; - } else { - s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10)); - s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7)); - ab = a & b; - maj = ab ^ (a & c) ^ bc; - ch = (e & f) ^ (~e & g); - t1 = h + s1 + ch + K[j] + blocks[j]; - t2 = s0 + maj; - h = d + t1 << 0; - d = t1 + t2 << 0; - } - s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10)); - s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7)); - da = d & a; - maj = da ^ (d & b) ^ ab; - ch = (h & e) ^ (~h & f); - t1 = g + s1 + ch + K[j + 1] + blocks[j + 1]; - t2 = s0 + maj; - g = c + t1 << 0; - c = t1 + t2 << 0; - s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10)); - s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7)); - cd = c & d; - maj = cd ^ (c & a) ^ da; - ch = (g & h) ^ (~g & e); - t1 = f + s1 + ch + K[j + 2] + blocks[j + 2]; - t2 = s0 + maj; - f = b + t1 << 0; - b = t1 + t2 << 0; - s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10)); - s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7)); - bc = b & c; - maj = bc ^ (b & d) ^ cd; - ch = (f & g) ^ (~f & h); - t1 = e + s1 + ch + K[j + 3] + blocks[j + 3]; - t2 = s0 + maj; - e = a + t1 << 0; - a = t1 + t2 << 0; - this.chromeBugWorkAround = true; - } - - this.h0 = this.h0 + a << 0; - this.h1 = this.h1 + b << 0; - this.h2 = this.h2 + c << 0; - this.h3 = this.h3 + d << 0; - this.h4 = this.h4 + e << 0; - this.h5 = this.h5 + f << 0; - this.h6 = this.h6 + g << 0; - this.h7 = this.h7 + h << 0; - } - - digest() { - this.finalize(); - - const h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5, - h6 = this.h6, h7 = this.h7, - arr = [ - (h0 >> 24) & 0xFF, (h0 >> 16) & 0xFF, (h0 >> 8) & 0xFF, h0 & 0xFF, - (h1 >> 24) & 0xFF, (h1 >> 16) & 0xFF, (h1 >> 8) & 0xFF, h1 & 0xFF, - (h2 >> 24) & 0xFF, (h2 >> 16) & 0xFF, (h2 >> 8) & 0xFF, h2 & 0xFF, - (h3 >> 24) & 0xFF, (h3 >> 16) & 0xFF, (h3 >> 8) & 0xFF, h3 & 0xFF, - (h4 >> 24) & 0xFF, (h4 >> 16) & 0xFF, (h4 >> 8) & 0xFF, h4 & 0xFF, - (h5 >> 24) & 0xFF, (h5 >> 16) & 0xFF, (h5 >> 8) & 0xFF, h5 & 0xFF, - (h6 >> 24) & 0xFF, (h6 >> 16) & 0xFF, (h6 >> 8) & 0xFF, h6 & 0xFF - ]; - if (!this.is224) - arr.push((h7 >> 24) & 0xFF, (h7 >> 16) & 0xFF, (h7 >> 8) & 0xFF, h7 & 0xFF); - return arr; - } - - hex() { - const d = this.digest(); - let res = ''; - for (let i = 0; i < d.length; ++i) - res += HEX_CHARS[(d[i] >> 4) & 0xF] + HEX_CHARS[d[i] & 0xF]; - return res; - } - - toString() { - return this.hex(); - } - -} // class Sha256 - -function sha256(message, as_hex) { - const m = new Sha256(false); - m.update(message); - return as_hex ? m.hex() : m.digest(); -} - -function sha256_2(message, arr, as_hex) { - const m = new Sha256(false); - m.update(message); - m.update(arr); - return as_hex ? m.hex() : m.digest(); -} - -export { sha256, sha256_2 }; diff --git a/js/modules/base/svg2pdf.mjs b/js/modules/base/svg2pdf.mjs deleted file mode 100644 index d39299ca011e4..0000000000000 --- a/js/modules/base/svg2pdf.mjs +++ /dev/null @@ -1,5953 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2015-2026 yWorks GmbH - * Copyright (c) 2013-2015 by Vitaly Puzrin - * - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { jsPDF, GState, ShadingPattern, TilingPattern } from './jspdf.mjs'; - -/****************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -/* global Reflect, Promise, SuppressedError, Symbol, Iterator */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; - -function __awaiter(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()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["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 }; - } -} - -typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { - var e = new Error(message); - return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; -}; - -/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types */ -var RGBColor = /** @class */ (function () { - function RGBColor(colorString) { - this.a = undefined; - this.r = 0; - this.g = 0; - this.b = 0; - this.simpleColors = {}; - // eslint-disable-next-line @typescript-eslint/ban-types - this.colorDefs = []; - this.ok = false; - if (!colorString) { - return; - } - // strip any leading # - if (colorString.charAt(0) == '#') { - // remove # if any - colorString = colorString.substr(1, 6); - } - colorString = colorString.replace(/ /g, ''); - colorString = colorString.toLowerCase(); - // before getting into regexps, try simple matches - // and overwrite the input - this.simpleColors = { - 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', - darkgrey: 'a9a9a9', - darkgreen: '006400', - darkkhaki: 'bdb76b', - darkmagenta: '8b008b', - darkolivegreen: '556b2f', - darkorange: 'ff8c00', - darkorchid: '9932cc', - darkred: '8b0000', - darksalmon: 'e9967a', - darkseagreen: '8fbc8f', - darkslateblue: '483d8b', - darkslategray: '2f4f4f', - darkslategrey: '2f4f4f', - darkturquoise: '00ced1', - darkviolet: '9400d3', - deeppink: 'ff1493', - deepskyblue: '00bfff', - dimgray: '696969', - dimgrey: '696969', - dodgerblue: '1e90ff', - feldspar: 'd19275', - firebrick: 'b22222', - floralwhite: 'fffaf0', - forestgreen: '228b22', - fuchsia: 'ff00ff', - gainsboro: 'dcdcdc', - ghostwhite: 'f8f8ff', - gold: 'ffd700', - goldenrod: 'daa520', - gray: '808080', - grey: '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', - lightgray: 'd3d3d3', - lightgrey: 'd3d3d3', - lightgreen: '90ee90', - lightpink: 'ffb6c1', - lightsalmon: 'ffa07a', - lightseagreen: '20b2aa', - lightskyblue: '87cefa', - lightslateblue: '8470ff', - lightslategray: '778899', - lightslategrey: '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', - slategrey: '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 this.simpleColors) { - if (colorString == key) { - colorString = this.simpleColors[key]; - } - } - // emd of simple type-in colors - // array of color definition objects - this.colorDefs = [ - { - 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]), parseInt(bits[2]), parseInt(bits[3])]; - } - }, - { - re: /^rgb\(([0-9.]+)%,\s*([0-9.]+)%,\s*([0-9.]+)%\)$/, - example: ['rgb(50.5%, 25.75%, 75.5%)', 'rgb(100%,0%,0%)'], - process: function (bits) { - return [ - Math.round(parseFloat(bits[1]) * 2.55), - Math.round(parseFloat(bits[2]) * 2.55), - Math.round(parseFloat(bits[3]) * 2.55) - ]; - } - }, - { - 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 < this.colorDefs.length; i++) { - var re = this.colorDefs[i].re; - var processor = this.colorDefs[i].process; - var bits = re.exec(colorString); - if (bits) { - var 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; - } - RGBColor.prototype.toRGB = function () { - return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')'; - }; - RGBColor.prototype.toRGBA = function () { - return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ', ' + (this.a || '1') + ')'; - }; - RGBColor.prototype.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; - }; - // help - RGBColor.prototype.getHelpXML = function () { - var examples = []; - // add regexps - for (var i = 0; i < this.colorDefs.length; i++) { - var example = this.colorDefs[i].example; - for (var j = 0; j < example.length; j++) { - examples[examples.length] = example[j]; - } - } - // add type-in colors - for (var sc in this.simpleColors) { - examples[examples.length] = sc; - } - var xml = document.createElement('ul'); - xml.setAttribute('id', 'rgbcolor-examples'); - for (var i = 0; i < examples.length; i++) { - try { - var listItem = document.createElement('li'); - var listColor = new RGBColor(examples[i]); - var exampleDiv = document.createElement('div'); - exampleDiv.style.cssText = - 'margin: 3px; ' + - 'border: 1px solid black; ' + - 'background:' + - listColor.toHex() + - '; ' + - 'color:' + - listColor.toHex(); - exampleDiv.appendChild(document.createTextNode('test')); - var listItemValue = document.createTextNode(' ' + examples[i] + ' -> ' + listColor.toRGB() + ' -> ' + listColor.toHex()); - listItem.appendChild(exampleDiv); - listItem.appendChild(listItemValue); - xml.appendChild(listItem); - } - catch (_a) { } - } - return xml; - }; - return RGBColor; -}()); - -var ColorFill = /** @class */ (function () { - function ColorFill(color) { - this.color = color; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ColorFill.prototype.getFillData = function (forNode, context) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - return [2 /*return*/, undefined]; - }); - }); - }; - return ColorFill; -}()); - -var AttributeState = /** @class */ (function () { - function AttributeState() { - this.xmlSpace = ''; - this.whiteSpace = ''; - this.fill = null; - this.fillOpacity = 1.0; - // public fillRule: string = null - this.fontFamily = ''; - this.fontSize = 16; - this.fontStyle = ''; - // public fontVariant: string - this.fontWeight = ''; - this.opacity = 1.0; - this.stroke = null; - this.strokeDasharray = null; - this.strokeDashoffset = 0; - this.strokeLinecap = ''; - this.strokeLinejoin = ''; - this.strokeMiterlimit = 4.0; - this.strokeOpacity = 1.0; - this.strokeWidth = 1.0; - // public textAlign: string - this.alignmentBaseline = ''; - this.textAnchor = ''; - this.visibility = ''; - this.color = null; - this.contextFill = null; - this.contextStroke = null; - this.fillRule = null; - } - AttributeState.prototype.clone = function () { - var clone = new AttributeState(); - clone.xmlSpace = this.xmlSpace; - clone.whiteSpace = this.whiteSpace; - clone.fill = this.fill; - clone.fillOpacity = this.fillOpacity; - // clone.fillRule = this.fillRule; - clone.fontFamily = this.fontFamily; - clone.fontSize = this.fontSize; - clone.fontStyle = this.fontStyle; - // clone.fontVariant = this.fontVariant; - clone.fontWeight = this.fontWeight; - clone.opacity = this.opacity; - clone.stroke = this.stroke; - clone.strokeDasharray = this.strokeDasharray; - clone.strokeDashoffset = this.strokeDashoffset; - clone.strokeLinecap = this.strokeLinecap; - clone.strokeLinejoin = this.strokeLinejoin; - clone.strokeMiterlimit = this.strokeMiterlimit; - clone.strokeOpacity = this.strokeOpacity; - clone.strokeWidth = this.strokeWidth; - // clone.textAlign = this.textAlign; - clone.textAnchor = this.textAnchor; - clone.alignmentBaseline = this.alignmentBaseline; - clone.visibility = this.visibility; - clone.color = this.color; - clone.fillRule = this.fillRule; - clone.contextFill = this.contextFill; - clone.contextStroke = this.contextStroke; - return clone; - }; - AttributeState.default = function () { - var attributeState = new AttributeState(); - attributeState.xmlSpace = 'default'; - attributeState.whiteSpace = 'normal'; - attributeState.fill = new ColorFill(new RGBColor('rgb(0, 0, 0)')); - attributeState.fillOpacity = 1.0; - // attributeState.fillRule = "nonzero"; - attributeState.fontFamily = 'times'; - attributeState.fontSize = 16; - attributeState.fontStyle = 'normal'; - // attributeState.fontVariant = "normal"; - attributeState.fontWeight = 'normal'; - attributeState.opacity = 1.0; - attributeState.stroke = null; - attributeState.strokeDasharray = null; - attributeState.strokeDashoffset = 0; - attributeState.strokeLinecap = 'butt'; - attributeState.strokeLinejoin = 'miter'; - attributeState.strokeMiterlimit = 4.0; - attributeState.strokeOpacity = 1.0; - attributeState.strokeWidth = 1.0; - // attributeState.textAlign = "start"; - attributeState.alignmentBaseline = 'baseline'; - attributeState.textAnchor = 'start'; - attributeState.visibility = 'visible'; - attributeState.color = new RGBColor('rgb(0, 0, 0)'); - attributeState.fillRule = 'nonzero'; - attributeState.contextFill = null; - attributeState.contextStroke = null; - return attributeState; - }; - AttributeState.getContextColors = function (context, includeCurrentColor) { - if (includeCurrentColor === void 0) { includeCurrentColor = false; } - var colors = {}; - if (context.attributeState.contextFill) { - colors['contextFill'] = context.attributeState.contextFill; - } - if (context.attributeState.contextStroke) { - colors['contextStroke'] = context.attributeState.contextStroke; - } - if (includeCurrentColor && context.attributeState.color) { - colors['color'] = context.attributeState.color; - } - return colors; - }; - return AttributeState; -}()); - -/** - * - * @package - * @param values - * @constructor - * @property pdf - * @property attributeState Keeps track of parent attributes that are inherited automatically - * @property refsHandler The handler that will render references on demand - * @property styleSheets - * @property textMeasure - * @property transform The current transformation matrix - * @property withinClipPath - */ -var Context = /** @class */ (function () { - function Context(pdf, values) { - var _a, _b, _c; - this.pdf = pdf; - this.svg2pdfParameters = values.svg2pdfParameters; - this.attributeState = values.attributeState - ? values.attributeState.clone() - : AttributeState.default(); - this.viewport = values.viewport; - this.refsHandler = values.refsHandler; - this.styleSheets = values.styleSheets; - this.textMeasure = values.textMeasure; - this.transform = (_a = values.transform) !== null && _a !== void 0 ? _a : this.pdf.unitMatrix; - this.withinClipPath = (_b = values.withinClipPath) !== null && _b !== void 0 ? _b : false; - this.withinUse = (_c = values.withinUse) !== null && _c !== void 0 ? _c : false; - } - Context.prototype.clone = function (values) { - var _a, _b, _c, _d; - if (values === void 0) { values = {}; } - return new Context(this.pdf, { - svg2pdfParameters: this.svg2pdfParameters, - attributeState: values.attributeState - ? values.attributeState.clone() - : this.attributeState.clone(), - viewport: (_a = values.viewport) !== null && _a !== void 0 ? _a : this.viewport, - refsHandler: this.refsHandler, - styleSheets: this.styleSheets, - textMeasure: this.textMeasure, - transform: (_b = values.transform) !== null && _b !== void 0 ? _b : this.transform, - withinClipPath: (_c = values.withinClipPath) !== null && _c !== void 0 ? _c : this.withinClipPath, - withinUse: (_d = values.withinUse) !== null && _d !== void 0 ? _d : this.withinUse - }); - }; - return Context; -}()); - -var ReferencesHandler = /** @class */ (function () { - function ReferencesHandler(idMap) { - this.renderedElements = {}; - this.idMap = idMap; - this.idPrefix = String(ReferencesHandler.instanceCounter++); - } - ReferencesHandler.prototype.getRendered = function (id, contextColors, renderCallback) { - return __awaiter(this, void 0, void 0, function () { - var key, svgNode; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - key = this.generateKey(id, contextColors); - if (this.renderedElements.hasOwnProperty(key)) { - return [2 /*return*/, this.renderedElements[id]]; - } - svgNode = this.get(id); - this.renderedElements[key] = svgNode; - return [4 /*yield*/, renderCallback(svgNode)]; - case 1: - _a.sent(); - return [2 /*return*/, svgNode]; - } - }); - }); - }; - ReferencesHandler.prototype.get = function (id) { - // return this.idMap[cssEsc(id, { isIdentifier: true })] - return this.idMap[id]; // jsroot uses plain ids - }; - ReferencesHandler.prototype.generateKey = function (id, contextColors) { - var colorHash = ''; - var keys = ['color', 'contextFill', 'contextStroke']; - if (contextColors) { - colorHash = keys.map(function (key) { var _a, _b; return (_b = (_a = contextColors[key]) === null || _a === void 0 ? void 0 : _a.toRGBA()) !== null && _b !== void 0 ? _b : ''; }).join('|'); - } - return this.idPrefix + '|' + id + '|' + colorHash; - }; - ReferencesHandler.instanceCounter = 0; - return ReferencesHandler; -}()); - -function getAngle(from, to) { - return Math.atan2(to[1] - from[1], to[0] - from[0]); -} -var cToQ = 2 / 3; // ratio to convert quadratic bezier curves to cubic ones -// transforms a cubic bezier control point to a quadratic one: returns from + (2/3) * (to - from) -function toCubic(from, to) { - return [cToQ * (to[0] - from[0]) + from[0], cToQ * (to[1] - from[1]) + from[1]]; -} -function normalize(v) { - var length = Math.sqrt(v[0] * v[0] + v[1] * v[1]); - return [v[0] / length, v[1] / length]; -} -function getDirectionVector(from, to) { - var v = [to[0] - from[0], to[1] - from[1]]; - return normalize(v); -} -function addVectors(v1, v2) { - return [v1[0] + v2[0], v1[1] + v2[1]]; -} -// multiplies a vector with a matrix: vec' = vec * matrix -function multVecMatrix(vec, matrix) { - var x = vec[0]; - var y = vec[1]; - return [matrix.a * x + matrix.c * y + matrix.e, matrix.b * x + matrix.d * y + matrix.f]; -} - -var Path = /** @class */ (function () { - function Path() { - this.segments = []; - } - Path.prototype.moveTo = function (x, y) { - this.segments.push(new MoveTo(x, y)); - return this; - }; - Path.prototype.lineTo = function (x, y) { - this.segments.push(new LineTo(x, y)); - return this; - }; - Path.prototype.curveTo = function (x1, y1, x2, y2, x, y) { - this.segments.push(new CurveTo(x1, y1, x2, y2, x, y)); - return this; - }; - Path.prototype.close = function () { - this.segments.push(new Close()); - return this; - }; - /** - * Transforms the path in place - */ - Path.prototype.transform = function (matrix) { - this.segments.forEach(function (seg) { - if (seg instanceof MoveTo || seg instanceof LineTo || seg instanceof CurveTo) { - var p = multVecMatrix([seg.x, seg.y], matrix); - seg.x = p[0]; - seg.y = p[1]; - } - if (seg instanceof CurveTo) { - var p1 = multVecMatrix([seg.x1, seg.y1], matrix); - var p2 = multVecMatrix([seg.x2, seg.y2], matrix); - seg.x1 = p1[0]; - seg.y1 = p1[1]; - seg.x2 = p2[0]; - seg.y2 = p2[1]; - } - }); - }; - Path.prototype.draw = function (context) { - var p = context.pdf; - this.segments.forEach(function (s) { - if (s instanceof MoveTo) { - p.moveTo(s.x, s.y); - } - else if (s instanceof LineTo) { - p.lineTo(s.x, s.y); - } - else if (s instanceof CurveTo) { - p.curveTo(s.x1, s.y1, s.x2, s.y2, s.x, s.y); - } - else { - p.close(); - } - }); - }; - return Path; -}()); -var MoveTo = /** @class */ (function () { - function MoveTo(x, y) { - this.x = x; - this.y = y; - } - return MoveTo; -}()); -var LineTo = /** @class */ (function () { - function LineTo(x, y) { - this.x = x; - this.y = y; - } - return LineTo; -}()); -var CurveTo = /** @class */ (function () { - function CurveTo(x1, y1, x2, y2, x, y) { - this.x1 = x1; - this.y1 = y1; - this.x2 = x2; - this.y2 = y2; - this.x = x; - this.y = y; - } - return CurveTo; -}()); -var Close = /** @class */ (function () { - function Close() { - } - return Close; -}()); - -function nodeIs(node, tagsString) { - return tagsString.split(',').indexOf((node.nodeName || node.tagName).toLowerCase()) >= 0; -} -function forEachChild(node, fn) { - // copy list of children, as the original might be modified - var children = []; - for (var i = 0; i < node.childNodes.length; i++) { - var childNode = node.childNodes[i]; - if (childNode.nodeName.charAt(0) !== '#') - children.push(childNode); - } - for (var i = 0; i < children.length; i++) { - fn(i, children[i]); - } -} -// returns an attribute of a node, either from the node directly or from css -function getAttribute(node, styleSheets, propertyNode, propertyCss) { - var _a; - if (propertyCss === void 0) { propertyCss = propertyNode; } - var attribute = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propertyCss); - if (attribute) { - return attribute; - } - else { - var propertyValue = styleSheets.getPropertyValue(node, propertyCss); - if (propertyValue) { - return propertyValue; - } - else if (node.hasAttribute(propertyNode)) { - return node.getAttribute(propertyNode) || undefined; - } - else { - return undefined; - } - } -} -function svgNodeIsVisible(svgNode, parentVisible, context) { - if (getAttribute(svgNode.element, context.styleSheets, 'display') === 'none') { - return false; - } - var visible = parentVisible; - var visibility = getAttribute(svgNode.element, context.styleSheets, 'visibility'); - if (visibility) { - visible = visibility !== 'hidden'; - } - return visible; -} -function svgNodeAndChildrenVisible(svgNode, parentVisible, context) { - var visible = svgNodeIsVisible(svgNode, parentVisible, context); - if (svgNode.element.childNodes.length === 0) { - return false; - } - svgNode.children.forEach(function (child) { - if (child.isVisible(visible, context)) { - visible = true; - } - }); - return visible; -} - -/** - * @constructor - * @property {Marker[]} markers - */ -var MarkerList = /** @class */ (function () { - function MarkerList() { - this.markers = []; - } - MarkerList.prototype.addMarker = function (markers) { - this.markers.push(markers); - }; - MarkerList.prototype.draw = function (context) { - return __awaiter(this, void 0, void 0, function () { - var i, marker, tf, angle, anchor, cos, sin, contextColors; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - i = 0; - _a.label = 1; - case 1: - if (!(i < this.markers.length)) return [3 /*break*/, 4]; - marker = this.markers[i]; - tf = void 0; - angle = marker.angle, anchor = marker.anchor; - cos = Math.cos(angle); - sin = Math.sin(angle); - // position at and rotate around anchor - tf = context.pdf.Matrix(cos, sin, -sin, cos, anchor[0], anchor[1]); - // scale with stroke-width - tf = context.pdf.matrixMult(context.pdf.Matrix(context.attributeState.strokeWidth, 0, 0, context.attributeState.strokeWidth, 0, 0), tf); - tf = context.pdf.matrixMult(tf, context.transform); - // as the marker is already scaled by the current line width we must not apply the line width twice! - context.pdf.saveGraphicsState(); - contextColors = AttributeState.getContextColors(context); - return [4 /*yield*/, context.refsHandler.getRendered(marker.id, contextColors, function (node) { - return node.apply(context); - })]; - case 2: - _a.sent(); - context.pdf.doFormObject(context.refsHandler.generateKey(marker.id, contextColors), tf); - context.pdf.restoreGraphicsState(); - _a.label = 3; - case 3: - i++; - return [3 /*break*/, 1]; - case 4: return [2 /*return*/]; - } - }); - }); - }; - return MarkerList; -}()); -/** - * @param {string} id - * @param {[number,number]} anchor - * @param {number} angle - */ -var Marker = /** @class */ (function () { - function Marker(id, anchor, angle, isStartMarker) { - if (isStartMarker === void 0) { isStartMarker = false; } - this.id = id; - this.anchor = anchor; - this.angle = angle; - this.isStartMarker = isStartMarker; - } - return Marker; -}()); - -var iriReference = /url\(["']?#([^"']+)["']?\)/; -var alignmentBaselineMap = { - bottom: 'bottom', - 'text-bottom': 'bottom', - top: 'top', - 'text-top': 'top', - hanging: 'hanging', - middle: 'middle', - central: 'middle', - center: 'middle', - mathematical: 'middle', - ideographic: 'ideographic', - alphabetic: 'alphabetic', - baseline: 'alphabetic' -}; -var svgNamespaceURI = 'http://www.w3.org/2000/svg'; - -/** - * Convert em, px and bare number attributes to pixel values - * @param {string} value - * @param {number} pdfFontSize - */ -function toPixels(value, pdfFontSize) { - var match; - // em - match = value && value.toString().match(/^([-0-9.]+)em$/); - if (match) { - return parseFloat(match[1]) * pdfFontSize; - } - // pixels - match = value && value.toString().match(/^([-0-9.]+)(px|)$/); - if (match) { - return parseFloat(match[1]); - } - return 0; -} -function mapAlignmentBaseline(value) { - return alignmentBaselineMap[value] || 'alphabetic'; -} - -function parseFloats(str) { - var floats = []; - var regex = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g; - var match; - while ((match = regex.exec(str))) { - floats.push(parseFloat(match[0])); - } - return floats; -} -/** - * extends RGBColor by rgba colors as RGBColor is not capable of it - * currentcolor: the color to return if colorString === 'currentcolor' - */ -function parseColor(colorString, contextColors) { - if (colorString === 'transparent') { - var transparent = new RGBColor('rgb(0,0,0)'); - transparent.a = 0; - return transparent; - } - if (contextColors && colorString.toLowerCase() === 'currentcolor') { - return contextColors.color || new RGBColor('rgb(0,0,0)'); - } - if (contextColors && colorString.toLowerCase() === 'context-stroke') { - return contextColors.contextStroke || new RGBColor('rgb(0,0,0)'); - } - if (contextColors && colorString.toLowerCase() === 'context-fill') { - return contextColors.contextFill || new RGBColor('rgb(0,0,0)'); - } - var match = /\s*rgba\(((?:[^,)]*,){3}[^,)]*)\)\s*/.exec(colorString); - if (match) { - var floats = parseFloats(match[1]); - var color = new RGBColor('rgb(' + floats.slice(0, 3).join(',') + ')'); - color.a = floats[3]; - return color; - } - else { - return new RGBColor(colorString); - } -} - -function getDefaultExportFromCjs (x) { - return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; -} - -var fontFamilyPapandreou; -var hasRequiredFontFamilyPapandreou; - -function requireFontFamilyPapandreou () { - if (hasRequiredFontFamilyPapandreou) return fontFamilyPapandreou; - hasRequiredFontFamilyPapandreou = 1; - // parse - // ===== - - // states - // ------ - - var PLAIN = 0; - var STRINGS = 1; - var ESCAPING = 2; - var IDENTIFIER = 3; - var SEPARATING = 4; - var SPACEAFTERIDENTIFIER = 5; - - // patterns - // -------- - - var identifierPattern = /[a-z0-9_-]/i; - var spacePattern = /[\s\t]/; - - // --- - - var parse = function(str) { - - // vars - // ---- - - var starting = true; - var state = PLAIN; - var buffer = ''; - var i = 0; - var quote; - var c; - - // result - // ------ - - var names = []; - - // parse - // ----- - - while (true) { - - c = str[i]; - - if (state === PLAIN) { - - if (!c && starting) { - - break; - - } else if (!c && !starting) { - - throw new Error('Parse error'); - - } else if (c === '"' || c === "'") { - - quote = c; - state = STRINGS; - starting = false; - - } else if (spacePattern.test(c)) ; else if (identifierPattern.test(c)) { - - state = IDENTIFIER; - starting = false; - i--; - - } else { - - throw new Error('Parse error'); - - } - - } else if (state === STRINGS) { - - if (!c) { - - throw new Error('Parse Error'); - - } else if (c === "\\") { - - state = ESCAPING; - - } else if (c === quote) { - - names.push(buffer); - buffer = ''; - state = SEPARATING; - - } else { - - buffer += c; - - } - - } else if (state === ESCAPING) { - - if (c === quote || c === "\\") { - - buffer += c; - state = STRINGS; - - } else { - - throw new Error('Parse error'); - - } - - } else if (state === IDENTIFIER) { - - if (!c) { - - names.push(buffer); - break; - - } else if (identifierPattern.test(c)) { - - buffer += c; - - } else if (c === ',') { - - names.push(buffer); - buffer = ''; - state = PLAIN; - - } else if (spacePattern.test(c)) { - - state = SPACEAFTERIDENTIFIER; - } else { - - throw new Error('Parse error'); - - } - } else if (state === SPACEAFTERIDENTIFIER) { - - if (!c) { - - names.push(buffer); - break; - - } else if (identifierPattern.test(c)) { - - buffer += ' ' + c; - state = IDENTIFIER; - - } else if (c === ',') { - - names.push(buffer); - buffer = ''; - state = PLAIN; - - } else if (spacePattern.test(c)) ; else { - - throw new Error('Parse error'); - - } - - } else if (state === SEPARATING) { - - if (!c) { - - break; - - } else if (c === ',') { - - state = PLAIN; - - } else if (spacePattern.test(c)) ; else { - - throw new Error('Parse error'); - - } - - } - - i++; - - } - - // result - // ------ - - return names; - - }; - - // stringify - // ========= - - // pattern - // ------- - - var stringsPattern = /[^a-z0-9_-]/i; - - // --- - - var stringify = function(names, options) { - - // quote - // ----- - - var quote = options && options.quote || '"'; - if (quote !== '"' && quote !== "'") { - throw new Error('Quote must be `\'` or `"`'); - } - var quotePattern = new RegExp(quote, 'g'); - - // stringify - // --------- - - var safeNames = []; - - for (var i = 0; i < names.length; ++i) { - var name = names[i]; - - if (stringsPattern.test(name)) { - name = name - .replace(/\\/g, "\\\\") - .replace(quotePattern, "\\" + quote); - name = quote + name + quote; - } - safeNames.push(name); - } - - // result - // ------ - - return safeNames.join(', '); - }; - - // export - // ====== - - fontFamilyPapandreou = { - parse: parse, - stringify: stringify, - }; - return fontFamilyPapandreou; -} - -var fontFamilyPapandreouExports = requireFontFamilyPapandreou(); -var FontFamily = /*@__PURE__*/getDefaultExportFromCjs(fontFamilyPapandreouExports); - -var fontAliases = { - 'sans-serif': 'helvetica', - verdana: 'helvetica', - arial: 'helvetica', - fixed: 'courier', - monospace: 'courier', - terminal: 'courier', - serif: 'times', - cursive: 'times', - fantasy: 'times' -}; -function findFirstAvailableFontFamily(attributeState, fontFamilies, context) { - var fontType = combineFontStyleAndFontWeight(attributeState.fontStyle, attributeState.fontWeight); - var availableFonts = context.pdf.getFontList(); - var firstAvailable = ''; - var fontIsAvailable = fontFamilies.some(function (font) { - var availableStyles = availableFonts[font]; - if (availableStyles && availableStyles.indexOf(fontType) >= 0) { - firstAvailable = font; - return true; - } - font = font.toLowerCase(); - if (fontAliases.hasOwnProperty(font)) { - firstAvailable = font; - return true; - } - return false; - }); - if (!fontIsAvailable) { - firstAvailable = 'times'; - } - return firstAvailable; -} -var isJsPDF23 = (function () { - var parts = jsPDF.version.split('.'); - return parseFloat(parts[0]) === 2 && parseFloat(parts[1]) === 3; -})(); -function combineFontStyleAndFontWeight(fontStyle, fontWeight) { - if (isJsPDF23) { - return fontWeight == 400 - ? fontStyle == 'italic' - ? 'italic' - : 'normal' - : fontWeight == 700 && fontStyle !== 'italic' - ? 'bold' - : fontStyle + '' + fontWeight; - } - else { - return fontWeight == 400 || fontWeight === 'normal' - ? fontStyle === 'italic' - ? 'italic' - : 'normal' - : (fontWeight == 700 || fontWeight === 'bold') && fontStyle === 'normal' - ? 'bold' - : (fontWeight == 700 ? 'bold' : fontWeight) + '' + fontStyle; - } -} - -function getBoundingBoxByChildren(context, svgnode) { - if (getAttribute(svgnode.element, context.styleSheets, 'display') === 'none') { - return [0, 0, 0, 0]; - } - var boundingBox = []; - svgnode.children.forEach(function (child) { - var nodeBox = child.getBoundingBox(context); - if (nodeBox[0] === 0 && nodeBox[1] === 0 && nodeBox[2] === 0 && nodeBox[3] === 0) - return; - var transform = child.computeNodeTransform(context); - // TODO: take into account rotation matrix - nodeBox[0] = nodeBox[0] * transform.sx + transform.tx; - nodeBox[1] = nodeBox[1] * transform.sy + transform.ty; - nodeBox[2] = nodeBox[2] * transform.sx; - nodeBox[3] = nodeBox[3] * transform.sy; - if (boundingBox.length === 0) - boundingBox = nodeBox; - else - boundingBox = [ - Math.min(boundingBox[0], nodeBox[0]), - Math.min(boundingBox[1], nodeBox[1]), - Math.max(boundingBox[0] + boundingBox[2], nodeBox[0] + nodeBox[2]) - - Math.min(boundingBox[0], nodeBox[0]), - Math.max(boundingBox[1] + boundingBox[3], nodeBox[1] + nodeBox[3]) - - Math.min(boundingBox[1], nodeBox[1]) - ]; - }); - return boundingBox.length === 0 ? [0, 0, 0, 0] : boundingBox; -} -function defaultBoundingBox(element, context) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - var pf = parseFloat; - // TODO: check if there are other possible coordinate attributes - var x1 = pf(element.getAttribute('x1')) || - pf(getAttribute(element, context.styleSheets, 'x')) || - pf(getAttribute(element, context.styleSheets, 'cx')) - - pf(getAttribute(element, context.styleSheets, 'r')) || - 0; - var x2 = pf(element.getAttribute('x2')) || - x1 + pf(getAttribute(element, context.styleSheets, 'width')) || - pf(getAttribute(element, context.styleSheets, 'cx')) + - pf(getAttribute(element, context.styleSheets, 'r')) || - 0; - var y1 = pf(element.getAttribute('y1')) || - pf(getAttribute(element, context.styleSheets, 'y')) || - pf(getAttribute(element, context.styleSheets, 'cy')) - - pf(getAttribute(element, context.styleSheets, 'r')) || - 0; - var y2 = pf(element.getAttribute('y2')) || - y1 + pf(getAttribute(element, context.styleSheets, 'height')) || - pf(getAttribute(element, context.styleSheets, 'cy')) + - pf(getAttribute(element, context.styleSheets, 'r')) || - 0; - return [ - Math.min(x1, x2), - Math.min(y1, y2), - Math.max(x1, x2) - Math.min(x1, x2), - Math.max(y1, y2) - Math.min(y1, y2) - ]; -} - -function computeViewBoxTransform(node, viewBox, eX, eY, eWidth, eHeight, context, noTranslate) { - if (noTranslate === void 0) { noTranslate = false; } - var vbX = viewBox[0]; - var vbY = viewBox[1]; - var vbWidth = viewBox[2]; - var vbHeight = viewBox[3]; - var scaleX = eWidth / vbWidth; - var scaleY = eHeight / vbHeight; - var align, meetOrSlice; - var preserveAspectRatio = node.getAttribute('preserveAspectRatio'); - if (preserveAspectRatio) { - var alignAndMeetOrSlice = preserveAspectRatio.split(' '); - if (alignAndMeetOrSlice[0] === 'defer') { - alignAndMeetOrSlice = alignAndMeetOrSlice.slice(1); - } - align = alignAndMeetOrSlice[0]; - meetOrSlice = alignAndMeetOrSlice[1] || 'meet'; - } - else { - align = 'xMidYMid'; - meetOrSlice = 'meet'; - } - if (align !== 'none') { - if (meetOrSlice === 'meet') { - // uniform scaling with min scale - scaleX = scaleY = Math.min(scaleX, scaleY); - } - else if (meetOrSlice === 'slice') { - // uniform scaling with max scale - scaleX = scaleY = Math.max(scaleX, scaleY); - } - } - if (noTranslate) { - return context.pdf.Matrix(scaleX, 0, 0, scaleY, 0, 0); - } - var translateX = eX - vbX * scaleX; - var translateY = eY - vbY * scaleY; - if (align.indexOf('xMid') >= 0) { - translateX += (eWidth - vbWidth * scaleX) / 2; - } - else if (align.indexOf('xMax') >= 0) { - translateX += eWidth - vbWidth * scaleX; - } - if (align.indexOf('YMid') >= 0) { - translateY += (eHeight - vbHeight * scaleY) / 2; - } - else if (align.indexOf('YMax') >= 0) { - translateY += eHeight - vbHeight * scaleY; - } - var translate = context.pdf.Matrix(1, 0, 0, 1, translateX, translateY); - var scale = context.pdf.Matrix(scaleX, 0, 0, scaleY, 0, 0); - return context.pdf.matrixMult(scale, translate); -} -// parses the "transform" string -function parseTransform(transformString, context) { - if (!transformString || transformString === 'none') - return context.pdf.unitMatrix; - var mRegex = /^[\s,]*matrix\(([^)]+)\)\s*/, tRegex = /^[\s,]*translate\(([^)]+)\)\s*/, rRegex = /^[\s,]*rotate\(([^)]+)\)\s*/, sRegex = /^[\s,]*scale\(([^)]+)\)\s*/, sXRegex = /^[\s,]*skewX\(([^)]+)\)\s*/, sYRegex = /^[\s,]*skewY\(([^)]+)\)\s*/; - var resultMatrix = context.pdf.unitMatrix; - var m; - var tSLength; - while (transformString.length > 0 && transformString.length !== tSLength) { - tSLength = transformString.length; - var match = mRegex.exec(transformString); - if (match) { - m = parseFloats(match[1]); - resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(m[0], m[1], m[2], m[3], m[4], m[5]), resultMatrix); - transformString = transformString.substr(match[0].length); - } - match = rRegex.exec(transformString); - if (match) { - m = parseFloats(match[1]); - var a = (Math.PI * m[0]) / 180; - resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0), resultMatrix); - if (m[1] || m[2]) { - var t1 = context.pdf.Matrix(1, 0, 0, 1, m[1], m[2]); - var t2 = context.pdf.Matrix(1, 0, 0, 1, -m[1], -m[2]); - resultMatrix = context.pdf.matrixMult(t2, context.pdf.matrixMult(resultMatrix, t1)); - } - transformString = transformString.substr(match[0].length); - } - match = tRegex.exec(transformString); - if (match) { - m = parseFloats(match[1]); - resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, 0, 0, 1, m[0], m[1] || 0), resultMatrix); - transformString = transformString.substr(match[0].length); - } - match = sRegex.exec(transformString); - if (match) { - m = parseFloats(match[1]); - if (!m[1]) - m[1] = m[0]; - resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(m[0], 0, 0, m[1], 0, 0), resultMatrix); - transformString = transformString.substr(match[0].length); - } - match = sXRegex.exec(transformString); - if (match) { - m = parseFloat(match[1]); - m *= Math.PI / 180; - resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, 0, Math.tan(m), 1, 0, 0), resultMatrix); - transformString = transformString.substr(match[0].length); - } - match = sYRegex.exec(transformString); - if (match) { - m = parseFloat(match[1]); - m *= Math.PI / 180; - resultMatrix = context.pdf.matrixMult(context.pdf.Matrix(1, Math.tan(m), 0, 1, 0, 0), resultMatrix); - transformString = transformString.substr(match[0].length); - } - } - return resultMatrix; -} - -var SvgNode = /** @class */ (function () { - function SvgNode(element, children) { - this.element = element; - this.children = children; - this.parent = null; - } - SvgNode.prototype.setParent = function (parent) { - this.parent = parent; - }; - SvgNode.prototype.getParent = function () { - return this.parent; - }; - SvgNode.prototype.getBoundingBox = function (context) { - if (getAttribute(this.element, context.styleSheets, 'display') === 'none') { - return [0, 0, 0, 0]; - } - return this.getBoundingBoxCore(context); - }; - SvgNode.prototype.computeNodeTransform = function (context) { - var nodeTransform = this.computeNodeTransformCore(context); - var transformString = getAttribute(this.element, context.styleSheets, 'transform'); - if (!transformString) - return nodeTransform; - else - return context.pdf.matrixMult(nodeTransform, parseTransform(transformString, context)); - }; - return SvgNode; -}()); - -var NonRenderedNode = /** @class */ (function (_super) { - __extends(NonRenderedNode, _super); - function NonRenderedNode() { - return _super !== null && _super.apply(this, arguments) || this; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - NonRenderedNode.prototype.render = function (parentContext) { - return Promise.resolve(); - }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - NonRenderedNode.prototype.getBoundingBoxCore = function (context) { - return []; - }; - NonRenderedNode.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - return NonRenderedNode; -}(SvgNode)); - -var Gradient = /** @class */ (function (_super) { - __extends(Gradient, _super); - function Gradient(pdfGradientType, element, children) { - var _this = _super.call(this, element, children) || this; - _this.pdfGradientType = pdfGradientType; - _this.contextColor = undefined; - return _this; - } - Gradient.prototype.apply = function (context) { - return __awaiter(this, void 0, void 0, function () { - var id, colors, opacitySum, hasOpacity, gState, pattern; - return __generator(this, function (_a) { - id = this.element.getAttribute('id'); - if (!id) { - return [2 /*return*/]; - } - colors = this.getStops(context.styleSheets); - opacitySum = 0; - hasOpacity = false; - colors.forEach(function (_a) { - var opacity = _a.opacity; - if (opacity && opacity !== 1) { - opacitySum += opacity; - hasOpacity = true; - } - }); - if (hasOpacity) { - gState = new GState({ opacity: opacitySum / colors.length }); - } - pattern = new ShadingPattern(this.pdfGradientType, this.getCoordinates(), colors, gState); - context.pdf.addShadingPattern(id, pattern); - return [2 /*return*/]; - }); - }); - }; - Gradient.prototype.getStops = function (styleSheets) { - var _this = this; - if (this.stops) { - return this.stops; - } - // Only need to calculate contextColor once - if (this.contextColor === undefined) { - this.contextColor = null; - var ancestor = this; - while (ancestor) { - var colorAttr = getAttribute(ancestor.element, styleSheets, 'color'); - if (colorAttr) { - this.contextColor = parseColor(colorAttr, null); - break; - } - ancestor = ancestor.getParent(); - } - } - var stops = []; - this.children.forEach(function (stop) { - if (stop.element.tagName.toLowerCase() === 'stop') { - var colorAttr = getAttribute(stop.element, styleSheets, 'color'); - var color = parseColor(getAttribute(stop.element, styleSheets, 'stop-color') || '', colorAttr - ? { color: parseColor(colorAttr, null) } - : { color: _this.contextColor }); - var opacity = parseFloat(getAttribute(stop.element, styleSheets, 'stop-opacity') || '1'); - stops.push({ - offset: Gradient.parseGradientOffset(stop.element.getAttribute('offset') || '0'), - color: [color.r, color.g, color.b], - opacity: opacity - }); - } - }); - return (this.stops = stops); - }; - Gradient.prototype.getBoundingBoxCore = function (context) { - return defaultBoundingBox(this.element, context); - }; - Gradient.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - Gradient.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - /** - * Convert percentage to decimal - */ - Gradient.parseGradientOffset = function (value) { - var parsedValue = parseFloat(value); - if (!isNaN(parsedValue) && value.indexOf('%') >= 0) { - return parsedValue / 100; - } - return parsedValue; - }; - return Gradient; -}(NonRenderedNode)); - -var LinearGradient = /** @class */ (function (_super) { - __extends(LinearGradient, _super); - function LinearGradient(element, children) { - return _super.call(this, 'axial', element, children) || this; - } - LinearGradient.prototype.getCoordinates = function () { - return [ - parseFloat(this.element.getAttribute('x1') || '0'), - parseFloat(this.element.getAttribute('y1') || '0'), - parseFloat(this.element.getAttribute('x2') || '1'), - parseFloat(this.element.getAttribute('y2') || '0') - ]; - }; - return LinearGradient; -}(Gradient)); - -var RadialGradient = /** @class */ (function (_super) { - __extends(RadialGradient, _super); - function RadialGradient(element, children) { - return _super.call(this, 'radial', element, children) || this; - } - RadialGradient.prototype.getCoordinates = function () { - var cx = this.element.getAttribute('cx'); - var cy = this.element.getAttribute('cy'); - var fx = this.element.getAttribute('fx'); - var fy = this.element.getAttribute('fy'); - return [ - parseFloat(fx || cx || '0.5'), - parseFloat(fy || cy || '0.5'), - 0, - parseFloat(cx || '0.5'), - parseFloat(cy || '0.5'), - parseFloat(this.element.getAttribute('r') || '0.5') - ]; - }; - return RadialGradient; -}(Gradient)); - -var GradientFill = /** @class */ (function () { - function GradientFill(key, gradient) { - this.key = key; - this.gradient = gradient; - } - GradientFill.prototype.getFillData = function (forNode, context) { - return __awaiter(this, void 0, void 0, function () { - var gradientUnitsMatrix, bBox, gradientTransform; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, context.refsHandler.getRendered(this.key, null, function (node) { - return node.apply(new Context(context.pdf, { - refsHandler: context.refsHandler, - textMeasure: context.textMeasure, - styleSheets: context.styleSheets, - viewport: context.viewport, - svg2pdfParameters: context.svg2pdfParameters - })); - }) - // matrix to convert between gradient space and user space - // for "userSpaceOnUse" this is the current transformation: tfMatrix - // for "objectBoundingBox" or default, the gradient gets scaled and transformed to the bounding box - ]; - case 1: - _a.sent(); - if (!this.gradient.element.hasAttribute('gradientUnits') || - this.gradient.element.getAttribute('gradientUnits').toLowerCase() === 'objectboundingbox') { - bBox = forNode.getBoundingBox(context); - gradientUnitsMatrix = context.pdf.Matrix(bBox[2], 0, 0, bBox[3], bBox[0], bBox[1]); - } - else { - gradientUnitsMatrix = context.pdf.unitMatrix; - } - gradientTransform = parseTransform(getAttribute(this.gradient.element, context.styleSheets, 'gradientTransform', 'transform'), context); - return [2 /*return*/, { - key: this.key, - matrix: context.pdf.matrixMult(gradientTransform, gradientUnitsMatrix) - }]; - } - }); - }); - }; - return GradientFill; -}()); - -var Pattern = /** @class */ (function (_super) { - __extends(Pattern, _super); - function Pattern() { - return _super !== null && _super.apply(this, arguments) || this; - } - Pattern.prototype.apply = function (context) { - return __awaiter(this, void 0, void 0, function () { - var id, bBox, pattern, _i, _a, child; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - id = this.element.getAttribute('id'); - if (!id) { - return [2 /*return*/]; - } - bBox = this.getBoundingBox(context); - pattern = new TilingPattern([bBox[0], bBox[1], bBox[0] + bBox[2], bBox[1] + bBox[3]], bBox[2], bBox[3]); - context.pdf.beginTilingPattern(pattern); - _i = 0, _a = this.children; - _b.label = 1; - case 1: - if (!(_i < _a.length)) return [3 /*break*/, 4]; - child = _a[_i]; - return [4 /*yield*/, child.render(new Context(context.pdf, { - attributeState: context.attributeState, - refsHandler: context.refsHandler, - styleSheets: context.styleSheets, - viewport: context.viewport, - svg2pdfParameters: context.svg2pdfParameters, - textMeasure: context.textMeasure - }))]; - case 2: - _b.sent(); - _b.label = 3; - case 3: - _i++; - return [3 /*break*/, 1]; - case 4: - context.pdf.endTilingPattern(id, pattern); - return [2 /*return*/]; - } - }); - }); - }; - Pattern.prototype.getBoundingBoxCore = function (context) { - return defaultBoundingBox(this.element, context); - }; - Pattern.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - Pattern.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - return Pattern; -}(NonRenderedNode)); - -var PatternFill = /** @class */ (function () { - function PatternFill(key, pattern) { - this.key = key; - this.pattern = pattern; - } - PatternFill.prototype.getFillData = function (forNode, context) { - return __awaiter(this, void 0, void 0, function () { - var patternData, bBox, patternUnitsMatrix, fillBBox, x, y, width, height, patternContentUnitsMatrix, fillBBox, x, y, width, height, patternTransformMatrix, patternTransform, matrix; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, context.refsHandler.getRendered(this.key, null, function (node) { - return node.apply(new Context(context.pdf, { - refsHandler: context.refsHandler, - textMeasure: context.textMeasure, - styleSheets: context.styleSheets, - viewport: context.viewport, - svg2pdfParameters: context.svg2pdfParameters - })); - })]; - case 1: - _a.sent(); - patternData = { - key: this.key, - boundingBox: undefined, - xStep: 0, - yStep: 0, - matrix: undefined - }; - patternUnitsMatrix = context.pdf.unitMatrix; - if (!this.pattern.element.hasAttribute('patternUnits') || - this.pattern.element.getAttribute('patternUnits').toLowerCase() === 'objectboundingbox') { - bBox = forNode.getBoundingBox(context); - patternUnitsMatrix = context.pdf.Matrix(1, 0, 0, 1, bBox[0], bBox[1]); - fillBBox = this.pattern.getBoundingBox(context); - x = fillBBox[0] * bBox[0] || 0; - y = fillBBox[1] * bBox[1] || 0; - width = fillBBox[2] * bBox[2] || 0; - height = fillBBox[3] * bBox[3] || 0; - patternData.boundingBox = [x, y, x + width, y + height]; - patternData.xStep = width; - patternData.yStep = height; - } - patternContentUnitsMatrix = context.pdf.unitMatrix; - if (this.pattern.element.hasAttribute('patternContentUnits') && - this.pattern.element.getAttribute('patternContentUnits').toLowerCase() === - 'objectboundingbox') { - if (!bBox) - bBox = forNode.getBoundingBox(context); - patternContentUnitsMatrix = context.pdf.Matrix(bBox[2], 0, 0, bBox[3], 0, 0); - fillBBox = patternData.boundingBox || this.pattern.getBoundingBox(context); - x = fillBBox[0] / bBox[0] || 0; - y = fillBBox[1] / bBox[1] || 0; - width = fillBBox[2] / bBox[2] || 0; - height = fillBBox[3] / bBox[3] || 0; - patternData.boundingBox = [x, y, x + width, y + height]; - patternData.xStep = width; - patternData.yStep = height; - } - patternTransformMatrix = context.pdf.unitMatrix; - patternTransform = getAttribute(this.pattern.element, context.styleSheets, 'patternTransform', 'transform'); - if (patternTransform) { - patternTransformMatrix = parseTransform(patternTransform, context); - } - matrix = patternContentUnitsMatrix; - matrix = context.pdf.matrixMult(matrix, patternUnitsMatrix); // translate by - matrix = context.pdf.matrixMult(matrix, patternTransformMatrix); - matrix = context.pdf.matrixMult(matrix, context.transform); - patternData.matrix = matrix; - return [2 /*return*/, patternData]; - } - }); - }); - }; - return PatternFill; -}()); - -function parseFill(fill, context) { - var url = iriReference.exec(fill); - if (url) { - var fillUrl = url[1]; - var fillNode = context.refsHandler.get(fillUrl); - if (fillNode && (fillNode instanceof LinearGradient || fillNode instanceof RadialGradient)) { - return getGradientFill(fillUrl, fillNode, context); - } - else if (fillNode && fillNode instanceof Pattern) { - return new PatternFill(fillUrl, fillNode); - } - else { - // unsupported fill argument -> fill black - return new ColorFill(new RGBColor('rgb(0, 0, 0)')); - } - } - else { - // plain color - var fillColor = parseColor(fill, context.attributeState); - if (fillColor.ok) { - return new ColorFill(fillColor); - } - else if (fill === 'none') { - return null; - } - else { - return null; - } - } -} -function getGradientFill(fillUrl, gradient, context) { - // "It is necessary that at least two stops are defined to have a gradient effect. If no stops are - // defined, then painting shall occur as if 'none' were specified as the paint style. If one stop - // is defined, then paint with the solid color fill using the color defined for that gradient - // stop." - var stops = gradient.getStops(context.styleSheets); - if (stops.length === 0) { - return null; - } - if (stops.length === 1) { - var stopColor = stops[0].color; - var rgbColor = new RGBColor(); - rgbColor.ok = true; - rgbColor.r = stopColor[0]; - rgbColor.g = stopColor[1]; - rgbColor.b = stopColor[2]; - rgbColor.a = stops[0].opacity; - return new ColorFill(rgbColor); - } - return new GradientFill(fillUrl, gradient); -} - -function parseAttributes(context, svgNode, node) { - var domNode = node || svgNode.element; - // update color first so currentColor becomes available for this node - var color = getAttribute(domNode, context.styleSheets, 'color'); - if (color) { - var fillColor = parseColor(color, context.attributeState); - if (fillColor.ok) { - context.attributeState.color = fillColor; - } - else { - // invalid color passed, reset to black - context.attributeState.color = new RGBColor('rgb(0,0,0)'); - } - } - var visibility = getAttribute(domNode, context.styleSheets, 'visibility'); - if (visibility) { - context.attributeState.visibility = visibility; - } - // fill mode - var fill = getAttribute(domNode, context.styleSheets, 'fill'); - if (fill) { - context.attributeState.fill = parseFill(fill, context); - } - // opacity is realized via a pdf graphics state - var fillOpacity = getAttribute(domNode, context.styleSheets, 'fill-opacity'); - if (fillOpacity) { - context.attributeState.fillOpacity = parseFloat(fillOpacity); - } - var strokeOpacity = getAttribute(domNode, context.styleSheets, 'stroke-opacity'); - if (strokeOpacity) { - context.attributeState.strokeOpacity = parseFloat(strokeOpacity); - } - var opacity = getAttribute(domNode, context.styleSheets, 'opacity'); - if (opacity) { - context.attributeState.opacity = parseFloat(opacity); - } - // stroke mode - var strokeWidth = getAttribute(domNode, context.styleSheets, 'stroke-width'); - if (strokeWidth !== void 0 && strokeWidth !== '') { - context.attributeState.strokeWidth = Math.abs(parseFloat(strokeWidth)); - } - var stroke = getAttribute(domNode, context.styleSheets, 'stroke'); - if (stroke) { - if (stroke === 'none') { - context.attributeState.stroke = null; - } - else { - // gradients, patterns not supported for strokes ... - var strokeRGB = parseColor(stroke, context.attributeState); - if (strokeRGB.ok) { - context.attributeState.stroke = new ColorFill(strokeRGB); - } - } - } - if (stroke && context.attributeState.stroke instanceof ColorFill) { - context.attributeState.contextStroke = context.attributeState.stroke.color; - } - if (fill && context.attributeState.fill instanceof ColorFill) { - context.attributeState.contextFill = context.attributeState.fill.color; - } - var lineCap = getAttribute(domNode, context.styleSheets, 'stroke-linecap'); - if (lineCap) { - context.attributeState.strokeLinecap = lineCap; - } - var lineJoin = getAttribute(domNode, context.styleSheets, 'stroke-linejoin'); - if (lineJoin) { - context.attributeState.strokeLinejoin = lineJoin; - } - var dashArray = getAttribute(domNode, context.styleSheets, 'stroke-dasharray'); - if (dashArray) { - var dashOffset = parseInt(getAttribute(domNode, context.styleSheets, 'stroke-dashoffset') || '0'); - context.attributeState.strokeDasharray = parseFloats(dashArray); - context.attributeState.strokeDashoffset = dashOffset; - } - var miterLimit = getAttribute(domNode, context.styleSheets, 'stroke-miterlimit'); - if (miterLimit !== void 0 && miterLimit !== '') { - context.attributeState.strokeMiterlimit = parseFloat(miterLimit); - } - var xmlSpace = domNode.getAttribute('xml:space'); - if (xmlSpace) { - context.attributeState.xmlSpace = xmlSpace; - } - var whiteSpace = getAttribute(domNode, context.styleSheets, 'white-space'); - if (whiteSpace) { - context.attributeState.whiteSpace = whiteSpace; - } - var fontWeight = getAttribute(domNode, context.styleSheets, 'font-weight'); - if (fontWeight) { - context.attributeState.fontWeight = fontWeight; - } - var fontStyle = getAttribute(domNode, context.styleSheets, 'font-style'); - if (fontStyle) { - context.attributeState.fontStyle = fontStyle; - } - var fontFamily = getAttribute(domNode, context.styleSheets, 'font-family'); - if (fontFamily) { - var fontFamilies = FontFamily.parse(fontFamily); - context.attributeState.fontFamily = findFirstAvailableFontFamily(context.attributeState, fontFamilies, context); - } - var fontSize = getAttribute(domNode, context.styleSheets, 'font-size'); - if (fontSize) { - var pdfFontSize = context.pdf.getFontSize(); - context.attributeState.fontSize = toPixels(fontSize, pdfFontSize); - } - var alignmentBaseline = getAttribute(domNode, context.styleSheets, 'vertical-align') || - getAttribute(domNode, context.styleSheets, 'alignment-baseline'); - if (alignmentBaseline) { - var matchArr = alignmentBaseline.match(/(baseline|text-bottom|alphabetic|ideographic|middle|central|mathematical|text-top|bottom|center|top|hanging)/); - if (matchArr) { - context.attributeState.alignmentBaseline = matchArr[0]; - } - } - var textAnchor = getAttribute(domNode, context.styleSheets, 'text-anchor'); - if (textAnchor) { - context.attributeState.textAnchor = textAnchor; - } - var fillRule = getAttribute(domNode, context.styleSheets, 'fill-rule'); - if (fillRule) { - context.attributeState.fillRule = fillRule; - } -} -function applyAttributes(childContext, parentContext, node) { - var fillOpacity = 1.0, strokeOpacity = 1.0; - fillOpacity *= childContext.attributeState.fillOpacity; - fillOpacity *= childContext.attributeState.opacity; - if (childContext.attributeState.fill instanceof ColorFill && - typeof childContext.attributeState.fill.color.a !== 'undefined') { - fillOpacity *= childContext.attributeState.fill.color.a; - } - strokeOpacity *= childContext.attributeState.strokeOpacity; - strokeOpacity *= childContext.attributeState.opacity; - if (childContext.attributeState.stroke instanceof ColorFill && - typeof childContext.attributeState.stroke.color.a !== 'undefined') { - strokeOpacity *= childContext.attributeState.stroke.color.a; - } - var hasFillOpacity = fillOpacity < 1.0; - var hasStrokeOpacity = strokeOpacity < 1.0; - // This is a workaround for symbols that are used multiple times with different - // fill/stroke attributes. All paths within symbols are both filled and stroked - // and we set the fill/stroke to transparent if the use element has - // fill/stroke="none". - if (nodeIs(node, 'use')) { - hasFillOpacity = true; - hasStrokeOpacity = true; - fillOpacity *= childContext.attributeState.fill ? 1 : 0; - strokeOpacity *= childContext.attributeState.stroke ? 1 : 0; - } - else if (childContext.withinUse) { - if (childContext.attributeState.fill !== parentContext.attributeState.fill) { - hasFillOpacity = true; - fillOpacity *= childContext.attributeState.fill ? 1 : 0; - } - else if (hasFillOpacity && !childContext.attributeState.fill) { - fillOpacity = 0; - } - if (childContext.attributeState.stroke !== parentContext.attributeState.stroke) { - hasStrokeOpacity = true; - strokeOpacity *= childContext.attributeState.stroke ? 1 : 0; - } - else if (hasStrokeOpacity && !childContext.attributeState.stroke) { - strokeOpacity = 0; - } - } - if (hasFillOpacity || hasStrokeOpacity) { - var gState = {}; - if (hasFillOpacity) - gState['opacity'] = fillOpacity; - if (hasStrokeOpacity) - gState['stroke-opacity'] = strokeOpacity; - childContext.pdf.setGState(new GState(gState)); - } - if (childContext.attributeState.fill && - childContext.attributeState.fill !== parentContext.attributeState.fill && - childContext.attributeState.fill instanceof ColorFill && - childContext.attributeState.fill.color.ok && - !nodeIs(node, 'text')) { - // text fill color will be applied through setTextColor() - childContext.pdf.setFillColor(childContext.attributeState.fill.color.r, childContext.attributeState.fill.color.g, childContext.attributeState.fill.color.b); - } - if (childContext.attributeState.strokeWidth !== parentContext.attributeState.strokeWidth) { - childContext.pdf.setLineWidth(childContext.attributeState.strokeWidth); - } - if (childContext.attributeState.stroke !== parentContext.attributeState.stroke && - childContext.attributeState.stroke instanceof ColorFill) { - childContext.pdf.setDrawColor(childContext.attributeState.stroke.color.r, childContext.attributeState.stroke.color.g, childContext.attributeState.stroke.color.b); - } - if (childContext.attributeState.strokeLinecap !== parentContext.attributeState.strokeLinecap) { - childContext.pdf.setLineCap(childContext.attributeState.strokeLinecap); - } - if (childContext.attributeState.strokeLinejoin !== parentContext.attributeState.strokeLinejoin) { - childContext.pdf.setLineJoin(childContext.attributeState.strokeLinejoin); - } - if ((childContext.attributeState.strokeDasharray !== parentContext.attributeState.strokeDasharray || - childContext.attributeState.strokeDashoffset !== - parentContext.attributeState.strokeDashoffset) && - childContext.attributeState.strokeDasharray) { - childContext.pdf.setLineDashPattern(childContext.attributeState.strokeDasharray, childContext.attributeState.strokeDashoffset); - } - if (childContext.attributeState.strokeMiterlimit !== parentContext.attributeState.strokeMiterlimit) { - childContext.pdf.setLineMiterLimit(childContext.attributeState.strokeMiterlimit); - } - var font; - if (childContext.attributeState.fontFamily !== parentContext.attributeState.fontFamily) { - if (fontAliases.hasOwnProperty(childContext.attributeState.fontFamily)) { - font = fontAliases[childContext.attributeState.fontFamily]; - } - else { - font = childContext.attributeState.fontFamily; - } - } - if (childContext.attributeState.fill && - childContext.attributeState.fill !== parentContext.attributeState.fill && - childContext.attributeState.fill instanceof ColorFill && - childContext.attributeState.fill.color.ok) { - var fillColor = childContext.attributeState.fill.color; - childContext.pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b); - } - var fontStyle; - if (childContext.attributeState.fontWeight !== parentContext.attributeState.fontWeight || - childContext.attributeState.fontStyle !== parentContext.attributeState.fontStyle) { - fontStyle = combineFontStyleAndFontWeight(childContext.attributeState.fontStyle, childContext.attributeState.fontWeight); - } - if (font !== undefined || fontStyle !== undefined) { - if (font === undefined) { - if (fontAliases.hasOwnProperty(childContext.attributeState.fontFamily)) { - font = fontAliases[childContext.attributeState.fontFamily]; - } - else { - font = childContext.attributeState.fontFamily; - } - } - childContext.pdf.setFont(font, fontStyle); - } - if (childContext.attributeState.fontSize !== parentContext.attributeState.fontSize) { - // correct for a jsPDF-instance measurement unit that differs from `pt` - childContext.pdf.setFontSize(childContext.attributeState.fontSize * childContext.pdf.internal.scaleFactor); - } -} -function applyContext(context) { - var attributeState = context.attributeState, pdf = context.pdf; - var fillOpacity = 1.0, strokeOpacity = 1.0; - fillOpacity *= attributeState.fillOpacity; - fillOpacity *= attributeState.opacity; - if (attributeState.fill instanceof ColorFill && - typeof attributeState.fill.color.a !== 'undefined') { - fillOpacity *= attributeState.fill.color.a; - } - strokeOpacity *= attributeState.strokeOpacity; - strokeOpacity *= attributeState.opacity; - if (attributeState.stroke instanceof ColorFill && - typeof attributeState.stroke.color.a !== 'undefined') { - strokeOpacity *= attributeState.stroke.color.a; - } - var gState = {}; - gState['opacity'] = fillOpacity; - gState['stroke-opacity'] = strokeOpacity; - pdf.setGState(new GState(gState)); - if (attributeState.fill && - attributeState.fill instanceof ColorFill && - attributeState.fill.color.ok) { - // text fill color will be applied through setTextColor() - pdf.setFillColor(attributeState.fill.color.r, attributeState.fill.color.g, attributeState.fill.color.b); - } - else { - pdf.setFillColor(0, 0, 0); - } - pdf.setLineWidth(attributeState.strokeWidth); - if (attributeState.stroke instanceof ColorFill) { - pdf.setDrawColor(attributeState.stroke.color.r, attributeState.stroke.color.g, attributeState.stroke.color.b); - } - else { - pdf.setDrawColor(0, 0, 0); - } - pdf.setLineCap(attributeState.strokeLinecap); - pdf.setLineJoin(attributeState.strokeLinejoin); - if (attributeState.strokeDasharray) { - pdf.setLineDashPattern(attributeState.strokeDasharray, attributeState.strokeDashoffset); - } - else { - pdf.setLineDashPattern([], 0); - } - pdf.setLineMiterLimit(attributeState.strokeMiterlimit); - var font; - if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { - font = fontAliases[attributeState.fontFamily]; - } - else { - font = attributeState.fontFamily; - } - if (attributeState.fill && - attributeState.fill instanceof ColorFill && - attributeState.fill.color.ok) { - var fillColor = attributeState.fill.color; - pdf.setTextColor(fillColor.r, fillColor.g, fillColor.b); - } - else { - pdf.setTextColor(0, 0, 0); - } - var fontStyle = ''; - if (attributeState.fontWeight === 'bold') { - fontStyle = 'bold'; - } - if (attributeState.fontStyle === 'italic') { - fontStyle += 'italic'; - } - if (fontStyle === '') { - fontStyle = 'normal'; - } - if (font !== undefined || fontStyle !== undefined) { - if (font === undefined) { - if (fontAliases.hasOwnProperty(attributeState.fontFamily)) { - font = fontAliases[attributeState.fontFamily]; - } - else { - font = attributeState.fontFamily; - } - } - pdf.setFont(font, fontStyle); - } - else { - pdf.setFont('helvetica', fontStyle); - } - // correct for a jsPDF-instance measurement unit that differs from `pt` - pdf.setFontSize(attributeState.fontSize * pdf.internal.scaleFactor); -} - -function getClipPathNode(clipPathAttr, targetNode, context) { - var match = iriReference.exec(clipPathAttr); - if (!match) { - return undefined; - } - var clipPathId = match[1]; - var clipNode = context.refsHandler.get(clipPathId); - return clipNode || undefined; -} -function applyClipPath(targetNode, clipPathNode, context) { - return __awaiter(this, void 0, void 0, function () { - var clipContext, bBox; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - clipContext = context.clone(); - if (clipPathNode.element.hasAttribute('clipPathUnits') && - clipPathNode.element.getAttribute('clipPathUnits').toLowerCase() === 'objectboundingbox') { - bBox = targetNode.getBoundingBox(context); - clipContext.transform = context.pdf.matrixMult(context.pdf.Matrix(bBox[2], 0, 0, bBox[3], bBox[0], bBox[1]), context.transform); - } - return [4 /*yield*/, clipPathNode.apply(clipContext)]; - case 1: - _a.sent(); - return [2 /*return*/]; - } - }); - }); -} - -var RenderedNode = /** @class */ (function (_super) { - __extends(RenderedNode, _super); - function RenderedNode() { - return _super !== null && _super.apply(this, arguments) || this; - } - RenderedNode.prototype.render = function (parentContext) { - return __awaiter(this, void 0, void 0, function () { - var context, clipPathAttribute, hasClipPath, clipNode; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!this.isVisible(parentContext.attributeState.visibility !== 'hidden', parentContext)) { - return [2 /*return*/]; - } - context = parentContext.clone(); - context.transform = context.pdf.matrixMult(this.computeNodeTransform(context), parentContext.transform); - parseAttributes(context, this); - clipPathAttribute = getAttribute(this.element, context.styleSheets, 'clip-path'); - hasClipPath = clipPathAttribute && clipPathAttribute !== 'none'; - if (!hasClipPath) return [3 /*break*/, 5]; - clipNode = getClipPathNode(clipPathAttribute, this, context); - if (!clipNode) return [3 /*break*/, 4]; - if (!clipNode.isVisible(true, context)) return [3 /*break*/, 2]; - context.pdf.saveGraphicsState(); - return [4 /*yield*/, applyClipPath(this, clipNode, context)]; - case 1: - _a.sent(); - return [3 /*break*/, 3]; - case 2: return [2 /*return*/]; - case 3: return [3 /*break*/, 5]; - case 4: - hasClipPath = false; - _a.label = 5; - case 5: - if (!context.withinClipPath) { - context.pdf.saveGraphicsState(); - } - applyAttributes(context, parentContext, this.element); - return [4 /*yield*/, this.renderCore(context)]; - case 6: - _a.sent(); - if (!context.withinClipPath) { - context.pdf.restoreGraphicsState(); - } - if (hasClipPath) { - context.pdf.restoreGraphicsState(); - } - return [2 /*return*/]; - } - }); - }); - }; - return RenderedNode; -}(SvgNode)); - -var GraphicsNode = /** @class */ (function (_super) { - __extends(GraphicsNode, _super); - function GraphicsNode() { - return _super !== null && _super.apply(this, arguments) || this; - } - return GraphicsNode; -}(RenderedNode)); - -var GeometryNode = /** @class */ (function (_super) { - __extends(GeometryNode, _super); - function GeometryNode(hasMarkers, element, children) { - var _this = _super.call(this, element, children) || this; - _this.cachedPath = null; - _this.hasMarkers = hasMarkers; - return _this; - } - GeometryNode.prototype.renderCore = function (context) { - return __awaiter(this, void 0, void 0, function () { - var path; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - path = this.getCachedPath(context); - if (path === null || path.segments.length === 0) { - return [2 /*return*/]; - } - if (context.withinClipPath) { - path.transform(context.transform); - } - else { - context.pdf.setCurrentTransformationMatrix(context.transform); - } - path.draw(context); - return [4 /*yield*/, this.fillOrStroke(context)]; - case 1: - _a.sent(); - if (!this.hasMarkers) return [3 /*break*/, 3]; - return [4 /*yield*/, this.drawMarkers(context, path)]; - case 2: - _a.sent(); - _a.label = 3; - case 3: return [2 /*return*/]; - } - }); - }); - }; - GeometryNode.prototype.getCachedPath = function (context) { - return this.cachedPath || (this.cachedPath = this.getPath(context)); - }; - GeometryNode.prototype.drawMarkers = function (context, path) { - return __awaiter(this, void 0, void 0, function () { - var markers; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - markers = this.getMarkers(path, context); - return [4 /*yield*/, markers.draw(context.clone({ transform: context.pdf.unitMatrix }))]; - case 1: - _a.sent(); - return [2 /*return*/]; - } - }); - }); - }; - GeometryNode.prototype.fillOrStroke = function (context) { - return __awaiter(this, void 0, void 0, function () { - var fill, stroke, fillData, _a, isNodeFillRuleEvenOdd; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - if (context.withinClipPath) { - return [2 /*return*/]; - } - fill = context.attributeState.fill; - stroke = context.attributeState.stroke && context.attributeState.strokeWidth !== 0; - if (!fill) return [3 /*break*/, 2]; - return [4 /*yield*/, fill.getFillData(this, context)]; - case 1: - _a = _b.sent(); - return [3 /*break*/, 3]; - case 2: - _a = undefined; - _b.label = 3; - case 3: - fillData = _a; - isNodeFillRuleEvenOdd = context.attributeState.fillRule === 'evenodd'; - // This is a workaround for symbols that are used multiple times with different - // fill/stroke attributes. All paths within symbols are both filled and stroked - // and we set the fill/stroke to transparent if the use element has - // fill/stroke="none". - if ((fill && stroke) || context.withinUse) { - if (isNodeFillRuleEvenOdd) { - context.pdf.fillStrokeEvenOdd(fillData); - } - else { - context.pdf.fillStroke(fillData); - } - } - else if (fill) { - if (isNodeFillRuleEvenOdd) { - context.pdf.fillEvenOdd(fillData); - } - else { - context.pdf.fill(fillData); - } - } - else if (stroke) { - context.pdf.stroke(); - } - else { - context.pdf.discardPath(); - } - return [2 /*return*/]; - } - }); - }); - }; - GeometryNode.prototype.getBoundingBoxCore = function (context) { - var path = this.getCachedPath(context); - if (!path || !path.segments.length) { - return [0, 0, 0, 0]; - } - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxX = Number.NEGATIVE_INFINITY; - var maxY = Number.NEGATIVE_INFINITY; - var x = 0, y = 0; - for (var i = 0; i < path.segments.length; i++) { - var seg = path.segments[i]; - if (seg instanceof MoveTo || seg instanceof LineTo || seg instanceof CurveTo) { - x = seg.x; - y = seg.y; - } - if (seg instanceof CurveTo) { - minX = Math.min(minX, x, seg.x1, seg.x2, seg.x); - maxX = Math.max(maxX, x, seg.x1, seg.x2, seg.x); - minY = Math.min(minY, y, seg.y1, seg.y2, seg.y); - maxY = Math.max(maxY, y, seg.y1, seg.y2, seg.y); - } - else { - minX = Math.min(minX, x); - maxX = Math.max(maxX, x); - minY = Math.min(minY, y); - maxY = Math.max(maxY, y); - } - } - return [minX, minY, maxX - minX, maxY - minY]; - }; - GeometryNode.prototype.getMarkers = function (path, context) { - var markerStart = getAttribute(this.element, context.styleSheets, 'marker-start'); - var markerMid = getAttribute(this.element, context.styleSheets, 'marker-mid'); - var markerEnd = getAttribute(this.element, context.styleSheets, 'marker-end'); - var markers = new MarkerList(); - if (markerStart || markerMid || markerEnd) { - if (markerEnd) - markerEnd = iri(markerEnd); - if (markerStart) - markerStart = iri(markerStart); - if (markerMid) - markerMid = iri(markerMid); - var list_1 = path.segments; - var prevAngle = [1, 0], curAngle = void 0, first = false, firstAngle = [1, 0], last_1 = false; - var _loop_1 = function (i) { - var curr = list_1[i]; - var hasStartMarker = markerStart && - (i === 1 || (!(list_1[i] instanceof MoveTo) && list_1[i - 1] instanceof MoveTo)); - if (hasStartMarker) { - list_1.forEach(function (value, index) { - if (!last_1 && value instanceof Close && index > i) { - var tmp = list_1[index - 1]; - last_1 = - (tmp instanceof MoveTo || tmp instanceof LineTo || tmp instanceof CurveTo) && tmp; - } - }); - } - var hasEndMarker = markerEnd && - (i === list_1.length - 1 || (!(list_1[i] instanceof MoveTo) && list_1[i + 1] instanceof MoveTo)); - var hasMidMarker = markerMid && i > 0 && !(i === 1 && list_1[i - 1] instanceof MoveTo); - var prev = list_1[i - 1] || null; - if (prev instanceof MoveTo || prev instanceof LineTo || prev instanceof CurveTo) { - if (curr instanceof CurveTo) { - if (hasStartMarker) { - markers.addMarker(new Marker(markerStart, [prev.x, prev.y], - // @ts-ignore - getAngle(last_1 ? [last_1.x, last_1.y] : [prev.x, prev.y], [curr.x1, curr.y1]), true)); - } - if (hasEndMarker) { - markers.addMarker(new Marker(markerEnd, [curr.x, curr.y], getAngle([curr.x2, curr.y2], [curr.x, curr.y]))); - } - if (hasMidMarker) { - curAngle = getDirectionVector([prev.x, prev.y], [curr.x1, curr.y1]); - curAngle = - prev instanceof MoveTo ? curAngle : normalize(addVectors(prevAngle, curAngle)); - markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(curAngle[1], curAngle[0]))); - } - prevAngle = getDirectionVector([curr.x2, curr.y2], [curr.x, curr.y]); - } - else if (curr instanceof MoveTo || curr instanceof LineTo) { - curAngle = getDirectionVector([prev.x, prev.y], [curr.x, curr.y]); - if (hasStartMarker) { - // @ts-ignore - var angle = last_1 ? getDirectionVector([last_1.x, last_1.y], [curr.x, curr.y]) : curAngle; - markers.addMarker(new Marker(markerStart, [prev.x, prev.y], Math.atan2(angle[1], angle[0]), true)); - } - if (hasEndMarker) { - markers.addMarker(new Marker(markerEnd, [curr.x, curr.y], Math.atan2(curAngle[1], curAngle[0]))); - } - if (hasMidMarker) { - var angle = curr instanceof MoveTo - ? prevAngle - : prev instanceof MoveTo - ? curAngle - : normalize(addVectors(prevAngle, curAngle)); - markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(angle[1], angle[0]))); - } - prevAngle = curAngle; - } - else if (curr instanceof Close) { - // @ts-ignore - curAngle = getDirectionVector([prev.x, prev.y], [first.x, first.y]); - if (hasMidMarker) { - var angle = prev instanceof MoveTo ? curAngle : normalize(addVectors(prevAngle, curAngle)); - markers.addMarker(new Marker(markerMid, [prev.x, prev.y], Math.atan2(angle[1], angle[0]))); - } - if (hasEndMarker) { - var angle = normalize(addVectors(curAngle, firstAngle)); - markers.addMarker( - // @ts-ignore - new Marker(markerEnd, [first.x, first.y], Math.atan2(angle[1], angle[0]))); - } - prevAngle = curAngle; - } - } - else { - first = curr instanceof MoveTo && curr; - var next = list_1[i + 1]; - if (next instanceof MoveTo || next instanceof LineTo || next instanceof CurveTo) { - // @ts-ignore - firstAngle = getDirectionVector([first.x, first.y], [next.x, next.y]); - } - } - }; - for (var i = 0; i < list_1.length; i++) { - _loop_1(i); - } - } - markers.markers.forEach(function (marker) { - var markerNode = context.refsHandler.get(marker.id); - if (!markerNode) - return; - var orient = getAttribute(markerNode.element, context.styleSheets, 'orient'); - if (orient == null) - return; - if (marker.isStartMarker && orient === 'auto-start-reverse') { - marker.angle += Math.PI; - } - if (!isNaN(Number(orient))) { - marker.angle = (parseFloat(orient) / 180) * Math.PI; - } - }); - return markers; - }; - return GeometryNode; -}(GraphicsNode)); -function iri(attribute) { - var match = iriReference.exec(attribute); - return (match && match[1]) || undefined; -} - -var Line = /** @class */ (function (_super) { - __extends(Line, _super); - function Line(node, children) { - return _super.call(this, true, node, children) || this; - } - Line.prototype.getPath = function (context) { - if (context.withinClipPath || context.attributeState.stroke === null) { - return null; - } - var x1 = parseFloat(this.element.getAttribute('x1') || '0'), y1 = parseFloat(this.element.getAttribute('y1') || '0'); - var x2 = parseFloat(this.element.getAttribute('x2') || '0'), y2 = parseFloat(this.element.getAttribute('y2') || '0'); - if (!(x1 || x2 || y1 || y2)) { - return null; - } - return new Path().moveTo(x1, y1).lineTo(x2, y2); - }; - Line.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - Line.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - Line.prototype.fillOrStroke = function (context) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - context.attributeState.fill = null; - return [4 /*yield*/, _super.prototype.fillOrStroke.call(this, context)]; - case 1: - _a.sent(); - return [2 /*return*/]; - } - }); - }); - }; - return Line; -}(GeometryNode)); - -var Symbol$1 = /** @class */ (function (_super) { - __extends(Symbol, _super); - function Symbol() { - return _super !== null && _super.apply(this, arguments) || this; - } - Symbol.prototype.apply = function (parentContext) { - return __awaiter(this, void 0, void 0, function () { - var context, clipPathAttribute, hasClipPath, clipNode, _i, _a, child; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - if (!this.isVisible(parentContext.attributeState.visibility !== 'hidden', parentContext)) { - return [2 /*return*/]; - } - context = parentContext.clone(); - context.transform = context.pdf.unitMatrix; - parseAttributes(context, this); - clipPathAttribute = getAttribute(this.element, context.styleSheets, 'clip-path'); - hasClipPath = clipPathAttribute && clipPathAttribute !== 'none'; - if (!hasClipPath) return [3 /*break*/, 3]; - clipNode = getClipPathNode(clipPathAttribute, this, context); - if (!clipNode) return [3 /*break*/, 3]; - if (!clipNode.isVisible(true, context)) return [3 /*break*/, 2]; - return [4 /*yield*/, applyClipPath(this, clipNode, context)]; - case 1: - _b.sent(); - return [3 /*break*/, 3]; - case 2: return [2 /*return*/]; - case 3: - applyAttributes(context, parentContext, this.element); - _i = 0, _a = this.children; - _b.label = 4; - case 4: - if (!(_i < _a.length)) return [3 /*break*/, 7]; - child = _a[_i]; - return [4 /*yield*/, child.render(context)]; - case 5: - _b.sent(); - _b.label = 6; - case 6: - _i++; - return [3 /*break*/, 4]; - case 7: return [2 /*return*/]; - } - }); - }); - }; - Symbol.prototype.getBoundingBoxCore = function (context) { - return getBoundingBoxByChildren(context, this); - }; - Symbol.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - Symbol.prototype.computeNodeTransformCore = function (context) { - var x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'); - var y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); - // TODO: implement refX/refY - this is still to do because common browsers don't seem to support the feature yet - // x += parseFloat(this.element.getAttribute("refX")) || 0; ??? - // y += parseFloat(this.element.getAttribute("refY")) || 0; ??? - var viewBox = this.element.getAttribute('viewBox'); - if (viewBox) { - var box = parseFloats(viewBox); - var width = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || - getAttribute(this.element.ownerSVGElement, context.styleSheets, 'width') || - viewBox[2]); - var height = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || - getAttribute(this.element.ownerSVGElement, context.styleSheets, 'height') || - viewBox[3]); - return computeViewBoxTransform(this.element, box, x, y, width, height, context); - } - else { - return context.pdf.Matrix(1, 0, 0, 1, x, y); - } - }; - return Symbol; -}(NonRenderedNode)); - -var Viewport = /** @class */ (function () { - function Viewport(width, height) { - this.width = width; - this.height = height; - } - return Viewport; -}()); - -/** - * Draws the element referenced by a use node, makes use of pdf's XObjects/FormObjects so nodes are only written once - * to the pdf document. This highly reduces the file size and computation time. - */ -var Use = /** @class */ (function (_super) { - __extends(Use, _super); - function Use() { - return _super !== null && _super.apply(this, arguments) || this; - } - Use.prototype.renderCore = function (context) { - return __awaiter(this, void 0, void 0, function () { - var pf, url, id, refNode, refNodeOpensViewport, x, y, width, height, t, viewBox, contextColors, refContext; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - pf = parseFloat; - url = this.element.getAttribute('href') || this.element.getAttribute('xlink:href'); - // just in case someone has the idea to use empty use-tags, wtf??? - if (!url) - return [2 /*return*/]; - id = url.substring(1); - refNode = context.refsHandler.get(id); - refNodeOpensViewport = nodeIs(refNode.element, 'symbol,svg') && refNode.element.hasAttribute('viewBox'); - x = pf(getAttribute(this.element, context.styleSheets, 'x') || '0'); - y = pf(getAttribute(this.element, context.styleSheets, 'y') || '0'); - width = undefined; - height = undefined; - if (refNodeOpensViewport) { - // <use> inherits width/height only to svg/symbol - // if there is no viewBox attribute, width/height don't have an effect - // in theory, the default value for width/height is 100%, but we currently don't support this - width = pf(getAttribute(this.element, context.styleSheets, 'width') || - getAttribute(refNode.element, context.styleSheets, 'width') || - '0'); - height = pf(getAttribute(this.element, context.styleSheets, 'height') || - getAttribute(refNode.element, context.styleSheets, 'height') || - '0'); - // accumulate x/y to calculate the viewBox transform - x += pf(getAttribute(refNode.element, context.styleSheets, 'x') || '0'); - y += pf(getAttribute(refNode.element, context.styleSheets, 'y') || '0'); - viewBox = parseFloats(refNode.element.getAttribute('viewBox')); - t = computeViewBoxTransform(refNode.element, viewBox, x, y, width, height, context); - } - else { - t = context.pdf.Matrix(1, 0, 0, 1, x, y); - } - contextColors = AttributeState.getContextColors(context, true); - refContext = new Context(context.pdf, { - refsHandler: context.refsHandler, - styleSheets: context.styleSheets, - withinUse: true, - viewport: refNodeOpensViewport ? new Viewport(width, height) : context.viewport, - svg2pdfParameters: context.svg2pdfParameters, - textMeasure: context.textMeasure, - attributeState: Object.assign(AttributeState.default(), contextColors) - }); - return [4 /*yield*/, context.refsHandler.getRendered(id, contextColors, function (node) { - return Use.renderReferencedNode(node, id, refContext); - })]; - case 1: - _a.sent(); - context.pdf.saveGraphicsState(); - context.pdf.setCurrentTransformationMatrix(context.transform); - // apply the bbox (i.e. clip) if needed - if (refNodeOpensViewport && - getAttribute(refNode.element, context.styleSheets, 'overflow') !== 'visible') { - context.pdf.rect(x, y, width, height); - context.pdf.clip().discardPath(); - } - context.pdf.doFormObject(context.refsHandler.generateKey(id, contextColors), t); - context.pdf.restoreGraphicsState(); - return [2 /*return*/]; - } - }); - }); - }; - Use.renderReferencedNode = function (node, id, refContext) { - return __awaiter(this, void 0, void 0, function () { - var bBox; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - bBox = node.getBoundingBox(refContext); - // The content of a PDF form object is implicitly clipped at its /BBox property. - // SVG, however, applies its clip rect at the <use> attribute, which may modify it. - // So, make the bBox a lot larger than it needs to be and hope any thick strokes are - // still within. - bBox = [bBox[0] - 0.5 * bBox[2], bBox[1] - 0.5 * bBox[3], bBox[2] * 2, bBox[3] * 2]; - refContext.pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], refContext.pdf.unitMatrix); - if (!(node instanceof Symbol$1)) return [3 /*break*/, 2]; - return [4 /*yield*/, node.apply(refContext)]; - case 1: - _a.sent(); - return [3 /*break*/, 4]; - case 2: return [4 /*yield*/, node.render(refContext)]; - case 3: - _a.sent(); - _a.label = 4; - case 4: - refContext.pdf.endFormObject(refContext.refsHandler.generateKey(id, refContext.attributeState)); - return [2 /*return*/]; - } - }); - }); - }; - Use.prototype.getBoundingBoxCore = function (context) { - return defaultBoundingBox(this.element, context); - }; - Use.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - Use.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - return Use; -}(GraphicsNode)); - -var Rect = /** @class */ (function (_super) { - __extends(Rect, _super); - function Rect(element, children) { - return _super.call(this, false, element, children) || this; - } - Rect.prototype.getPath = function (context) { - var w = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || '0'); - var h = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || '0'); - if (!isFinite(w) || w <= 0 || !isFinite(h) || h <= 0) { - return null; - } - var rxAttr = getAttribute(this.element, context.styleSheets, 'rx'); - var ryAttr = getAttribute(this.element, context.styleSheets, 'ry'); - var rx = Math.min(parseFloat(rxAttr || ryAttr || '0'), w * 0.5); - var ry = Math.min(parseFloat(ryAttr || rxAttr || '0'), h * 0.5); - var x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'); - var y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); - var arc = (4 / 3) * (Math.SQRT2 - 1); - if (rx === 0 && ry === 0) { - return new Path() - .moveTo(x, y) - .lineTo(x + w, y) - .lineTo(x + w, y + h) - .lineTo(x, y + h) - .close(); - } - else { - return new Path() - .moveTo((x += rx), y) - .lineTo((x += w - 2 * rx), y) - .curveTo(x + rx * arc, y, x + rx, y + (ry - ry * arc), (x += rx), (y += ry)) - .lineTo(x, (y += h - 2 * ry)) - .curveTo(x, y + ry * arc, x - rx * arc, y + ry, (x -= rx), (y += ry)) - .lineTo((x += -w + 2 * rx), y) - .curveTo(x - rx * arc, y, x - rx, y - ry * arc, (x -= rx), (y -= ry)) - .lineTo(x, (y += -h + 2 * ry)) - .curveTo(x, y - ry * arc, x + rx * arc, y - ry, (x += rx), (y -= ry)) - .close(); - } - }; - Rect.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - Rect.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - return Rect; -}(GeometryNode)); - -var EllipseBase = /** @class */ (function (_super) { - __extends(EllipseBase, _super); - function EllipseBase(element, children) { - return _super.call(this, false, element, children) || this; - } - EllipseBase.prototype.getPath = function (context) { - var rx = this.getRx(context); - var ry = this.getRy(context); - if (!isFinite(rx) || ry <= 0 || !isFinite(ry) || ry <= 0) { - return null; - } - var x = parseFloat(getAttribute(this.element, context.styleSheets, 'cx') || '0'), y = parseFloat(getAttribute(this.element, context.styleSheets, 'cy') || '0'); - var lx = (4 / 3) * (Math.SQRT2 - 1) * rx, ly = (4 / 3) * (Math.SQRT2 - 1) * ry; - return new Path() - .moveTo(x + rx, y) - .curveTo(x + rx, y - ly, x + lx, y - ry, x, y - ry) - .curveTo(x - lx, y - ry, x - rx, y - ly, x - rx, y) - .curveTo(x - rx, y + ly, x - lx, y + ry, x, y + ry) - .curveTo(x + lx, y + ry, x + rx, y + ly, x + rx, y); - }; - EllipseBase.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - EllipseBase.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - return EllipseBase; -}(GeometryNode)); - -var Ellipse = /** @class */ (function (_super) { - __extends(Ellipse, _super); - function Ellipse(element, children) { - return _super.call(this, element, children) || this; - } - Ellipse.prototype.getRx = function (context) { - return parseFloat(getAttribute(this.element, context.styleSheets, 'rx') || '0'); - }; - Ellipse.prototype.getRy = function (context) { - return parseFloat(getAttribute(this.element, context.styleSheets, 'ry') || '0'); - }; - return Ellipse; -}(EllipseBase)); - -function getTextRenderingMode(attributeState) { - var renderingMode = 'invisible'; - var doStroke = attributeState.stroke && attributeState.strokeWidth !== 0; - var doFill = attributeState.fill; - if (doFill && doStroke) { - renderingMode = 'fillThenStroke'; - } - else if (doFill) { - renderingMode = 'fill'; - } - else if (doStroke) { - renderingMode = 'stroke'; - } - return renderingMode; -} -function transformXmlSpace(trimmedText, attributeState) { - trimmedText = removeNewlines(trimmedText); - trimmedText = replaceTabsBySpace(trimmedText); - var shouldPreserve = attributeState.xmlSpace === 'preserve' || attributeState.whiteSpace === 'pre'; - if (!shouldPreserve) { - trimmedText = trimmedText.trim(); - trimmedText = consolidateSpaces(trimmedText); - } - return trimmedText; -} -function removeNewlines(str) { - return str.replace(/[\n\r]/g, ''); -} -function replaceTabsBySpace(str) { - return str.replace(/[\t]/g, ' '); -} -function consolidateSpaces(str) { - return str.replace(/ +/g, ' '); -} -// applies text transformations to a text node -function transformText(node, text, context) { - var textTransform = getAttribute(node, context.styleSheets, 'text-transform'); - switch (textTransform) { - case 'uppercase': - return text.toUpperCase(); - case 'lowercase': - return text.toLowerCase(); - default: - return text; - // TODO: capitalize, full-width - } -} -function trimLeft(str) { - return str.replace(/^\s+/, ''); -} -function trimRight(str) { - return str.replace(/\s+$/, ''); -} - -/** - * @param {string} textAnchor - * @param {number} originX - * @param {number} originY - * @constructor - */ -var TextChunk = /** @class */ (function () { - function TextChunk(parent, textAnchor, originX, originY) { - this.textNode = parent; - this.texts = []; - this.textNodes = []; - this.contexts = []; - this.textAnchor = textAnchor; - this.originX = originX; - this.originY = originY; - this.textMeasures = []; - } - TextChunk.prototype.setX = function (originX) { - this.originX = originX; - }; - TextChunk.prototype.setY = function (originY) { - this.originY = originY; - }; - TextChunk.prototype.add = function (tSpan, text, context) { - this.texts.push(text); - this.textNodes.push(tSpan); - this.contexts.push(context); - }; - TextChunk.prototype.rightTrimText = function () { - for (var r = this.texts.length - 1; r >= 0; r--) { - var shouldPreserve = this.contexts[r].attributeState.xmlSpace === 'preserve' || - this.contexts[r].attributeState.whiteSpace === 'pre'; - if (!shouldPreserve) { - this.texts[r] = trimRight(this.texts[r]); - } - // If find a letter, stop right-trimming - if (this.texts[r].match(/[^\s]/)) { - return false; - } - } - return true; - }; - TextChunk.prototype.measureText = function (context) { - for (var i = 0; i < this.texts.length; i++) { - this.textMeasures.push({ - width: context.textMeasure.measureTextWidth(this.texts[i], this.contexts[i].attributeState), - length: this.texts[i].length - }); - } - }; - TextChunk.prototype.put = function (context, charSpace) { - var i, textNode, textNodeContext, textMeasure; - var alreadySeen = []; - var xs = [], ys = []; - var currentTextX = this.originX, currentTextY = this.originY; - var minX = currentTextX, maxX = currentTextX; - for (i = 0; i < this.textNodes.length; i++) { - textNode = this.textNodes[i]; - textNodeContext = this.contexts[i]; - textMeasure = this.textMeasures[i] || { - width: context.textMeasure.measureTextWidth(this.texts[i], this.contexts[i].attributeState), - length: this.texts[i].length - }; - var x = currentTextX; - var y = currentTextY; - if (textNode.nodeName !== '#text') { - if (!alreadySeen.includes(textNode)) { - alreadySeen.push(textNode); - var tSpanDx = TextChunk.resolveRelativePositionAttribute(textNode, 'dx'); - if (tSpanDx !== null) { - x += toPixels(tSpanDx, textNodeContext.attributeState.fontSize); - } - var tSpanDy = TextChunk.resolveRelativePositionAttribute(textNode, 'dy'); - if (tSpanDy !== null) { - y += toPixels(tSpanDy, textNodeContext.attributeState.fontSize); - } - } - } - xs[i] = x; - ys[i] = y; - currentTextX = x + textMeasure.width + textMeasure.length * charSpace; - currentTextY = y; - minX = Math.min(minX, x); - maxX = Math.max(maxX, currentTextX); - } - var textOffset = 0; - switch (this.textAnchor) { - case 'start': - textOffset = 0; - break; - case 'middle': - textOffset = (maxX - minX) / 2; - break; - case 'end': - textOffset = maxX - minX; - break; - } - for (i = 0; i < this.textNodes.length; i++) { - textNode = this.textNodes[i]; - textNodeContext = this.contexts[i]; - if (textNode.nodeName !== '#text') { - if (textNodeContext.attributeState.visibility === 'hidden') { - continue; - } - } - context.pdf.saveGraphicsState(); - applyAttributes(textNodeContext, context, textNode); - var alignmentBaseline = textNodeContext.attributeState.alignmentBaseline; - var textRenderingMode = getTextRenderingMode(textNodeContext.attributeState); - context.pdf.text(this.texts[i], xs[i] - textOffset, ys[i], { - baseline: mapAlignmentBaseline(alignmentBaseline), - angle: context.transform, - renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, - charSpace: charSpace === 0 ? void 0 : charSpace - }); - context.pdf.restoreGraphicsState(); - } - return [currentTextX, currentTextY]; - }; - /** - * Resolves a positional attribute (dx, dy) on a given tSpan, possibly - * inheriting it from the nearest ancestor. Positional attributes - * are only inherited from a parent to its first child. - */ - TextChunk.resolveRelativePositionAttribute = function (element, attributeName) { - var _a; - var currentElement = element; - while (currentElement && nodeIs(currentElement, 'tspan')) { - if (currentElement.hasAttribute(attributeName)) { - return currentElement.getAttribute(attributeName); - } - if (!(((_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.firstChild) === element)) { - // positional attributes are only inherited from a parent to its first child - break; - } - currentElement = currentElement.parentElement; - } - return null; - }; - return TextChunk; -}()); - -var TextNode = /** @class */ (function (_super) { - __extends(TextNode, _super); - function TextNode() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.boundingBox = []; - return _this; - } - TextNode.prototype.processTSpans = function (textNode, node, context, textChunks, currentTextSegment, trimInfo) { - var pdfFontSize = context.pdf.getFontSize(); - var shouldPreserve = context.attributeState.xmlSpace === 'preserve' || context.attributeState.whiteSpace === 'pre'; - var firstText = true, initialSpace = false; - for (var i = 0; i < node.childNodes.length; i++) { - var childNode = node.childNodes[i]; - if (!childNode.textContent) { - continue; - } - var textContent = childNode.textContent; - if (childNode.nodeName === '#text') { - var trimmedText = removeNewlines(textContent); - trimmedText = replaceTabsBySpace(trimmedText); - if (!shouldPreserve) { - trimmedText = consolidateSpaces(trimmedText); - // If first text in tspan and starts with a space - if (firstText && trimmedText.match(/^\s/)) { - initialSpace = true; - } - // No longer the first text if we've found a letter - if (trimmedText.match(/[^\s]/)) { - firstText = false; - } - // Consolidate spaces across different children - if (trimInfo.prevText.match(/\s$/)) { - trimmedText = trimLeft(trimmedText); - } - } - var transformedText = transformText(node, trimmedText, context); - currentTextSegment.add(node, transformedText, context); - trimInfo.prevText = textContent; - trimInfo.prevContext = context; - } - else if (nodeIs(childNode, 'title')) ; - else if (nodeIs(childNode, 'tspan')) { - var tSpan = childNode; - var tSpanAbsX = tSpan.getAttribute('x'); - if (tSpanAbsX !== null) { - var x = toPixels(tSpanAbsX, pdfFontSize); - currentTextSegment = new TextChunk(this, getAttribute(tSpan, context.styleSheets, 'text-anchor') || - context.attributeState.textAnchor, x, 0); - textChunks.push({ type: 'y', chunk: currentTextSegment }); - } - var tSpanAbsY = tSpan.getAttribute('y'); - if (tSpanAbsY !== null) { - var y = toPixels(tSpanAbsY, pdfFontSize); - currentTextSegment = new TextChunk(this, getAttribute(tSpan, context.styleSheets, 'text-anchor') || - context.attributeState.textAnchor, 0, y); - textChunks.push({ type: 'x', chunk: currentTextSegment }); - } - var childContext = context.clone(); - parseAttributes(childContext, textNode, tSpan); - this.processTSpans(textNode, tSpan, childContext, textChunks, currentTextSegment, trimInfo); - } - } - return initialSpace; - }; - TextNode.prototype.renderCore = function (context) { - return __awaiter(this, void 0, void 0, function () { - var xOffset, charSpace, lengthAdjustment, pdfFontSize, textX, textY, dx, dy, textLength, visibility, tSpanCount, textContent, trimmedText, transformedText, defaultSize, shouldPreserve, alignmentBaseline, textRenderingMode, textBottom, textChunks, currentTextSegment, initialSpace, trimRight, r, totalDefaultWidth_1, totalLength_1; - return __generator(this, function (_a) { - context.pdf.saveGraphicsState(); - xOffset = 0; - charSpace = 0; - lengthAdjustment = 1; - pdfFontSize = context.pdf.getFontSize(); - textX = toPixels(this.element.getAttribute('x'), pdfFontSize); - textY = toPixels(this.element.getAttribute('y'), pdfFontSize); - dx = toPixels(this.element.getAttribute('dx'), pdfFontSize); - dy = toPixels(this.element.getAttribute('dy'), pdfFontSize); - textLength = parseFloat(this.element.getAttribute('textLength') || '0'); - visibility = context.attributeState.visibility; - tSpanCount = this.element.childElementCount; - if (tSpanCount === 0) { - textContent = this.element.textContent || ''; - trimmedText = transformXmlSpace(textContent, context.attributeState); - transformedText = transformText(this.element, trimmedText, context); - xOffset = context.textMeasure.getTextOffset(transformedText, context.attributeState); - if (textLength > 0) { - defaultSize = context.textMeasure.measureTextWidth(transformedText, context.attributeState); - shouldPreserve = context.attributeState.xmlSpace === 'preserve' || - context.attributeState.whiteSpace === 'pre'; - if (!shouldPreserve && textContent.match(/^\s/)) { - lengthAdjustment = 0; - } - charSpace = (textLength - defaultSize) / (transformedText.length - lengthAdjustment) || 0; - } - if (visibility === 'visible') { - alignmentBaseline = context.attributeState.alignmentBaseline; - textRenderingMode = getTextRenderingMode(context.attributeState); - context.pdf.text(transformedText, textX + dx - xOffset, textY + dy, { - baseline: mapAlignmentBaseline(alignmentBaseline), - angle: context.transform, - renderingMode: textRenderingMode === 'fill' ? void 0 : textRenderingMode, - charSpace: charSpace === 0 ? void 0 : charSpace - }); - textBottom = textY + dy + 0.1 * pdfFontSize; - this.boundingBox = [ - textX + dx - xOffset, - textBottom - pdfFontSize, - context.textMeasure.measureTextWidth(transformedText, context.attributeState), - pdfFontSize - ]; - } - } - else { - textChunks = []; - currentTextSegment = new TextChunk(this, context.attributeState.textAnchor, textX + dx, textY + dy); - textChunks.push({ type: '', chunk: currentTextSegment }); - initialSpace = this.processTSpans(this, this.element, context, textChunks, currentTextSegment, - // Set prevText to ' ' so any spaces on left of <text> are trimmed - { prevText: ' ', prevContext: context }); - lengthAdjustment = initialSpace ? 0 : 1; - trimRight = true; - for (r = textChunks.length - 1; r >= 0; r--) { - if (trimRight) { - trimRight = textChunks[r].chunk.rightTrimText(); - } - } - if (textLength > 0) { - totalDefaultWidth_1 = 0; - totalLength_1 = 0; - textChunks.forEach(function (_a) { - var chunk = _a.chunk; - chunk.measureText(context); - chunk.textMeasures.forEach(function (_a) { - var width = _a.width, length = _a.length; - totalDefaultWidth_1 += width; - totalLength_1 += length; - }); - }); - charSpace = (textLength - totalDefaultWidth_1) / (totalLength_1 - lengthAdjustment); - } - // Put the textchunks - textChunks.reduce(function (lastPositions, _a) { - var type = _a.type, chunk = _a.chunk; - if (type === 'x') { - chunk.setX(lastPositions[0]); - } - else if (type === 'y') { - chunk.setY(lastPositions[1]); - } - return chunk.put(context, charSpace); - }, [0, 0]); - } - context.pdf.restoreGraphicsState(); - return [2 /*return*/]; - }); - }); - }; - TextNode.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - TextNode.prototype.getBoundingBoxCore = function (context) { - return this.boundingBox.length > 0 - ? this.boundingBox - : defaultBoundingBox(this.element, context); - }; - TextNode.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - return TextNode; -}(GraphicsNode)); - -var path_parse; -var hasRequiredPath_parse; - -function requirePath_parse () { - if (hasRequiredPath_parse) return path_parse; - hasRequiredPath_parse = 1; - - - var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0 }; - - var SPECIAL_SPACES = [ - 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, - 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF - ]; - - function isSpace(ch) { - return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029) || // Line terminators - // White spaces - (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) || - (ch >= 0x1680 && SPECIAL_SPACES.indexOf(ch) >= 0); - } - - function isCommand(code) { - /*eslint-disable no-bitwise*/ - switch (code | 0x20) { - case 0x6D/* m */: - case 0x7A/* z */: - case 0x6C/* l */: - case 0x68/* h */: - case 0x76/* v */: - case 0x63/* c */: - case 0x73/* s */: - case 0x71/* q */: - case 0x74/* t */: - case 0x61/* a */: - case 0x72/* r */: - return true; - } - return false; - } - - function isArc(code) { - return (code | 0x20) === 0x61; - } - - function isDigit(code) { - return (code >= 48 && code <= 57); // 0..9 - } - - function isDigitStart(code) { - return (code >= 48 && code <= 57) || /* 0..9 */ - code === 0x2B || /* + */ - code === 0x2D || /* - */ - code === 0x2E; /* . */ - } - - - function State(path) { - this.index = 0; - this.path = path; - this.max = path.length; - this.result = []; - this.param = 0.0; - this.err = ''; - this.segmentStart = 0; - this.data = []; - } - - function skipSpaces(state) { - while (state.index < state.max && isSpace(state.path.charCodeAt(state.index))) { - state.index++; - } - } - - - function scanFlag(state) { - var ch = state.path.charCodeAt(state.index); - - if (ch === 0x30/* 0 */) { - state.param = 0; - state.index++; - return; - } - - if (ch === 0x31/* 1 */) { - state.param = 1; - state.index++; - return; - } - - state.err = 'SvgPath: arc flag can be 0 or 1 only (at pos ' + state.index + ')'; - } - - - function scanParam(state) { - var start = state.index, - index = start, - max = state.max, - zeroFirst = false, - hasCeiling = false, - hasDecimal = false, - hasDot = false, - ch; - - if (index >= max) { - state.err = 'SvgPath: missed param (at pos ' + index + ')'; - return; - } - ch = state.path.charCodeAt(index); - - if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { - index++; - ch = (index < max) ? state.path.charCodeAt(index) : 0; - } - - // This logic is shamelessly borrowed from Esprima - // https://github.com/ariya/esprimas - // - if (!isDigit(ch) && ch !== 0x2E/* . */) { - state.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')'; - return; - } - - if (ch !== 0x2E/* . */) { - zeroFirst = (ch === 0x30/* 0 */); - index++; - - ch = (index < max) ? state.path.charCodeAt(index) : 0; - - if (zeroFirst && index < max) { - // decimal number starts with '0' such as '09' is illegal. - if (ch && isDigit(ch)) { - state.err = 'SvgPath: numbers started with `0` such as `09` are illegal (at pos ' + start + ')'; - return; - } - } - - while (index < max && isDigit(state.path.charCodeAt(index))) { - index++; - hasCeiling = true; - } - ch = (index < max) ? state.path.charCodeAt(index) : 0; - } - - if (ch === 0x2E/* . */) { - hasDot = true; - index++; - while (isDigit(state.path.charCodeAt(index))) { - index++; - hasDecimal = true; - } - ch = (index < max) ? state.path.charCodeAt(index) : 0; - } - - if (ch === 0x65/* e */ || ch === 0x45/* E */) { - if (hasDot && !hasCeiling && !hasDecimal) { - state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')'; - return; - } - - index++; - - ch = (index < max) ? state.path.charCodeAt(index) : 0; - if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { - index++; - } - if (index < max && isDigit(state.path.charCodeAt(index))) { - while (index < max && isDigit(state.path.charCodeAt(index))) { - index++; - } - } else { - state.err = 'SvgPath: invalid float exponent (at pos ' + index + ')'; - return; - } - } - - state.index = index; - state.param = parseFloat(state.path.slice(start, index)) + 0.0; - } - - - function finalizeSegment(state) { - var cmd, cmdLC; - - // Process duplicated commands (without comand name) - - // This logic is shamelessly borrowed from Raphael - // https://github.com/DmitryBaranovskiy/raphael/ - // - cmd = state.path[state.segmentStart]; - cmdLC = cmd.toLowerCase(); - - var params = state.data; - - if (cmdLC === 'm' && params.length > 2) { - state.result.push([ cmd, params[0], params[1] ]); - params = params.slice(2); - cmdLC = 'l'; - cmd = (cmd === 'm') ? 'l' : 'L'; - } - - if (cmdLC === 'r') { - state.result.push([ cmd ].concat(params)); - } else { - - while (params.length >= paramCounts[cmdLC]) { - state.result.push([ cmd ].concat(params.splice(0, paramCounts[cmdLC]))); - if (!paramCounts[cmdLC]) { - break; - } - } - } - } - - - function scanSegment(state) { - var max = state.max, - cmdCode, is_arc, comma_found, need_params, i; - - state.segmentStart = state.index; - cmdCode = state.path.charCodeAt(state.index); - is_arc = isArc(cmdCode); - - if (!isCommand(cmdCode)) { - state.err = 'SvgPath: bad command ' + state.path[state.index] + ' (at pos ' + state.index + ')'; - return; - } - - need_params = paramCounts[state.path[state.index].toLowerCase()]; - - state.index++; - skipSpaces(state); - - state.data = []; - - if (!need_params) { - // Z - finalizeSegment(state); - return; - } - - comma_found = false; - - for (;;) { - for (i = need_params; i > 0; i--) { - if (is_arc && (i === 3 || i === 4)) scanFlag(state); - else scanParam(state); - - if (state.err.length) { - return; - } - state.data.push(state.param); - - skipSpaces(state); - comma_found = false; - - if (state.index < max && state.path.charCodeAt(state.index) === 0x2C/* , */) { - state.index++; - skipSpaces(state); - comma_found = true; - } - } - - // after ',' param is mandatory - if (comma_found) { - continue; - } - - if (state.index >= state.max) { - break; - } - - // Stop on next segment - if (!isDigitStart(state.path.charCodeAt(state.index))) { - break; - } - } - - finalizeSegment(state); - } - - - /* Returns array of segments: - * - * [ - * [ command, coord1, coord2, ... ] - * ] - */ - path_parse = function pathParse(svgPath) { - var state = new State(svgPath); - var max = state.max; - - skipSpaces(state); - - while (state.index < max && !state.err.length) { - scanSegment(state); - } - - if (state.err.length) { - state.result = []; - - } else if (state.result.length) { - - if ('mM'.indexOf(state.result[0][0]) < 0) { - state.err = 'SvgPath: string should start with `M` or `m`'; - state.result = []; - } else { - state.result[0][0] = 'M'; - } - } - - return { - err: state.err, - segments: state.result - }; - }; - return path_parse; -} - -var matrix; -var hasRequiredMatrix; - -function requireMatrix () { - if (hasRequiredMatrix) return matrix; - hasRequiredMatrix = 1; - - // combine 2 matrixes - // m1, m2 - [a, b, c, d, e, g] - // - function combine(m1, m2) { - return [ - m1[0] * m2[0] + m1[2] * m2[1], - m1[1] * m2[0] + m1[3] * m2[1], - m1[0] * m2[2] + m1[2] * m2[3], - m1[1] * m2[2] + m1[3] * m2[3], - m1[0] * m2[4] + m1[2] * m2[5] + m1[4], - m1[1] * m2[4] + m1[3] * m2[5] + m1[5] - ]; - } - - - function Matrix() { - if (!(this instanceof Matrix)) { return new Matrix(); } - this.queue = []; // list of matrixes to apply - this.cache = null; // combined matrix cache - } - - - Matrix.prototype.matrix = function (m) { - if (m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0) { - return this; - } - this.cache = null; - this.queue.push(m); - return this; - }; - - - Matrix.prototype.translate = function (tx, ty) { - if (tx !== 0 || ty !== 0) { - this.cache = null; - this.queue.push([ 1, 0, 0, 1, tx, ty ]); - } - return this; - }; - - - Matrix.prototype.scale = function (sx, sy) { - if (sx !== 1 || sy !== 1) { - this.cache = null; - this.queue.push([ sx, 0, 0, sy, 0, 0 ]); - } - return this; - }; - - - Matrix.prototype.rotate = function (angle, rx, ry) { - var rad, cos, sin; - - if (angle !== 0) { - this.translate(rx, ry); - - rad = angle * Math.PI / 180; - cos = Math.cos(rad); - sin = Math.sin(rad); - - this.queue.push([ cos, sin, -sin, cos, 0, 0 ]); - this.cache = null; - - this.translate(-rx, -ry); - } - return this; - }; - - - Matrix.prototype.skewX = function (angle) { - if (angle !== 0) { - this.cache = null; - this.queue.push([ 1, 0, Math.tan(angle * Math.PI / 180), 1, 0, 0 ]); - } - return this; - }; - - - Matrix.prototype.skewY = function (angle) { - if (angle !== 0) { - this.cache = null; - this.queue.push([ 1, Math.tan(angle * Math.PI / 180), 0, 1, 0, 0 ]); - } - return this; - }; - - - // Flatten queue - // - Matrix.prototype.toArray = function () { - if (this.cache) { - return this.cache; - } - - if (!this.queue.length) { - this.cache = [ 1, 0, 0, 1, 0, 0 ]; - return this.cache; - } - - this.cache = this.queue[0]; - - if (this.queue.length === 1) { - return this.cache; - } - - for (var i = 1; i < this.queue.length; i++) { - this.cache = combine(this.cache, this.queue[i]); - } - - return this.cache; - }; - - - // Apply list of matrixes to (x,y) point. - // If `isRelative` set, `translate` component of matrix will be skipped - // - Matrix.prototype.calc = function (x, y, isRelative) { - var m; - - // Don't change point on empty transforms queue - if (!this.queue.length) { return [ x, y ]; } - - // Calculate final matrix, if not exists - // - // NB. if you deside to apply transforms to point one-by-one, - // they should be taken in reverse order - - if (!this.cache) { - this.cache = this.toArray(); - } - - m = this.cache; - - // Apply matrix to point - return [ - x * m[0] + y * m[2] + (isRelative ? 0 : m[4]), - x * m[1] + y * m[3] + (isRelative ? 0 : m[5]) - ]; - }; - - - matrix = Matrix; - return matrix; -} - -var transform_parse; -var hasRequiredTransform_parse; - -function requireTransform_parse () { - if (hasRequiredTransform_parse) return transform_parse; - hasRequiredTransform_parse = 1; - - - var Matrix = requireMatrix(); - - var operations = { - matrix: true, - scale: true, - rotate: true, - translate: true, - skewX: true, - skewY: true - }; - - var CMD_SPLIT_RE = /\s*(matrix|translate|scale|rotate|skewX|skewY)\s*\(\s*(.+?)\s*\)[\s,]*/; - var PARAMS_SPLIT_RE = /[\s,]+/; - - - transform_parse = function transformParse(transformString) { - var matrix = new Matrix(); - var cmd, params; - - // Split value into ['', 'translate', '10 50', '', 'scale', '2', '', 'rotate', '-45', ''] - transformString.split(CMD_SPLIT_RE).forEach(function (item) { - - // Skip empty elements - if (!item.length) { return; } - - // remember operation - if (typeof operations[item] !== 'undefined') { - cmd = item; - return; - } - - // extract params & att operation to matrix - params = item.split(PARAMS_SPLIT_RE).map(function (i) { - return +i || 0; - }); - - // If params count is not correct - ignore command - switch (cmd) { - case 'matrix': - if (params.length === 6) { - matrix.matrix(params); - } - return; - - case 'scale': - if (params.length === 1) { - matrix.scale(params[0], params[0]); - } else if (params.length === 2) { - matrix.scale(params[0], params[1]); - } - return; - - case 'rotate': - if (params.length === 1) { - matrix.rotate(params[0], 0, 0); - } else if (params.length === 3) { - matrix.rotate(params[0], params[1], params[2]); - } - return; - - case 'translate': - if (params.length === 1) { - matrix.translate(params[0], 0); - } else if (params.length === 2) { - matrix.translate(params[0], params[1]); - } - return; - - case 'skewX': - if (params.length === 1) { - matrix.skewX(params[0]); - } - return; - - case 'skewY': - if (params.length === 1) { - matrix.skewY(params[0]); - } - return; - } - }); - - return matrix; - }; - return transform_parse; -} - -var a2c; -var hasRequiredA2c; - -function requireA2c () { - if (hasRequiredA2c) return a2c; - hasRequiredA2c = 1; - - - var TAU = Math.PI * 2; - - - /* eslint-disable space-infix-ops */ - - // Calculate an angle between two unit vectors - // - // Since we measure angle between radii of circular arcs, - // we can use simplified math (without length normalization) - // - function unit_vector_angle(ux, uy, vx, vy) { - var sign = (ux * vy - uy * vx < 0) ? -1 : 1; - var dot = ux * vx + uy * vy; - - // Add this to work with arbitrary vectors: - // dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy); - - // rounding errors, e.g. -1.0000000000000002 can screw up this - if (dot > 1.0) { dot = 1.0; } - if (dot < -1) { dot = -1; } - - return sign * Math.acos(dot); - } - - - // Convert from endpoint to center parameterization, - // see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - // - // Return [cx, cy, theta1, delta_theta] - // - function get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi) { - // Step 1. - // - // Moving an ellipse so origin will be the middlepoint between our two - // points. After that, rotate it to line up ellipse axes with coordinate - // axes. - // - var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; - var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; - - var rx_sq = rx * rx; - var ry_sq = ry * ry; - var x1p_sq = x1p * x1p; - var y1p_sq = y1p * y1p; - - // Step 2. - // - // Compute coordinates of the centre of this ellipse (cx', cy') - // in the new coordinate system. - // - var radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq); - - if (radicant < 0) { - // due to rounding errors it might be e.g. -1.3877787807814457e-17 - radicant = 0; - } - - radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq); - radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1); - - var cxp = radicant * rx/ry * y1p; - var cyp = radicant * -ry/rx * x1p; - - // Step 3. - // - // Transform back to get centre coordinates (cx, cy) in the original - // coordinate system. - // - var cx = cos_phi*cxp - sin_phi*cyp + (x1+x2)/2; - var cy = sin_phi*cxp + cos_phi*cyp + (y1+y2)/2; - - // Step 4. - // - // Compute angles (theta1, delta_theta). - // - var v1x = (x1p - cxp) / rx; - var v1y = (y1p - cyp) / ry; - var v2x = (-x1p - cxp) / rx; - var v2y = (-y1p - cyp) / ry; - - var theta1 = unit_vector_angle(1, 0, v1x, v1y); - var delta_theta = unit_vector_angle(v1x, v1y, v2x, v2y); - - if (fs === 0 && delta_theta > 0) { - delta_theta -= TAU; - } - if (fs === 1 && delta_theta < 0) { - delta_theta += TAU; - } - - return [ cx, cy, theta1, delta_theta ]; - } - - // - // Approximate one unit arc segment with bézier curves, - // see http://math.stackexchange.com/questions/873224 - // - function approximate_unit_arc(theta1, delta_theta) { - var alpha = 4/3 * Math.tan(delta_theta/4); - - var x1 = Math.cos(theta1); - var y1 = Math.sin(theta1); - var x2 = Math.cos(theta1 + delta_theta); - var y2 = Math.sin(theta1 + delta_theta); - - return [ x1, y1, x1 - y1*alpha, y1 + x1*alpha, x2 + y2*alpha, y2 - x2*alpha, x2, y2 ]; - } - - a2c = function a2c(x1, y1, x2, y2, fa, fs, rx, ry, phi) { - var sin_phi = Math.sin(phi * TAU / 360); - var cos_phi = Math.cos(phi * TAU / 360); - - // Make sure radii are valid - // - var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; - var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; - - if (x1p === 0 && y1p === 0) { - // we're asked to draw line to itself - return []; - } - - if (rx === 0 || ry === 0) { - // one of the radii is zero - return []; - } - - - // Compensate out-of-range radii - // - rx = Math.abs(rx); - ry = Math.abs(ry); - - var lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry); - if (lambda > 1) { - rx *= Math.sqrt(lambda); - ry *= Math.sqrt(lambda); - } - - - // Get center parameters (cx, cy, theta1, delta_theta) - // - var cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi); - - var result = []; - var theta1 = cc[2]; - var delta_theta = cc[3]; - - // Split an arc to multiple segments, so each segment - // will be less than τ/4 (= 90°) - // - var segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1); - delta_theta /= segments; - - for (var i = 0; i < segments; i++) { - result.push(approximate_unit_arc(theta1, delta_theta)); - theta1 += delta_theta; - } - - // We have a bezier approximation of a unit circle, - // now need to transform back to the original ellipse - // - return result.map(function (curve) { - for (var i = 0; i < curve.length; i += 2) { - var x = curve[i + 0]; - var y = curve[i + 1]; - - // scale - x *= rx; - y *= ry; - - // rotate - var xp = cos_phi*x - sin_phi*y; - var yp = sin_phi*x + cos_phi*y; - - // translate - curve[i + 0] = xp + cc[0]; - curve[i + 1] = yp + cc[1]; - } - - return curve; - }); - }; - return a2c; -} - -var ellipse; -var hasRequiredEllipse; - -function requireEllipse () { - if (hasRequiredEllipse) return ellipse; - hasRequiredEllipse = 1; - - /* eslint-disable space-infix-ops */ - - // The precision used to consider an ellipse as a circle - // - var epsilon = 0.0000000001; - - // To convert degree in radians - // - var torad = Math.PI / 180; - - // Class constructor : - // an ellipse centred at 0 with radii rx,ry and x - axis - angle ax. - // - function Ellipse(rx, ry, ax) { - if (!(this instanceof Ellipse)) { return new Ellipse(rx, ry, ax); } - this.rx = rx; - this.ry = ry; - this.ax = ax; - } - - // Apply a linear transform m to the ellipse - // m is an array representing a matrix : - // - - - // | m[0] m[2] | - // | m[1] m[3] | - // - - - // - Ellipse.prototype.transform = function (m) { - // We consider the current ellipse as image of the unit circle - // by first scale(rx,ry) and then rotate(ax) ... - // So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle. - var c = Math.cos(this.ax * torad), s = Math.sin(this.ax * torad); - var ma = [ - this.rx * (m[0]*c + m[2]*s), - this.rx * (m[1]*c + m[3]*s), - this.ry * (-m[0]*s + m[2]*c), - this.ry * (-m[1]*s + m[3]*c) - ]; - - // ma * transpose(ma) = [ J L ] - // [ L K ] - // L is calculated later (if the image is not a circle) - var J = ma[0]*ma[0] + ma[2]*ma[2], - K = ma[1]*ma[1] + ma[3]*ma[3]; - - // the discriminant of the characteristic polynomial of ma * transpose(ma) - var D = ((ma[0]-ma[3])*(ma[0]-ma[3]) + (ma[2]+ma[1])*(ma[2]+ma[1])) * - ((ma[0]+ma[3])*(ma[0]+ma[3]) + (ma[2]-ma[1])*(ma[2]-ma[1])); - - // the "mean eigenvalue" - var JK = (J + K) / 2; - - // check if the image is (almost) a circle - if (D < epsilon * JK) { - // if it is - this.rx = this.ry = Math.sqrt(JK); - this.ax = 0; - return this; - } - - // if it is not a circle - var L = ma[0]*ma[1] + ma[2]*ma[3]; - - D = Math.sqrt(D); - - // {l1,l2} = the two eigen values of ma * transpose(ma) - var l1 = JK + D/2, - l2 = JK - D/2; - // the x - axis - rotation angle is the argument of the l1 - eigenvector - /*eslint-disable indent*/ - this.ax = (Math.abs(L) < epsilon && Math.abs(l1 - K) < epsilon) ? - 90 - : - Math.atan(Math.abs(L) > Math.abs(l1 - K) ? - (l1 - J) / L - : - L / (l1 - K) - ) * 180 / Math.PI; - /*eslint-enable indent*/ - - // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90 - if (this.ax >= 0) { - // if ax in [0,90] - this.rx = Math.sqrt(l1); - this.ry = Math.sqrt(l2); - } else { - // if ax in ]-90,0[ => exchange axes - this.ax += 90; - this.rx = Math.sqrt(l2); - this.ry = Math.sqrt(l1); - } - - return this; - }; - - // Check if the ellipse is (almost) degenerate, i.e. rx = 0 or ry = 0 - // - Ellipse.prototype.isDegenerate = function () { - return (this.rx < epsilon * this.ry || this.ry < epsilon * this.rx); - }; - - ellipse = Ellipse; - return ellipse; -} - -var svgpath$1; -var hasRequiredSvgpath$1; - -function requireSvgpath$1 () { - if (hasRequiredSvgpath$1) return svgpath$1; - hasRequiredSvgpath$1 = 1; - - - var pathParse = requirePath_parse(); - var transformParse = requireTransform_parse(); - var matrix = requireMatrix(); - var a2c = requireA2c(); - var ellipse = requireEllipse(); - - - // Class constructor - // - function SvgPath(path) { - if (!(this instanceof SvgPath)) { return new SvgPath(path); } - - var pstate = pathParse(path); - - // Array of path segments. - // Each segment is array [command, param1, param2, ...] - this.segments = pstate.segments; - - // Error message on parse error. - this.err = pstate.err; - - // Transforms stack for lazy evaluation - this.__stack = []; - } - - SvgPath.from = function (src) { - if (typeof src === 'string') return new SvgPath(src); - - if (src instanceof SvgPath) { - // Create empty object - var s = new SvgPath(''); - - // Clone properies - s.err = src.err; - s.segments = src.segments.map(function (sgm) { return sgm.slice(); }); - s.__stack = src.__stack.map(function (m) { - return matrix().matrix(m.toArray()); - }); - - return s; - } - - throw new Error('SvgPath.from: invalid param type ' + src); - }; - - - SvgPath.prototype.__matrix = function (m) { - var self = this, i; - - // Quick leave for empty matrix - if (!m.queue.length) { return; } - - this.iterate(function (s, index, x, y) { - var p, result, name, isRelative; - - switch (s[0]) { - - // Process 'assymetric' commands separately - case 'v': - p = m.calc(0, s[1], true); - result = (p[0] === 0) ? [ 'v', p[1] ] : [ 'l', p[0], p[1] ]; - break; - - case 'V': - p = m.calc(x, s[1], false); - result = (p[0] === m.calc(x, y, false)[0]) ? [ 'V', p[1] ] : [ 'L', p[0], p[1] ]; - break; - - case 'h': - p = m.calc(s[1], 0, true); - result = (p[1] === 0) ? [ 'h', p[0] ] : [ 'l', p[0], p[1] ]; - break; - - case 'H': - p = m.calc(s[1], y, false); - result = (p[1] === m.calc(x, y, false)[1]) ? [ 'H', p[0] ] : [ 'L', p[0], p[1] ]; - break; - - case 'a': - case 'A': - // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - - // Drop segment if arc is empty (end point === start point) - /*if ((s[0] === 'A' && s[6] === x && s[7] === y) || - (s[0] === 'a' && s[6] === 0 && s[7] === 0)) { - return []; - }*/ - - // Transform rx, ry and the x-axis-rotation - var ma = m.toArray(); - var e = ellipse(s[1], s[2], s[3]).transform(ma); - - // flip sweep-flag if matrix is not orientation-preserving - if (ma[0] * ma[3] - ma[1] * ma[2] < 0) { - s[5] = s[5] ? '0' : '1'; - } - - // Transform end point as usual (without translation for relative notation) - p = m.calc(s[6], s[7], s[0] === 'a'); - - // Empty arcs can be ignored by renderer, but should not be dropped - // to avoid collisions with `S A S` and so on. Replace with empty line. - if ((s[0] === 'A' && s[6] === x && s[7] === y) || - (s[0] === 'a' && s[6] === 0 && s[7] === 0)) { - result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ]; - break; - } - - // if the resulting ellipse is (almost) a segment ... - if (e.isDegenerate()) { - // replace the arc by a line - result = [ s[0] === 'a' ? 'l' : 'L', p[0], p[1] ]; - } else { - // if it is a real ellipse - // s[0], s[4] and s[5] are not modified - result = [ s[0], e.rx, e.ry, e.ax, s[4], s[5], p[0], p[1] ]; - } - - break; - - case 'm': - // Edge case. The very first `m` should be processed as absolute, if happens. - // Make sense for coord shift transforms. - isRelative = index > 0; - - p = m.calc(s[1], s[2], isRelative); - result = [ 'm', p[0], p[1] ]; - break; - - default: - name = s[0]; - result = [ name ]; - isRelative = (name.toLowerCase() === name); - - // Apply transformations to the segment - for (i = 1; i < s.length; i += 2) { - p = m.calc(s[i], s[i + 1], isRelative); - result.push(p[0], p[1]); - } - } - - self.segments[index] = result; - }, true); - }; - - - // Apply stacked commands - // - SvgPath.prototype.__evaluateStack = function () { - var m, i; - - if (!this.__stack.length) { return; } - - if (this.__stack.length === 1) { - this.__matrix(this.__stack[0]); - this.__stack = []; - return; - } - - m = matrix(); - i = this.__stack.length; - - while (--i >= 0) { - m.matrix(this.__stack[i].toArray()); - } - - this.__matrix(m); - this.__stack = []; - }; - - - // Convert processed SVG Path back to string - // - SvgPath.prototype.toString = function () { - var result = '', prevCmd = '', cmdSkipped = false; - - this.__evaluateStack(); - - for (var i = 0, len = this.segments.length; i < len; i++) { - var segment = this.segments[i]; - var cmd = segment[0]; - - // Command not repeating => store - if (cmd !== prevCmd || cmd === 'm' || cmd === 'M') { - // workaround for FontForge SVG importing bug, keep space between "z m". - if (cmd === 'm' && prevCmd === 'z') result += ' '; - result += cmd; - - cmdSkipped = false; - } else { - cmdSkipped = true; - } - - // Store segment params - for (var pos = 1; pos < segment.length; pos++) { - var val = segment[pos]; - // Space can be skipped - // 1. After command (always) - // 2. For negative value (with '-' at start) - if (pos === 1) { - if (cmdSkipped && val >= 0) result += ' '; - } else if (val >= 0) result += ' '; - - result += val; - } - - prevCmd = cmd; - } - - return result; - }; - - - // Translate path to (x [, y]) - // - SvgPath.prototype.translate = function (x, y) { - this.__stack.push(matrix().translate(x, y || 0)); - return this; - }; - - - // Scale path to (sx [, sy]) - // sy = sx if not defined - // - SvgPath.prototype.scale = function (sx, sy) { - this.__stack.push(matrix().scale(sx, (!sy && (sy !== 0)) ? sx : sy)); - return this; - }; - - - // Rotate path around point (sx [, sy]) - // sy = sx if not defined - // - SvgPath.prototype.rotate = function (angle, rx, ry) { - this.__stack.push(matrix().rotate(angle, rx || 0, ry || 0)); - return this; - }; - - - // Skew path along the X axis by `degrees` angle - // - SvgPath.prototype.skewX = function (degrees) { - this.__stack.push(matrix().skewX(degrees)); - return this; - }; - - - // Skew path along the Y axis by `degrees` angle - // - SvgPath.prototype.skewY = function (degrees) { - this.__stack.push(matrix().skewY(degrees)); - return this; - }; - - - // Apply matrix transform (array of 6 elements) - // - SvgPath.prototype.matrix = function (m) { - this.__stack.push(matrix().matrix(m)); - return this; - }; - - - // Transform path according to "transform" attr of SVG spec - // - SvgPath.prototype.transform = function (transformString) { - if (!transformString.trim()) { - return this; - } - this.__stack.push(transformParse(transformString)); - return this; - }; - - - // Round coords with given decimal precition. - // 0 by default (to integers) - // - SvgPath.prototype.round = function (d) { - var contourStartDeltaX = 0, contourStartDeltaY = 0, deltaX = 0, deltaY = 0, l; - - d = d || 0; - - this.__evaluateStack(); - - this.segments.forEach(function (s) { - var isRelative = (s[0].toLowerCase() === s[0]); - - switch (s[0]) { - case 'H': - case 'h': - if (isRelative) { s[1] += deltaX; } - deltaX = s[1] - s[1].toFixed(d); - s[1] = +s[1].toFixed(d); - return; - - case 'V': - case 'v': - if (isRelative) { s[1] += deltaY; } - deltaY = s[1] - s[1].toFixed(d); - s[1] = +s[1].toFixed(d); - return; - - case 'Z': - case 'z': - deltaX = contourStartDeltaX; - deltaY = contourStartDeltaY; - return; - - case 'M': - case 'm': - if (isRelative) { - s[1] += deltaX; - s[2] += deltaY; - } - - deltaX = s[1] - s[1].toFixed(d); - deltaY = s[2] - s[2].toFixed(d); - - contourStartDeltaX = deltaX; - contourStartDeltaY = deltaY; - - s[1] = +s[1].toFixed(d); - s[2] = +s[2].toFixed(d); - return; - - case 'A': - case 'a': - // [cmd, rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - if (isRelative) { - s[6] += deltaX; - s[7] += deltaY; - } - - deltaX = s[6] - s[6].toFixed(d); - deltaY = s[7] - s[7].toFixed(d); - - s[1] = +s[1].toFixed(d); - s[2] = +s[2].toFixed(d); - s[3] = +s[3].toFixed(d + 2); // better precision for rotation - s[6] = +s[6].toFixed(d); - s[7] = +s[7].toFixed(d); - return; - - default: - // a c l q s t - l = s.length; - - if (isRelative) { - s[l - 2] += deltaX; - s[l - 1] += deltaY; - } - - deltaX = s[l - 2] - s[l - 2].toFixed(d); - deltaY = s[l - 1] - s[l - 1].toFixed(d); - - s.forEach(function (val, i) { - if (!i) { return; } - s[i] = +s[i].toFixed(d); - }); - return; - } - }); - - return this; - }; - - - // Apply iterator function to all segments. If function returns result, - // current segment will be replaced to array of returned segments. - // If empty array is returned, current regment will be deleted. - // - SvgPath.prototype.iterate = function (iterator, keepLazyStack) { - var segments = this.segments, - replacements = {}, - needReplace = false, - lastX = 0, - lastY = 0, - countourStartX = 0, - countourStartY = 0; - var i, j, newSegments; - - if (!keepLazyStack) { - this.__evaluateStack(); - } - - segments.forEach(function (s, index) { - - var res = iterator(s, index, lastX, lastY); - - if (Array.isArray(res)) { - replacements[index] = res; - needReplace = true; - } - - var isRelative = (s[0] === s[0].toLowerCase()); - - // calculate absolute X and Y - switch (s[0]) { - case 'm': - case 'M': - lastX = s[1] + (isRelative ? lastX : 0); - lastY = s[2] + (isRelative ? lastY : 0); - countourStartX = lastX; - countourStartY = lastY; - return; - - case 'h': - case 'H': - lastX = s[1] + (isRelative ? lastX : 0); - return; - - case 'v': - case 'V': - lastY = s[1] + (isRelative ? lastY : 0); - return; - - case 'z': - case 'Z': - // That make sence for multiple contours - lastX = countourStartX; - lastY = countourStartY; - return; - - default: - lastX = s[s.length - 2] + (isRelative ? lastX : 0); - lastY = s[s.length - 1] + (isRelative ? lastY : 0); - } - }); - - // Replace segments if iterator return results - - if (!needReplace) { return this; } - - newSegments = []; - - for (i = 0; i < segments.length; i++) { - if (typeof replacements[i] !== 'undefined') { - for (j = 0; j < replacements[i].length; j++) { - newSegments.push(replacements[i][j]); - } - } else { - newSegments.push(segments[i]); - } - } - - this.segments = newSegments; - - return this; - }; - - - // Converts segments from relative to absolute - // - SvgPath.prototype.abs = function () { - - this.iterate(function (s, index, x, y) { - var name = s[0], - nameUC = name.toUpperCase(), - i; - - // Skip absolute commands - if (name === nameUC) { return; } - - s[0] = nameUC; - - switch (name) { - case 'v': - // v has shifted coords parity - s[1] += y; - return; - - case 'a': - // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - // touch x, y only - s[6] += x; - s[7] += y; - return; - - default: - for (i = 1; i < s.length; i++) { - s[i] += i % 2 ? x : y; // odd values are X, even - Y - } - } - }, true); - - return this; - }; - - - // Converts segments from absolute to relative - // - SvgPath.prototype.rel = function () { - - this.iterate(function (s, index, x, y) { - var name = s[0], - nameLC = name.toLowerCase(), - i; - - // Skip relative commands - if (name === nameLC) { return; } - - // Don't touch the first M to avoid potential confusions. - if (index === 0 && name === 'M') { return; } - - s[0] = nameLC; - - switch (name) { - case 'V': - // V has shifted coords parity - s[1] -= y; - return; - - case 'A': - // ARC is: ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - // touch x, y only - s[6] -= x; - s[7] -= y; - return; - - default: - for (i = 1; i < s.length; i++) { - s[i] -= i % 2 ? x : y; // odd values are X, even - Y - } - } - }, true); - - return this; - }; - - - // Converts arcs to cubic bézier curves - // - SvgPath.prototype.unarc = function () { - this.iterate(function (s, index, x, y) { - var new_segments, nextX, nextY, result = [], name = s[0]; - - // Skip anything except arcs - if (name !== 'A' && name !== 'a') { return null; } - - if (name === 'a') { - // convert relative arc coordinates to absolute - nextX = x + s[6]; - nextY = y + s[7]; - } else { - nextX = s[6]; - nextY = s[7]; - } - - new_segments = a2c(x, y, nextX, nextY, s[4], s[5], s[1], s[2], s[3]); - - // Degenerated arcs can be ignored by renderer, but should not be dropped - // to avoid collisions with `S A S` and so on. Replace with empty line. - if (new_segments.length === 0) { - return [ [ s[0] === 'a' ? 'l' : 'L', s[6], s[7] ] ]; - } - - new_segments.forEach(function (s) { - result.push([ 'C', s[2], s[3], s[4], s[5], s[6], s[7] ]); - }); - - return result; - }); - - return this; - }; - - - // Converts smooth curves (with missed control point) to generic curves - // - SvgPath.prototype.unshort = function () { - var segments = this.segments; - var prevControlX, prevControlY, prevSegment; - var curControlX, curControlY; - - // TODO: add lazy evaluation flag when relative commands supported - - this.iterate(function (s, idx, x, y) { - var name = s[0], nameUC = name.toUpperCase(), isRelative; - - // First command MUST be M|m, it's safe to skip. - // Protect from access to [-1] for sure. - if (!idx) { return; } - - if (nameUC === 'T') { // quadratic curve - isRelative = (name === 't'); - - prevSegment = segments[idx - 1]; - - if (prevSegment[0] === 'Q') { - prevControlX = prevSegment[1] - x; - prevControlY = prevSegment[2] - y; - } else if (prevSegment[0] === 'q') { - prevControlX = prevSegment[1] - prevSegment[3]; - prevControlY = prevSegment[2] - prevSegment[4]; - } else { - prevControlX = 0; - prevControlY = 0; - } - - curControlX = -prevControlX; - curControlY = -prevControlY; - - if (!isRelative) { - curControlX += x; - curControlY += y; - } - - segments[idx] = [ - isRelative ? 'q' : 'Q', - curControlX, curControlY, - s[1], s[2] - ]; - - } else if (nameUC === 'S') { // cubic curve - isRelative = (name === 's'); - - prevSegment = segments[idx - 1]; - - if (prevSegment[0] === 'C') { - prevControlX = prevSegment[3] - x; - prevControlY = prevSegment[4] - y; - } else if (prevSegment[0] === 'c') { - prevControlX = prevSegment[3] - prevSegment[5]; - prevControlY = prevSegment[4] - prevSegment[6]; - } else { - prevControlX = 0; - prevControlY = 0; - } - - curControlX = -prevControlX; - curControlY = -prevControlY; - - if (!isRelative) { - curControlX += x; - curControlY += y; - } - - segments[idx] = [ - isRelative ? 'c' : 'C', - curControlX, curControlY, - s[1], s[2], s[3], s[4] - ]; - } - }); - - return this; - }; - - - svgpath$1 = SvgPath; - return svgpath$1; -} - -var svgpath; -var hasRequiredSvgpath; - -function requireSvgpath () { - if (hasRequiredSvgpath) return svgpath; - hasRequiredSvgpath = 1; - - svgpath = requireSvgpath$1(); - return svgpath; -} - -var svgpathExports = requireSvgpath(); -var SvgPath = /*@__PURE__*/getDefaultExportFromCjs(svgpathExports); - -var PathNode = /** @class */ (function (_super) { - __extends(PathNode, _super); - function PathNode(node, children) { - return _super.call(this, true, node, children) || this; - } - PathNode.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - PathNode.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - PathNode.prototype.getPath = function (context) { - var svgPath = new SvgPath(getAttribute(this.element, context.styleSheets, 'd') || '') - .unshort() - .unarc() - .abs(); - var path = new Path(); - var prevX; - var prevY; - svgPath.iterate(function (seg) { - switch (seg[0]) { - case 'M': - path.moveTo(seg[1], seg[2]); - break; - case 'L': - path.lineTo(seg[1], seg[2]); - break; - case 'H': - path.lineTo(seg[1], prevY); - break; - case 'V': - path.lineTo(prevX, seg[1]); - break; - case 'C': - path.curveTo(seg[1], seg[2], seg[3], seg[4], seg[5], seg[6]); - break; - case 'Q': - var p2 = toCubic([prevX, prevY], [seg[1], seg[2]]); - var p3 = toCubic([seg[3], seg[4]], [seg[1], seg[2]]); - path.curveTo(p2[0], p2[1], p3[0], p3[1], seg[3], seg[4]); - break; - case 'Z': - path.close(); - break; - } - switch (seg[0]) { - case 'M': - case 'L': - prevX = seg[1]; - prevY = seg[2]; - break; - case 'H': - prevX = seg[1]; - break; - case 'V': - prevY = seg[1]; - break; - case 'C': - prevX = seg[5]; - prevY = seg[6]; - break; - case 'Q': - prevX = seg[3]; - prevY = seg[4]; - break; - } - }); - return path; - }; - return PathNode; -}(GeometryNode)); - -// groups: 1: mime-type (+ charset), 2: mime-type (w/o charset), 3: charset, 4: base64?, 5: body -var dataUriRegex = /^\s*data:(([^/,;]+\/[^/,;]+)(?:;([^,;=]+=[^,;=]+))?)?(?:;(base64))?,((?:.|\s)*)$/i; -var ImageNode = /** @class */ (function (_super) { - __extends(ImageNode, _super); - function ImageNode(element, children, context) { - var _this = _super.call(this, element, children) || this; - _this.imageLoadingPromise = null; - _this.imageUrl = _this.element.getAttribute('xlink:href') || _this.element.getAttribute('href'); - if (_this.imageUrl && - ImageNode.shouldLoadImage(_this.imageUrl, context.svg2pdfParameters.loadImages)) { - // start loading the image as early as possible - _this.imageLoadingPromise = ImageNode.fetchImageData(_this.imageUrl); - } - return _this; - } - ImageNode.shouldLoadImage = function (imageUrl, loadImages) { - if (loadImages === false) { - return false; - } - if (loadImages instanceof RegExp) { - loadImages.lastIndex = 0; - return loadImages.test(imageUrl); - } - return true; - }; - ImageNode.prototype.renderCore = function (context) { - return __awaiter(this, void 0, void 0, function () { - var width, height, x, y, _a, data, format, parser, svgElement, preserveAspectRatio, idMap, nestedContext, svgNode, dataUri, _b, imgWidth, imgHeight, viewBox, transform, e_1; - return __generator(this, function (_c) { - switch (_c.label) { - case 0: - if (!this.imageLoadingPromise) { - return [2 /*return*/]; - } - context.pdf.setCurrentTransformationMatrix(context.transform); - width = parseFloat(getAttribute(this.element, context.styleSheets, 'width') || '0'), height = parseFloat(getAttribute(this.element, context.styleSheets, 'height') || '0'), x = parseFloat(getAttribute(this.element, context.styleSheets, 'x') || '0'), y = parseFloat(getAttribute(this.element, context.styleSheets, 'y') || '0'); - if (!isFinite(width) || width <= 0 || !isFinite(height) || height <= 0) { - return [2 /*return*/]; - } - return [4 /*yield*/, this.imageLoadingPromise]; - case 1: - _a = _c.sent(), data = _a.data, format = _a.format; - if (!(format.indexOf('svg') === 0)) return [3 /*break*/, 3]; - parser = new DOMParser(); - svgElement = parser.parseFromString(data, 'image/svg+xml').firstElementChild; - preserveAspectRatio = this.element.getAttribute('preserveAspectRatio'); - if (!preserveAspectRatio || - preserveAspectRatio.indexOf('defer') < 0 || - !svgElement.getAttribute('preserveAspectRatio')) { - svgElement.setAttribute('preserveAspectRatio', preserveAspectRatio || ''); - } - svgElement.setAttribute('x', String(x)); - svgElement.setAttribute('y', String(y)); - svgElement.setAttribute('width', String(width)); - svgElement.setAttribute('height', String(height)); - idMap = {}; - nestedContext = new Context(context.pdf, { - refsHandler: new ReferencesHandler(idMap), - styleSheets: context.styleSheets, - viewport: new Viewport(width, height), - svg2pdfParameters: context.svg2pdfParameters, - textMeasure: context.textMeasure - }); - svgNode = parse(svgElement, nestedContext, idMap); - return [4 /*yield*/, svgNode.render(nestedContext)]; - case 2: - _c.sent(); - return [2 /*return*/]; - case 3: - dataUri = "data:image/".concat(format, ";base64,").concat(btoa(data)); - _c.label = 4; - case 4: - _c.trys.push([4, 6, , 7]); - return [4 /*yield*/, ImageNode.getImageDimensions(dataUri)]; - case 5: - _b = _c.sent(), imgWidth = _b[0], imgHeight = _b[1]; - viewBox = [0, 0, imgWidth, imgHeight]; - transform = computeViewBoxTransform(this.element, viewBox, x, y, width, height, context); - context.pdf.setCurrentTransformationMatrix(transform); - context.pdf.addImage(dataUri, '', // will be ignored anyways if imageUrl is a data url - 0, 0, imgWidth, imgHeight); - return [3 /*break*/, 7]; - case 6: - e_1 = _c.sent(); - if (typeof console === 'object' && console.warn) { - console.warn("Could not load image ".concat(this.imageUrl, ". \n").concat(e_1)); - } - return [3 /*break*/, 7]; - case 7: return [2 /*return*/]; - } - }); - }); - }; - ImageNode.prototype.getBoundingBoxCore = function (context) { - return defaultBoundingBox(this.element, context); - }; - ImageNode.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - ImageNode.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - ImageNode.fetchImageData = function (imageUrl) { - return __awaiter(this, void 0, void 0, function () { - var data, format, match, mimeType, mimeTypeParts; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - match = imageUrl.match(dataUriRegex); - if (!match) return [3 /*break*/, 1]; - mimeType = match[2]; - mimeTypeParts = mimeType.split('/'); - if (mimeTypeParts[0] !== 'image') { - throw new Error("Unsupported image URL: ".concat(imageUrl)); - } - format = mimeTypeParts[1]; - data = match[5]; - if (match[4] === 'base64') { - data = data.replace(/\s/g, ''); - data = atob(data); - } - else { - data = decodeURIComponent(data); - } - return [3 /*break*/, 3]; - case 1: return [4 /*yield*/, ImageNode.fetchImage(imageUrl)]; - case 2: - data = _a.sent(); - format = imageUrl.substring(imageUrl.lastIndexOf('.') + 1); - _a.label = 3; - case 3: return [2 /*return*/, { - data: data, - format: format - }]; - } - }); - }); - }; - ImageNode.fetchImage = function (imageUrl) { - return new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', imageUrl, true); - xhr.responseType = 'arraybuffer'; - xhr.onload = function () { - if (xhr.status !== 200) { - throw new Error("Error ".concat(xhr.status, ": Failed to load image '").concat(imageUrl, "'")); - } - var bytes = new Uint8Array(xhr.response); - var data = ''; - for (var i = 0; i < bytes.length; i++) { - data += String.fromCharCode(bytes[i]); - } - resolve(data); - }; - xhr.onerror = reject; - xhr.onabort = reject; - xhr.send(null); - }); - }; - ImageNode.getMimeType = function (format) { - format = format.toLowerCase(); - switch (format) { - case 'jpg': - case 'jpeg': - return 'image/jpeg'; - default: - return "image/".concat(format); - } - }; - ImageNode.getImageDimensions = function (src) { - return new Promise(function (resolve, reject) { - var img = new Image(); - img.onload = function () { - resolve([img.width, img.height]); - }; - img.onerror = reject; - img.src = src; - }); - }; - return ImageNode; -}(GraphicsNode)); - -var Traverse = /** @class */ (function (_super) { - __extends(Traverse, _super); - function Traverse(closed, node, children) { - var _this = _super.call(this, true, node, children) || this; - _this.closed = closed; - return _this; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - Traverse.prototype.getPath = function (context) { - if (!this.element.hasAttribute('points') || this.element.getAttribute('points') === '') { - return null; - } - // @ts-ignore - var points = Traverse.parsePointsString(this.element.getAttribute('points')); - var path = new Path(); - if (points.length < 1) { - return path; - } - path.moveTo(points[0][0], points[0][1]); - for (var i = 1; i < points.length; i++) { - path.lineTo(points[i][0], points[i][1]); - } - if (this.closed) { - path.close(); - } - return path; - }; - Traverse.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - Traverse.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - Traverse.parsePointsString = function (string) { - var floats = parseFloats(string); - var result = []; - for (var i = 0; i < floats.length - 1; i += 2) { - var x = floats[i]; - var y = floats[i + 1]; - result.push([x, y]); - } - return result; - }; - return Traverse; -}(GeometryNode)); - -var Polygon = /** @class */ (function (_super) { - __extends(Polygon, _super); - function Polygon(node, children) { - return _super.call(this, true, node, children) || this; - } - return Polygon; -}(Traverse)); - -var VoidNode = /** @class */ (function (_super) { - __extends(VoidNode, _super); - function VoidNode() { - return _super !== null && _super.apply(this, arguments) || this; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - VoidNode.prototype.render = function (parentContext) { - return Promise.resolve(); - }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - VoidNode.prototype.getBoundingBoxCore = function (context) { - return [0, 0, 0, 0]; - }; - VoidNode.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - VoidNode.prototype.isVisible = function (parentVisible, context) { - return svgNodeIsVisible(this, parentVisible, context); - }; - return VoidNode; -}(SvgNode)); - -var MarkerNode = /** @class */ (function (_super) { - __extends(MarkerNode, _super); - function MarkerNode() { - return _super !== null && _super.apply(this, arguments) || this; - } - MarkerNode.prototype.apply = function (parentContext) { - return __awaiter(this, void 0, void 0, function () { - var tfMatrix, bBox, contextColors, childContext, _i, _a, child; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - tfMatrix = this.computeNodeTransform(parentContext); - bBox = this.getBoundingBox(parentContext); - parentContext.pdf.beginFormObject(bBox[0], bBox[1], bBox[2], bBox[3], tfMatrix); - contextColors = AttributeState.getContextColors(parentContext); - childContext = new Context(parentContext.pdf, { - refsHandler: parentContext.refsHandler, - styleSheets: parentContext.styleSheets, - viewport: parentContext.viewport, - svg2pdfParameters: parentContext.svg2pdfParameters, - textMeasure: parentContext.textMeasure, - attributeState: Object.assign(AttributeState.default(), contextColors) - }); - // "Properties do not inherit from the element referencing the 'marker' into the contents of the - // marker. However, by using the context-stroke value for the fill or stroke on elements in its - // definition, a single marker can be designed to match the style of the element referencing the - // marker." - // -> we need to reset all attributes - applyContext(childContext); - _i = 0, _a = this.children; - _b.label = 1; - case 1: - if (!(_i < _a.length)) return [3 /*break*/, 4]; - child = _a[_i]; - return [4 /*yield*/, child.render(childContext)]; - case 2: - _b.sent(); - _b.label = 3; - case 3: - _i++; - return [3 /*break*/, 1]; - case 4: - parentContext.pdf.endFormObject(childContext.refsHandler.generateKey(this.element.getAttribute('id'), contextColors)); - return [2 /*return*/]; - } - }); - }); - }; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - MarkerNode.prototype.getBoundingBoxCore = function (context) { - var viewBox = this.element.getAttribute('viewBox'); - var vb; - if (viewBox) { - vb = parseFloats(viewBox); - } - return [ - (vb && vb[0]) || 0, - (vb && vb[1]) || 0, - (vb && vb[2]) || parseFloat(this.element.getAttribute('markerWidth') || '3'), - (vb && vb[3]) || parseFloat(this.element.getAttribute('markerHeight') || '3') - ]; - }; - MarkerNode.prototype.computeNodeTransformCore = function (context) { - var refX = parseFloat(this.element.getAttribute('refX') || '0'); - var refY = parseFloat(this.element.getAttribute('refY') || '0'); - var viewBox = this.element.getAttribute('viewBox'); - var nodeTransform; - if (viewBox) { - var bounds = parseFloats(viewBox); - // "Markers are drawn such that their reference point (i.e., attributes ‘refX’ and ‘refY’) - // is positioned at the given vertex." - The "translate" part of the viewBox transform is - // ignored. - nodeTransform = computeViewBoxTransform(this.element, bounds, 0, 0, parseFloat(this.element.getAttribute('markerWidth') || '3'), parseFloat(this.element.getAttribute('markerHeight') || '3'), context, true); - nodeTransform = context.pdf.matrixMult(context.pdf.Matrix(1, 0, 0, 1, -refX, -refY), nodeTransform); - } - else { - nodeTransform = context.pdf.Matrix(1, 0, 0, 1, -refX, -refY); - } - return nodeTransform; - }; - MarkerNode.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - return MarkerNode; -}(NonRenderedNode)); - -var Circle = /** @class */ (function (_super) { - __extends(Circle, _super); - function Circle(node, children) { - return _super.call(this, node, children) || this; - } - Circle.prototype.getR = function (context) { - var _a; - return ((_a = this.r) !== null && _a !== void 0 ? _a : (this.r = parseFloat(getAttribute(this.element, context.styleSheets, 'r') || '0'))); - }; - Circle.prototype.getRx = function (context) { - return this.getR(context); - }; - Circle.prototype.getRy = function (context) { - return this.getR(context); - }; - return Circle; -}(EllipseBase)); - -var Polyline = /** @class */ (function (_super) { - __extends(Polyline, _super); - function Polyline(node, children) { - return _super.call(this, false, node, children) || this; - } - return Polyline; -}(Traverse)); - -var ContainerNode = /** @class */ (function (_super) { - __extends(ContainerNode, _super); - function ContainerNode() { - return _super !== null && _super.apply(this, arguments) || this; - } - ContainerNode.prototype.renderCore = function (context) { - return __awaiter(this, void 0, void 0, function () { - var _i, _a, child; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - _i = 0, _a = this.children; - _b.label = 1; - case 1: - if (!(_i < _a.length)) return [3 /*break*/, 4]; - child = _a[_i]; - return [4 /*yield*/, child.render(context)]; - case 2: - _b.sent(); - _b.label = 3; - case 3: - _i++; - return [3 /*break*/, 1]; - case 4: return [2 /*return*/]; - } - }); - }); - }; - ContainerNode.prototype.getBoundingBoxCore = function (context) { - return getBoundingBoxByChildren(context, this); - }; - return ContainerNode; -}(RenderedNode)); - -var Svg = /** @class */ (function (_super) { - __extends(Svg, _super); - function Svg() { - return _super !== null && _super.apply(this, arguments) || this; - } - Svg.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - Svg.prototype.render = function (context) { - return __awaiter(this, void 0, void 0, function () { - var x, y, width, height, transform; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - if (!this.isVisible(context.attributeState.visibility !== 'hidden', context)) { - return [2 /*return*/]; - } - x = this.getX(context); - y = this.getY(context); - width = this.getWidth(context); - height = this.getHeight(context); - context.pdf.saveGraphicsState(); - transform = context.transform; - if (this.element.hasAttribute('transform')) { - // SVG 2 allows transforms on SVG elements - // "The transform should be applied as if the ‘svg’ had a parent element with that transform set." - transform = context.pdf.matrixMult( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - parseTransform(this.element.getAttribute('transform'), context), transform); - } - context.pdf.setCurrentTransformationMatrix(transform); - if (!context.withinUse && - getAttribute(this.element, context.styleSheets, 'overflow') !== 'visible') { - // establish a new viewport - context.pdf.rect(x, y, width, height).clip().discardPath(); - } - return [4 /*yield*/, _super.prototype.render.call(this, context.clone({ - transform: context.pdf.unitMatrix, - viewport: context.withinUse ? context.viewport : new Viewport(width, height) - }))]; - case 1: - _a.sent(); - context.pdf.restoreGraphicsState(); - return [2 /*return*/]; - } - }); - }); - }; - Svg.prototype.computeNodeTransform = function (context) { - return this.computeNodeTransformCore(context); - }; - Svg.prototype.computeNodeTransformCore = function (context) { - if (context.withinUse) { - return context.pdf.unitMatrix; - } - var x = this.getX(context); - var y = this.getY(context); - var viewBox = this.getViewBox(); - var nodeTransform; - if (viewBox) { - var width = this.getWidth(context); - var height = this.getHeight(context); - nodeTransform = computeViewBoxTransform(this.element, viewBox, x, y, width, height, context); - } - else { - nodeTransform = context.pdf.Matrix(1, 0, 0, 1, x, y); - } - return nodeTransform; - }; - Svg.prototype.getWidth = function (context) { - if (this.width !== undefined) { - return this.width; - } - var width; - var parameters = context.svg2pdfParameters; - if (this.isOutermostSvg(context)) { - // special treatment for the outermost SVG element - if (parameters.width != null) { - // if there is a user defined width, use it - width = parameters.width; - } - else { - // otherwise check if the SVG element defines the width itself - var widthAttr = getAttribute(this.element, context.styleSheets, 'width'); - if (widthAttr) { - width = parseFloat(widthAttr); - } - else { - // if not, check if we can figure out the aspect ratio from the viewBox attribute - var viewBox = this.getViewBox(); - if (viewBox && - (parameters.height != null || getAttribute(this.element, context.styleSheets, 'height'))) { - // if there is a viewBox and the height is defined, use the width that matches the height together with the aspect ratio - var aspectRatio = viewBox[2] / viewBox[3]; - width = this.getHeight(context) * aspectRatio; - } - else { - // if there is no viewBox use a default of 300 or the largest size that fits into the outer viewport - // at an aspect ratio of 2:1 - width = Math.min(300, context.viewport.width, context.viewport.height * 2); - } - } - } - } - else { - var widthAttr = getAttribute(this.element, context.styleSheets, 'width'); - width = widthAttr ? parseFloat(widthAttr) : context.viewport.width; - } - return (this.width = width); - }; - Svg.prototype.getHeight = function (context) { - if (this.height !== undefined) { - return this.height; - } - var height; - var parameters = context.svg2pdfParameters; - if (this.isOutermostSvg(context)) { - // special treatment for the outermost SVG element - if (parameters.height != null) { - // if there is a user defined height, use it - height = parameters.height; - } - else { - // otherwise check if the SVG element defines the height itself - var heightAttr = getAttribute(this.element, context.styleSheets, 'height'); - if (heightAttr) { - height = parseFloat(heightAttr); - } - else { - // if not, check if we can figure out the aspect ratio from the viewBox attribute - var viewBox = this.getViewBox(); - if (viewBox) { - // if there is a viewBox, use the height that matches the width together with the aspect ratio - var aspectRatio = viewBox[2] / viewBox[3]; - height = this.getWidth(context) / aspectRatio; - } - else { - // if there is no viewBox use a default of 150 or the largest size that fits into the outer viewport - // at an aspect ratio of 2:1 - height = Math.min(150, context.viewport.width / 2, context.viewport.height); - } - } - } - } - else { - var heightAttr = getAttribute(this.element, context.styleSheets, 'height'); - height = heightAttr ? parseFloat(heightAttr) : context.viewport.height; - } - return (this.height = height); - }; - Svg.prototype.getX = function (context) { - if (this.x !== undefined) { - return this.x; - } - if (this.isOutermostSvg(context)) { - return (this.x = 0); - } - var xAttr = getAttribute(this.element, context.styleSheets, 'x'); - return (this.x = xAttr ? parseFloat(xAttr) : 0); - }; - Svg.prototype.getY = function (context) { - if (this.y !== undefined) { - return this.y; - } - if (this.isOutermostSvg(context)) { - return (this.y = 0); - } - var yAttr = getAttribute(this.element, context.styleSheets, 'y'); - return (this.y = yAttr ? parseFloat(yAttr) : 0); - }; - Svg.prototype.getViewBox = function () { - if (this.viewBox !== undefined) { - return this.viewBox; - } - var viewBox = this.element.getAttribute('viewBox'); - return (this.viewBox = viewBox ? parseFloats(viewBox) : undefined); - }; - Svg.prototype.isOutermostSvg = function (context) { - return context.svg2pdfParameters.element === this.element; - }; - return Svg; -}(ContainerNode)); - -var Group = /** @class */ (function (_super) { - __extends(Group, _super); - function Group() { - return _super !== null && _super.apply(this, arguments) || this; - } - Group.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - Group.prototype.computeNodeTransformCore = function (context) { - return context.pdf.unitMatrix; - }; - return Group; -}(ContainerNode)); - -var Anchor = /** @class */ (function (_super) { - __extends(Anchor, _super); - function Anchor() { - return _super !== null && _super.apply(this, arguments) || this; - } - Anchor.prototype.renderCore = function (context) { - return __awaiter(this, void 0, void 0, function () { - var href, box, scale, ph; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, _super.prototype.renderCore.call(this, context)]; - case 1: - _a.sent(); - href = getAttribute(this.element, context.styleSheets, 'href'); - if (href) { - box = this.getBoundingBox(context); - scale = context.pdf.internal.scaleFactor; - ph = context.pdf.internal.pageSize.getHeight(); - context.pdf.link(scale * (box[0] * context.transform.sx + context.transform.tx), scale * (ph - box[1] * context.transform.sy - context.transform.ty), scale * context.transform.sx * box[2], scale * context.transform.sy * box[3], { url: href }); - } - return [2 /*return*/]; - } - }); - }); - }; - return Anchor; -}(Group)); - -var ClipPath = /** @class */ (function (_super) { - __extends(ClipPath, _super); - function ClipPath() { - return _super !== null && _super.apply(this, arguments) || this; - } - ClipPath.prototype.apply = function (context) { - return __awaiter(this, void 0, void 0, function () { - var clipPathMatrix, _i, _a, child, hasClipRuleFromFirstChild, clipRule; - return __generator(this, function (_b) { - switch (_b.label) { - case 0: - if (!this.isVisible(true, context)) { - return [2 /*return*/]; - } - clipPathMatrix = context.pdf.matrixMult(this.computeNodeTransform(context), context.transform); - context.pdf.setCurrentTransformationMatrix(clipPathMatrix); - _i = 0, _a = this.children; - _b.label = 1; - case 1: - if (!(_i < _a.length)) return [3 /*break*/, 4]; - child = _a[_i]; - return [4 /*yield*/, child.render(new Context(context.pdf, { - refsHandler: context.refsHandler, - styleSheets: context.styleSheets, - viewport: context.viewport, - withinClipPath: true, - svg2pdfParameters: context.svg2pdfParameters, - textMeasure: context.textMeasure - }))]; - case 2: - _b.sent(); - _b.label = 3; - case 3: - _i++; - return [3 /*break*/, 1]; - case 4: - hasClipRuleFromFirstChild = this.children.length > 0 && - !!getAttribute(this.children[0].element, context.styleSheets, 'clip-rule'); - clipRule = hasClipRuleFromFirstChild - ? this.getClipRuleAttr(this.children[0].element, context.styleSheets) - : this.getClipRuleAttr(this.element, context.styleSheets); - context.pdf.clip(clipRule).discardPath(); - // as we cannot use restoreGraphicsState() to reset the transform (this would reset the clipping path, as well), - // we must append the inverse instead - context.pdf.setCurrentTransformationMatrix(clipPathMatrix.inversed()); - return [2 /*return*/]; - } - }); - }); - }; - ClipPath.prototype.getBoundingBoxCore = function (context) { - return getBoundingBoxByChildren(context, this); - }; - ClipPath.prototype.isVisible = function (parentVisible, context) { - return svgNodeAndChildrenVisible(this, parentVisible, context); - }; - ClipPath.prototype.getClipRuleAttr = function (element, styleSheets) { - return getAttribute(element, styleSheets, 'clip-rule') === 'evenodd' ? 'evenodd' : undefined; - }; - return ClipPath; -}(NonRenderedNode)); - -function parse(node, context, idMap) { - var svgnode; - var children = []; - forEachChild(node, function (i, n) { return children.push(parse(n, context, idMap)); }); - switch (node.tagName.toLowerCase()) { - case 'a': - svgnode = new Anchor(node, children); - break; - case 'g': - svgnode = new Group(node, children); - break; - case 'circle': - svgnode = new Circle(node, children); - break; - case 'clippath': - svgnode = new ClipPath(node, children); - break; - case 'ellipse': - svgnode = new Ellipse(node, children); - break; - case 'lineargradient': - svgnode = new LinearGradient(node, children); - break; - case 'image': - svgnode = new ImageNode(node, children, context); - break; - case 'line': - svgnode = new Line(node, children); - break; - case 'marker': - svgnode = new MarkerNode(node, children); - break; - case 'path': - svgnode = new PathNode(node, children); - break; - case 'pattern': - svgnode = new Pattern(node, children); - break; - case 'polygon': - svgnode = new Polygon(node, children); - break; - case 'polyline': - svgnode = new Polyline(node, children); - break; - case 'radialgradient': - svgnode = new RadialGradient(node, children); - break; - case 'rect': - svgnode = new Rect(node, children); - break; - case 'svg': - svgnode = new Svg(node, children); - break; - case 'symbol': - svgnode = new Symbol$1(node, children); - break; - case 'text': - svgnode = new TextNode(node, children); - break; - case 'use': - svgnode = new Use(node, children); - break; - default: - svgnode = new VoidNode(node, children); - break; - } - if (idMap != undefined && svgnode.element.hasAttribute('id')) { - // const id = cssesc(svgnode.element.id, { isIdentifier: true }) - var id = svgnode.element.id; // jsroot has plain ids - idMap[id] = idMap[id] || svgnode; - } - svgnode.children.forEach(function (c) { return c.setParent(svgnode); }); - return svgnode; -} - -var StyleSheets = /** @class */ (function () { - function StyleSheets(rootSvg, loadExtSheets) { - this.rootSvg = rootSvg; - this.loadExternalSheets = loadExtSheets; - this.styleSheets = []; - } - StyleSheets.prototype.load = function () { - return __awaiter(this, void 0, void 0, function () { - var sheetTexts; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, this.collectStyleSheetTexts()]; - case 1: - sheetTexts = _a.sent(); - this.parseCssSheets(sheetTexts); - return [2 /*return*/]; - } - }); - }); - }; - StyleSheets.prototype.collectStyleSheetTexts = function () { - return __awaiter(this, void 0, void 0, function () { - var sheetTexts, i, node, styleElements, i, styleElement; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - sheetTexts = []; - if (this.loadExternalSheets && this.rootSvg.ownerDocument) { - for (i = 0; i < this.rootSvg.ownerDocument.childNodes.length; i++) { - node = this.rootSvg.ownerDocument.childNodes[i]; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (node.nodeName === 'xml-stylesheet' && typeof node.data === 'string') { - sheetTexts.push(StyleSheets.loadSheet( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - node.data - .match(/href=["'].*?["']/)[0] - .split('=')[1] - .slice(1, -1))); - } - } - } - styleElements = this.rootSvg.querySelectorAll('style,link'); - for (i = 0; i < styleElements.length; i++) { - styleElement = styleElements[i]; - if (nodeIs(styleElement, 'style')) { - sheetTexts.push(styleElement.textContent); - } - else if (this.loadExternalSheets && - nodeIs(styleElement, 'link') && - styleElement.getAttribute('rel') === 'stylesheet' && - styleElement.hasAttribute('href')) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - sheetTexts.push(StyleSheets.loadSheet(styleElement.getAttribute('href'))); - } - } - return [4 /*yield*/, Promise.all(sheetTexts)]; - case 1: return [2 /*return*/, (_a.sent()).filter(function (sheet) { return sheet !== null; })]; - } - }); - }); - }; - StyleSheets.prototype.parseCssSheets = function (sheetTexts) { - var styleDoc = document.implementation.createHTMLDocument(''); - for (var _i = 0, sheetTexts_1 = sheetTexts; _i < sheetTexts_1.length; _i++) { - var sheetText = sheetTexts_1[_i]; - var style = styleDoc.createElement('style'); - style.textContent = sheetText; - styleDoc.body.appendChild(style); - var sheet = style.sheet; - if (sheet instanceof CSSStyleSheet) { - for (var i = sheet.cssRules.length - 1; i >= 0; i--) { - var cssRule = sheet.cssRules[i]; - if (!(cssRule instanceof CSSStyleRule)) { - sheet.deleteRule(i); - continue; - } - var cssStyleRule = cssRule; - if (cssStyleRule.selectorText.indexOf(',') >= 0) { - sheet.deleteRule(i); - var body = cssStyleRule.cssText.substring(cssStyleRule.selectorText.length); - var selectors = StyleSheets.splitSelectorAtCommas(cssStyleRule.selectorText); - for (var j = 0; j < selectors.length; j++) { - sheet.insertRule(selectors[j] + body, i + j); - } - } - } - this.styleSheets.push(sheet); - } - } - }; - StyleSheets.splitSelectorAtCommas = function (selectorText) { - if (selectorText.trim().length === 0) { - return []; - } - var initialRegex = /,|["']/g; - var closingDoubleQuotesRegex = /\\[\s\S]|"/g; - var closingSingleQuotesRegex = /\\[\s\S]|'/g; - var parts = []; - var state = 'initial'; - var match; - var lastCommaIndex = -1; - var closingQuotesRegex = closingDoubleQuotesRegex; - for (var i = 0; i < selectorText.length;) { - switch (state) { - case 'initial': - initialRegex.lastIndex = i; - match = initialRegex.exec(selectorText); - if (match) { - if (match[0] === ',') { - parts.push(selectorText.substring(lastCommaIndex + 1, initialRegex.lastIndex - 1).trim()); - lastCommaIndex = initialRegex.lastIndex - 1; - } - else { - state = 'withinQuotes'; - closingQuotesRegex = - match[0] === '"' ? closingDoubleQuotesRegex : closingSingleQuotesRegex; - } - i = initialRegex.lastIndex; - } - else { - parts.push(selectorText.substring(lastCommaIndex + 1).trim()); - i = selectorText.length; - } - break; - case 'withinQuotes': - closingQuotesRegex.lastIndex = i; - match = closingQuotesRegex.exec(selectorText); - if (!match) { - i = selectorText.length; - } - else { - i = closingQuotesRegex.lastIndex; - if (match[0].length === 1) { - state = 'initial'; - } - } - break; - } - } - return parts; - }; - StyleSheets.loadSheet = function (url) { - return (new Promise(function (resolve, reject) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = 'text'; - xhr.onload = function () { - if (xhr.status !== 200) { - reject(new Error("Error ".concat(xhr.status, ": Failed to load '").concat(url, "'"))); - } - resolve(xhr.responseText); - }; - xhr.onerror = reject; - xhr.onabort = reject; - xhr.send(null); - }) - // ignore the error since some stylesheets may not be accessible - // due to CORS policies - .catch(function () { return null; })); - }; - StyleSheets.prototype.getPropertyValue = function (node, propertyCss) { - var matchingRules = []; - for (var _i = 0, _a = this.styleSheets; _i < _a.length; _i++) { - var sheet = _a[_i]; - for (var i = 0; i < sheet.cssRules.length; i++) { - var rule = sheet.cssRules[i]; - if (rule.style.getPropertyValue(propertyCss) && node.matches(rule.selectorText)) { - matchingRules.push(rule); - } - } - } - if (matchingRules.length === 0) { - return undefined; - } - var compare = function (a, b) { - var priorityA = a.style.getPropertyPriority(propertyCss); - var priorityB = b.style.getPropertyPriority(propertyCss); - if (priorityA !== priorityB) { - return priorityA === 'important' ? 1 : -1; - } - // console.log('removed specificity check ', a.selectorText, b.selectorText); - return 0; - // return compareSpecificity(a.selectorText, b.selectorText) - }; - var mostSpecificRule = matchingRules.reduce(function (previousValue, currentValue) { - return compare(previousValue, currentValue) === 1 ? previousValue : currentValue; - }); - return mostSpecificRule.style.getPropertyValue(propertyCss) || undefined; - }; - return StyleSheets; -}()); - -var TextMeasure = /** @class */ (function () { - function TextMeasure() { - this.measureMethods = {}; - } - TextMeasure.prototype.getTextOffset = function (text, attributeState) { - var textAnchor = attributeState.textAnchor; - if (textAnchor === 'start') { - return 0; - } - var width = this.measureTextWidth(text, attributeState); - var xOffset = 0; - switch (textAnchor) { - case 'end': - xOffset = width; - break; - case 'middle': - xOffset = width / 2; - break; - } - return xOffset; - }; - TextMeasure.prototype.measureTextWidth = function (text, attributeState) { - if (text.length === 0) { - return 0; - } - var fontFamily = attributeState.fontFamily; - var measure = this.getMeasureFunction(fontFamily); - return measure.call(this, text, attributeState.fontFamily, attributeState.fontSize + 'px', attributeState.fontStyle, attributeState.fontWeight); - }; - TextMeasure.prototype.getMeasurementTextNode = function () { - if (!this.textMeasuringTextElement) { - this.textMeasuringTextElement = document.createElementNS(svgNamespaceURI, 'text'); - var svg = document.createElementNS(svgNamespaceURI, 'svg'); - svg.appendChild(this.textMeasuringTextElement); - svg.style.setProperty('position', 'absolute'); - svg.style.setProperty('visibility', 'hidden'); - document.body.appendChild(svg); - } - return this.textMeasuringTextElement; - }; - TextMeasure.prototype.canvasTextMeasure = function (text, fontFamily, fontSize, fontStyle, fontWeight) { - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - if (context != null) { - context.font = [fontStyle, fontWeight, fontSize, fontFamily].join(' '); - return context.measureText(text).width; - } - return 0; - }; - TextMeasure.prototype.svgTextMeasure = function (text, fontFamily, fontSize, fontStyle, fontWeight, measurementTextNode) { - if (measurementTextNode === void 0) { measurementTextNode = this.getMeasurementTextNode(); } - var textNode = measurementTextNode; - textNode.setAttribute('font-family', fontFamily); - textNode.setAttribute('font-size', fontSize); - textNode.setAttribute('font-style', fontStyle); - textNode.setAttribute('font-weight', fontWeight); - textNode.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); - textNode.textContent = text; - return textNode.getBBox().width; - }; - /** - * Canvas text measuring is a lot faster than svg measuring. However, it is inaccurate for some fonts. So test each - * font once and decide if canvas is accurate enough. - */ - TextMeasure.prototype.getMeasureFunction = function (fontFamily) { - var method = this.measureMethods[fontFamily]; - if (!method) { - var fontSize = '16px'; - var fontStyle = 'normal'; - var fontWeight = 'normal'; - var canvasWidth = this.canvasTextMeasure(TextMeasure.testString, fontFamily, fontSize, fontStyle, fontWeight); - var svgWidth = this.svgTextMeasure(TextMeasure.testString, fontFamily, fontSize, fontStyle, fontWeight); - method = - Math.abs(canvasWidth - svgWidth) < TextMeasure.epsilon - ? this.canvasTextMeasure - : this.svgTextMeasure; - this.measureMethods[fontFamily] = method; - } - return method; - }; - TextMeasure.prototype.cleanupTextMeasuring = function () { - if (this.textMeasuringTextElement) { - var parentNode = this.textMeasuringTextElement.parentNode; - if (parentNode) { - document.body.removeChild(parentNode); - } - this.textMeasuringTextElement = undefined; - } - }; - TextMeasure.testString = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789!"$%&/()=?\'\\+*-_.:,;^}][{#~|<>'; - TextMeasure.epsilon = 0.1; - return TextMeasure; -}()); - -function svg2pdf(element_1, pdf_1) { - return __awaiter(this, arguments, void 0, function (element, pdf, options) { - var x, y, extCss, idMap, refsHandler, styleSheets, viewport, svg2pdfParameters, textMeasure, context, fill, node; - var _a, _b, _c; - if (options === void 0) { options = {}; } - return __generator(this, function (_d) { - switch (_d.label) { - case 0: - x = (_a = options.x) !== null && _a !== void 0 ? _a : 0.0; - y = (_b = options.y) !== null && _b !== void 0 ? _b : 0.0; - extCss = (_c = options.loadExternalStyleSheets) !== null && _c !== void 0 ? _c : false; - idMap = {}; - refsHandler = new ReferencesHandler(idMap); - styleSheets = new StyleSheets(element, extCss); - return [4 /*yield*/, styleSheets.load() - // start with the entire page size as viewport - ]; - case 1: - _d.sent(); - viewport = new Viewport(pdf.internal.pageSize.getWidth(), pdf.internal.pageSize.getHeight()); - svg2pdfParameters = __assign(__assign({}, options), { element: element }); - textMeasure = new TextMeasure(); - context = new Context(pdf, { - refsHandler: refsHandler, - styleSheets: styleSheets, - viewport: viewport, - svg2pdfParameters: svg2pdfParameters, - textMeasure: textMeasure - }); - pdf.advancedAPI(); - pdf.saveGraphicsState(); - // set offsets - pdf.setCurrentTransformationMatrix(pdf.Matrix(1, 0, 0, 1, x, y)); - // set default values that differ from pdf defaults - pdf.setLineWidth(context.attributeState.strokeWidth); - fill = context.attributeState.fill.color; - pdf.setFillColor(fill.r, fill.g, fill.b); - pdf.setFont(context.attributeState.fontFamily); - // correct for a jsPDF-instance measurement unit that differs from `pt` - pdf.setFontSize(context.attributeState.fontSize * pdf.internal.scaleFactor); - node = parse(element, context, idMap); - return [4 /*yield*/, node.render(context)]; - case 2: - _d.sent(); - pdf.restoreGraphicsState(); - pdf.compatAPI(); - context.textMeasure.cleanupTextMeasuring(); - return [2 /*return*/, pdf]; - } - }); - }); -} -jsPDF.API.svg = function (element, options) { - if (options === void 0) { options = {}; } - return svg2pdf(element, this, options); -}; - -export { svg2pdf }; diff --git a/js/modules/base/zstd.mjs b/js/modules/base/zstd.mjs deleted file mode 100644 index eeb16809b3e82..0000000000000 --- a/js/modules/base/zstd.mjs +++ /dev/null @@ -1,17 +0,0 @@ -var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function t(e){var t={exports:{}};return e(t,t.exports),t.exports}var r=function(e){return e&&e.Math==Math&&e},A=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof e&&e)||function(){return this}()||Function("return this")(),n=function(e){try{return!!e()}catch(e){return!0}},i=!n((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),a={}.propertyIsEnumerable,o=Object.getOwnPropertyDescriptor,c={f:o&&!a.call({1:2},1)?function(e){var t=o(this,e);return!!t&&t.enumerable}:a},f=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}},s={}.toString,u=function(e){return s.call(e).slice(8,-1)},b="".split,l=n((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==u(e)?b.call(e,""):Object(e)}:Object,d=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e},k=function(e){return l(d(e))},E=function(e){return"object"==typeof e?null!==e:"function"==typeof e},p=function(e,t){if(!E(e))return e;var r,A;if(t&&"function"==typeof(r=e.toString)&&!E(A=r.call(e)))return A;if("function"==typeof(r=e.valueOf)&&!E(A=r.call(e)))return A;if(!t&&"function"==typeof(r=e.toString)&&!E(A=r.call(e)))return A;throw TypeError("Can't convert object to primitive value")},T={}.hasOwnProperty,g=function(e,t){return T.call(e,t)},h=A.document,O=E(h)&&E(h.createElement),_=function(e){return O?h.createElement(e):{}},w=!i&&!n((function(){return 7!=Object.defineProperty(_("div"),"a",{get:function(){return 7}}).a})),y=Object.getOwnPropertyDescriptor,R={f:i?y:function(e,t){if(e=k(e),t=p(t,!0),w)try{return y(e,t)}catch(e){}if(g(e,t))return f(!c.f.call(e,t),e[t])}},D=function(e){if(!E(e))throw TypeError(String(e)+" is not an object");return e},m=Object.defineProperty,v={f:i?m:function(e,t,r){if(D(e),t=p(t,!0),D(r),w)try{return m(e,t,r)}catch(e){}if("get"in r||"set"in r)throw TypeError("Accessors not supported");return"value"in r&&(e[t]=r.value),e}},M=i?function(e,t,r){return v.f(e,t,f(1,r))}:function(e,t,r){return e[t]=r,e},F=function(e,t){try{M(A,e,t)}catch(r){A[e]=t}return t},S=A["__core-js_shared__"]||F("__core-js_shared__",{}),P=Function.toString;"function"!=typeof S.inspectSource&&(S.inspectSource=function(e){return P.call(e)});var B,X,U,I=S.inspectSource,x=A.WeakMap,j="function"==typeof x&&/native code/.test(I(x)),Q=t((function(e){(e.exports=function(e,t){return S[e]||(S[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.9.0",mode:"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"})})),C=0,Z=Math.random(),G=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++C+Z).toString(36)},N=Q("keys"),H=function(e){return N[e]||(N[e]=G(e))},W={},Y=A.WeakMap;if(j){var V=S.state||(S.state=new Y),L=V.get,z=V.has,J=V.set;B=function(e,t){return t.facade=e,J.call(V,e,t),t},X=function(e){return L.call(V,e)||{}},U=function(e){return z.call(V,e)}}else{var K=H("state");W[K]=!0,B=function(e,t){return t.facade=e,M(e,K,t),t},X=function(e){return g(e,K)?e[K]:{}},U=function(e){return g(e,K)}}var q={set:B,get:X,has:U,enforce:function(e){return U(e)?X(e):B(e,{})},getterFor:function(e){return function(t){var r;if(!E(t)||(r=X(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return r}}},$=t((function(e){var t=q.get,r=q.enforce,n=String(String).split("String");(e.exports=function(e,t,i,a){var o,c=!!a&&!!a.unsafe,f=!!a&&!!a.enumerable,s=!!a&&!!a.noTargetGet;"function"==typeof i&&("string"!=typeof t||g(i,"name")||M(i,"name",t),(o=r(i)).source||(o.source=n.join("string"==typeof t?t:""))),e!==A?(c?!s&&e[t]&&(f=!0):delete e[t],f?e[t]=i:M(e,t,i)):f?e[t]=i:F(t,i)})(Function.prototype,"toString",(function(){return"function"==typeof this&&t(this).source||I(this)}))})),ee=A,te=function(e){return"function"==typeof e?e:void 0},re=function(e,t){return arguments.length<2?te(ee[e])||te(A[e]):ee[e]&&ee[e][t]||A[e]&&A[e][t]},Ae=Math.ceil,ne=Math.floor,ie=function(e){return isNaN(e=+e)?0:(e>0?ne:Ae)(e)},ae=Math.min,oe=function(e){return e>0?ae(ie(e),9007199254740991):0},ce=Math.max,fe=Math.min,se=function(e,t){var r=ie(e);return r<0?ce(r+t,0):fe(r,t)},ue=function(e){return function(t,r,A){var n,i=k(t),a=oe(i.length),o=se(A,a);if(e&&r!=r){for(;a>o;)if((n=i[o++])!=n)return!0}else for(;a>o;o++)if((e||o in i)&&i[o]===r)return e||o||0;return!e&&-1}},be={includes:ue(!0),indexOf:ue(!1)}.indexOf,le=function(e,t){var r,A=k(e),n=0,i=[];for(r in A)!g(W,r)&&g(A,r)&&i.push(r);for(;t.length>n;)g(A,r=t[n++])&&(~be(i,r)||i.push(r));return i},de=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],ke=de.concat("length","prototype"),Ee={f:Object.getOwnPropertyNames||function(e){return le(e,ke)}},pe={f:Object.getOwnPropertySymbols},Te=re("Reflect","ownKeys")||function(e){var t=Ee.f(D(e)),r=pe.f;return r?t.concat(r(e)):t},ge=function(e,t){for(var r=Te(t),A=v.f,n=R.f,i=0;i<r.length;i++){var a=r[i];g(e,a)||A(e,a,n(t,a))}},he=/#|\.prototype\./,Oe=function(e,t){var r=we[_e(e)];return r==Re||r!=ye&&("function"==typeof t?n(t):!!t)},_e=Oe.normalize=function(e){return String(e).replace(he,".").toLowerCase()},we=Oe.data={},ye=Oe.NATIVE="N",Re=Oe.POLYFILL="P",De=Oe,me=R.f,ve=function(e,t){var r,n,i,a,o,c=e.target,f=e.global,s=e.stat;if(r=f?A:s?A[c]||F(c,{}):(A[c]||{}).prototype)for(n in t){if(a=t[n],i=e.noTargetGet?(o=me(r,n))&&o.value:r[n],!De(f?n:c+(s?".":"#")+n,e.forced)&&void 0!==i){if(typeof a==typeof i)continue;ge(a,i)}(e.sham||i&&i.sham)&&M(a,"sham",!0),$(r,n,a,e)}},Me=Object.keys||function(e){return le(e,de)},Fe=function(e){return Object(d(e))},Se=Object.assign,Pe=Object.defineProperty,Be=!Se||n((function(){if(i&&1!==Se({b:1},Se(Pe({},"a",{enumerable:!0,get:function(){Pe(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},r=Symbol(),A="abcdefghijklmnopqrst";return e[r]=7,A.split("").forEach((function(e){t[e]=e})),7!=Se({},e)[r]||Me(Se({},t)).join("")!=A}))?function(e,t){for(var r=Fe(e),A=arguments.length,n=1,a=pe.f,o=c.f;A>n;)for(var f,s=l(arguments[n++]),u=a?Me(s).concat(a(s)):Me(s),b=u.length,d=0;b>d;)f=u[d++],i&&!o.call(s,f)||(r[f]=s[f]);return r}:Se;ve({target:"Object",stat:!0,forced:Object.assign!==Be},{assign:Be}),ee.Object.assign;var Xe="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof DataView,Ue=!!Object.getOwnPropertySymbols&&!n((function(){return!String(Symbol())})),Ie=Ue&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,xe=Q("wks"),je=A.Symbol,Qe=Ie?je:je&&je.withoutSetter||G,Ce=function(e){return g(xe,e)||(Ue&&g(je,e)?xe[e]=je[e]:xe[e]=Qe("Symbol."+e)),xe[e]},Ze={};Ze[Ce("toStringTag")]="z";var Ge,Ne="[object z]"===String(Ze),He=Ce("toStringTag"),We="Arguments"==u(function(){return arguments}()),Ye=Ne?u:function(e){var t,r,A;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),He))?r:We?u(t):"Object"==(A=u(t))&&"function"==typeof t.callee?"Arguments":A},Ve=!n((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype})),Le=H("IE_PROTO"),ze=Object.prototype,Je=Ve?Object.getPrototypeOf:function(e){return e=Fe(e),g(e,Le)?e[Le]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?ze:null},Ke=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,r={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(r,[]),t=r instanceof Array}catch(e){}return function(r,A){return D(r),function(e){if(!E(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype")}(A),t?e.call(r,A):r.__proto__=A,r}}():void 0),qe=v.f,$e=A.Int8Array,et=$e&&$e.prototype,tt=A.Uint8ClampedArray,rt=tt&&tt.prototype,At=$e&&Je($e),nt=et&&Je(et),it=Object.prototype,at=(it.isPrototypeOf,Ce("toStringTag")),ot=G("TYPED_ARRAY_TAG"),ct=Xe&&!!Ke&&"Opera"!==Ye(A.opera),ft={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},st={BigInt64Array:8,BigUint64Array:8},ut=function(e){if(!E(e))return!1;var t=Ye(e);return g(ft,t)||g(st,t)};for(Ge in ft)A[Ge]||(ct=!1);if((!ct||"function"!=typeof At||At===Function.prototype)&&(At=function(){throw TypeError("Incorrect invocation")},ct))for(Ge in ft)A[Ge]&&Ke(A[Ge],At);if((!ct||!nt||nt===it)&&(nt=At.prototype,ct))for(Ge in ft)A[Ge]&&Ke(A[Ge].prototype,nt);if(ct&&Je(rt)!==nt&&Ke(rt,nt),i&&!g(nt,at))for(Ge in!0,qe(nt,at,{get:function(){return E(this)?this[ot]:void 0}}),ft)A[Ge]&&M(A[Ge],ot,Ge);var bt=function(e){if(ut(e))return e;throw TypeError("Target is not a typed array")},lt=function(e,t,r){if(i){if(r)for(var n in ft){var a=A[n];a&&g(a.prototype,e)&&delete a.prototype[e]}nt[e]&&!r||$(nt,e,r?t:ct&&et[e]||t)}},dt=Math.min,kt=[].copyWithin||function(e,t){var r=Fe(this),A=oe(r.length),n=se(e,A),i=se(t,A),a=arguments.length>2?arguments[2]:void 0,o=dt((void 0===a?A:se(a,A))-i,A-n),c=1;for(i<n&&n<i+o&&(c=-1,i+=o-1,n+=o-1);o-- >0;)i in r?r[n]=r[i]:delete r[n],n+=c,i+=c;return r},Et=bt;lt("copyWithin",(function(e,t){return kt.call(Et(this),e,t,arguments.length>2?arguments[2]:void 0)}));var pt,Tt=i?Object.defineProperties:function(e,t){D(e);for(var r,A=Me(t),n=A.length,i=0;n>i;)v.f(e,r=A[i++],t[r]);return e},gt=re("document","documentElement"),ht=H("IE_PROTO"),Ot=function(){},_t=function(e){return"<script>"+e+"<\/script>"},wt=function(){try{pt=document.domain&&new ActiveXObject("htmlfile")}catch(e){}var e,t;wt=pt?function(e){e.write(_t("")),e.close();var t=e.parentWindow.Object;return e=null,t}(pt):((t=_("iframe")).style.display="none",gt.appendChild(t),t.src=String("javascript:"),(e=t.contentWindow.document).open(),e.write(_t("document.F=Object")),e.close(),e.F);for(var r=de.length;r--;)delete wt.prototype[de[r]];return wt()};W[ht]=!0;var yt=Object.create||function(e,t){var r;return null!==e?(Ot.prototype=D(e),r=new Ot,Ot.prototype=null,r[ht]=e):r=wt(),void 0===t?r:Tt(r,t)},Rt={},Dt=Ce("iterator"),mt=Array.prototype,vt=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e},Mt=function(e,t,r){if(vt(e),void 0===t)return e;switch(r){case 0:return function(){return e.call(t)};case 1:return function(r){return e.call(t,r)};case 2:return function(r,A){return e.call(t,r,A)};case 3:return function(r,A,n){return e.call(t,r,A,n)}}return function(){return e.apply(t,arguments)}},Ft=Ce("iterator"),St=function(e){var t=e.return;if(void 0!==t)return D(t.call(e)).value},Pt=function(e,t){this.stopped=e,this.result=t},Bt=function(e,t,r){var A,n,i,a,o,c,f,s,u=r&&r.that,b=!(!r||!r.AS_ENTRIES),l=!(!r||!r.IS_ITERATOR),d=!(!r||!r.INTERRUPTED),k=Mt(t,u,1+b+d),E=function(e){return A&&St(A),new Pt(!0,e)},p=function(e){return b?(D(e),d?k(e[0],e[1],E):k(e[0],e[1])):d?k(e,E):k(e)};if(l)A=e;else{if("function"!=typeof(n=function(e){if(null!=e)return e[Ft]||e["@@iterator"]||Rt[Ye(e)]}(e)))throw TypeError("Target is not iterable");if(void 0!==(s=n)&&(Rt.Array===s||mt[Dt]===s)){for(i=0,a=oe(e.length);a>i;i++)if((o=p(e[i]))&&o instanceof Pt)return o;return new Pt(!1)}A=n.call(e)}for(c=A.next;!(f=c.call(A)).done;){try{o=p(f.value)}catch(e){throw St(A),e}if("object"==typeof o&&o&&o instanceof Pt)return o}return new Pt(!1)},Xt=function(e,t){var r=this;if(!(r instanceof Xt))return new Xt(e,t);Ke&&(r=Ke(new Error(void 0),Je(r))),void 0!==t&&M(r,"message",String(t));var A=[];return Bt(e,A.push,{that:A}),M(r,"errors",A),r};Xt.prototype=yt(Error.prototype,{constructor:f(5,Xt),message:f(5,""),name:f(5,"AggregateError")}),ve({global:!0},{AggregateError:Xt});var Ut=A.Promise,It=v.f,xt=Ce("toStringTag"),jt=function(e,t,r){e&&!g(e=r?e:e.prototype,xt)&&It(e,xt,{configurable:!0,value:t})},Qt=Ce("species"),Ct=Ce("iterator"),Zt=!1;try{var Gt=0,Nt={next:function(){return{done:!!Gt++}},return:function(){Zt=!0}};Nt[Ct]=function(){return this},Array.from(Nt,(function(){throw 2}))}catch(e){}var Ht,Wt,Yt,Vt=Ce("species"),Lt=function(e,t){var r,A=D(e).constructor;return void 0===A||null==(r=D(A)[Vt])?t:vt(r)},zt=re("navigator","userAgent")||"",Jt=/(iphone|ipod|ipad).*applewebkit/i.test(zt),Kt="process"==u(A.process),qt=A.location,$t=A.setImmediate,er=A.clearImmediate,tr=A.process,rr=A.MessageChannel,Ar=A.Dispatch,nr=0,ir={},ar=function(e){if(ir.hasOwnProperty(e)){var t=ir[e];delete ir[e],t()}},or=function(e){return function(){ar(e)}},cr=function(e){ar(e.data)},fr=function(e){A.postMessage(e+"",qt.protocol+"//"+qt.host)};$t&&er||($t=function(e){for(var t=[],r=1;arguments.length>r;)t.push(arguments[r++]);return ir[++nr]=function(){("function"==typeof e?e:Function(e)).apply(void 0,t)},Ht(nr),nr},er=function(e){delete ir[e]},Kt?Ht=function(e){tr.nextTick(or(e))}:Ar&&Ar.now?Ht=function(e){Ar.now(or(e))}:rr&&!Jt?(Yt=(Wt=new rr).port2,Wt.port1.onmessage=cr,Ht=Mt(Yt.postMessage,Yt,1)):A.addEventListener&&"function"==typeof postMessage&&!A.importScripts&&qt&&"file:"!==qt.protocol&&!n(fr)?(Ht=fr,A.addEventListener("message",cr,!1)):Ht="onreadystatechange"in _("script")?function(e){gt.appendChild(_("script")).onreadystatechange=function(){gt.removeChild(this),ar(e)}}:function(e){setTimeout(or(e),0)});var sr,ur,br,lr,dr,kr,Er,pr,Tr={set:$t,clear:er},gr=/web0s(?!.*chrome)/i.test(zt),hr=R.f,Or=Tr.set,_r=A.MutationObserver||A.WebKitMutationObserver,wr=A.document,yr=A.process,Rr=A.Promise,Dr=hr(A,"queueMicrotask"),mr=Dr&&Dr.value;mr||(sr=function(){var e,t;for(Kt&&(e=yr.domain)&&e.exit();ur;){t=ur.fn,ur=ur.next;try{t()}catch(e){throw ur?lr():br=void 0,e}}br=void 0,e&&e.enter()},Jt||Kt||gr||!_r||!wr?Rr&&Rr.resolve?(Er=Rr.resolve(void 0),pr=Er.then,lr=function(){pr.call(Er,sr)}):lr=Kt?function(){yr.nextTick(sr)}:function(){Or.call(A,sr)}:(dr=!0,kr=wr.createTextNode(""),new _r(sr).observe(kr,{characterData:!0}),lr=function(){kr.data=dr=!dr}));var vr,Mr,Fr=mr||function(e){var t={fn:e,next:void 0};br&&(br.next=t),ur||(ur=t,lr()),br=t},Sr=function(e){var t,r;this.promise=new e((function(e,A){if(void 0!==t||void 0!==r)throw TypeError("Bad Promise constructor");t=e,r=A})),this.resolve=vt(t),this.reject=vt(r)},Pr={f:function(e){return new Sr(e)}},Br=function(e,t){if(D(e),E(t)&&t.constructor===e)return t;var r=Pr.f(e);return(0,r.resolve)(t),r.promise},Xr=function(e){try{return{error:!1,value:e()}}catch(e){return{error:!0,value:e}}},Ur=A.process,Ir=Ur&&Ur.versions,xr=Ir&&Ir.v8;xr?Mr=(vr=xr.split("."))[0]+vr[1]:zt&&(!(vr=zt.match(/Edge\/(\d+)/))||vr[1]>=74)&&(vr=zt.match(/Chrome\/(\d+)/))&&(Mr=vr[1]);var jr,Qr,Cr,Zr,Gr=Mr&&+Mr,Nr=Tr.set,Hr=Ce("species"),Wr="Promise",Yr=q.get,Vr=q.set,Lr=q.getterFor(Wr),zr=Ut,Jr=A.TypeError,Kr=A.document,qr=A.process,$r=re("fetch"),eA=Pr.f,tA=eA,rA=!!(Kr&&Kr.createEvent&&A.dispatchEvent),AA="function"==typeof PromiseRejectionEvent,nA=De(Wr,(function(){if(!(I(zr)!==String(zr))){if(66===Gr)return!0;if(!Kt&&!AA)return!0}if(Gr>=51&&/native code/.test(zr))return!1;var e=zr.resolve(1),t=function(e){e((function(){}),(function(){}))};return(e.constructor={})[Hr]=t,!(e.then((function(){}))instanceof t)})),iA=nA||!function(e,t){if(!t&&!Zt)return!1;var r=!1;try{var A={};A[Ct]=function(){return{next:function(){return{done:r=!0}}}},e(A)}catch(e){}return r}((function(e){zr.all(e).catch((function(){}))})),aA=function(e){var t;return!(!E(e)||"function"!=typeof(t=e.then))&&t},oA=function(e,t){if(!e.notified){e.notified=!0;var r=e.reactions;Fr((function(){for(var A=e.value,n=1==e.state,i=0;r.length>i;){var a,o,c,f=r[i++],s=n?f.ok:f.fail,u=f.resolve,b=f.reject,l=f.domain;try{s?(n||(2===e.rejection&&uA(e),e.rejection=1),!0===s?a=A:(l&&l.enter(),a=s(A),l&&(l.exit(),c=!0)),a===f.promise?b(Jr("Promise-chain cycle")):(o=aA(a))?o.call(a,u,b):u(a)):b(A)}catch(e){l&&!c&&l.exit(),b(e)}}e.reactions=[],e.notified=!1,t&&!e.rejection&&fA(e)}))}},cA=function(e,t,r){var n,i;rA?((n=Kr.createEvent("Event")).promise=t,n.reason=r,n.initEvent(e,!1,!0),A.dispatchEvent(n)):n={promise:t,reason:r},!AA&&(i=A["on"+e])?i(n):"unhandledrejection"===e&&function(e,t){var r=A.console;r&&r.error&&(1===arguments.length?r.error(e):r.error(e,t))}("Unhandled promise rejection",r)},fA=function(e){Nr.call(A,(function(){var t,r=e.facade,A=e.value;if(sA(e)&&(t=Xr((function(){Kt?qr.emit("unhandledRejection",A,r):cA("unhandledrejection",r,A)})),e.rejection=Kt||sA(e)?2:1,t.error))throw t.value}))},sA=function(e){return 1!==e.rejection&&!e.parent},uA=function(e){Nr.call(A,(function(){var t=e.facade;Kt?qr.emit("rejectionHandled",t):cA("rejectionhandled",t,e.value)}))},bA=function(e,t,r){return function(A){e(t,A,r)}},lA=function(e,t,r){e.done||(e.done=!0,r&&(e=r),e.value=t,e.state=2,oA(e,!0))},dA=function(e,t,r){if(!e.done){e.done=!0,r&&(e=r);try{if(e.facade===t)throw Jr("Promise can't be resolved itself");var A=aA(t);A?Fr((function(){var r={done:!1};try{A.call(t,bA(dA,r,e),bA(lA,r,e))}catch(t){lA(r,t,e)}})):(e.value=t,e.state=1,oA(e,!1))}catch(t){lA({done:!1},t,e)}}};nA&&(zr=function(e){!function(e,t,r){if(!(e instanceof t))throw TypeError("Incorrect "+(r?r+" ":"")+"invocation")}(this,zr,Wr),vt(e),jr.call(this);var t=Yr(this);try{e(bA(dA,t),bA(lA,t))}catch(e){lA(t,e)}},(jr=function(e){Vr(this,{type:Wr,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=function(e,t,r){for(var A in t)$(e,A,t[A],r);return e}(zr.prototype,{then:function(e,t){var r=Lr(this),A=eA(Lt(this,zr));return A.ok="function"!=typeof e||e,A.fail="function"==typeof t&&t,A.domain=Kt?qr.domain:void 0,r.parent=!0,r.reactions.push(A),0!=r.state&&oA(r,!1),A.promise},catch:function(e){return this.then(void 0,e)}}),Qr=function(){var e=new jr,t=Yr(e);this.promise=e,this.resolve=bA(dA,t),this.reject=bA(lA,t)},Pr.f=eA=function(e){return e===zr||e===Cr?new Qr(e):tA(e)},"function"==typeof Ut&&(Zr=Ut.prototype.then,$(Ut.prototype,"then",(function(e,t){var r=this;return new zr((function(e,t){Zr.call(r,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof $r&&ve({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return Br(zr,$r.apply(A,arguments))}}))),ve({global:!0,wrap:!0,forced:nA},{Promise:zr}),jt(zr,Wr,!1),function(e){var t=re(e),r=v.f;i&&t&&!t[Qt]&&r(t,Qt,{configurable:!0,get:function(){return this}})}(Wr),Cr=re(Wr),ve({target:Wr,stat:!0,forced:nA},{reject:function(e){var t=eA(this);return t.reject.call(void 0,e),t.promise}}),ve({target:Wr,stat:!0,forced:nA},{resolve:function(e){return Br(this,e)}}),ve({target:Wr,stat:!0,forced:iA},{all:function(e){var t=this,r=eA(t),A=r.resolve,n=r.reject,i=Xr((function(){var r=vt(t.resolve),i=[],a=0,o=1;Bt(e,(function(e){var c=a++,f=!1;i.push(void 0),o++,r.call(t,e).then((function(e){f||(f=!0,i[c]=e,--o||A(i))}),n)})),--o||A(i)}));return i.error&&n(i.value),r.promise},race:function(e){var t=this,r=eA(t),A=r.reject,n=Xr((function(){var n=vt(t.resolve);Bt(e,(function(e){n.call(t,e).then(r.resolve,A)}))}));return n.error&&A(n.value),r.promise}});ve({target:"Promise",stat:!0},{any:function(e){var t=this,r=Pr.f(t),A=r.resolve,n=r.reject,i=Xr((function(){var r=vt(t.resolve),i=[],a=0,o=1,c=!1;Bt(e,(function(e){var f=a++,s=!1;i.push(void 0),o++,r.call(t,e).then((function(e){s||c||(c=!0,A(e))}),(function(e){s||c||(s=!0,i[f]=e,--o||n(new(re("AggregateError"))(i,"No one promise resolved")))}))})),--o||n(new(re("AggregateError"))(i,"No one promise resolved"))}));return i.error&&n(i.value),r.promise}});var kA,EA,pA,TA=function(e){return function(t,r){var A,n,i=String(d(t)),a=ie(r),o=i.length;return a<0||a>=o?e?"":void 0:(A=i.charCodeAt(a))<55296||A>56319||a+1===o||(n=i.charCodeAt(a+1))<56320||n>57343?e?i.charAt(a):A:e?i.slice(a,a+2):n-56320+(A-55296<<10)+65536}},gA={codeAt:TA(!1),charAt:TA(!0)},hA=Ce("iterator"),OA=!1;[].keys&&("next"in(pA=[].keys())?(EA=Je(Je(pA)))!==Object.prototype&&(kA=EA):OA=!0),(null==kA||n((function(){var e={};return kA[hA].call(e)!==e})))&&(kA={}),g(kA,hA)||M(kA,hA,(function(){return this}));var _A={IteratorPrototype:kA,BUGGY_SAFARI_ITERATORS:OA},wA=_A.IteratorPrototype,yA=function(){return this},RA=_A.IteratorPrototype,DA=_A.BUGGY_SAFARI_ITERATORS,mA=Ce("iterator"),vA=function(){return this},MA=function(e,t,r,A,n,i,a){!function(e,t,r){var A=t+" Iterator";e.prototype=yt(wA,{next:f(1,r)}),jt(e,A,!1),Rt[A]=yA}(r,t,A);var o,c,s,u=function(e){if(e===n&&E)return E;if(!DA&&e in d)return d[e];switch(e){case"keys":case"values":case"entries":return function(){return new r(this,e)}}return function(){return new r(this)}},b=t+" Iterator",l=!1,d=e.prototype,k=d[mA]||d["@@iterator"]||n&&d[n],E=!DA&&k||u(n),p="Array"==t&&d.entries||k;if(p&&(o=Je(p.call(new e)),RA!==Object.prototype&&o.next&&(Je(o)!==RA&&(Ke?Ke(o,RA):"function"!=typeof o[mA]&&M(o,mA,vA)),jt(o,b,!0))),"values"==n&&k&&"values"!==k.name&&(l=!0,E=function(){return k.call(this)}),d[mA]!==E&&M(d,mA,E),Rt[t]=E,n)if(c={values:u("values"),keys:i?E:u("keys"),entries:u("entries")},a)for(s in c)(DA||l||!(s in d))&&$(d,s,c[s]);else ve({target:t,proto:!0,forced:DA||l},c);return c},FA=gA.charAt,SA=q.set,PA=q.getterFor("String Iterator");MA(String,"String",(function(e){SA(this,{type:"String Iterator",string:String(e),index:0})}),(function(){var e,t=PA(this),r=t.string,A=t.index;return A>=r.length?{value:void 0,done:!0}:(e=FA(r,A),t.index+=e.length,{value:e,done:!1})}));var BA={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0},XA=Ce("unscopables"),UA=Array.prototype;null==UA[XA]&&v.f(UA,XA,{configurable:!0,value:yt(null)});var IA=function(e){UA[XA][e]=!0},xA=q.set,jA=q.getterFor("Array Iterator"),QA=MA(Array,"Array",(function(e,t){xA(this,{type:"Array Iterator",target:k(e),index:0,kind:t})}),(function(){var e=jA(this),t=e.target,r=e.kind,A=e.index++;return!t||A>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==r?{value:A,done:!1}:"values"==r?{value:t[A],done:!1}:{value:[A,t[A]],done:!1}}),"values");Rt.Arguments=Rt.Array,IA("keys"),IA("values"),IA("entries");var CA=Ce("iterator"),ZA=Ce("toStringTag"),GA=QA.values;for(var NA in BA){var HA=A[NA],WA=HA&&HA.prototype;if(WA){if(WA[CA]!==GA)try{M(WA,CA,GA)}catch(e){WA[CA]=GA}if(WA[ZA]||M(WA,ZA,NA),BA[NA])for(var YA in QA)if(WA[YA]!==QA[YA])try{M(WA,YA,QA[YA])}catch(e){WA[YA]=QA[YA]}}}ee.Promise.any;var VA=!!Ut&&n((function(){Ut.prototype.finally.call({then:function(){}},(function(){}))}));ve({target:"Promise",proto:!0,real:!0,forced:VA},{finally:function(e){var t=Lt(this,re("Promise")),r="function"==typeof e;return this.then(r?function(r){return Br(t,e()).then((function(){return r}))}:e,r?function(r){return Br(t,e()).then((function(){throw r}))}:e)}}),"function"!=typeof Ut||Ut.prototype.finally||$(Ut.prototype,"finally",re("Promise").prototype.finally);var LA,zA,JA=Function.call;LA="finally",Mt(JA,A["Promise"].prototype[LA],zA),ve({target:"Promise",stat:!0},{try:function(e){var t=Pr.f(this),r=Xr(e);return(r.error?t.reject:t.resolve)(r.value),t.promise}}),ee.Promise.try,ve({target:"Promise",stat:!0},{allSettled:function(e){var t=this,r=Pr.f(t),A=r.resolve,n=r.reject,i=Xr((function(){var r=vt(t.resolve),n=[],i=0,a=1;Bt(e,(function(e){var o=i++,c=!1;n.push(void 0),a++,r.call(t,e).then((function(e){c||(c=!0,n[o]={status:"fulfilled",value:e},--a||A(n))}),(function(e){c||(c=!0,n[o]={status:"rejected",reason:e},--a||A(n))}))})),--a||A(n)}));return i.error&&n(i.value),r.promise}}),ee.Promise.allSettled;var KA=Ne?{}.toString:function(){return"[object "+Ye(this)+"]"};Ne||$(Object.prototype,"toString",KA,{unsafe:!0}),ee.Promise; -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -var qA=function(e,t){return(qA=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function $A(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}qA(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function en(e,t,r,A){return new(r||(r=Promise))((function(n,i){function a(e){try{c(A.next(e))}catch(e){i(e)}}function o(e){try{c(A.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?n(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(a,o)}c((A=A.apply(e,t||[])).next())}))}function tn(e,t){var r,A,n,i,a={label:0,sent:function(){if(1&n[0])throw n[1];return n[1]},trys:[],ops:[]};return i={next:o(0),throw:o(1),return:o(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function o(i){return function(o){return function(i){if(r)throw new TypeError("Generator is already executing.");for(;a;)try{if(r=1,A&&(n=2&i[0]?A.return:i[0]?A.throw||((n=A.return)&&n.call(A),0):A.next)&&!(n=n.call(A,i[1])).done)return n;switch(A=0,n&&(i=[2&i[0],n.value]),i[0]){case 0:case 1:n=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,A=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(n=a.trys,(n=n.length>0&&n[n.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!n||i[1]>n[0]&&i[1]<n[3])){a.label=i[1];break}if(6===i[0]&&a.label<n[1]){a.label=n[1],n=i;break}if(n&&a.label<n[2]){a.label=n[2],a.ops.push(i);break}n[2]&&a.ops.pop(),a.trys.pop();continue}i=t.call(e,a)}catch(e){i=[6,e],A=0}finally{r=n=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,o])}}}var rn=t((function(e,t){var r,A=(r="undefined"!=typeof document&&document.currentScript?document.currentScript.src:void 0,"undefined"!=typeof __filename&&(r=r||__filename),function(e){var t;e=e||{},t||(t=void 0!==e?e:{});var A,n,i=function(){function e(){}function t(e){if(!(this instanceof t))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],o(e,this)}function r(e,r){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,t._immediateFn((function(){var t=1===e._state?r.onFulfilled:r.onRejected;if(null!==t){var i;try{i=t(e._value)}catch(e){return void n(r.promise,e)}A(r.promise,i)}else(1===e._state?A:n)(r.promise,e._value)}))):e._deferreds.push(r)}function A(e,r){try{if(r===e)throw new TypeError("A promise cannot be resolved with itself.");if(r&&("object"==typeof r||"function"==typeof r)){var A=r.then;if(r instanceof t)return e._state=3,e._value=r,void i(e);if("function"==typeof A)return void o((a=A,c=r,function(){a.apply(c,arguments)}),e)}e._state=1,e._value=r,i(e)}catch(t){n(e,t)}var a,c}function n(e,t){e._state=2,e._value=t,i(e)}function i(e){2===e._state&&0===e._deferreds.length&&t._immediateFn((function(){e._handled||t._unhandledRejectionFn(e._value)}));for(var A=0,n=e._deferreds.length;A<n;A++)r(e,e._deferreds[A]);e._deferreds=null}function a(e,t,r){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.promise=r}function o(e,t){var r=!1;try{e((function(e){r||(r=!0,A(t,e))}),(function(e){r||(r=!0,n(t,e))}))}catch(e){if(r)return;r=!0,n(t,e)}}return t.prototype.catch=function(e){return this.then(null,e)},t.prototype.then=function(t,A){var n=new this.constructor(e);return r(this,new a(t,A,n)),n},t.all=function(e){return new t((function(t,r){if(!Array.isArray(e))return r(new TypeError("Promise.all accepts an array"));var A=Array.prototype.slice.call(e);if(0===A.length)return t([]);var n=A.length;function i(e,a){try{if(a&&("object"==typeof a||"function"==typeof a)){var o=a.then;if("function"==typeof o)return void o.call(a,(function(t){i(e,t)}),r)}A[e]=a,0==--n&&t(A)}catch(e){r(e)}}for(var a=0;a<A.length;a++)i(a,A[a])}))},t.resolve=function(e){return e&&"object"==typeof e&&e.constructor===t?e:new t((function(t){t(e)}))},t.reject=function(e){return new t((function(t,r){r(e)}))},t.race=function(e){return new t((function(r,A){if(!Array.isArray(e))return A(new TypeError("Promise.race accepts an array"));for(var n=0,i=e.length;n<i;n++)t.resolve(e[n]).then(r,A)}))},t._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){setTimeout(e,0)},t._unhandledRejectionFn=function(e){"undefined"!=typeof console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)},t}();t.ready=new i((function(e,t){A=e,n=t})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_getErrorName")||(Object.defineProperty(t.ready,"_ZSTD_getErrorName",{configurable:!0,get:function(){ee("You are getting _ZSTD_getErrorName on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_getErrorName",{configurable:!0,set:function(){ee("You are setting _ZSTD_getErrorName on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_isError")||(Object.defineProperty(t.ready,"_ZSTD_isError",{configurable:!0,get:function(){ee("You are getting _ZSTD_isError on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_isError",{configurable:!0,set:function(){ee("You are setting _ZSTD_isError on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_DStreamInSize")||(Object.defineProperty(t.ready,"_ZSTD_DStreamInSize",{configurable:!0,get:function(){ee("You are getting _ZSTD_DStreamInSize on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_DStreamInSize",{configurable:!0,set:function(){ee("You are setting _ZSTD_DStreamInSize on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_DStreamOutSize")||(Object.defineProperty(t.ready,"_ZSTD_DStreamOutSize",{configurable:!0,get:function(){ee("You are getting _ZSTD_DStreamOutSize on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_DStreamOutSize",{configurable:!0,set:function(){ee("You are setting _ZSTD_DStreamOutSize on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_createDStream")||(Object.defineProperty(t.ready,"_ZSTD_createDStream",{configurable:!0,get:function(){ee("You are getting _ZSTD_createDStream on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_createDStream",{configurable:!0,set:function(){ee("You are setting _ZSTD_createDStream on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_initDStream")||(Object.defineProperty(t.ready,"_ZSTD_initDStream",{configurable:!0,get:function(){ee("You are getting _ZSTD_initDStream on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_initDStream",{configurable:!0,set:function(){ee("You are setting _ZSTD_initDStream on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_decompressStream_simpleArgs")||(Object.defineProperty(t.ready,"_ZSTD_decompressStream_simpleArgs",{configurable:!0,get:function(){ee("You are getting _ZSTD_decompressStream_simpleArgs on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_decompressStream_simpleArgs",{configurable:!0,set:function(){ee("You are setting _ZSTD_decompressStream_simpleArgs on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_freeDStream")||(Object.defineProperty(t.ready,"_ZSTD_freeDStream",{configurable:!0,get:function(){ee("You are getting _ZSTD_freeDStream on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_freeDStream",{configurable:!0,set:function(){ee("You are setting _ZSTD_freeDStream on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_decompress")||(Object.defineProperty(t.ready,"_ZSTD_decompress",{configurable:!0,get:function(){ee("You are getting _ZSTD_decompress on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_decompress",{configurable:!0,set:function(){ee("You are setting _ZSTD_decompress on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_ZSTD_getFrameContentSize")||(Object.defineProperty(t.ready,"_ZSTD_getFrameContentSize",{configurable:!0,get:function(){ee("You are getting _ZSTD_getFrameContentSize on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_ZSTD_getFrameContentSize",{configurable:!0,set:function(){ee("You are setting _ZSTD_getFrameContentSize on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_emscripten_stack_get_end")||(Object.defineProperty(t.ready,"_emscripten_stack_get_end",{configurable:!0,get:function(){ee("You are getting _emscripten_stack_get_end on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_emscripten_stack_get_end",{configurable:!0,set:function(){ee("You are setting _emscripten_stack_get_end on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_emscripten_stack_get_free")||(Object.defineProperty(t.ready,"_emscripten_stack_get_free",{configurable:!0,get:function(){ee("You are getting _emscripten_stack_get_free on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_emscripten_stack_get_free",{configurable:!0,set:function(){ee("You are setting _emscripten_stack_get_free on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_emscripten_stack_init")||(Object.defineProperty(t.ready,"_emscripten_stack_init",{configurable:!0,get:function(){ee("You are getting _emscripten_stack_init on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_emscripten_stack_init",{configurable:!0,set:function(){ee("You are setting _emscripten_stack_init on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"___cxa_demangle")||(Object.defineProperty(t.ready,"___cxa_demangle",{configurable:!0,get:function(){ee("You are getting ___cxa_demangle on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"___cxa_demangle",{configurable:!0,set:function(){ee("You are setting ___cxa_demangle on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_stackSave")||(Object.defineProperty(t.ready,"_stackSave",{configurable:!0,get:function(){ee("You are getting _stackSave on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_stackSave",{configurable:!0,set:function(){ee("You are setting _stackSave on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_stackRestore")||(Object.defineProperty(t.ready,"_stackRestore",{configurable:!0,get:function(){ee("You are getting _stackRestore on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_stackRestore",{configurable:!0,set:function(){ee("You are setting _stackRestore on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_stackAlloc")||(Object.defineProperty(t.ready,"_stackAlloc",{configurable:!0,get:function(){ee("You are getting _stackAlloc on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_stackAlloc",{configurable:!0,set:function(){ee("You are setting _stackAlloc on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"___wasm_call_ctors")||(Object.defineProperty(t.ready,"___wasm_call_ctors",{configurable:!0,get:function(){ee("You are getting ___wasm_call_ctors on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"___wasm_call_ctors",{configurable:!0,set:function(){ee("You are setting ___wasm_call_ctors on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_fflush")||(Object.defineProperty(t.ready,"_fflush",{configurable:!0,get:function(){ee("You are getting _fflush on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_fflush",{configurable:!0,set:function(){ee("You are setting _fflush on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"___errno_location")||(Object.defineProperty(t.ready,"___errno_location",{configurable:!0,get:function(){ee("You are getting ___errno_location on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"___errno_location",{configurable:!0,set:function(){ee("You are setting ___errno_location on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_malloc")||(Object.defineProperty(t.ready,"_malloc",{configurable:!0,get:function(){ee("You are getting _malloc on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_malloc",{configurable:!0,set:function(){ee("You are setting _malloc on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"_free")||(Object.defineProperty(t.ready,"_free",{configurable:!0,get:function(){ee("You are getting _free on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"_free",{configurable:!0,set:function(){ee("You are setting _free on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}})),Object.getOwnPropertyDescriptor(t.ready,"onRuntimeInitialized")||(Object.defineProperty(t.ready,"onRuntimeInitialized",{configurable:!0,get:function(){ee("You are getting onRuntimeInitialized on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}),Object.defineProperty(t.ready,"onRuntimeInitialized",{configurable:!0,set:function(){ee("You are setting onRuntimeInitialized on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js")}}));var a,o,c,f,s,u={};for(a in t)t.hasOwnProperty(a)&&(u[a]=t[a]);if(o="object"==typeof window,c="function"==typeof importScripts,f="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,s=!o&&!f&&!c,t.ENVIRONMENT)throw new Error("Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option (for example, -s ENVIRONMENT=web or -s ENVIRONMENT=node)");var b,l,d,k,E,p="";if(f)p=c?require("path").dirname(p)+"/":__dirname+"/",b=function(e,t){var r=pe(e);return r?t?r:r.toString():(k||(k=require("fs")),E||(E=require("path")),e=E.normalize(e),k.readFileSync(e,t?null:"utf8"))},d=function(e){var t=b(e,!0);return t.buffer||(t=new Uint8Array(t)),R(t.buffer),t},process.argv.length>1&&process.argv[1].replace(/\\/g,"/"),process.argv.slice(2),process.on("uncaughtException",(function(e){if(!(e instanceof me))throw e})),process.on("unhandledRejection",ee),t.inspect=function(){return"[Emscripten Module object]"};else if(s)"undefined"!=typeof read&&(b=function(e){var t=pe(e);return t?ke(t):read(e)}),d=function(e){var t;return(t=pe(e))?t:"function"==typeof readbuffer?new Uint8Array(readbuffer(e)):(R("object"==typeof(t=read(e,"binary"))),t)},"undefined"!=typeof scriptArgs&&scriptArgs,"undefined"!=typeof print&&("undefined"==typeof console&&(console={}),console.log=print,console.warn=console.error="undefined"!=typeof printErr?printErr:print);else{if(!o&&!c)throw new Error("environment detection error");c?p=self.location.href:"undefined"!=typeof document&&document.currentScript&&(p=document.currentScript.src),r&&(p=r),p=0!==p.indexOf("blob:")?p.substr(0,p.lastIndexOf("/")+1):"",b=function(e){try{var t=new XMLHttpRequest;return t.open("GET",e,!1),t.send(null),t.responseText}catch(t){var r=pe(e);if(r)return ke(r);throw t}},c&&(d=function(e){try{var t=new XMLHttpRequest;return t.open("GET",e,!1),t.responseType="arraybuffer",t.send(null),new Uint8Array(t.response)}catch(t){var r=pe(e);if(r)return r;throw t}}),l=function(e,t,r){var A=new XMLHttpRequest;A.open("GET",e,!0),A.responseType="arraybuffer",A.onload=function(){if(200==A.status||0==A.status&&A.response)t(A.response);else{var n=pe(e);n?t(n.buffer):r()}},A.onerror=r,A.send(null)}}t.print||console.log.bind(console);var T=t.printErr||console.warn.bind(console);for(a in u)u.hasOwnProperty(a)&&(t[a]=u[a]);function g(e){g.shown||(g.shown={}),g.shown[e]||(g.shown[e]=1,T(e))}u=null,t.arguments&&t.arguments,Object.getOwnPropertyDescriptor(t,"arguments")||Object.defineProperty(t,"arguments",{configurable:!0,get:function(){ee("Module.arguments has been replaced with plain arguments_ (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),t.thisProgram&&t.thisProgram,Object.getOwnPropertyDescriptor(t,"thisProgram")||Object.defineProperty(t,"thisProgram",{configurable:!0,get:function(){ee("Module.thisProgram has been replaced with plain thisProgram (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),t.quit&&t.quit,Object.getOwnPropertyDescriptor(t,"quit")||Object.defineProperty(t,"quit",{configurable:!0,get:function(){ee("Module.quit has been replaced with plain quit_ (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),R(void 0===t.memoryInitializerPrefixURL,"Module.memoryInitializerPrefixURL option was removed, use Module.locateFile instead"),R(void 0===t.pthreadMainPrefixURL,"Module.pthreadMainPrefixURL option was removed, use Module.locateFile instead"),R(void 0===t.cdInitializerPrefixURL,"Module.cdInitializerPrefixURL option was removed, use Module.locateFile instead"),R(void 0===t.filePackagePrefixURL,"Module.filePackagePrefixURL option was removed, use Module.locateFile instead"),R(void 0===t.read,"Module.read option was removed (modify read_ in JS)"),R(void 0===t.readAsync,"Module.readAsync option was removed (modify readAsync in JS)"),R(void 0===t.readBinary,"Module.readBinary option was removed (modify readBinary in JS)"),R(void 0===t.setWindowTitle,"Module.setWindowTitle option was removed (modify setWindowTitle in JS)"),R(void 0===t.TOTAL_MEMORY,"Module.TOTAL_MEMORY has been renamed Module.INITIAL_MEMORY"),Object.getOwnPropertyDescriptor(t,"read")||Object.defineProperty(t,"read",{configurable:!0,get:function(){ee("Module.read has been replaced with plain read_ (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),Object.getOwnPropertyDescriptor(t,"readAsync")||Object.defineProperty(t,"readAsync",{configurable:!0,get:function(){ee("Module.readAsync has been replaced with plain readAsync (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),Object.getOwnPropertyDescriptor(t,"readBinary")||Object.defineProperty(t,"readBinary",{configurable:!0,get:function(){ee("Module.readBinary has been replaced with plain readBinary (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),Object.getOwnPropertyDescriptor(t,"setWindowTitle")||Object.defineProperty(t,"setWindowTitle",{configurable:!0,get:function(){ee("Module.setWindowTitle has been replaced with plain setWindowTitle (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}});var h,O=0;t.wasmBinary&&(h=t.wasmBinary),Object.getOwnPropertyDescriptor(t,"wasmBinary")||Object.defineProperty(t,"wasmBinary",{configurable:!0,get:function(){ee("Module.wasmBinary has been replaced with plain wasmBinary (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),t.noExitRuntime,Object.getOwnPropertyDescriptor(t,"noExitRuntime")||Object.defineProperty(t,"noExitRuntime",{configurable:!0,get:function(){ee("Module.noExitRuntime has been replaced with plain noExitRuntime (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}});var _,w={Memory:function(e){this.buffer=new ArrayBuffer(65536*e.initial)},Module:function(e){},Instance:function(e,t){this.exports=function(e){for(var t,r=new Uint8Array(123),A=25;A>=0;--A)r[48+A]=52+A,r[65+A]=A,r[97+A]=26+A;function n(e,t,A){for(var n,i,a=0,o=t,c=A.length,f=t+(3*c>>2)-("="==A[c-2])-("="==A[c-1]);a<c;a+=4)n=r[A.charCodeAt(a+1)],i=r[A.charCodeAt(a+2)],e[o++]=r[A.charCodeAt(a)]<<2|n>>4,o<f&&(e[o++]=n<<4|i>>2),o<f&&(e[o++]=i<<6|r[A.charCodeAt(a+3)])}r[43]=62,r[47]=63;var i=new ArrayBuffer(16),a=new Int32Array(i),o=new Float64Array(i);function c(e){return a[e]}function f(e,t){a[e]=t}function s(){return o[0]}function u(e){o[0]=e}return function(e){var r=e.memory,A=r.buffer;r.grow=function(e){e|=0;var n=0|oA(),c=n+e|0;if(n<c&&c<65536){var f=new ArrayBuffer(p(c,65536));new Int8Array(f).set(i),i=new Int8Array(f),a=new Int16Array(f),o=new Int32Array(f),b=new Uint8Array(f),l=new Uint16Array(f),d=new Uint32Array(f),k=new Float32Array(f),E=new Float64Array(f),A=f,r.buffer=A,t=b}return n};var i=new Int8Array(A),a=new Int16Array(A),o=new Int32Array(A),b=new Uint8Array(A),l=new Uint16Array(A),d=new Uint32Array(A),k=new Float32Array(A),E=new Float64Array(A),p=Math.imul,T=Math.abs,g=Math.clz32,h=e.abort,O=e.emscripten_resize_heap,_=e.emscripten_memcpy_big,w=e.setTempRet0,y=5259632,R=0,D=0;function m(e,t,r,A,n){var a,c,f=0,s=0,u=0,l=0,k=0,E=0,p=0,T=0,h=0,O=0,_=0,w=0,R=0,D=0,m=0,v=0,M=0,B=0,X=0,I=0,x=0,j=0,Q=0,C=0,Z=0,G=0,N=0,H=0,W=0,Y=0,V=0,L=0,J=0,q=0,$=0;y=a=y-224|0,c=!(s=o[4+(f=e+29384|0)>>2])&(f=o[f>>2])>>>0>33554432|0!=(0|s),w=-72;e:if(!(n>>>0>131071||(w=function(e,t,r){var A=0,n=0,a=0,c=0,f=0,s=0,u=0,l=0;u=-20;t:if(!(r>>>0<3)){r:{A:{n:{i:{a:switch((s=3&(f=b[0|t]))-1|0){case 0:break n;case 1:break i;case 2:break a;default:break A}if(!o[e+29432>>2])return-30}if(r>>>0<5)break t;c=3,A=b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24;i:{a:{o:{c:{f:switch((a=f>>>2&3)-2|0){case 0:break c;case 1:break o}n=A>>>14&1023,A=A>>>4&1023,a=!a;break i}n=A>>>18|0,c=4,A=A>>>4&16383;break a}n=b[t+4|0]<<10|A>>>22,c=5,A=A>>>4&262143}a=0}if(A>>>0>131072)break t;if((l=n+c|0)>>>0>r>>>0)break t;if(!o[e+29588>>2]|A>>>0<769)break r;for(r=0;;){if(r>>>0>=16324)break r;r=r+512|0}}n=2;n:{i:switch((f>>>2&3)-1|0){default:n=1,r=f>>>3|0;break n;case 0:r=(b[0|t]|b[t+1|0]<<8)>>>4|0;break n;case 2:}if(r>>>0<4)break t;if((r=b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16)>>>0>2097167)break t;n=3,r=r>>>4|0}t=ke(e+29672|0,b[t+n|0],r+32|0),o[e+29560>>2]=r,o[e+29544>>2]=t,u=n+1|0;break t}n=2,a=r;A:{n:switch((f>>>2&3)-1|0){default:n=1,A=f>>>3|0;break A;case 0:A=(b[0|t]|b[t+1|0]<<8)>>>4|0;break A;case 2:}n=3,A=(b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16)>>>4|0}if((c=a)>>>0<32+(a=A+n|0)>>>0){if(r>>>0<a>>>0)break t;return t=re(e+29672|0,t+n|0,A),o[e+29560>>2]=A,o[e+29544>>2]=t,i[24+(e=t+A|0)|0]=0,i[e+25|0]=0,i[e+26|0]=0,i[e+27|0]=0,i[e+28|0]=0,i[e+29|0]=0,i[e+30|0]=0,i[e+31|0]=0,i[e+16|0]=0,i[e+17|0]=0,i[e+18|0]=0,i[e+19|0]=0,i[e+20|0]=0,i[e+21|0]=0,i[e+22|0]=0,i[e+23|0]=0,i[e+8|0]=0,i[e+9|0]=0,i[e+10|0]=0,i[e+11|0]=0,i[e+12|0]=0,i[e+13|0]=0,i[e+14|0]=0,i[e+15|0]=0,i[0|e]=0,i[e+1|0]=0,i[e+2|0]=0,i[e+3|0]=0,i[e+4|0]=0,i[e+5|0]=0,i[e+6|0]=0,i[e+7|0]=0,a}return o[e+29560>>2]=A,o[e+29544>>2]=t+n,a}r:if(3!=(0|s))if(r=e+10280|0,a){if((t=U(r,a=t+c|0,n,e+27308|0))>>>0>4294967176|t>>>0>=n>>>0)break t;t=K(e+29672|0,A,t+a|0,n-t|0,r)}else t=ve(r,e+29672|0,A,t+c|0,n,e+27308|0);else{if(t=t+c|0,r=e+29672|0,c=o[e+12>>2],f=o[c>>2]>>>8|0,a){if(255&f){t=z(r,A,t,n,c);break r}t=K(r,A,t,n,c);break r}if(255&f){t=F(r,A,t,n,c);break r}t=P(r,A,t,n,c)}if(!(t>>>0>4294967176))return o[e+29560>>2]=A,o[e+29432>>2]=1,o[e+29544>>2]=e+29672,2==(0|s)&&(o[e+12>>2]=e+10280),i[0|(e=29696+(t=e+A|0)|0)]=0,i[e+1|0]=0,i[e+2|0]=0,i[e+3|0]=0,i[e+4|0]=0,i[e+5|0]=0,i[e+6|0]=0,i[e+7|0]=0,i[0|(e=t+29688|0)]=0,i[e+1|0]=0,i[e+2|0]=0,i[e+3|0]=0,i[e+4|0]=0,i[e+5|0]=0,i[e+6|0]=0,i[e+7|0]=0,i[0|(e=t+29680|0)]=0,i[e+1|0]=0,i[e+2|0]=0,i[e+3|0]=0,i[e+4|0]=0,i[e+5|0]=0,i[e+6|0]=0,i[e+7|0]=0,i[0|(e=t+29672|0)]=0,i[e+1|0]=0,i[e+2|0]=0,i[e+3|0]=0,i[e+4|0]=0,i[e+5|0]=0,i[e+6|0]=0,i[e+7|0]=0,l}return u}(e,A,n))>>>0>4294967176))if(s=o[e+29588>>2],k=A+w|0,f=A,(A=function(e,t,r,A){var n=0,i=0,a=0,c=0,f=0;f=-72;t:{r:if(A){if(!(n=b[0|r]))break t;if(i=r+1|0,!((0|(c=n<<24>>24))>-1))if(-1!=(0|c)){if((0|A)<2)break r;n=(b[r+1|0]|n<<8)-32768|0,i=r+2|0}else{if((0|A)<3)break r;n=32512+(b[r+1|0]|b[r+2|0]<<8)|0,i=r+3|0}o[t>>2]=n,(t=i+1|0)>>>0>(A=r+A|0)>>>0||(f=-20,c=e+27308|0,(a=he(e+16|0,e,(i=b[0|i])>>>6|0,35,9,t,A-t|0,2144,2288,2448,o[e+29436>>2],o[e+29588>>2],n,c))>>>0>4294967176||(a=he(e+4120|0,e+8|0,i>>>4&3,31,8,t=a>>>0<4294967177?t+a|0:t,A-t|0,1440,1568,2976,o[e+29436>>2],o[e+29588>>2],n,c))>>>0>4294967176||(e=he(e+6176|0,e+4|0,i>>>2&3,52,9,t=a>>>0<4294967177?t+a|0:t,A-t|0,1696,1920,3248,o[e+29436>>2],o[e+29588>>2],n,c))>>>0>4294967176||(f=(e+t|0)-r|0))}return f}return o[t>>2]=0,1==(0|A)?1:-72}(e,a+52|0,f=(A=w>>>0<4294967177)?k:f,n=n-(A?w:0)|0))>>>0>4294967176)w=A;else if(B=o[a+52>>2],t||(w=-70,!((0|B)>0))){k=n-A|0,_=A+f|0;t:{r:{if(s)o[e+29588>>2]=0;else{if((0|B)<5)break r;if(A=o[(n=e+29384|0)>>2],!o[n+4>>2]&A>>>0<=16777216)break r;for(A=(n=o[e+8>>2])+8|0,f=o[n+4>>2],w=0,n=0;w=(b[2+(A+(n<<3)|0)|0]>22)+w|0,!((n=n+1|0)>>>f|0););if(o[e+29588>>2]=0,w<<8-f>>>0<20)break t}if(n=o[e+29544>>2],o[a+204>>2]=n,H=t+r|0,V=o[e+29560>>2]+n|0,A=t,B){if(G=o[e+29368>>2],L=o[e+29364>>2],X=o[e+29360>>2],o[e+29436>>2]=1,o[a+108>>2]=o[e+26676>>2],r=o[4+(A=j=e+26668|0)>>2],o[a+100>>2]=o[A>>2],o[a+104>>2]=r,o[a+116>>2]=G,o[a+112>>2]=X,M=t-X|0,o[a+120>>2]=M,w=-20,!k)break e;if(o[a+68>>2]=_,o[a+72>>2]=_+4,k>>>0>=4){if(r=_+(A=k-4|0)|0,o[a+64>>2]=r,n=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+56>>2]=n,!(r=b[(k+_|0)-1|0])|k>>>0>4294967176)break e;r=g(r)-23|0}else{o[a+64>>2]=_,n=b[0|_],o[a+56>>2]=n;A:switch(k-2|0){case 1:n=b[_+2|0]<<16|n;case 0:n=(b[_+1|0]<<8)+n|0,o[a+56>>2]=n}if(!(r=b[(k+_|0)-1|0]))break e;A=0,r=9+(g(r)-(k<<3)|0)|0}l=o[e>>2],E=n>>>0-(r=(f=o[l+4>>2])+r|0)&o[3776+(f<<2)>>2],o[a+76>>2]=E,r>>>0>32?s=r:(0|A)>=4?(s=7&r,o[a+60>>2]=s,r=_+(A=A-(r>>>3|0)|0)|0,o[a+64>>2]=r,n=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+56>>2]=n):A?(n=_+(A=A-(f=_>>>0>(A+_|0)-(n=r>>>3|0)>>>0?A:n)|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24,o[a+56>>2]=n,s=r-(f<<3)|0):(A=0,s=r),k=(0|B)<4,I=l+8|0,o[a+80>>2]=I,f=s,s=o[e+8>>2],h=n>>>0-(f=f+(r=o[s+4>>2])|0)&o[3776+(r<<2)>>2],o[a+84>>2]=h,f>>>0>32||((0|A)>=4?(r=_+(A=A-(f>>>3|0)|0)|0,o[a+64>>2]=r,n=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+56>>2]=n,f&=7):A?(n=_+(A=A-(r=_>>>0>(A+_|0)-(r=f>>>3|0)>>>0?A:r)|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24,o[a+56>>2]=n,f=f-(r<<3)|0):A=0),J=k?B:4,x=s+8|0,o[a+88>>2]=x,s=o[e+4>>2],f=(e=o[s+4>>2])+f|0,o[a+60>>2]=f,r=0,v=o[3776+(e<<2)>>2]&n>>>0-f,o[a+92>>2]=v;A:{n:if(f>>>0>=33)o[a+96>>2]=s+8;else{i:{a:{if((0|A)>=4)r=7&f,o[a+60>>2]=r,A=_+(e=A-(f>>>3|0)|0)|0,o[a+64>>2]=A,n=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,o[a+56>>2]=n,f=r;else{if(A)break a;e=0}o[a+96>>2]=s+8;break i}if(f=f-((e=_>>>0>(A+_|0)-(e=f>>>3|0)>>>0?A:e)<<3)|0,o[a+60>>2]=f,r=_+(e=A-e|0)|0,o[a+64>>2]=r,n=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+96>>2]=s+8,o[a+56>>2]=n,r=0,f>>>0>32)break n}for(N=(0|J)>0?J:0,Z=s+8|0,p=e,k=e,l=e,u=e,A=e,r=0;;){T=a;i:{if((0|A)>=4)s=7&f,o[a+60>>2]=s,A=_+(e=A-(f>>>3|0)|0)|0,o[a+64>>2]=A,n=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24;else{if(!A){s=f,A=0;break i}s=f-((e=_>>>0>(A+_|0)-(e=f>>>3|0)>>>0?A:e)<<3)|0,o[a+60>>2]=s,A=_+(e=A-e|0)|0,o[a+64>>2]=A,n=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24}o[T+56>>2]=n,p=e,k=e,l=e,u=e,A=e}if((0|r)==(0|N)){f=s,r=N;break A}q=255&(Q=(65535&(E=T=o[4+(f=(E<<3)+I|0)>>2]))<<16|(D=o[f>>2])>>>16),$=255&(C=(65535&(T=O=o[4+(f=(v<<3)+Z|0)>>2]))<<16|(v=o[f>>2])>>>16),R=o[4+(f=(h<<3)+x|0)>>2],O=o[f>>2],h=f=R,W=T,R=E;i:if((m=255&(f=(65535&f)<<16|O>>>16))>>>0>=2){a:if(!c|m>>>0<25)if(h=h+(n<<s>>>0-f|0)|0,(s=s+m|0)>>>0>32)f=s;else{if(A=a,(0|u)>=4)f=7&s,o[a+60>>2]=f,n=_+(e=u-(s>>>3|0)|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24;else{if(!u){u=0,f=s,A=0;break a}f=s-((e=_>>>0>(u+_|0)-(e=s>>>3|0)>>>0?u:e)<<3)|0,o[a+60>>2]=f,n=_+(e=u-e|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24}o[A+56>>2]=n,p=e,k=e,l=e,u=e,A=e}else{Y=m,f=n<<s>>>0-(m=(f=32-s|0)>>>0>m>>>0?m:f)<<(Y=Y-m|0);o:if(!((s=s+m|0)>>>0>32)){if((0|l)>=4)A=_+(e=l-(s>>>3|0)|0)|0,o[a+64>>2]=A,n=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,o[a+56>>2]=n,s&=7;else{if(!l){l=0,u=0,A=0;break o}n=_+(e=l-(A=_>>>0>(l+_|0)-(e=s>>>3|0)>>>0?l:e)|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24,o[a+56>>2]=n,s=s-(A<<3)|0}p=e,k=e,l=e,u=e,A=e}if(h=f+h|0,!Y){f=s;break a}f=s+Y|0,o[a+60>>2]=f,h=(n<<s>>>0-Y|0)+h|0}m=o[a+104>>2],s=o[a+100>>2],o[a+100>>2]=h,o[a+104>>2]=s,o[a+108>>2]=m}else if(m){f=s+1|0,o[a+60>>2]=f;a:{if(3!=(0|(m=h+(!R+(n<<s>>>31|0)|0)|0))){if(h=!(s=o[100+((m<<2)+a|0)>>2])+s|0,1==(0|m))break a}else h=!(s=o[a+100>>2]-1|0)+s|0;o[a+108>>2]=o[a+104>>2]}o[a+104>>2]=o[a+100>>2],o[a+100>>2]=h}else{if(R){h=o[a+100>>2],f=s;break i}h=o[a+104>>2],o[a+104>>2]=o[a+100>>2],o[a+100>>2]=h,f=s}s=W,$&&(s=(n<<f>>>0-C|0)+s|0,f=f+$|0);i:if(!(q+$>>>0<20|f>>>0>32)){if((0|k)>=4)A=_+(e=k-(f>>>3|0)|0)|0,o[a+64>>2]=A,n=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,o[a+56>>2]=n,f&=7;else{if(!k){k=0,l=0,u=0,A=0;break i}n=_+(e=k-(A=_>>>0>(k+_|0)-(e=f>>>3|0)>>>0?k:e)|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24,o[a+56>>2]=n,f=f-(A<<3)|0}p=e,k=e,l=e,u=e,A=e}W=D,v=(16777215&T)<<8|(m=v)>>>24,E=(16777215&E)<<8|D>>>24,q&&(R=(n<<f>>>0-Q|0)+R|0,f=f+q|0);i:if(f>>>0>32)T=f;else{if(A=a,(0|p)>=4)T=7&f,o[a+60>>2]=T,n=_+(e=p-(f>>>3|0)|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24;else{if(!p){p=0,k=0,l=0,u=0,A=0,T=f;break i}T=f-((e=_>>>0>(p+_|0)-(e=f>>>3|0)>>>0?p:e)<<3)|0,o[a+60>>2]=T,n=_+(e=p-e|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24}o[A+56>>2]=n,p=e,k=e,l=e,u=e,A=e}M=(D=R+M|0)+s|0,o[a+120>>2]=M,E=(n>>>0-(f=(f=T)+(T=255&E)|0)&o[3776+(T<<2)>>2])+(65535&W)|0,o[a+76>>2]=E,f=(T=255&v)+f|0,v=(o[3776+(T<<2)>>2]&n>>>0-f)+(65535&m)|0,o[a+92>>2]=v,T=(D+(h>>>0>D>>>0?G:X)|0)-h|0;i:if(!(f>>>0>32)){if((0|e)>=4)A=_+(e=e-(f>>>3|0)|0)|0,o[a+64>>2]=A,n=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,o[a+56>>2]=n,f&=7;else{if(!e){e=0,p=0,k=0,l=0,u=0,A=0;break i}n=_+(e=e-(A=_>>>0>(e+_|0)-(A=f>>>3|0)>>>0?e:A)|0)|0,o[a+64>>2]=n,n=b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24,o[a+56>>2]=n,f=f-(A<<3)|0}p=e,k=e,l=e,u=e,A=e}if(o[12+(D=(a+128|0)+(r<<4)|0)>>2]=T,o[D+8>>2]=h,o[D+4>>2]=s,o[D>>2]=R,f=(s=O>>>24|0)+f|0,o[a+60>>2]=f,h=(o[3776+(s<<2)>>2]&n>>>0-f)+(65535&O)|0,o[a+84>>2]=h,r=r+1|0,!(f>>>0<=32))break}}if((0|r)<(0|J))break e}N=H-32|0,m=a+112|0,W=a+116|0,A=t;A:{for(;;){if(f>>>0>=33){if((0|r)<(0|B))break e;break A}if((e=o[a+64>>2])>>>0>=(M=o[a+72>>2])>>>0?(s=7&f,o[a+60>>2]=s,e=e-(f>>>3|0)|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):(0|(s=o[a+68>>2]))!=(0|e)?(e=e-(n=s>>>0>e-(n=f>>>3|0)>>>0?e-s|0:n)|0,o[a+64>>2]=e,s=f-(n<<3)|0,o[a+60>>2]=s,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):s=f,(0|r)>=(0|B))break A;n=o[a+80>>2]+(o[a+76>>2]<<3)|0,k=f=o[n+4>>2],x=255&(D=(65535&f)<<16|(T=o[n>>2])>>>16),n=o[a+96>>2]+(o[a+92>>2]<<3)|0,Z=255&(v=(65535&(f=l=o[n+4>>2]))<<16|(p=o[n>>2])>>>16),n=o[a+88>>2]+(o[a+84>>2]<<3)|0,l=o[n+4>>2],u=o[n>>2],Q=n=l,E=f,R=k;n:if((l=255&(n=(65535&n)<<16|u>>>16))>>>0>=2){i:if(!c|l>>>0<25)h=Q+(o[a+56>>2]<<s>>>0-n|0)|0,(s=s+l|0)>>>0>32?n=s:e>>>0>=M>>>0?(n=7&s,o[a+60>>2]=n,e=e-(s>>>3|0)|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):(0|(l=o[a+68>>2]))!=(0|e)?(e=e-(n=l>>>0>e-(n=s>>>3|0)>>>0?e-l|0:n)|0,o[a+64>>2]=e,n=s-(n<<3)|0,o[a+60>>2]=n,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):n=s;else{if(I=l-(n=(n=32-s|0)>>>0>l>>>0?l:n)|0,O=(l=o[a+56>>2])<<s>>>0-n<<I,(s=n+s|0)>>>0>32||(e>>>0>=M>>>0?(e=e-(s>>>3|0)|0,o[a+64>>2]=e,l=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,o[a+56>>2]=l,s&=7):(0|(C=o[a+68>>2]))!=(0|e)&&(e=e-(n=C>>>0>e-(n=s>>>3|0)>>>0?e-C|0:n)|0,o[a+64>>2]=e,l=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,o[a+56>>2]=l,s=s-(n<<3)|0)),h=O+Q|0,!I){n=s;break i}n=s+I|0,o[a+60>>2]=n,h=(l<<s>>>0-I|0)+h|0}l=o[a+104>>2],s=o[a+100>>2],o[a+100>>2]=h,o[a+104>>2]=s,o[a+108>>2]=l}else if(l){n=s+1|0,o[a+60>>2]=n;i:{if(3!=(0|(l=Q+!R+(o[a+56>>2]<<s>>>31)|0))){if(h=!(s=o[100+((l<<2)+a|0)>>2])+s|0,1==(0|l))break i}else h=!(s=o[a+100>>2]-1|0)+s|0;o[a+108>>2]=o[a+104>>2]}o[a+104>>2]=o[a+100>>2],o[a+100>>2]=h}else{if(R){h=o[a+100>>2],n=s;break n}h=o[a+104>>2],o[a+104>>2]=o[a+100>>2],o[a+100>>2]=h,n=s}s=E,Z&&(s=(o[a+56>>2]<<n>>>0-v|0)+s|0,n=n+Z|0),x+Z>>>0<20|n>>>0>32||(e>>>0>=M>>>0?(e=e-(n>>>3|0)|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,n&=7):(0|(E=o[a+68>>2]))!=(0|e)&&(e=e-(l=E>>>0>e-(l=n>>>3|0)>>>0?e-E|0:l)|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,n=n-(l<<3)|0)),O=p,p=(16777215&f)<<8|p>>>24,k=(16777215&k)<<8|(E=T)>>>24,x&&(R=(o[a+56>>2]<<n>>>0-D|0)+R|0,n=n+x|0),n>>>0>32?l=n:e>>>0>=M>>>0?(l=7&n,o[a+60>>2]=l,e=e-(n>>>3|0)|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):(0|(l=o[a+68>>2]))!=(0|e)?(e=e-(f=l>>>0>e-(f=n>>>3|0)>>>0?e-l|0:f)|0,o[a+64>>2]=e,l=n-(f<<3)|0,o[a+60>>2]=l,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):l=n,v=o[a+120>>2]+R|0,o[a+120>>2]=v+s,f=o[a+56>>2],n=(k&=255)+l|0,o[a+76>>2]=(f>>>0-n&o[3776+(k<<2)>>2])+(65535&E),n=(k=255&p)+n|0,o[a+92>>2]=(o[3776+(k<<2)>>2]&f>>>0-n)+(65535&O),T=o[(h>>>0>v>>>0?W:m)>>2],n>>>0>32||(e>>>0>=M>>>0?(e=e-(n>>>3|0)|0,o[a+64>>2]=e,f=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,o[a+56>>2]=f,n&=7):(0|(k=o[a+68>>2]))!=(0|e)&&(f=(l=e)-(e=k>>>0>e-(f=n>>>3|0)>>>0?e-k|0:f)|0,o[a+64>>2]=f,f=b[0|f]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24,o[a+56>>2]=f,n=n-(e<<3)|0)),e=(e=n)+(n=u>>>24|0)|0,o[a+60>>2]=e,o[a+84>>2]=(o[3776+(n<<2)>>2]&f>>>0-e)+(65535&u),e=o[12+(k=D=(a+128|0)+((3&r)<<4)|0)>>2],f=o[k+8>>2],o[a+216>>2]=f,o[a+220>>2]=e,n=o[k+4>>2],e=o[k>>2],o[a+208>>2]=e,o[a+212>>2]=n;n:if(!((u=e+(O=o[a+204>>2])|0)>>>0>V>>>0||N>>>0<(k=e+(l=o[a+212>>2])|0)+A>>>0)&&k+32>>>0<=H-A>>>0){if(n=e+A|0,E=b[O+4|0]|b[O+5|0]<<8|b[O+6|0]<<16|b[O+7|0]<<24,p=b[0|O]|b[O+1|0]<<8|b[O+2|0]<<16|b[O+3|0]<<24,i[0|A]=p,i[A+1|0]=p>>>8,i[A+2|0]=p>>>16,i[A+3|0]=p>>>24,i[A+4|0]=E,i[A+5|0]=E>>>8,i[A+6|0]=E>>>16,i[A+7|0]=E>>>24,E=b[O+12|0]|b[O+13|0]<<8|b[O+14|0]<<16|b[O+15|0]<<24,p=b[O+8|0]|b[O+9|0]<<8|b[O+10|0]<<16|b[O+11|0]<<24,i[A+8|0]=p,i[A+9|0]=p>>>8,i[A+10|0]=p>>>16,i[A+11|0]=p>>>24,i[A+12|0]=E,i[A+13|0]=E>>>8,i[A+14|0]=E>>>16,i[A+15|0]=E>>>24,!(e>>>0<17||(E=b[O+20|0]|b[O+21|0]<<8|b[O+22|0]<<16|b[O+23|0]<<24,p=b[O+16|0]|b[O+17|0]<<8|b[O+18|0]<<16|b[O+19|0]<<24,i[A+16|0]=p,i[A+17|0]=p>>>8,i[A+18|0]=p>>>16,i[A+19|0]=p>>>24,i[A+20|0]=E,i[A+21|0]=E>>>8,i[A+22|0]=E>>>16,i[A+23|0]=E>>>24,E=b[O+28|0]|b[O+29|0]<<8|b[O+30|0]<<16|b[O+31|0]<<24,p=b[O+24|0]|b[O+25|0]<<8|b[O+26|0]<<16|b[O+27|0]<<24,i[A+24|0]=p,i[A+25|0]=p>>>8,i[A+26|0]=p>>>16,i[A+27|0]=p>>>24,i[A+28|0]=E,i[A+29|0]=E>>>8,i[A+30|0]=E>>>16,i[A+31|0]=E>>>24,(e-16|0)<17)))for(e=O+16|0,p=A+32|0;O=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,E=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|p]=E,i[p+1|0]=E>>>8,i[p+2|0]=E>>>16,i[p+3|0]=E>>>24,i[p+4|0]=O,i[p+5|0]=O>>>8,i[p+6|0]=O>>>16,i[p+7|0]=O>>>24,O=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,E=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[p+8|0]=E,i[p+9|0]=E>>>8,i[p+10|0]=E>>>16,i[p+11|0]=E>>>24,i[p+12|0]=O,i[p+13|0]=O>>>8,i[p+14|0]=O>>>16,i[p+15|0]=O>>>24,O=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,E=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[p+16|0]=E,i[p+17|0]=E>>>8,i[p+18|0]=E>>>16,i[p+19|0]=E>>>24,i[p+20|0]=O,i[p+21|0]=O>>>8,i[p+22|0]=O>>>16,i[p+23|0]=O>>>24,O=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,E=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[p+24|0]=E,i[p+25|0]=E>>>8,i[p+26|0]=E>>>16,i[p+27|0]=E>>>24,i[p+28|0]=O,i[p+29|0]=O>>>8,i[p+30|0]=O>>>16,i[p+31|0]=O>>>24,e=e+32|0,(p=p+32|0)>>>0<n>>>0;);if(e=n-f|0,o[a+204>>2]=u,f>>>0>n-X>>>0){if(f>>>0>n-L>>>0)break e;if(G>>>0>=(e=(u=e-X|0)+G|0)+l>>>0){le(n,e,l);break n}e=le(n,e,0-u|0),l=u+l|0,o[a+212>>2]=l,n=e-u|0,e=X}if(f>>>0>=16){if(u=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,f=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|n]=f,i[n+1|0]=f>>>8,i[n+2|0]=f>>>16,i[n+3|0]=f>>>24,i[n+4|0]=u,i[n+5|0]=u>>>8,i[n+6|0]=u>>>16,i[n+7|0]=u>>>24,u=b[e+12|0]|b[e+13|0]<<8|b[e+14|0]<<16|b[e+15|0]<<24,f=b[e+8|0]|b[e+9|0]<<8|b[e+10|0]<<16|b[e+11|0]<<24,i[n+8|0]=f,i[n+9|0]=f>>>8,i[n+10|0]=f>>>16,i[n+11|0]=f>>>24,i[n+12|0]=u,i[n+13|0]=u>>>8,i[n+14|0]=u>>>16,i[n+15|0]=u>>>24,(0|l)<17)break n;for(f=n+l|0,n=n+16|0;u=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,l=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|n]=l,i[n+1|0]=l>>>8,i[n+2|0]=l>>>16,i[n+3|0]=l>>>24,i[n+4|0]=u,i[n+5|0]=u>>>8,i[n+6|0]=u>>>16,i[n+7|0]=u>>>24,u=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,l=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[n+8|0]=l,i[n+9|0]=l>>>8,i[n+10|0]=l>>>16,i[n+11|0]=l>>>24,i[n+12|0]=u,i[n+13|0]=u>>>8,i[n+14|0]=u>>>16,i[n+15|0]=u>>>24,u=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,l=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[n+16|0]=l,i[n+17|0]=l>>>8,i[n+18|0]=l>>>16,i[n+19|0]=l>>>24,i[n+20|0]=u,i[n+21|0]=u>>>8,i[n+22|0]=u>>>16,i[n+23|0]=u>>>24,u=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,l=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[n+24|0]=l,i[n+25|0]=l>>>8,i[n+26|0]=l>>>16,i[n+27|0]=l>>>24,i[n+28|0]=u,i[n+29|0]=u>>>8,i[n+30|0]=u>>>16,i[n+31|0]=u>>>24,e=e+32|0,f>>>0>(n=n+32|0)>>>0;);}else if(f>>>0<=7?(i[0|n]=b[0|e],i[n+1|0]=b[e+1|0],i[n+2|0]=b[e+2|0],i[n+3|0]=b[e+3|0],u=(p=e)+o[3936+(e=f<<2)>>2]|0,f=b[0|u]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,i[n+4|0]=f,i[n+5|0]=f>>>8,i[n+6|0]=f>>>16,i[n+7|0]=f>>>24,e=u-o[e+3968>>2]|0):(u=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,f=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|n]=f,i[n+1|0]=f>>>8,i[n+2|0]=f>>>16,i[n+3|0]=f>>>24,i[n+4|0]=u,i[n+5|0]=u>>>8,i[n+6|0]=u>>>16,i[n+7|0]=u>>>24),!(l>>>0<9)){if(u=n+l|0,((f=n+8|0)-(e=e+8|0)|0)<=15)for(;;)if(l=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,n=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|f]=n,i[f+1|0]=n>>>8,i[f+2|0]=n>>>16,i[f+3|0]=n>>>24,i[f+4|0]=l,i[f+5|0]=l>>>8,i[f+6|0]=l>>>16,i[f+7|0]=l>>>24,e=e+8|0,!(u>>>0>(f=f+8|0)>>>0))break n;if(E=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,p=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|f]=p,i[f+1|0]=p>>>8,i[f+2|0]=p>>>16,i[f+3|0]=p>>>24,i[f+4|0]=E,i[f+5|0]=E>>>8,i[f+6|0]=E>>>16,i[f+7|0]=E>>>24,E=b[e+12|0]|b[e+13|0]<<8|b[e+14|0]<<16|b[e+15|0]<<24,p=b[e+8|0]|b[e+9|0]<<8|b[e+10|0]<<16|b[e+11|0]<<24,i[f+8|0]=p,i[f+9|0]=p>>>8,i[f+10|0]=p>>>16,i[f+11|0]=p>>>24,i[f+12|0]=E,i[f+13|0]=E>>>8,i[f+14|0]=E>>>16,i[f+15|0]=E>>>24,!((0|l)<25))for(n=n+24|0;l=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,f=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|n]=f,i[n+1|0]=f>>>8,i[n+2|0]=f>>>16,i[n+3|0]=f>>>24,i[n+4|0]=l,i[n+5|0]=l>>>8,i[n+6|0]=l>>>16,i[n+7|0]=l>>>24,l=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,f=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[n+8|0]=f,i[n+9|0]=f>>>8,i[n+10|0]=f>>>16,i[n+11|0]=f>>>24,i[n+12|0]=l,i[n+13|0]=l>>>8,i[n+14|0]=l>>>16,i[n+15|0]=l>>>24,l=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,f=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[n+16|0]=f,i[n+17|0]=f>>>8,i[n+18|0]=f>>>16,i[n+19|0]=f>>>24,i[n+20|0]=l,i[n+21|0]=l>>>8,i[n+22|0]=l>>>16,i[n+23|0]=l>>>24,l=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,f=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[n+24|0]=f,i[n+25|0]=f>>>8,i[n+26|0]=f>>>16,i[n+27|0]=f>>>24,i[n+28|0]=l,i[n+29|0]=l>>>8,i[n+30|0]=l>>>16,i[n+31|0]=l>>>24,e=e+32|0,u>>>0>(n=n+32|0)>>>0;);}}else e=o[a+220>>2],o[a+40>>2]=o[a+216>>2],o[a+44>>2]=e,e=o[a+212>>2],o[a+32>>2]=o[a+208>>2],o[a+36>>2]=e,k=S(A,H,a+32|0,a+204|0,V,X,L,G);if(!(k>>>0<=4294967176))break;o[D>>2]=R,o[D+12>>2]=(T+v|0)-h,o[D+8>>2]=h,o[D+4>>2]=s,r=r+1|0,A=A+k|0,f=o[a+60>>2]}w=k;break e}if((0|B)>(0|(p=r-J|0)))for(l=H-32|0;;){f=o[8+(n=(a+128|0)+((3&p)<<4)|0)>>2],e=o[n+12>>2],o[a+216>>2]=f,o[a+220>>2]=e,r=o[n+4>>2],e=o[n>>2],o[a+208>>2]=e,o[a+212>>2]=r;A:if(!((k=e+(E=o[a+204>>2])|0)>>>0>V>>>0||l>>>0<(s=e+(r=o[a+212>>2])|0)+A>>>0)&&s+32>>>0<=H-A>>>0){if(n=e+A|0,T=b[E+4|0]|b[E+5|0]<<8|b[E+6|0]<<16|b[E+7|0]<<24,u=b[0|E]|b[E+1|0]<<8|b[E+2|0]<<16|b[E+3|0]<<24,i[0|A]=u,i[A+1|0]=u>>>8,i[A+2|0]=u>>>16,i[A+3|0]=u>>>24,i[A+4|0]=T,i[A+5|0]=T>>>8,i[A+6|0]=T>>>16,i[A+7|0]=T>>>24,T=b[E+12|0]|b[E+13|0]<<8|b[E+14|0]<<16|b[E+15|0]<<24,u=b[E+8|0]|b[E+9|0]<<8|b[E+10|0]<<16|b[E+11|0]<<24,i[A+8|0]=u,i[A+9|0]=u>>>8,i[A+10|0]=u>>>16,i[A+11|0]=u>>>24,i[A+12|0]=T,i[A+13|0]=T>>>8,i[A+14|0]=T>>>16,i[A+15|0]=T>>>24,!(e>>>0<17||(T=b[E+20|0]|b[E+21|0]<<8|b[E+22|0]<<16|b[E+23|0]<<24,u=b[E+16|0]|b[E+17|0]<<8|b[E+18|0]<<16|b[E+19|0]<<24,i[A+16|0]=u,i[A+17|0]=u>>>8,i[A+18|0]=u>>>16,i[A+19|0]=u>>>24,i[A+20|0]=T,i[A+21|0]=T>>>8,i[A+22|0]=T>>>16,i[A+23|0]=T>>>24,T=b[E+28|0]|b[E+29|0]<<8|b[E+30|0]<<16|b[E+31|0]<<24,u=b[E+24|0]|b[E+25|0]<<8|b[E+26|0]<<16|b[E+27|0]<<24,i[A+24|0]=u,i[A+25|0]=u>>>8,i[A+26|0]=u>>>16,i[A+27|0]=u>>>24,i[A+28|0]=T,i[A+29|0]=T>>>8,i[A+30|0]=T>>>16,i[A+31|0]=T>>>24,(e-16|0)<17)))for(e=E+16|0,h=A+32|0;T=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,u=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|h]=u,i[h+1|0]=u>>>8,i[h+2|0]=u>>>16,i[h+3|0]=u>>>24,i[h+4|0]=T,i[h+5|0]=T>>>8,i[h+6|0]=T>>>16,i[h+7|0]=T>>>24,T=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,u=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[h+8|0]=u,i[h+9|0]=u>>>8,i[h+10|0]=u>>>16,i[h+11|0]=u>>>24,i[h+12|0]=T,i[h+13|0]=T>>>8,i[h+14|0]=T>>>16,i[h+15|0]=T>>>24,T=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,u=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[h+16|0]=u,i[h+17|0]=u>>>8,i[h+18|0]=u>>>16,i[h+19|0]=u>>>24,i[h+20|0]=T,i[h+21|0]=T>>>8,i[h+22|0]=T>>>16,i[h+23|0]=T>>>24,T=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,u=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[h+24|0]=u,i[h+25|0]=u>>>8,i[h+26|0]=u>>>16,i[h+27|0]=u>>>24,i[h+28|0]=T,i[h+29|0]=T>>>8,i[h+30|0]=T>>>16,i[h+31|0]=T>>>24,e=e+32|0,(h=h+32|0)>>>0<n>>>0;);if(e=n-f|0,o[a+204>>2]=k,f>>>0>n-X>>>0){if(f>>>0>n-L>>>0)break e;if(G>>>0>=(e=(k=e-X|0)+G|0)+r>>>0){le(n,e,r);break A}e=le(n,e,0-k|0),r=r+k|0,o[a+212>>2]=r,n=e-k|0,e=X}if(f>>>0>=16){if(k=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,f=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|n]=f,i[n+1|0]=f>>>8,i[n+2|0]=f>>>16,i[n+3|0]=f>>>24,i[n+4|0]=k,i[n+5|0]=k>>>8,i[n+6|0]=k>>>16,i[n+7|0]=k>>>24,k=b[e+12|0]|b[e+13|0]<<8|b[e+14|0]<<16|b[e+15|0]<<24,f=b[e+8|0]|b[e+9|0]<<8|b[e+10|0]<<16|b[e+11|0]<<24,i[n+8|0]=f,i[n+9|0]=f>>>8,i[n+10|0]=f>>>16,i[n+11|0]=f>>>24,i[n+12|0]=k,i[n+13|0]=k>>>8,i[n+14|0]=k>>>16,i[n+15|0]=k>>>24,(0|r)<17)break A;for(r=r+n|0,n=n+16|0;k=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,f=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|n]=f,i[n+1|0]=f>>>8,i[n+2|0]=f>>>16,i[n+3|0]=f>>>24,i[n+4|0]=k,i[n+5|0]=k>>>8,i[n+6|0]=k>>>16,i[n+7|0]=k>>>24,k=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,f=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[n+8|0]=f,i[n+9|0]=f>>>8,i[n+10|0]=f>>>16,i[n+11|0]=f>>>24,i[n+12|0]=k,i[n+13|0]=k>>>8,i[n+14|0]=k>>>16,i[n+15|0]=k>>>24,k=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,f=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[n+16|0]=f,i[n+17|0]=f>>>8,i[n+18|0]=f>>>16,i[n+19|0]=f>>>24,i[n+20|0]=k,i[n+21|0]=k>>>8,i[n+22|0]=k>>>16,i[n+23|0]=k>>>24,k=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,f=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[n+24|0]=f,i[n+25|0]=f>>>8,i[n+26|0]=f>>>16,i[n+27|0]=f>>>24,i[n+28|0]=k,i[n+29|0]=k>>>8,i[n+30|0]=k>>>16,i[n+31|0]=k>>>24,e=e+32|0,r>>>0>(n=n+32|0)>>>0;);}else if(f>>>0<=7?(i[0|n]=b[0|e],i[n+1|0]=b[e+1|0],i[n+2|0]=b[e+2|0],i[n+3|0]=b[e+3|0],k=(k=e)+o[3936+(e=f<<2)>>2]|0,f=b[0|k]|b[k+1|0]<<8|b[k+2|0]<<16|b[k+3|0]<<24,i[n+4|0]=f,i[n+5|0]=f>>>8,i[n+6|0]=f>>>16,i[n+7|0]=f>>>24,e=k-o[e+3968>>2]|0):(k=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,f=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|n]=f,i[n+1|0]=f>>>8,i[n+2|0]=f>>>16,i[n+3|0]=f>>>24,i[n+4|0]=k,i[n+5|0]=k>>>8,i[n+6|0]=k>>>16,i[n+7|0]=k>>>24),!(r>>>0<9)){if(k=r+n|0,((f=n+8|0)-(e=e+8|0)|0)<=15)for(;;)if(n=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,r=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|f]=r,i[f+1|0]=r>>>8,i[f+2|0]=r>>>16,i[f+3|0]=r>>>24,i[f+4|0]=n,i[f+5|0]=n>>>8,i[f+6|0]=n>>>16,i[f+7|0]=n>>>24,e=e+8|0,!(k>>>0>(f=f+8|0)>>>0))break A;if(T=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,u=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|f]=u,i[f+1|0]=u>>>8,i[f+2|0]=u>>>16,i[f+3|0]=u>>>24,i[f+4|0]=T,i[f+5|0]=T>>>8,i[f+6|0]=T>>>16,i[f+7|0]=T>>>24,T=b[e+12|0]|b[e+13|0]<<8|b[e+14|0]<<16|b[e+15|0]<<24,u=b[e+8|0]|b[e+9|0]<<8|b[e+10|0]<<16|b[e+11|0]<<24,i[f+8|0]=u,i[f+9|0]=u>>>8,i[f+10|0]=u>>>16,i[f+11|0]=u>>>24,i[f+12|0]=T,i[f+13|0]=T>>>8,i[f+14|0]=T>>>16,i[f+15|0]=T>>>24,!((0|r)<25))for(n=n+24|0;f=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,r=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|n]=r,i[n+1|0]=r>>>8,i[n+2|0]=r>>>16,i[n+3|0]=r>>>24,i[n+4|0]=f,i[n+5|0]=f>>>8,i[n+6|0]=f>>>16,i[n+7|0]=f>>>24,f=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,r=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[n+8|0]=r,i[n+9|0]=r>>>8,i[n+10|0]=r>>>16,i[n+11|0]=r>>>24,i[n+12|0]=f,i[n+13|0]=f>>>8,i[n+14|0]=f>>>16,i[n+15|0]=f>>>24,f=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,r=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[n+16|0]=r,i[n+17|0]=r>>>8,i[n+18|0]=r>>>16,i[n+19|0]=r>>>24,i[n+20|0]=f,i[n+21|0]=f>>>8,i[n+22|0]=f>>>16,i[n+23|0]=f>>>24,f=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,r=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[n+24|0]=r,i[n+25|0]=r>>>8,i[n+26|0]=r>>>16,i[n+27|0]=r>>>24,i[n+28|0]=f,i[n+29|0]=f>>>8,i[n+30|0]=f>>>16,i[n+31|0]=f>>>24,e=e+32|0,k>>>0>(n=n+32|0)>>>0;);}}else e=o[a+220>>2],o[a+24>>2]=o[a+216>>2],o[a+28>>2]=e,e=o[a+212>>2],o[a+16>>2]=o[a+208>>2],o[a+20>>2]=e,s=S(A,H,a+16|0,a+204|0,V,X,L,G);if(s>>>0>4294967176){w=s;break e}if(A=A+s|0,(0|B)==(0|(p=p+1|0)))break}e=o[a+104>>2],o[j>>2]=o[a+100>>2],o[j+4>>2]=e,o[j+8>>2]=o[a+108>>2],n=o[a+204>>2]}if(w=-70,(e=V-n|0)>>>0>H-A>>>0)break e;if(!A){w=0-t|0;break e}w=(re(A,n,e)+e|0)-t|0;break e}o[e+29588>>2]=0}if(n=o[e+29544>>2],o[a+208>>2]=n,I=t+r|0,Z=o[e+29560>>2]+n|0,B){if(Q=o[e+29368>>2],m=o[e+29364>>2],v=o[e+29360>>2],o[e+29436>>2]=1,o[a+108>>2]=o[e+26676>>2],r=o[4+(A=O=e+26668|0)>>2],o[a+100>>2]=o[A>>2],o[a+104>>2]=r,!k){w=-20;break e}o[a+68>>2]=_,o[a+72>>2]=_+4;t:{r:{if(k>>>0>=4){if(r=_+(f=k-4|0)|0,o[a+64>>2]=r,w=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+56>>2]=w,r=b[(k+_|0)-1|0])break r;w=-20;break e}o[a+64>>2]=_,w=b[0|_],o[a+56>>2]=w;A:switch(k-2|0){case 1:w=b[_+2|0]<<16|w;case 0:w=(b[_+1|0]<<8)+w|0,o[a+56>>2]=w}if(!(r=b[(k+_|0)-1|0])){w=-20;break e}f=0,r=9+(g(r)-(k<<3)|0)|0;break t}if(k>>>0>4294967176){w=-20;break e}r=g(r)-23|0}for(s=o[e>>2],n=w>>>0-(r=(A=o[s+4>>2])+r|0)&o[3776+(A<<2)>>2],o[a+76>>2]=n,r>>>0>32?R=r:(0|f)>=4?(R=7&r,o[a+60>>2]=R,r=_+(f=f-(r>>>3|0)|0)|0,o[a+64>>2]=r,w=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+56>>2]=w):f?(k=_+(f=f-(A=_>>>0>(f+_|0)-(A=r>>>3|0)>>>0?f:A)|0)|0,o[a+64>>2]=k,w=b[0|k]|b[k+1|0]<<8|b[k+2|0]<<16|b[k+3|0]<<24,o[a+56>>2]=w,R=r-(A<<3)|0):(f=0,R=r),r=s+8|0,o[a+80>>2]=r,k=o[e+8>>2],h=w>>>0-(A=(s=o[k+4>>2])+R|0)&o[3776+(s<<2)>>2],o[a+84>>2]=h,A>>>0>32||((0|f)>=4?(s=_+(f=f-(A>>>3|0)|0)|0,o[a+64>>2]=s,w=b[0|s]|b[s+1|0]<<8|b[s+2|0]<<16|b[s+3|0]<<24,o[a+56>>2]=w,A&=7):f?(l=_+(f=f-(s=_>>>0>(f+_|0)-(s=A>>>3|0)>>>0?f:s)|0)|0,o[a+64>>2]=l,w=b[0|l]|b[l+1|0]<<8|b[l+2|0]<<16|b[l+3|0]<<24,o[a+56>>2]=w,A=A-(s<<3)|0):f=0),R=k+8|0,o[a+88>>2]=R,s=o[e+4>>2],A=(e=o[s+4>>2])+A|0,o[a+60>>2]=A,e=o[3776+(e<<2)>>2]&w>>>0-A,o[a+92>>2]=e,A>>>0>32?f=A:(k=f+_|0,(0|f)>=4?(f=7&A,o[a+60>>2]=f,A=k-(A>>>3|0)|0,o[a+64>>2]=A,o[a+56>>2]=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24):f?(f=(l=A)-((A=_>>>0>k-(A=A>>>3|0)>>>0?f:A)<<3)|0,o[a+60>>2]=f,A=k-A|0,o[a+64>>2]=A,o[a+56>>2]=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24):f=A),p=s+8|0,o[a+96>>2]=p,W=I-32|0,A=t,w=0;;){s=n=o[4+(r=(n<<3)+r|0)>>2],C=255&(X=(65535&n)<<16|(u=o[r>>2])>>>16),r=n=o[4+(e=(e<<3)+p|0)>>2],N=255&(D=(65535&n)<<16|(k=o[e>>2])>>>16),n=o[4+(e=(h<<3)+R|0)>>2],l=o[e>>2],j=e=n,T=r,n=s;t:if((E=255&(e=(65535&e)<<16|l>>>16))>>>0>=2){r:if(!c|E>>>0<25)p=j+(o[a+56>>2]<<f>>>0-e|0)|0,(f=f+E|0)>>>0>32?e=f:(R=o[a+64>>2])>>>0>=d[a+72>>2]?(e=7&f,o[a+60>>2]=e,f=R-(f>>>3|0)|0,o[a+64>>2]=f,o[a+56>>2]=b[0|f]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24):(0|(E=o[a+68>>2]))!=(0|R)?(e=(j=f)-((f=E>>>0>R-(e=f>>>3|0)>>>0?R-E|0:e)<<3)|0,o[a+60>>2]=e,f=R-f|0,o[a+64>>2]=f,o[a+56>>2]=b[0|f]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24):e=f;else{if(x=E-(e=(e=32-f|0)>>>0>E>>>0?E:e)|0,p=(R=o[a+56>>2])<<f>>>0-e<<x,(f=e+f|0)>>>0>32||((M=o[a+64>>2])>>>0>=d[a+72>>2]?(e=M-(f>>>3|0)|0,o[a+64>>2]=e,R=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,o[a+56>>2]=R,f&=7):(0|(E=o[a+68>>2]))!=(0|M)&&(E=M-(e=E>>>0>M-(e=f>>>3|0)>>>0?M-E|0:e)|0,o[a+64>>2]=E,R=b[0|E]|b[E+1|0]<<8|b[E+2|0]<<16|b[E+3|0]<<24,o[a+56>>2]=R,f=f-(e<<3)|0)),p=p+j|0,!x){e=f;break r}e=f+x|0,o[a+60>>2]=e,p=(R<<f>>>0-x|0)+p|0}E=o[a+104>>2],f=o[a+100>>2],o[a+100>>2]=p,o[a+104>>2]=f,o[a+108>>2]=E}else if(E){e=f+1|0,o[a+60>>2]=e;r:{if(3!=(0|(E=j+!n+(o[a+56>>2]<<f>>>31)|0))){if(p=!(f=o[100+((E<<2)+a|0)>>2])+f|0,1==(0|E))break r}else p=!(f=o[a+100>>2]-1|0)+f|0;o[a+108>>2]=o[a+104>>2]}o[a+104>>2]=o[a+100>>2],o[a+100>>2]=p}else{if(n){p=o[a+100>>2],e=f;break t}p=o[a+104>>2],o[a+104>>2]=o[a+100>>2],o[a+100>>2]=p,e=f}f=T,N&&(f=(o[a+56>>2]<<e>>>0-D|0)+f|0,e=e+N|0),N+C>>>0<20|e>>>0>32||((D=o[a+64>>2])>>>0>=d[a+72>>2]?(T=D-(e>>>3|0)|0,o[a+64>>2]=T,o[a+56>>2]=b[0|T]|b[T+1|0]<<8|b[T+2|0]<<16|b[T+3|0]<<24,e&=7):(0|(E=o[a+68>>2]))!=(0|D)&&(E=D-(T=E>>>0>D-(T=e>>>3|0)>>>0?D-E|0:T)|0,o[a+64>>2]=E,o[a+56>>2]=b[0|E]|b[E+1|0]<<8|b[E+2|0]<<16|b[E+3|0]<<24,e=e-(T<<3)|0)),T=u,D=(16777215&r)<<8|(E=k)>>>24,s=(16777215&s)<<8|u>>>24,C&&(n=(o[a+56>>2]<<e>>>0-X|0)+n|0,e=e+C|0),e>>>0>32?k=e:(u=o[a+64>>2])>>>0>=d[a+72>>2]?(k=7&e,o[a+60>>2]=k,e=u-(e>>>3|0)|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):(0|(k=o[a+68>>2]))!=(0|u)?(k=u-(r=k>>>0>u-(r=e>>>3|0)>>>0?u-k|0:r)|0,o[a+64>>2]=k,o[a+56>>2]=b[0|k]|b[k+1|0]<<8|b[k+2|0]<<16|b[k+3|0]<<24,k=e-(r<<3)|0):k=e,r=o[a+56>>2],e=(s&=255)+k|0,o[a+76>>2]=(r>>>0-e&o[3776+(s<<2)>>2])+(65535&T),e=(s=255&D)+e|0,o[a+92>>2]=(r>>>0-e&o[3776+(s<<2)>>2])+(65535&E),e>>>0>32||((k=o[a+64>>2])>>>0>=d[a+72>>2]?(r=k-(e>>>3|0)|0,o[a+64>>2]=r,r=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+56>>2]=r,e&=7):(0|(s=o[a+68>>2]))!=(0|k)&&(r=k-(s=s>>>0>k-(r=e>>>3|0)>>>0?k-s|0:r)|0,o[a+64>>2]=r,r=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,o[a+56>>2]=r,e=e-(s<<3)|0)),e=(s=l>>>24|0)+e|0,o[a+60>>2]=e,o[a+84>>2]=(o[3776+(s<<2)>>2]&r>>>0-e)+(65535&l),o[a+128>>2]=n,u=o[a+208>>2],o[a+136>>2]=p,o[a+132>>2]=f;t:if(!(W>>>0<(k=n+f|0)+A>>>0||(s=n+u|0)>>>0>Z>>>0)&&k+32>>>0<=I-A>>>0){if(r=A+n|0,l=b[u+4|0]|b[u+5|0]<<8|b[u+6|0]<<16|b[u+7|0]<<24,e=b[0|u]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,i[0|A]=e,i[A+1|0]=e>>>8,i[A+2|0]=e>>>16,i[A+3|0]=e>>>24,i[A+4|0]=l,i[A+5|0]=l>>>8,i[A+6|0]=l>>>16,i[A+7|0]=l>>>24,l=b[u+12|0]|b[u+13|0]<<8|b[u+14|0]<<16|b[u+15|0]<<24,e=b[u+8|0]|b[u+9|0]<<8|b[u+10|0]<<16|b[u+11|0]<<24,i[A+8|0]=e,i[A+9|0]=e>>>8,i[A+10|0]=e>>>16,i[A+11|0]=e>>>24,i[A+12|0]=l,i[A+13|0]=l>>>8,i[A+14|0]=l>>>16,i[A+15|0]=l>>>24,!(n>>>0<17||(l=b[u+20|0]|b[u+21|0]<<8|b[u+22|0]<<16|b[u+23|0]<<24,e=b[u+16|0]|b[u+17|0]<<8|b[u+18|0]<<16|b[u+19|0]<<24,i[A+16|0]=e,i[A+17|0]=e>>>8,i[A+18|0]=e>>>16,i[A+19|0]=e>>>24,i[A+20|0]=l,i[A+21|0]=l>>>8,i[A+22|0]=l>>>16,i[A+23|0]=l>>>24,l=b[u+28|0]|b[u+29|0]<<8|b[u+30|0]<<16|b[u+31|0]<<24,e=b[u+24|0]|b[u+25|0]<<8|b[u+26|0]<<16|b[u+27|0]<<24,i[A+24|0]=e,i[A+25|0]=e>>>8,i[A+26|0]=e>>>16,i[A+27|0]=e>>>24,i[A+28|0]=l,i[A+29|0]=l>>>8,i[A+30|0]=l>>>16,i[A+31|0]=l>>>24,(n-16|0)<17)))for(e=u+16|0,n=A+32|0;u=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,l=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|n]=l,i[n+1|0]=l>>>8,i[n+2|0]=l>>>16,i[n+3|0]=l>>>24,i[n+4|0]=u,i[n+5|0]=u>>>8,i[n+6|0]=u>>>16,i[n+7|0]=u>>>24,u=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,l=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[n+8|0]=l,i[n+9|0]=l>>>8,i[n+10|0]=l>>>16,i[n+11|0]=l>>>24,i[n+12|0]=u,i[n+13|0]=u>>>8,i[n+14|0]=u>>>16,i[n+15|0]=u>>>24,u=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,l=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[n+16|0]=l,i[n+17|0]=l>>>8,i[n+18|0]=l>>>16,i[n+19|0]=l>>>24,i[n+20|0]=u,i[n+21|0]=u>>>8,i[n+22|0]=u>>>16,i[n+23|0]=u>>>24,u=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,l=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[n+24|0]=l,i[n+25|0]=l>>>8,i[n+26|0]=l>>>16,i[n+27|0]=l>>>24,i[n+28|0]=u,i[n+29|0]=u>>>8,i[n+30|0]=u>>>16,i[n+31|0]=u>>>24,e=e+32|0,(n=n+32|0)>>>0<r>>>0;);if(e=r-p|0,o[a+208>>2]=s,r-v>>>0<p>>>0){if(r-m>>>0<p>>>0){k=-20;break t}if(Q>>>0>=(e=(n=e-v|0)+Q|0)+f>>>0){le(r,e,f);break t}e=le(r,e,0-n|0),f=n+f|0,o[a+132>>2]=f,r=e-n|0,e=v}if(p>>>0>=16){if(s=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,n=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|r]=n,i[r+1|0]=n>>>8,i[r+2|0]=n>>>16,i[r+3|0]=n>>>24,i[r+4|0]=s,i[r+5|0]=s>>>8,i[r+6|0]=s>>>16,i[r+7|0]=s>>>24,s=b[e+12|0]|b[e+13|0]<<8|b[e+14|0]<<16|b[e+15|0]<<24,n=b[e+8|0]|b[e+9|0]<<8|b[e+10|0]<<16|b[e+11|0]<<24,i[r+8|0]=n,i[r+9|0]=n>>>8,i[r+10|0]=n>>>16,i[r+11|0]=n>>>24,i[r+12|0]=s,i[r+13|0]=s>>>8,i[r+14|0]=s>>>16,i[r+15|0]=s>>>24,(0|f)<17)break t;for(f=r+f|0,n=r+16|0;s=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,r=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|n]=r,i[n+1|0]=r>>>8,i[n+2|0]=r>>>16,i[n+3|0]=r>>>24,i[n+4|0]=s,i[n+5|0]=s>>>8,i[n+6|0]=s>>>16,i[n+7|0]=s>>>24,s=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,r=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[n+8|0]=r,i[n+9|0]=r>>>8,i[n+10|0]=r>>>16,i[n+11|0]=r>>>24,i[n+12|0]=s,i[n+13|0]=s>>>8,i[n+14|0]=s>>>16,i[n+15|0]=s>>>24,s=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,r=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[n+16|0]=r,i[n+17|0]=r>>>8,i[n+18|0]=r>>>16,i[n+19|0]=r>>>24,i[n+20|0]=s,i[n+21|0]=s>>>8,i[n+22|0]=s>>>16,i[n+23|0]=s>>>24,s=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,r=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[n+24|0]=r,i[n+25|0]=r>>>8,i[n+26|0]=r>>>16,i[n+27|0]=r>>>24,i[n+28|0]=s,i[n+29|0]=s>>>8,i[n+30|0]=s>>>16,i[n+31|0]=s>>>24,e=e+32|0,f>>>0>(n=n+32|0)>>>0;);}else if(p>>>0<=7?(i[0|r]=b[0|e],i[r+1|0]=b[e+1|0],i[r+2|0]=b[e+2|0],i[r+3|0]=b[e+3|0],s=(n=e)+o[3936+(e=p<<2)>>2]|0,n=b[0|s]|b[s+1|0]<<8|b[s+2|0]<<16|b[s+3|0]<<24,i[r+4|0]=n,i[r+5|0]=n>>>8,i[r+6|0]=n>>>16,i[r+7|0]=n>>>24,e=s-o[e+3968>>2]|0):(s=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,n=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|r]=n,i[r+1|0]=n>>>8,i[r+2|0]=n>>>16,i[r+3|0]=n>>>24,i[r+4|0]=s,i[r+5|0]=s>>>8,i[r+6|0]=s>>>16,i[r+7|0]=s>>>24),!(f>>>0<9)){if(s=r+f|0,((n=r+8|0)-(e=e+8|0)|0)<=15)for(;;)if(f=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,r=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|n]=r,i[n+1|0]=r>>>8,i[n+2|0]=r>>>16,i[n+3|0]=r>>>24,i[n+4|0]=f,i[n+5|0]=f>>>8,i[n+6|0]=f>>>16,i[n+7|0]=f>>>24,e=e+8|0,!(s>>>0>(n=n+8|0)>>>0))break t;if(u=b[e+4|0]|b[e+5|0]<<8|b[e+6|0]<<16|b[e+7|0]<<24,l=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[0|n]=l,i[n+1|0]=l>>>8,i[n+2|0]=l>>>16,i[n+3|0]=l>>>24,i[n+4|0]=u,i[n+5|0]=u>>>8,i[n+6|0]=u>>>16,i[n+7|0]=u>>>24,u=b[e+12|0]|b[e+13|0]<<8|b[e+14|0]<<16|b[e+15|0]<<24,l=b[e+8|0]|b[e+9|0]<<8|b[e+10|0]<<16|b[e+11|0]<<24,i[n+8|0]=l,i[n+9|0]=l>>>8,i[n+10|0]=l>>>16,i[n+11|0]=l>>>24,i[n+12|0]=u,i[n+13|0]=u>>>8,i[n+14|0]=u>>>16,i[n+15|0]=u>>>24,!((0|f)<25))for(n=r+24|0;f=b[e+20|0]|b[e+21|0]<<8|b[e+22|0]<<16|b[e+23|0]<<24,r=b[e+16|0]|b[e+17|0]<<8|b[e+18|0]<<16|b[e+19|0]<<24,i[0|n]=r,i[n+1|0]=r>>>8,i[n+2|0]=r>>>16,i[n+3|0]=r>>>24,i[n+4|0]=f,i[n+5|0]=f>>>8,i[n+6|0]=f>>>16,i[n+7|0]=f>>>24,f=b[e+28|0]|b[e+29|0]<<8|b[e+30|0]<<16|b[e+31|0]<<24,r=b[e+24|0]|b[e+25|0]<<8|b[e+26|0]<<16|b[e+27|0]<<24,i[n+8|0]=r,i[n+9|0]=r>>>8,i[n+10|0]=r>>>16,i[n+11|0]=r>>>24,i[n+12|0]=f,i[n+13|0]=f>>>8,i[n+14|0]=f>>>16,i[n+15|0]=f>>>24,f=b[e+36|0]|b[e+37|0]<<8|b[e+38|0]<<16|b[e+39|0]<<24,r=b[e+32|0]|b[e+33|0]<<8|b[e+34|0]<<16|b[e+35|0]<<24,i[n+16|0]=r,i[n+17|0]=r>>>8,i[n+18|0]=r>>>16,i[n+19|0]=r>>>24,i[n+20|0]=f,i[n+21|0]=f>>>8,i[n+22|0]=f>>>16,i[n+23|0]=f>>>24,f=b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,r=b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,i[n+24|0]=r,i[n+25|0]=r>>>8,i[n+26|0]=r>>>16,i[n+27|0]=r>>>24,i[n+28|0]=f,i[n+29|0]=f>>>8,i[n+30|0]=f>>>16,i[n+31|0]=f>>>24,e=e+32|0,s>>>0>(n=n+32|0)>>>0;);}}else e=o[a+140>>2],o[a+8>>2]=o[a+136>>2],o[a+12>>2]=e,e=o[a+132>>2],o[a>>2]=o[a+128>>2],o[a+4>>2]=e,k=S(A,I,a,a+208|0,Z,v,m,Q);if((e=o[a+60>>2])>>>0>32?f=e:(n=o[a+64>>2])>>>0>=d[a+72>>2]?(f=7&e,o[a+60>>2]=f,e=n-(e>>>3|0)|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):(0|(r=o[a+68>>2]))!=(0|n)?(f=(f=e)-((e=r>>>0>n-(e=e>>>3|0)>>>0?n-r|0:e)<<3)|0,o[a+60>>2]=f,e=n-e|0,o[a+64>>2]=e,o[a+56>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24):f=e,r=B-1|0,B=(e=k>>>0<4294967177)?r:B,w=e?w:k,A=A+k|0,!r|k>>>0>4294967176)break;h=o[a+84>>2],R=o[a+88>>2],e=o[a+92>>2],p=o[a+96>>2],n=o[a+76>>2],r=o[a+80>>2]}if(w>>>0>4294967176)break e;if(B){w=-20;break e}if(f>>>0<=32){if(32!=(0|f)){w=-20;break e}if((e=o[a+64>>2])>>>0>=d[a+72>>2]){w=-20;break e}if(w=-20,(0|e)!=o[a+68>>2])break e}e=o[a+104>>2],o[O>>2]=o[a+100>>2],o[O+4>>2]=e,o[O+8>>2]=o[a+108>>2],n=o[a+208>>2]}else A=t;w=-70,(e=Z-n|0)>>>0>I-A>>>0||(w=(e=A?re(A,n,e)+e|0:0)-t|0)}return y=a+224|0,w}function v(e){var t,r=0,A=0,n=0,a=0,c=0,f=0;y=t=y-704|0,A=dr(t+688|0,6044),n=o[A+4>>2],o[t+328>>2]=o[A>>2],o[t+332>>2]=n,c=t,f=rt(e,t+328|0),i[c+703|0]=f;e:if(!(o[e+4>>2]-o[e>>2]>>>0<2)){t:{r:{A:{n:{i:{a:{o:{c:{f:{s:{u:{b:{l:{d:{k:{E:{p:{T:{g:{h:{O:{_:switch(A=o[e>>2],(n=i[0|A])-76|0){case 36:break f;case 35:break s;case 34:break u;case 33:break b;case 32:break l;case 29:break d;case 27:break k;case 25:break E;case 24:break p;case 23:break T;case 21:break g;case 26:break h;case 8:break O;case 0:break _;case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 9:case 10:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 22:case 28:case 30:case 31:break A;case 40:break i;case 39:break a;case 38:break o;case 37:break c;default:break n}r=G(e);break e}r=Te(e);break e}h:{if(r=o[e>>2],112!=(0|(r=(A=o[e+4>>2]-r>>>0>1?i[r+1|0]:0)<<24>>24))){if(76!=(255&r))break h;if(r=o[e>>2],((A=o[e+4>>2]-r>>>0>2?i[r+2|0]:0)<<24>>24)-48>>>0>9)break h}r=Me(e);break e}r=function(e){var t=0,r=0,A=0,n=0,a=0,c=0;y=t=y-528|0;h:if(ir(e,102)){A=t,c=(n=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24,n=1;O:if(76!=(0|c)){if(114!=(0|(r=255&c))){if(n=1,108==(0|r))break O;if(82!=(0|r))break h}n=0}if(i[A+527|0]=n,o[e>>2]=o[e>>2]+1,o[(A=n=t+512|0)>>2]=0,o[A+4>>2]=0,r=dr(t+504|0,8376),A=o[r+4>>2],o[t+240>>2]=o[r>>2],o[t+244>>2]=A,rt(e,t+240|0))dr(t+496|0,6047);else if(r=dr(t+488|0,8379),A=o[r+4>>2],o[t+232>>2]=o[r>>2],o[t+236>>2]=A,rt(e,t+232|0))dr(t+496|0,6050);else if(r=dr(t+480|0,8382),A=o[r+4>>2],o[t+224>>2]=o[r>>2],o[t+228>>2]=A,rt(e,t+224|0))dr(t+496|0,6052);else if(r=dr(t+472|0,8385),A=o[r+4>>2],o[t+216>>2]=o[r>>2],o[t+220>>2]=A,rt(e,t+216|0))dr(t+496|0,6055);else if(r=dr(t+464|0,8388),A=o[r+4>>2],o[t+208>>2]=o[r>>2],o[t+212>>2]=A,rt(e,t+208|0))dr(t+496|0,6078);else if(r=dr(t+456|0,8391),A=o[r+4>>2],o[t+200>>2]=o[r>>2],o[t+204>>2]=A,rt(e,t+200|0))dr(t+496|0,6097);else if(r=dr(t+448|0,8394),A=o[r+4>>2],o[t+192>>2]=o[r>>2],o[t+196>>2]=A,rt(e,t+192|0))dr(t+496|0,6102);else if(r=dr(t+440|0,8397),A=o[r+4>>2],o[t+184>>2]=o[r>>2],o[t+188>>2]=A,rt(e,t+184|0))dr(t+496|0,6104);else if(r=dr(t+432|0,8400),A=o[r+4>>2],o[t+176>>2]=o[r>>2],o[t+180>>2]=A,rt(e,t+176|0))dr(t+496|0,6107);else if(r=dr(t+424|0,8403),A=o[r+4>>2],o[t+168>>2]=o[r>>2],o[t+172>>2]=A,rt(e,t+168|0))dr(t+496|0,6109);else if(r=dr(t+416|0,8406),A=o[r+4>>2],o[t+160>>2]=o[r>>2],o[t+164>>2]=A,rt(e,t+160|0))dr(t+496|0,6112);else if(r=dr(t+408|0,8409),A=o[r+4>>2],o[t+152>>2]=o[r>>2],o[t+156>>2]=A,rt(e,t+152|0))dr(t+496|0,6115);else if(r=dr(t+400|0,8412),A=o[r+4>>2],o[t+144>>2]=o[r>>2],o[t+148>>2]=A,rt(e,t+144|0))dr(t+496|0,6118);else if(r=dr(t+392|0,8415),A=o[r+4>>2],o[t+136>>2]=o[r>>2],o[t+140>>2]=A,rt(e,t+136|0))dr(t+496|0,6120);else if(r=dr(t+384|0,8418),A=o[r+4>>2],o[t+128>>2]=o[r>>2],o[t+132>>2]=A,rt(e,t+128|0))dr(t+496|0,6123);else if(r=dr(t+376|0,8421),A=o[r+4>>2],o[t+120>>2]=o[r>>2],o[t+124>>2]=A,rt(e,t+120|0))dr(t+496|0,6126);else if(r=dr(t+368|0,8424),A=o[r+4>>2],o[t+112>>2]=o[r>>2],o[t+116>>2]=A,rt(e,t+112|0))dr(t+496|0,6130);else if(r=dr(t+360|0,8427),A=o[r+4>>2],o[t+104>>2]=o[r>>2],o[t+108>>2]=A,rt(e,t+104|0))dr(t+496|0,6132);else if(r=dr(t+352|0,8430),A=o[r+4>>2],o[t+96>>2]=o[r>>2],o[t+100>>2]=A,rt(e,t+96|0))dr(t+496|0,6134);else if(r=dr(t+344|0,8433),A=o[r+4>>2],o[t+88>>2]=o[r>>2],o[t+92>>2]=A,rt(e,t+88|0))dr(t+496|0,6095);else if(r=dr(t+336|0,8436),A=o[r+4>>2],o[t+80>>2]=o[r>>2],o[t+84>>2]=A,rt(e,t+80|0))dr(t+496|0,6137);else if(r=dr(t+328|0,8439),A=o[r+4>>2],o[t+72>>2]=o[r>>2],o[t+76>>2]=A,rt(e,t+72|0))dr(t+496|0,6143);else if(r=dr(t+320|0,8442),A=o[r+4>>2],o[t+64>>2]=o[r>>2],o[t+68>>2]=A,rt(e,t- -64|0))dr(t+496|0,6159);else if(r=dr(t+312|0,8445),A=o[r+4>>2],o[t+56>>2]=o[r>>2],o[t+60>>2]=A,rt(e,t+56|0))dr(t+496|0,6162);else if(r=dr(t+304|0,8448),A=o[r+4>>2],o[t+48>>2]=o[r>>2],o[t+52>>2]=A,rt(e,t+48|0))dr(t+496|0,6164);else if(r=dr(t+296|0,8451),A=o[r+4>>2],o[t+40>>2]=o[r>>2],o[t+44>>2]=A,rt(e,t+40|0))dr(t+496|0,6171);else if(r=dr(t+288|0,8454),A=o[r+4>>2],o[t+32>>2]=o[r>>2],o[t+36>>2]=A,rt(e,t+32|0))dr(t+496|0,6173);else if(r=dr(t+280|0,8457),A=o[r+4>>2],o[t+24>>2]=o[r>>2],o[t+28>>2]=A,rt(e,t+24|0))dr(t+496|0,6199);else if(r=dr(t+272|0,8460),A=o[r+4>>2],o[t+16>>2]=o[r>>2],o[t+20>>2]=A,rt(e,t+16|0))dr(t+496|0,6201);else if(r=dr(t+264|0,8463),A=o[r+4>>2],o[t+8>>2]=o[r>>2],o[t+12>>2]=A,rt(e,t+8|0))dr(t+496|0,6204);else{if(r=dr(t+256|0,8466),A=o[r+4>>2],o[t>>2]=o[r>>2],o[t+4>>2]=A,!rt(e,t))break h;dr(t+496|0,6207)}A=o[t+500>>2],o[t+512>>2]=o[t+496>>2],o[t+516>>2]=A,A=v(e),o[t+496>>2]=A,o[t+252>>2]=0,A&&(76!=(255&c)&&82!=(0|c)||(a=v(e),o[t+252>>2]=a,a)?(!b[t+527|0]|!a||(A=o[t+496>>2],o[t+496>>2]=o[t+252>>2],o[t+252>>2]=A),a=function(e,t,r,A,n){var a=0,c=0;return y=a=y-16|0,c=kt(e,28),t=b[0|t],e=o[r+4>>2],r=o[r>>2],o[a+8>>2]=r,o[a+12>>2]=e,n=o[n>>2],A=o[A>>2],o[a>>2]=r,o[a+4>>2]=e,e=function(e,t,r,A,n){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=62,o[e>>2]=5508,o[e+12>>2]=n,o[e+8>>2]=A,o[e>>2]=8480,A=o[r+4>>2],r=o[r>>2],i[e+24|0]=t,o[e+16>>2]=r,o[e+20>>2]=A,e}(c,t,a,A,n),y=a+16|0,e}(e+416|0,t+527|0,n,t+496|0,t+252|0)):a=0)}return y=t+528|0,a}(e);break e}g:{h:{O:{_:{w:{y:{R:switch((n=i[A+1|0])-97|0){case 1:case 2:break e;case 3:break w;case 0:break y}if(78==(0|n))break O;if(83==(0|n))break h;if(110==(0|n))break _;if(116==(0|n))break g;if(122!=(0|n))break e;if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;r=Zr(e,6057,t+632|0);break e}o[e>>2]=A+2,r=dr(t+680|0,6047),A=o[r+4>>2],o[t+16>>2]=o[r>>2],o[t+20>>2]=A,r=_t(e,t+16|0);break e}o[e>>2]=A+2,r=dr(t+672|0,6050),A=o[r+4>>2],o[t+24>>2]=o[r>>2],o[t+28>>2]=A,r=Jt(e,t+24|0);break e}o[e>>2]=A+2,r=dr(t+664|0,6050),A=o[r+4>>2],o[t+32>>2]=o[r>>2],o[t+36>>2]=A,r=_t(e,t+32|0);break e}o[e>>2]=A+2,r=dr(t+656|0,6052),A=o[r+4>>2],o[t+40>>2]=o[r>>2],o[t+44>>2]=A,r=_t(e,t+40|0);break e}o[e>>2]=A+2,r=dr(t+648|0,6055),A=o[r+4>>2],o[t+48>>2]=o[r>>2],o[t+52>>2]=A,r=_t(e,t+48|0);break e}if(o[e>>2]=A+2,r=X(e),o[t+632>>2]=r,!r)break t;r=Zr(e,6057,t+632|0);break e}T:{g:{h:{O:{_:switch((n=i[A+1|0])-108|0){case 2:break e;case 3:break g;case 1:break h;case 0:break O}if(118==(0|n))break T;if(99!=(0|n))break e;if(o[e>>2]=A+2,r=X(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=vt(e+416|0,6067,t+632|0,t+644|0);break e}if(o[e>>2]=A+2,A=v(e),o[t+644>>2]=A,!A)break e;for(n=o[4+(A=e+8|0)>>2]-o[A>>2]>>2;!ir(e,69);){if(a=v(e),o[t+632>>2]=a,!a)break e;Gt(A,t+632|0)}or(t+632|0,e,n),r=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=55,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=9028,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}(e+416|0,t+644|0,t+632|0);break e}o[e>>2]=A+2,r=dr(t+624|0,6078),A=o[r+4>>2],o[t+56>>2]=o[r>>2],o[t+60>>2]=A,r=_t(e,t+56|0);break e}o[e>>2]=A+2,r=dr(t+616|0,6080),A=o[r+4>>2],o[t+64>>2]=o[r>>2],o[t+68>>2]=A,r=Jt(e,t- -64|0);break e}r=function(e){var t=0,r=0,A=0,n=0,a=0;y=t=y-48|0,r=dr(t+40|0,9120),A=o[r+4>>2],o[t+8>>2]=o[r>>2],o[t+12>>2]=A;T:if(rt(e,t+8|0)&&(r=Nt(t+24|0,e+388|0,0),A=X(e),o[t+36>>2]=A,b[r+5|0]&&(i[o[r>>2]]=b[r+4|0]),A))if(ir(e,95)){for(A=o[4+(r=e+8|0)>>2]-o[r>>2]>>2;!ir(e,69);){if(n=v(e),o[t+24>>2]=n,!n)break T;Gt(r,t+24|0)}or(t+24|0,e,A),a=Gr(e,t+36|0,t+24|0)}else r=v(e),o[t+20>>2]=r,r&&(ar(t+24|0,e,t+20|0,t+24|0),a=Gr(e,t+36|0,t+24|0));return y=t+48|0,a}(e);break e}p:{T:{g:{h:{O:{_:{w:switch((n=i[A+1|0])-108|0){case 1:case 3:case 4:case 5:case 6:case 9:break e;case 10:break T;case 8:break g;case 7:break h;case 2:break O;case 0:break _}w:switch(n-97|0){case 0:if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;i[t+644|0]=1,r=gr(e,t+632|0,t+703|0,t+644|0);break e;case 2:if(o[e>>2]=A+2,r=X(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=vt(e+416|0,6082,t+632|0,t+644|0);break e;case 1:case 3:break e;case 4:break w;default:break p}o[e>>2]=A+2,r=dr(t+608|0,6095),A=o[r+4>>2],o[t+72>>2]=o[r>>2],o[t+76>>2]=A,r=Jt(e,t+72|0);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;i[t+644|0]=0,r=gr(e,t+632|0,t+703|0,t+644|0);break e}r=q(e);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=xr(e,t+632|0,6097,t+644|0);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=mt(e+416|0,t+632|0,6100,t+644|0);break e}o[e>>2]=A+2,r=dr(t+600|0,6102),A=o[r+4>>2],o[t+80>>2]=o[r>>2],o[t+84>>2]=A,r=_t(e,t+80|0);break e}if(86!=(0|n))break e;o[e>>2]=A+2,r=dr(t+592|0,6104),A=o[r+4>>2],o[t+88>>2]=o[r>>2],o[t+92>>2]=A,r=_t(e,t+88|0);break e}E:{p:{T:switch((n=i[A+1|0])-111|0){case 1:break e;case 2:break E;case 0:break T;default:break p}o[e>>2]=A+2,r=dr(t+584|0,6107),A=o[r+4>>2],o[t+96>>2]=o[r>>2],o[t+100>>2]=A,r=_t(e,t+96|0);break e}if(79!=(0|n))break e;o[e>>2]=A+2,r=dr(t+576|0,6109),A=o[r+4>>2],o[t+104>>2]=o[r>>2],o[t+108>>2]=A,r=_t(e,t+104|0);break e}o[e>>2]=A+2,r=dr(t+568|0,6112),A=o[r+4>>2],o[t+112>>2]=o[r>>2],o[t+116>>2]=A,r=_t(e,t+112|0);break e}if(116!=(0|(n=i[A+1|0]))){if(101!=(0|n))break e;o[e>>2]=A+2,r=dr(t+560|0,6115),A=o[r+4>>2],o[t+120>>2]=o[r>>2],o[t+124>>2]=A,r=_t(e,t+120|0);break e}o[e>>2]=A+2,r=dr(t+552|0,6118),A=o[r+4>>2],o[t+128>>2]=o[r>>2],o[t+132>>2]=A,r=_t(e,t+128|0);break e}if(108!=(0|(n=i[A+1|0]))){if(120!=(0|n))break e;if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=48,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=10572,e}(kt(e+416|0,16),o[t+632>>2],o[t+644>>2]);break e}for(o[e>>2]=A+2,n=o[4+(A=e+8|0)>>2]-o[A>>2]>>2;!ir(e,69);){if(a=de(e),o[t+632>>2]=a,!a)break e;Gt(A,t+632|0)}o[t+644>>2]=0,or(t+632|0,e,n),r=function(e,t){var r=0,A=0;return y=r=y-16|0,A=kt(e,20),e=o[t+4>>2],t=o[t>>2],o[r>>2]=t,o[r+4>>2]=e,o[r+8>>2]=t,o[r+12>>2]=e,e=St(A,0,r),y=r+16|0,e}(e+416|0,t+632|0);break e}l:{d:{k:{E:switch((n=i[A+1|0])-115|0){case 1:break l;case 0:break k}if(83==(0|n))break d;if(101!=(0|n))break e;o[e>>2]=A+2,r=dr(t+544|0,6120),A=o[r+4>>2],o[t+136>>2]=o[r>>2],o[t+140>>2]=A,r=_t(e,t+136|0);break e}o[e>>2]=A+2,r=dr(t+536|0,6123),A=o[r+4>>2],o[t+144>>2]=o[r>>2],o[t+148>>2]=A,r=_t(e,t+144|0);break e}o[e>>2]=A+2,r=dr(t+528|0,6126),A=o[r+4>>2],o[t+152>>2]=o[r>>2],o[t+156>>2]=A,r=_t(e,t+152|0);break e}o[e>>2]=A+2,r=dr(t+520|0,6130),A=o[r+4>>2],o[t+160>>2]=o[r>>2],o[t+164>>2]=A,r=_t(e,t+160|0);break e}b:{l:{d:{k:{E:{p:switch((n=i[A+1|0])-105|0){case 1:case 2:break e;case 4:break b;case 3:break d;case 0:break E}switch(n-73|0){case 3:break l;case 0:break k;default:break e}}o[e>>2]=A+2,r=dr(t+512|0,6132),A=o[r+4>>2],o[t+168>>2]=o[r>>2],o[t+172>>2]=A,r=_t(e,t+168|0);break e}o[e>>2]=A+2,r=dr(t+504|0,6134),A=o[r+4>>2],o[t+176>>2]=o[r>>2],o[t+180>>2]=A,r=_t(e,t+176|0);break e}o[e>>2]=A+2,r=dr(t+496|0,6095),A=o[r+4>>2],o[t+184>>2]=o[r>>2],o[t+188>>2]=A,r=_t(e,t+184|0);break e}o[e>>2]=A+2,r=dr(t+488|0,6137),A=o[r+4>>2],o[t+192>>2]=o[r>>2],o[t+196>>2]=A,r=_t(e,t+192|0);break e}if(o[e>>2]=A+2,ir(e,95)){r=dr(t+480|0,6140),A=o[r+4>>2],o[t+200>>2]=o[r>>2],o[t+204>>2]=A,r=Jt(e,t+200|0);break e}if(r=v(e),o[t+632>>2]=r,!r)break t;r=Hr(e,t+632|0,6140);break e}u:{b:{l:{d:{k:{E:switch((n=i[A+1|0])-97|0){case 1:case 2:case 3:case 5:break e;case 6:break l;case 4:break d;case 0:break k}switch(n-116|0){case 4:break u;case 0:break b;case 3:break k;default:break e}}r=function(e){var t=0,r=0,A=0,n=0,a=0,c=0,f=0;y=t=y-112|0,r=dr(t+96|0,6044),A=o[r+4>>2],o[t+32>>2]=o[r>>2],o[t+36>>2]=A,c=t,f=rt(e,t+32|0),i[c+111|0]=f,n=t,r=o[e>>2],r=o[e+4>>2]-r>>>0>1?b[r+1|0]:0,i[n+95|0]=97==(255&r),r=dr(t+80|0,11120),A=o[r+4>>2],o[t+24>>2]=o[r>>2],o[t+28>>2]=A;k:if(rt(e,t+24|0)||(r=dr(t+72|0,11123),A=o[r+4>>2],o[t+16>>2]=o[r>>2],o[t+20>>2]=A,rt(e,t+16|0))){r=o[4+(n=e+8|0)>>2]-o[n>>2]>>2;E:{for(;!ir(e,95);){if(A=v(e),o[t+64>>2]=A,!A)break E;Gt(n,t- -64|0)}if(or(t- -64|0,e,r),A=X(e),o[t+60>>2]=A,!A)break k;if(r=dr(t+48|0,11126),A=o[r+4>>2],o[t+8>>2]=o[r>>2],o[t+12>>2]=A,rt(e,t+8|0)){for(r=o[n+4>>2]-o[n>>2]>>2;!ir(e,69);){if(A=v(e),o[t+40>>2]=A,!A)break k;Gt(n,t+40|0)}or(t+40|0,e,r),a=Dr(e,t- -64|0,t+60|0,t+40|0,t+111|0,t+95|0);break k}if(!ir(e,69))break k;o[(A=t+40|0)>>2]=0,o[A+4>>2]=0,a=Dr(e,t- -64|0,t+60|0,A,t+111|0,t+95|0);break k}}return y=t+112|0,a}(e);break e}o[e>>2]=A+2,r=dr(t+472|0,6143),A=o[r+4>>2],o[t+208>>2]=o[r>>2],o[t+212>>2]=A,r=_t(e,t+208|0);break e}o[e>>2]=A+2,r=dr(t+464|0,6132),A=o[r+4>>2],o[t+216>>2]=o[r>>2],o[t+220>>2]=A,r=Jt(e,t+216|0);break e}o[e>>2]=A+2,r=dr(t+456|0,6146),A=o[r+4>>2],o[t+224>>2]=o[r>>2],o[t+228>>2]=A,r=Jt(e,t+224|0);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;r=ct(e+416|0,6148,t+632|0);break e}s:{u:{b:{l:switch((n=i[A+1|0])-110|0){case 2:case 3:break e;case 4:break u;case 1:break b;case 0:break l;default:break s}r=q(e);break e}o[e>>2]=A+2,r=dr(t+448|0,6159),A=o[r+4>>2],o[t+232>>2]=o[r>>2],o[t+236>>2]=A,r=_t(e,t+232|0);break e}o[e>>2]=A+2,r=dr(t+440|0,6162),A=o[r+4>>2],o[t+240>>2]=o[r>>2],o[t+244>>2]=A,r=_t(e,t+240|0);break e}if(82!=(0|n))break e;o[e>>2]=A+2,r=dr(t+432|0,6164),A=o[r+4>>2],o[t+248>>2]=o[r>>2],o[t+252>>2]=A,r=_t(e,t+248|0);break e}f:{s:{u:{b:{l:{d:switch((n=i[A+1|0])-108|0){case 2:case 3:case 5:case 6:break e;case 8:break f;case 7:break s;case 4:break u;case 0:break l;case 1:break d;default:break b}o[e>>2]=A+2,r=dr(t+424|0,6167),A=o[r+4>>2],o[t+256>>2]=o[r>>2],o[t+260>>2]=A,r=_t(e,t+256|0);break e}o[e>>2]=A+2,r=dr(t+416|0,6171),A=o[r+4>>2],o[t+264>>2]=o[r>>2],o[t+268>>2]=A,r=_t(e,t+264|0);break e}if(76!=(0|n))break e;o[e>>2]=A+2,r=dr(t+408|0,6173),A=o[r+4>>2],o[t+272>>2]=o[r>>2],o[t+276>>2]=A,r=_t(e,t+272|0);break e}if(o[e>>2]=A+2,ir(e,95)){r=dr(t+400|0,6176),A=o[r+4>>2],o[t+280>>2]=o[r>>2],o[t+284>>2]=A,r=Jt(e,t+280|0);break e}if(r=v(e),o[t+632>>2]=r,!r)break t;r=Hr(e,t+632|0,6176);break e}o[e>>2]=A+2,r=dr(t+392|0,6171),A=o[r+4>>2],o[t+288>>2]=o[r>>2],o[t+292>>2]=A,r=Jt(e,t+288|0);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=xr(e,t+632|0,6179,t+644|0);break e}if(117!=b[A+1|0])break e;if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;if(r=v(e),o[t+388>>2]=r,!r)break t;r=function(e,t,r,A){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=50,o[e>>2]=5508,o[e+16>>2]=A,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=11364,e}(kt(e+416|0,20),o[t+632>>2],o[t+644>>2],o[t+388>>2]);break e}o:{c:{if(77!=(0|(n=i[A+1|0]))){if(83==(0|n))break o;if(115==(0|n))break c;if(109!=(0|n)){if(99!=(0|n))break e;if(o[e>>2]=A+2,r=X(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=vt(e+416|0,6182,t+632|0,t+644|0);break e}o[e>>2]=A+2,r=dr(t+376|0,6199),A=o[r+4>>2],o[t+296>>2]=o[r>>2],o[t+300>>2]=A,r=_t(e,t+296|0);break e}o[e>>2]=A+2,r=dr(t+368|0,6201),A=o[r+4>>2],o[t+304>>2]=o[r>>2],o[t+308>>2]=A,r=_t(e,t+304|0);break e}o[e>>2]=A+2,r=dr(t+360|0,6204),A=o[r+4>>2],o[t+312>>2]=o[r>>2],o[t+316>>2]=A,r=_t(e,t+312|0);break e}o[e>>2]=A+2,r=dr(t+352|0,6207),A=o[r+4>>2],o[t+320>>2]=o[r>>2],o[t+324>>2]=A,r=_t(e,t+320|0);break e}a:{o:{c:{f:{s:{u:{b:switch((n=i[A+1|0])-112|0){case 1:case 3:break e;case 4:break f;case 2:break s;case 0:break u}if(80==(0|n))break a;if(90==(0|n))break o;if(122==(0|n))break c;if(99!=(0|n))break e;if(o[e>>2]=A+2,r=X(e),o[t+632>>2]=r,!r)break t;if(r=v(e),o[t+644>>2]=r,!r)break t;r=vt(e+416|0,6211,t+632|0,t+644|0);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;r=Mr(e,t+632|0);break e}r=q(e);break e}if(o[e>>2]=A+2,r=X(e),o[t+632>>2]=r,!r)break t;r=Zr(e,6223,t+632|0);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;r=Zr(e,6223,t+632|0);break e}if(o[e>>2]=A+2,102!=(0|(A=(A=(0|(A=o[e>>2]))!=o[e+4>>2]?i[0|A]:0)<<24>>24))){if(84!=(255&A))break e;if(r=Te(e),o[t+632>>2]=r,!r)break t;r=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=54,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=11484,e}(e+416|0,t+632|0);break e}if(r=Me(e),o[t+632>>2]=r,!r)break t;r=Cr(e,t+632|0);break e}for(o[e>>2]=A+2,n=o[4+(A=e+8|0)>>2]-o[A>>2]>>2;!ir(e,69);){if(a=Oe(e),o[t+632>>2]=a,!a)break e;Gt(A,t+632|0)}or(t+632|0,e,n),c=t,f=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=0,o[r>>2]=5508,o[r>>2]=11608,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,t+632|0),o[c+644>>2]=f,r=Cr(e,t+644|0);break e}i:{a:{o:{c:{f:switch((n=i[A+1|0])-105|0){case 1:case 2:break e;case 3:break o;case 0:break c}if(119==(0|n))break i;if(114==(0|n))break a;if(101!=(0|n))break e;if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;r=Zr(e,6244,t+632|0);break e}if(o[e>>2]=A+2,r=X(e),o[t+632>>2]=r,!r)break t;r=Zr(e,6244,t+632|0);break e}if(o[e>>2]=A+2,r=X(e),o[t+644>>2]=r,!r)break t;for(A=o[4+(r=e+8|0)>>2]-o[r>>2]>>2;!ir(e,69);){if(n=de(e),o[t+632>>2]=n,!n)break t;Gt(r,t+632|0)}or(t+632|0,e,A),r=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=St(n,t,A),y=A+16|0,e}(e+416|0,t+644|0,t+632|0);break e}o[e>>2]=A+2,r=Vr(e,6253);break e}if(o[e>>2]=A+2,r=v(e),o[t+632>>2]=r,!r)break t;r=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=63,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=11716,e}(e+416|0,t+632|0);break e}if(n-49>>>0<9)break r}if(A=dr(t+344|0,6259),n=o[A+4>>2],o[t+8>>2]=o[A>>2],o[t+12>>2]=n,rt(e,t+8|0)){if(r=X(e),o[t+632>>2]=r,!r)break t;r=Sr(e,t+632|0);break e}if(A=dr(t+336|0,6271),n=o[A+4>>2],o[t>>2]=o[A>>2],o[t+4>>2]=n,!rt(e,t))break e;if(r=v(e),o[t+632>>2]=r,!r)break t;r=Sr(e,t+632|0);break e}r=q(e);break e}r=0}return y=t+704|0,r}function M(e){var t,r=0,A=0,n=0,i=0,a=0,c=0,f=0,s=0,u=0,l=0,k=0,E=0,p=0;y=t=y-16|0;e:{t:{r:{A:{n:{i:{a:{o:{c:{f:{s:{u:{if((e|=0)>>>0<=244){if(3&(r=(a=o[4042])>>>(A=(s=e>>>0<11?16:e+11&-8)>>>3|0)|0)){e=(i=o[16216+(r=(n=A+(1&(-1^r))|0)<<3)>>2])+8|0,(0|(A=o[i+8>>2]))!=(0|(r=r+16208|0))?(o[A+12>>2]=r,o[r+8>>2]=A):(E=16168,p=Er(n)&a,o[E>>2]=p),r=n<<3,o[i+4>>2]=3|r,o[4+(r=r+i|0)>>2]=1|o[r+4>>2];break e}if((l=o[4044])>>>0>=s>>>0)break u;if(r){A=e=(r=(0-(e=(0-(e=2<<A)|e)&r<<A)&e)-1|0)>>>12&16,A|=e=(r=r>>>e|0)>>>5&8,A|=e=(r=r>>>e|0)>>>2&4,c=o[16216+(e=(A=((A|=e=(r=r>>>e|0)>>>1&2)|(e=(r=r>>>e|0)>>>1&1))+(r>>>e|0)|0)<<3)>>2],(0|(r=o[c+8>>2]))!=(0|(e=e+16208|0))?(o[r+12>>2]=e,o[e+8>>2]=r):(a=Er(A)&a,o[4042]=a),e=c+8|0,o[c+4>>2]=3|s,i=(r=A<<3)-s|0,o[4+(n=c+s|0)>>2]=1|i,o[r+c>>2]=i,l&&(A=16208+((r=l>>>3|0)<<3)|0,c=o[4047],(r=1<<r)&a?r=o[A+8>>2]:(o[4042]=r|a,r=A),o[A+8>>2]=c,o[r+12>>2]=c,o[c+12>>2]=A,o[c+8>>2]=r),o[4047]=n,o[4044]=i;break e}if(!(f=o[4043]))break u;for(A=e=(r=(f&0-f)-1|0)>>>12&16,A|=e=(r=r>>>e|0)>>>5&8,A|=e=(r=r>>>e|0)>>>2&4,r=o[16472+(((A|=e=(r=r>>>e|0)>>>1&2)|(e=(r=r>>>e|0)>>>1&1))+(r>>>e|0)<<2)>>2],i=(-8&o[r+4>>2])-s|0,A=r;(e=o[A+16>>2])||(e=o[A+20>>2]);)i=(n=(A=(-8&o[e+4>>2])-s|0)>>>0<i>>>0)?A:i,r=n?e:r,A=e;if((u=r+s|0)>>>0<=r>>>0)break s;if(k=o[r+24>>2],(0|(n=o[r+12>>2]))!=(0|r)){e=o[r+8>>2],o[e+12>>2]=n,o[n+8>>2]=e;break t}if(!(e=o[(A=r+20|0)>>2])){if(!(e=o[r+16>>2]))break f;A=r+16|0}for(;c=A,n=e,(e=o[(A=e+20|0)>>2])||(A=n+16|0,e=o[n+16>>2]););o[c>>2]=0;break t}if(s=-1,!(e>>>0>4294967231)&&(s=-8&(e=e+11|0),u=o[4043])){a=31,i=0-s|0,s>>>0<=16777215&&(e=e>>>8|0,e<<=c=e+1048320>>>16&8,a=28+((e=((e<<=A=e+520192>>>16&4)<<(r=e+245760>>>16&2)>>>15|0)-(r|A|c)|0)<<1|s>>>e+21&1)|0);b:{l:{if(A=o[16472+(a<<2)>>2])for(e=0,r=s<<(31==(0|a)?0:25-(a>>>1|0)|0);;){if(!((c=(-8&o[A+4>>2])-s|0)>>>0>=i>>>0||(n=A,i=c))){i=0,e=A;break l}if(c=o[A+20>>2],A=o[16+((r>>>29&4)+A|0)>>2],e=c?(0|c)==(0|A)?e:c:e,r<<=1,!A)break}else e=0;if(!(e|n)){if(!(e=(0-(e=2<<a)|e)&u))break u;A=e=(r=(e&0-e)-1|0)>>>12&16,A|=e=(r=r>>>e|0)>>>5&8,A|=e=(r=r>>>e|0)>>>2&4,e=o[16472+(((A|=e=(r=r>>>e|0)>>>1&2)|(e=(r=r>>>e|0)>>>1&1))+(r>>>e|0)<<2)>>2]}if(!e)break b}for(;i=(A=(r=(-8&o[e+4>>2])-s|0)>>>0<i>>>0)?r:i,n=A?e:n,e=(r=o[e+16>>2])||o[e+20>>2];);}if(!(!n|o[4044]-s>>>0<=i>>>0)){if((f=n+s|0)>>>0<=n>>>0)break s;if(a=o[n+24>>2],(0|n)!=(0|(r=o[n+12>>2]))){e=o[n+8>>2],o[e+12>>2]=r,o[r+8>>2]=e;break r}if(!(e=o[(A=n+20|0)>>2])){if(!(e=o[n+16>>2]))break c;A=n+16|0}for(;c=A,r=e,(e=o[(A=e+20|0)>>2])||(A=r+16|0,e=o[r+16>>2]););o[c>>2]=0;break r}}}if((A=o[4044])>>>0>=s>>>0){n=o[4047],(r=A-s|0)>>>0>=16?(o[4044]=r,e=n+s|0,o[4047]=e,o[e+4>>2]=1|r,o[A+n>>2]=r,o[n+4>>2]=3|s):(o[4047]=0,o[4044]=0,o[n+4>>2]=3|A,o[4+(e=A+n|0)>>2]=1|o[e+4>>2]),e=n+8|0;break e}if((f=o[4045])>>>0>s>>>0){r=f-s|0,o[4045]=r,e=(A=o[4048])+s|0,o[4048]=e,o[e+4>>2]=1|r,o[A+4>>2]=3|s,e=A+8|0;break e}if(e=0,r=u=s+47|0,o[4160]?A=o[4162]:(o[4163]=-1,o[4164]=-1,o[4161]=4096,o[4162]=4096,o[4160]=t+12&-16^1431655768,o[4165]=0,o[4153]=0,A=4096),(A=(c=r+A|0)&(i=0-A|0))>>>0<=s>>>0)break e;if((n=o[4152])&&n>>>0<(a=(r=o[4150])+A|0)>>>0|r>>>0>=a>>>0)break e;if(4&b[16612])break i;u:{b:{if(n=o[4048])for(e=16616;;){if(n>>>0<(r=o[e>>2])+o[e+4>>2]>>>0&&r>>>0<=n>>>0)break b;if(!(e=o[e+8>>2]))break}if(-1==(0|(r=gt(0))))break a;if(a=A,(e=(n=o[4161])-1|0)&r&&(a=(A-r|0)+(e+r&0-n)|0),a>>>0<=s>>>0|a>>>0>2147483646)break a;if((n=o[4152])&&n>>>0<(i=(e=o[4150])+a|0)>>>0|e>>>0>=i>>>0)break a;if((0|r)!=(0|(e=gt(a))))break u;break n}if((a=i&c-f)>>>0>2147483646)break a;if((0|(r=gt(a)))==(o[e>>2]+o[e+4>>2]|0))break o;e=r}if(!(-1==(0|e)|s+48>>>0<=a>>>0)){if((r=(r=o[4162])+(u-a|0)&0-r)>>>0>2147483646){r=e;break n}if(-1!=(0|gt(r))){a=r+a|0,r=e;break n}gt(0-a|0);break a}if(r=e,-1!=(0|e))break n;break a}h()}n=0;break t}r=0;break r}if(-1!=(0|r))break n}o[4153]=4|o[4153]}if(A>>>0>2147483646)break A;if((r=gt(A))>>>0>=(e=gt(0))>>>0|-1==(0|r)|-1==(0|e))break A;if((a=e-r|0)>>>0<=s+40>>>0)break A}e=o[4150]+a|0,o[4150]=e,e>>>0>d[4151]&&(o[4151]=e);n:{i:{a:{if(c=o[4048]){for(e=16616;;){if(((n=o[e>>2])+(A=o[e+4>>2])|0)==(0|r))break a;if(!(e=o[e+8>>2]))break}break i}for((e=o[4046])>>>0<=r>>>0&&e||(o[4046]=r),e=0,o[4155]=a,o[4154]=r,o[4050]=-1,o[4051]=o[4160],o[4157]=0;A=16208+(n=e<<3)|0,o[n+16216>>2]=A,o[n+16220>>2]=A,32!=(0|(e=e+1|0)););A=(n=a-40|0)-(e=r+8&7?-8-r&7:0)|0,o[4045]=A,e=e+r|0,o[4048]=e,o[e+4>>2]=1|A,o[4+(r+n|0)>>2]=40,o[4049]=o[4164];break n}if(!(8&o[e+12>>2]|r>>>0<=c>>>0|n>>>0>c>>>0)){o[e+4>>2]=A+a,A=(e=c+8&7?-8-c&7:0)+c|0,o[4048]=A,e=(r=o[4045]+a|0)-e|0,o[4045]=e,o[A+4>>2]=1|e,o[4+(r+c|0)>>2]=40,o[4049]=o[4164];break n}}(n=o[4046])>>>0>r>>>0&&(o[4046]=r,n=0),A=r+a|0,e=16616;i:{a:{o:{c:{f:{s:{for(;;){if((0|A)!=o[e>>2]){if(e=o[e+8>>2])continue;break s}break}if(!(8&b[e+12|0]))break f}for(e=16616;;){if((A=o[e>>2])>>>0<=c>>>0&&(i=A+o[e+4>>2]|0)>>>0>c>>>0)break c;e=o[e+8>>2]}}if(o[e>>2]=r,o[e+4>>2]=o[e+4>>2]+a,o[4+(u=(r+8&7?-8-r&7:0)+r|0)>>2]=3|s,A=((a=A+(A+8&7?-8-A&7:0)|0)-u|0)-s|0,f=s+u|0,(0|a)==(0|c)){o[4048]=f,e=o[4045]+A|0,o[4045]=e,o[f+4>>2]=1|e;break a}if(o[4047]==(0|a)){o[4047]=f,e=o[4044]+A|0,o[4044]=e,o[f+4>>2]=1|e,o[e+f>>2]=e;break a}if(1==(3&(e=o[a+4>>2]))){c=-8&e;f:if(e>>>0<=255){if(n=o[a+8>>2],e=e>>>3|0,(0|(r=o[a+12>>2]))==(0|n)){E=16168,p=o[4042]&Er(e),o[E>>2]=p;break f}o[n+12>>2]=r,o[r+8>>2]=n}else{if(s=o[a+24>>2],(0|a)==(0|(r=o[a+12>>2])))if((i=o[(e=a+20|0)>>2])||(i=o[(e=a+16|0)>>2])){for(;n=e,(i=o[(e=(r=i)+20|0)>>2])||(e=r+16|0,i=o[r+16>>2]););o[n>>2]=0}else r=0;else e=o[a+8>>2],o[e+12>>2]=r,o[r+8>>2]=e;if(s){n=o[a+28>>2];s:{if(o[(e=16472+(n<<2)|0)>>2]==(0|a)){if(o[e>>2]=r,r)break s;E=16172,p=o[4043]&Er(n),o[E>>2]=p;break f}if(o[s+(o[s+16>>2]==(0|a)?16:20)>>2]=r,!r)break f}o[r+24>>2]=s,(e=o[a+16>>2])&&(o[r+16>>2]=e,o[e+24>>2]=r),(e=o[a+20>>2])&&(o[r+20>>2]=e,o[e+24>>2]=r)}}a=a+c|0,A=A+c|0}if(o[a+4>>2]=-2&o[a+4>>2],o[f+4>>2]=1|A,o[A+f>>2]=A,A>>>0<=255){r=16208+((e=A>>>3|0)<<3)|0,(A=o[4042])&(e=1<<e)?e=o[r+8>>2]:(o[4042]=e|A,e=r),o[r+8>>2]=f,o[e+12>>2]=f,o[f+12>>2]=r,o[f+8>>2]=e;break a}if(e=31,A>>>0<=16777215&&(e=A>>>8|0,e<<=i=e+1048320>>>16&8,e=28+((e=((e<<=n=e+520192>>>16&4)<<(r=e+245760>>>16&2)>>>15|0)-(r|n|i)|0)<<1|A>>>e+21&1)|0),o[f+28>>2]=e,o[f+16>>2]=0,o[f+20>>2]=0,i=16472+(e<<2)|0,(n=o[4043])&(r=1<<e)){for(e=A<<(31==(0|e)?0:25-(e>>>1|0)|0),r=o[i>>2];;){if(n=r,(-8&o[r+4>>2])==(0|A))break o;if(r=e>>>29|0,e<<=1,!(r=o[16+(i=n+(4&r)|0)>>2]))break}o[i+16>>2]=f,o[f+24>>2]=n}else o[4043]=r|n,o[i>>2]=f,o[f+24>>2]=i;o[f+12>>2]=f,o[f+8>>2]=f;break a}for(A=(n=a-40|0)-(e=r+8&7?-8-r&7:0)|0,o[4045]=A,e=e+r|0,o[4048]=e,o[e+4>>2]=1|A,o[4+(r+n|0)>>2]=40,o[4049]=o[4164],o[(A=(e=(i+(i-39&7?39-i&7:0)|0)-47|0)>>>0<c+16>>>0?c:e)+4>>2]=27,e=o[4157],o[A+16>>2]=o[4156],o[A+20>>2]=e,e=o[4155],o[A+8>>2]=o[4154],o[A+12>>2]=e,o[4156]=A+8,o[4155]=a,o[4154]=r,o[4157]=0,e=A+24|0;o[e+4>>2]=7,r=e+8|0,e=e+4|0,r>>>0<i>>>0;);if((0|A)==(0|c))break n;if(o[A+4>>2]=-2&o[A+4>>2],i=A-c|0,o[c+4>>2]=1|i,o[A>>2]=i,i>>>0<=255){r=16208+((e=i>>>3|0)<<3)|0,(A=o[4042])&(e=1<<e)?e=o[r+8>>2]:(o[4042]=e|A,e=r),o[r+8>>2]=c,o[e+12>>2]=c,o[c+12>>2]=r,o[c+8>>2]=e;break n}if(e=31,o[c+16>>2]=0,o[c+20>>2]=0,i>>>0<=16777215&&(e=i>>>8|0,e<<=n=e+1048320>>>16&8,e=28+((e=((e<<=A=e+520192>>>16&4)<<(r=e+245760>>>16&2)>>>15|0)-(r|A|n)|0)<<1|i>>>e+21&1)|0),o[c+28>>2]=e,n=16472+(e<<2)|0,(A=o[4043])&(r=1<<e)){for(e=i<<(31==(0|e)?0:25-(e>>>1|0)|0),r=o[n>>2];;){if(A=r,(0|i)==(-8&o[r+4>>2]))break i;if(r=e>>>29|0,e<<=1,!(r=o[16+(n=A+(4&r)|0)>>2]))break}o[n+16>>2]=c,o[c+24>>2]=A}else o[4043]=r|A,o[n>>2]=c,o[c+24>>2]=n;o[c+12>>2]=c,o[c+8>>2]=c;break n}e=o[n+8>>2],o[e+12>>2]=f,o[n+8>>2]=f,o[f+24>>2]=0,o[f+12>>2]=n,o[f+8>>2]=e}e=u+8|0;break e}e=o[A+8>>2],o[e+12>>2]=c,o[A+8>>2]=c,o[c+24>>2]=0,o[c+12>>2]=A,o[c+8>>2]=e}if(!((e=o[4045])>>>0<=s>>>0)){r=e-s|0,o[4045]=r,e=(A=o[4048])+s|0,o[4048]=e,o[e+4>>2]=1|r,o[A+4>>2]=3|s,e=A+8|0;break e}}o[4041]=48,e=0;break e}r:if(a){A=o[n+28>>2];A:{if(o[(e=16472+(A<<2)|0)>>2]==(0|n)){if(o[e>>2]=r,r)break A;u=Er(A)&u,o[4043]=u;break r}if(o[a+(o[a+16>>2]==(0|n)?16:20)>>2]=r,!r)break r}o[r+24>>2]=a,(e=o[n+16>>2])&&(o[r+16>>2]=e,o[e+24>>2]=r),(e=o[n+20>>2])&&(o[r+20>>2]=e,o[e+24>>2]=r)}r:if(i>>>0<=15)e=i+s|0,o[n+4>>2]=3|e,o[4+(e=e+n|0)>>2]=1|o[e+4>>2];else if(o[n+4>>2]=3|s,o[f+4>>2]=1|i,o[i+f>>2]=i,i>>>0<=255)r=16208+((e=i>>>3|0)<<3)|0,(A=o[4042])&(e=1<<e)?e=o[r+8>>2]:(o[4042]=e|A,e=r),o[r+8>>2]=f,o[e+12>>2]=f,o[f+12>>2]=r,o[f+8>>2]=e;else{e=31,i>>>0<=16777215&&(e=i>>>8|0,e<<=c=e+1048320>>>16&8,e=28+((e=((e<<=A=e+520192>>>16&4)<<(r=e+245760>>>16&2)>>>15|0)-(r|A|c)|0)<<1|i>>>e+21&1)|0),o[f+28>>2]=e,o[f+16>>2]=0,o[f+20>>2]=0,A=16472+(e<<2)|0;A:{if((r=1<<e)&u){for(e=i<<(31==(0|e)?0:25-(e>>>1|0)|0),s=o[A>>2];;){if((-8&o[(r=s)+4>>2])==(0|i))break A;if(A=e>>>29|0,e<<=1,!(s=o[16+(A=r+(4&A)|0)>>2]))break}o[A+16>>2]=f,o[f+24>>2]=r}else o[4043]=r|u,o[A>>2]=f,o[f+24>>2]=A;o[f+12>>2]=f,o[f+8>>2]=f;break r}e=o[r+8>>2],o[e+12>>2]=f,o[r+8>>2]=f,o[f+24>>2]=0,o[f+12>>2]=r,o[f+8>>2]=e}e=n+8|0;break e}t:if(k){A=o[r+28>>2];r:{if(o[(e=16472+(A<<2)|0)>>2]==(0|r)){if(o[e>>2]=n,n)break r;E=16172,p=Er(A)&f,o[E>>2]=p;break t}if(o[(o[k+16>>2]==(0|r)?16:20)+k>>2]=n,!n)break t}o[n+24>>2]=k,(e=o[r+16>>2])&&(o[n+16>>2]=e,o[e+24>>2]=n),(e=o[r+20>>2])&&(o[n+20>>2]=e,o[e+24>>2]=n)}i>>>0<=15?(e=i+s|0,o[r+4>>2]=3|e,o[4+(e=e+r|0)>>2]=1|o[e+4>>2]):(o[r+4>>2]=3|s,o[u+4>>2]=1|i,o[i+u>>2]=i,l&&(A=16208+((e=l>>>3|0)<<3)|0,n=o[4047],(e=1<<e)&a?e=o[A+8>>2]:(o[4042]=e|a,e=A),o[A+8>>2]=n,o[e+12>>2]=n,o[n+12>>2]=A,o[n+8>>2]=e),o[4047]=u,o[4044]=i),e=r+8|0}return y=t+16|0,0|e}function F(e,t,r,A,n){var a=0,o=0,c=0,f=0,s=0,u=0,d=0,k=0,E=0,p=0,T=0,h=0,O=0,_=0,w=0,y=0,R=0,D=0,m=0,v=0,M=0,F=0,S=0,P=0,B=0,X=0,U=0,I=0,x=0,j=0,Q=0,C=0,Z=0,G=0;e:if(!(A>>>0<10||(E=(s=b[r+4|0]|b[r+5|0]<<8)+((d=6+(o=b[0|r]|b[r+1|0]<<8)|0)+(a=b[r+2|0]|b[r+3|0]<<8)|0)|0)>>>0>A>>>0)){if(!o)return-72;c=r+6|0,U=l[n+2>>1];t:{if(o>>>0>=4){if(p=-1,!(c=b[(o+c|0)-1|0]))break t;f=b[0|(o=(O=o+2|0)+r|0)]|b[o+1|0]<<8|b[o+2|0]<<16|b[o+3|0]<<24,u=g(c)-23|0}else{f=b[0|c];r:switch(o-2|0){case 1:f=b[r+8|0]<<16|f;case 0:f=(b[r+7|0]<<8)+f|0}if(!(c=b[(o+c|0)-1|0]))break e;O=6,u=9+(g(c)-(o<<3)|0)|0}if(!a)return-72;if(D=r+d|0,a>>>0>=4){if(p=-1,!(c=b[(a+D|0)-1|0]))break t;_=b[0|(o=D+(w=a-4|0)|0)]|b[o+1|0]<<8|b[o+2|0]<<16|b[o+3|0]<<24,o=g(c)-23|0}else{_=b[0|D];r:switch(a-2|0){case 1:_=b[D+2|0]<<16|_;case 0:_=(b[D+1|0]<<8)+_|0}if(!(o=b[(a+D|0)-1|0]))break e;o=9+(g(o)-(a<<3)|0)|0}if(!s)return-72;if(m=a+D|0,s>>>0>=4){if(p=-1,!(c=b[(s+m|0)-1|0]))break t;v=b[0|(a=m+(y=s-4|0)|0)]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,c=g(c)-23|0}else{v=b[0|m];r:switch(s-2|0){case 1:v=b[m+2|0]<<16|v;case 0:v=(b[m+1|0]<<8)+v|0}if(!(a=b[(s+m|0)-1|0]))break e;c=9+(g(a)-(s<<3)|0)|0}if(p=-72,A=A-E|0){M=s+m|0;r:{A:{if(A>>>0>=4){if(p=-1,!(a=b[(A+M|0)-1|0]))break t;if(A>>>0<=4294967176)break A;return A}F=b[0|M];n:switch(A-2|0){case 1:F=b[M+2|0]<<16|F;case 0:F=(b[M+1|0]<<8)+F|0}if(!(a=b[(A+M|0)-1|0]))break e;A=9+(g(a)-(A<<3)|0)|0;break r}F=b[0|(A=M+(R=A-4|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,A=g(a)-23|0}k=n+4|0;r:if((B=(n=t+3>>>2|0)+(P=n+(h=n+e|0)|0)|0)>>>0>=(C=(j=e+t|0)-3|0)>>>0)E=B,d=P,s=h;else for(n=0-U&31,s=h,d=P,E=B;;){if(T=l[(a=(f<<u>>>n<<2)+k|0)>>1],i[0|e]=T,i[e+1|0]=T>>>8,p=b[a+2|0],T=b[a+3|0],S=l[(a=(_<<o>>>n<<2)+k|0)>>1],i[0|s]=S,i[s+1|0]=S>>>8,S=b[a+2|0],X=b[a+3|0],I=l[(a=(v<<c>>>n<<2)+k|0)>>1],i[0|d]=I,i[d+1|0]=I>>>8,I=b[a+2|0],Q=b[a+3|0],x=l[(a=(F<<A>>>n<<2)+k|0)>>1],i[0|E]=x,i[E+1|0]=x>>>8,x=b[a+2|0],a=b[a+3|0],T=e+T|0,p=l[(e=(f<<(u=u+p|0)>>>n<<2)+k|0)>>1],i[0|T]=p,i[T+1|0]=p>>>8,p=b[e+2|0],Z=b[e+3|0],s=s+X|0,S=l[(e=(_<<(o=o+S|0)>>>n<<2)+k|0)>>1],i[0|s]=S,i[s+1|0]=S>>>8,S=b[e+2|0],G=b[e+3|0],d=d+Q|0,X=l[(e=(v<<(c=c+I|0)>>>n<<2)+k|0)>>1],i[0|d]=X,i[d+1|0]=X>>>8,X=b[e+2|0],I=b[e+3|0],E=a+E|0,e=l[(A=(F<<(Q=A+x|0)>>>n<<2)+k|0)>>1],i[0|E]=e,i[E+1|0]=e>>>8,u=u+p|0,(0|O)<10?p=3:(f=b[0|(e=(O=O-(u>>>3|0)|0)+r|0)]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,u&=7,p=0),x=b[A+3|0],A=b[A+2|0],o=o+S|0,a=3,(0|w)<4?S=3:(_=b[0|(e=D+(w=w-(o>>>3|0)|0)|0)]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,o&=7,S=0),c=c+X|0,(0|y)>=4&&(v=b[0|(e=m+(y=y-(c>>>3|0)|0)|0)]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,a=0,c&=7),E=E+x|0,A=A+Q|0,(0|R)<4?X=3:(F=b[0|(e=M+(R=R-(A>>>3|0)|0)|0)]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,A&=7,X=0),e=T+Z|0,s=s+G|0,d=d+I|0,E>>>0>=C>>>0)break r;if(X|p|S|a)break}if(d>>>0>B>>>0|s>>>0>P>>>0)break e;if(p=-20,!(e>>>0>h>>>0)){r:{A:{n:{if(u>>>0>=33)n=0-U&31;else for(p=h-3|0,n=0-U&31;;){if((0|O)>=10)f=u>>>3|0,T=0,a=7&u;else{if(6==(0|O)){T=h-2|0,O=6,a=u;break A}a=u-((f=(T=(O-(a=u>>>3|0)|0)<6)?O-6|0:a)<<3)|0}if(f=b[0|(u=(O=O-f|0)+r|0)]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,T|e>>>0>=p>>>0)break n;if(T=l[(u=(f<<a>>>n<<2)+k|0)>>1],i[0|e]=T,i[e+1|0]=T>>>8,e=b[u+3|0]+e|0,u=b[u+2|0]+a|0,T=l[(a=(f<<u>>>n<<2)+k|0)>>1],i[0|e]=T,i[e+1|0]=T>>>8,e=e+b[a+3|0]|0,!((u=u+b[a+2|0]|0)>>>0<33))break}T=h-2|0;break r}if(T=h-2|0,!(a>>>0<=32)){u=a;break r}}for(;;){if((0|O)>=10)u=7&a,f=a>>>3|0,a=0;else{if(6==(0|O)){O=6,u=a;break r}u=(u=a)-((f=(a=(O-(f=a>>>3|0)|0)<6)?O-6|0:f)<<3)|0}if(f=b[0|(f=(O=O-f|0)+r|0)]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24,a|e>>>0>T>>>0)break r;if(p=l[(a=(f<<u>>>n<<2)+k|0)>>1],i[0|e]=p,i[e+1|0]=p>>>8,e=b[a+3|0]+e|0,!((a=b[a+2|0]+u|0)>>>0<=32))break}u=a}if(e>>>0<=T>>>0)for(;a=l[(r=(f<<u>>>n<<2)+k|0)>>1],i[0|e]=a,i[e+1|0]=a>>>8,u=b[r+2|0]+u|0,T>>>0>=(e=b[r+3|0]+e|0)>>>0;);e>>>0>=h>>>0||(a=e,e=((r=f<<u>>>n|0)<<2)+k|0,i[0|a]=b[0|e],1!=b[e+3|0]?u>>>0>31||(u=(e=b[2+((r<<2)+k|0)|0]+u|0)>>>0<32?e:32):u=b[e+2|0]+u|0);r:{A:{n:{if(o>>>0>=33)e=0-U&31;else for(h=P-3|0,e=0-U&31;;){if((0|w)>=4)n=o>>>3|0,f=0,r=7&o;else{if(!w){n=P-2|0,w=0,a=D,r=o;break A}r=o-((n=(f=(D+w|0)-(r=o>>>3|0)>>>0<D>>>0)?w:r)<<3)|0}if(_=b[0|(a=D+(w=w-n|0)|0)]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,f|s>>>0>=h>>>0)break n;if(a=l[(n=(_<<r>>>e<<2)+k|0)>>1],i[0|s]=a,i[s+1|0]=a>>>8,a=b[n+3|0]+s|0,n=b[n+2|0]+r|0,o=l[(r=(_<<n>>>e<<2)+k|0)>>1],i[0|a]=o,i[a+1|0]=o>>>8,s=a+b[r+3|0]|0,!((o=n+b[r+2|0]|0)>>>0<33))break}a=D+w|0,n=P-2|0;break r}if(n=P-2|0,!(r>>>0<=32)){o=r;break r}}for(;;){if((0|w)>=4)f=r>>>3|0,T=0,o=7&r;else{if(!w){o=r;break r}o=r-((f=(T=(o=a)-(a=r>>>3|0)>>>0<D>>>0)?w:a)<<3)|0}if(_=b[0|(a=D+(w=w-f|0)|0)]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,T|n>>>0<s>>>0)break r;if(h=l[(r=(_<<o>>>e<<2)+k|0)>>1],i[0|s]=h,i[s+1|0]=h>>>8,s=b[r+3|0]+s|0,!((r=b[r+2|0]+o|0)>>>0<=32))break}o=r}if(n>>>0>=s>>>0)for(;h=l[(r=(_<<o>>>e<<2)+k|0)>>1],i[0|s]=h,i[s+1|0]=h>>>8,o=b[r+2|0]+o|0,(s=b[r+3|0]+s|0)>>>0<=n>>>0;);s>>>0>=P>>>0||(e=((r=_<<o>>>e|0)<<2)+k|0,i[0|s]=b[0|e],1!=b[e+3|0]?o>>>0>31||(o=(e=b[2+((r<<2)+k|0)|0]+o|0)>>>0<32?e:32):o=b[e+2|0]+o|0);r:{A:{n:{if(c>>>0>=33)e=0-U&31;else for(h=B-3|0,e=0-U&31;;){if((0|y)>=4)n=c>>>3|0,f=0,r=7&c;else{if(!y){n=B-2|0,y=0,s=m,r=c;break A}r=c-((n=(f=(m+y|0)-(r=c>>>3|0)>>>0<m>>>0)?y:r)<<3)|0}if(v=b[0|(s=m+(y=y-n|0)|0)]|b[s+1|0]<<8|b[s+2|0]<<16|b[s+3|0]<<24,f|d>>>0>=h>>>0)break n;if(c=l[(n=(v<<r>>>e<<2)+k|0)>>1],i[0|d]=c,i[d+1|0]=c>>>8,c=b[n+3|0]+d|0,n=b[n+2|0]+r|0,s=l[(r=(v<<n>>>e<<2)+k|0)>>1],i[0|c]=s,i[c+1|0]=s>>>8,d=c+b[r+3|0]|0,!((c=n+b[r+2|0]|0)>>>0<33))break}s=m+y|0,n=B-2|0;break r}if(n=B-2|0,!(r>>>0<=32)){c=r;break r}}for(;;){if((0|y)>=4)f=r>>>3|0,_=0,c=7&r;else{if(!y){c=r;break r}c=r-((f=(_=s-(c=r>>>3|0)>>>0<m>>>0)?y:c)<<3)|0}if(v=b[0|(s=m+(y=y-f|0)|0)]|b[s+1|0]<<8|b[s+2|0]<<16|b[s+3|0]<<24,_|n>>>0<d>>>0)break r;if(h=l[(r=(v<<c>>>e<<2)+k|0)>>1],i[0|d]=h,i[d+1|0]=h>>>8,d=b[r+3|0]+d|0,!((r=b[r+2|0]+c|0)>>>0<=32))break}c=r}if(n>>>0>=d>>>0)for(;h=l[(r=(v<<c>>>e<<2)+k|0)>>1],i[0|d]=h,i[d+1|0]=h>>>8,c=b[r+2|0]+c|0,(d=b[r+3|0]+d|0)>>>0<=n>>>0;);d>>>0>=B>>>0||(e=((r=v<<c>>>e|0)<<2)+k|0,i[0|d]=b[0|e],1!=b[e+3|0]?c>>>0>31||(c=(e=b[2+((r<<2)+k|0)|0]+c|0)>>>0<32?e:32):c=b[e+2|0]+c|0),e=0-U&31;r:{A:{n:{if(A>>>0<=32)for(;;){if((0|R)>=4)n=A>>>3|0,d=0,r=7&A;else{if(!R){n=j-2|0,R=0,f=M,r=A;break A}r=A-((n=(d=(R+M|0)-(r=A>>>3|0)>>>0<M>>>0)?R:r)<<3)|0}if(F=b[0|(f=M+(R=R-n|0)|0)]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24,d|E>>>0>=C>>>0)break n;if(n=l[(A=(F<<r>>>e<<2)+k|0)>>1],i[0|E]=n,i[E+1|0]=n>>>8,n=b[A+3|0]+E|0,A=b[A+2|0]+r|0,d=l[(r=(F<<A>>>e<<2)+k|0)>>1],i[0|n]=d,i[n+1|0]=d>>>8,E=n+b[r+3|0]|0,!((A=A+b[r+2|0]|0)>>>0<33))break}f=R+M|0,n=j-2|0;break r}if(n=j-2|0,!(r>>>0<=32)){A=r;break r}}for(;;){if((0|R)>=4)d=r>>>3|0,_=0,A=7&r;else{if(!R){A=r;break r}A=r-((d=(_=f-(A=r>>>3|0)>>>0<M>>>0)?R:A)<<3)|0}if(F=b[0|(f=M+(R=R-d|0)|0)]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24,_|n>>>0<E>>>0)break r;if(d=l[(r=(F<<A>>>e<<2)+k|0)>>1],i[0|E]=d,i[E+1|0]=d>>>8,E=b[r+3|0]+E|0,!((r=b[r+2|0]+A|0)>>>0<=32))break}A=r}if(n>>>0>=E>>>0)for(;d=l[(r=(F<<A>>>e<<2)+k|0)>>1],i[0|E]=d,i[E+1|0]=d>>>8,A=b[r+2|0]+A|0,(E=b[r+3|0]+E|0)>>>0<=n>>>0;);E>>>0>=j>>>0||(e=((r=F<<A>>>e|0)<<2)+k|0,i[0|E]=b[0|e],1!=b[e+3|0]?A>>>0>31||(A=(e=b[2+((r<<2)+k|0)|0]+A|0)>>>0<32?e:32):A=b[e+2|0]+A|0),p=6==(0|O)&&32==(0|u)&&(0|a)==(0|D)&&32==(0|o)&&(0|s)==(0|m)&&32==(0|c)&&(0|f)==(0|M)&&32==(0|A)?t:-20}}}return p}return-20}function S(e,t,r,A,n,a,c,f){var s,u=0,l=0,d=0,k=0,E=0,p=0,T=0,g=0;p=-70;e:if(!((s=(d=o[r>>2])+o[r+4>>2]|0)>>>0>t-e>>>0||(p=-20,d>>>0>n-(u=o[A>>2])>>>0))){n=t-32|0,g=u+d|0,t=e+d|0,E=o[r+8>>2];t:if((0|d)<=7){if(l=E,(0|d)<1)break t;for(;i[0|e]=b[0|u],u=u+1|0,t>>>0>(e=e+1|0)>>>0;);l=E}else if(t>>>0<=n>>>0){if(l=b[u+4|0]|b[u+5|0]<<8|b[u+6|0]<<16|b[u+7|0]<<24,k=b[0|u]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,i[0|e]=k,i[e+1|0]=k>>>8,i[e+2|0]=k>>>16,i[e+3|0]=k>>>24,i[e+4|0]=l,i[e+5|0]=l>>>8,i[e+6|0]=l>>>16,i[e+7|0]=l>>>24,l=b[u+12|0]|b[u+13|0]<<8|b[u+14|0]<<16|b[u+15|0]<<24,k=b[u+8|0]|b[u+9|0]<<8|b[u+10|0]<<16|b[u+11|0]<<24,i[e+8|0]=k,i[e+9|0]=k>>>8,i[e+10|0]=k>>>16,i[e+11|0]=k>>>24,i[e+12|0]=l,i[e+13|0]=l>>>8,i[e+14|0]=l>>>16,i[e+15|0]=l>>>24,l=E,(0|d)<17)break t;for(e=e+16|0;l=b[u+20|0]|b[u+21|0]<<8|b[u+22|0]<<16|b[u+23|0]<<24,d=b[u+16|0]|b[u+17|0]<<8|b[u+18|0]<<16|b[u+19|0]<<24,i[0|e]=d,i[e+1|0]=d>>>8,i[e+2|0]=d>>>16,i[e+3|0]=d>>>24,i[e+4|0]=l,i[e+5|0]=l>>>8,i[e+6|0]=l>>>16,i[e+7|0]=l>>>24,l=b[u+28|0]|b[u+29|0]<<8|b[u+30|0]<<16|b[u+31|0]<<24,d=b[u+24|0]|b[u+25|0]<<8|b[u+26|0]<<16|b[u+27|0]<<24,i[e+8|0]=d,i[e+9|0]=d>>>8,i[e+10|0]=d>>>16,i[e+11|0]=d>>>24,i[e+12|0]=l,i[e+13|0]=l>>>8,i[e+14|0]=l>>>16,i[e+15|0]=l>>>24,l=b[u+36|0]|b[u+37|0]<<8|b[u+38|0]<<16|b[u+39|0]<<24,d=b[u+32|0]|b[u+33|0]<<8|b[u+34|0]<<16|b[u+35|0]<<24,i[e+16|0]=d,i[e+17|0]=d>>>8,i[e+18|0]=d>>>16,i[e+19|0]=d>>>24,i[e+20|0]=l,i[e+21|0]=l>>>8,i[e+22|0]=l>>>16,i[e+23|0]=l>>>24,l=b[u+44|0]|b[u+45|0]<<8|b[u+46|0]<<16|b[u+47|0]<<24,d=b[u+40|0]|b[u+41|0]<<8|b[u+42|0]<<16|b[u+43|0]<<24,i[e+24|0]=d,i[e+25|0]=d>>>8,i[e+26|0]=d>>>16,i[e+27|0]=d>>>24,i[e+28|0]=l,i[e+29|0]=l>>>8,i[e+30|0]=l>>>16,i[e+31|0]=l>>>24,u=u+32|0,t>>>0>(e=e+32|0)>>>0;);l=E}else{if(e>>>0<=n>>>0){if(l=b[u+4|0]|b[u+5|0]<<8|b[u+6|0]<<16|b[u+7|0]<<24,d=b[0|u]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,i[0|e]=d,i[e+1|0]=d>>>8,i[e+2|0]=d>>>16,i[e+3|0]=d>>>24,i[e+4|0]=l,i[e+5|0]=l>>>8,i[e+6|0]=l>>>16,i[e+7|0]=l>>>24,l=b[u+12|0]|b[u+13|0]<<8|b[u+14|0]<<16|b[u+15|0]<<24,d=b[u+8|0]|b[u+9|0]<<8|b[u+10|0]<<16|b[u+11|0]<<24,i[e+8|0]=d,i[e+9|0]=d>>>8,i[e+10|0]=d>>>16,i[e+11|0]=d>>>24,i[e+12|0]=l,i[e+13|0]=l>>>8,i[e+14|0]=l>>>16,i[e+15|0]=l>>>24,(0|(T=n-e|0))>=17)for(e=e+16|0,l=u;d=b[l+20|0]|b[l+21|0]<<8|b[l+22|0]<<16|b[l+23|0]<<24,k=b[l+16|0]|b[l+17|0]<<8|b[l+18|0]<<16|b[l+19|0]<<24,i[0|e]=k,i[e+1|0]=k>>>8,i[e+2|0]=k>>>16,i[e+3|0]=k>>>24,i[e+4|0]=d,i[e+5|0]=d>>>8,i[e+6|0]=d>>>16,i[e+7|0]=d>>>24,d=b[l+28|0]|b[l+29|0]<<8|b[l+30|0]<<16|b[l+31|0]<<24,k=b[l+24|0]|b[l+25|0]<<8|b[l+26|0]<<16|b[l+27|0]<<24,i[e+8|0]=k,i[e+9|0]=k>>>8,i[e+10|0]=k>>>16,i[e+11|0]=k>>>24,i[e+12|0]=d,i[e+13|0]=d>>>8,i[e+14|0]=d>>>16,i[e+15|0]=d>>>24,d=b[l+36|0]|b[l+37|0]<<8|b[l+38|0]<<16|b[l+39|0]<<24,k=b[l+32|0]|b[l+33|0]<<8|b[l+34|0]<<16|b[l+35|0]<<24,i[e+16|0]=k,i[e+17|0]=k>>>8,i[e+18|0]=k>>>16,i[e+19|0]=k>>>24,i[e+20|0]=d,i[e+21|0]=d>>>8,i[e+22|0]=d>>>16,i[e+23|0]=d>>>24,d=b[l+44|0]|b[l+45|0]<<8|b[l+46|0]<<16|b[l+47|0]<<24,k=b[l+40|0]|b[l+41|0]<<8|b[l+42|0]<<16|b[l+43|0]<<24,i[e+24|0]=k,i[e+25|0]=k>>>8,i[e+26|0]=k>>>16,i[e+27|0]=k>>>24,i[e+28|0]=d,i[e+29|0]=d>>>8,i[e+30|0]=d>>>16,i[e+31|0]=d>>>24,l=l+32|0,n>>>0>(e=e+32|0)>>>0;);u=u+T|0,e=n}if(l=E,!(e>>>0>=t>>>0)){for(;i[0|e]=b[0|u],u=u+1|0,(0|t)!=(0|(e=e+1|0)););l=o[r+8>>2]}}e=t-E|0,o[A>>2]=g;t:{if(l>>>0>t-a>>>0){if(l>>>0>t-c>>>0)break e;if((A=(e=e-a|0)+f|0)+(c=o[r+4>>2])>>>0<=f>>>0){le(t,A,c);break t}t=le(t,A,0-e|0),l=e+c|0,o[r+4>>2]=l,t=t-e|0}else l=o[r+4>>2],a=e;if(r=t+l|0,(0|l)<=7){if((0|l)<1)break t;for(;i[0|t]=b[0|a],a=a+1|0,r>>>0>(t=t+1|0)>>>0;);}else if((e=t-a|0)>>>0<=7?(i[0|t]=b[0|a],i[t+1|0]=b[a+1|0],i[t+2|0]=b[a+2|0],i[t+3|0]=b[a+3|0],e=(A=a)+o[3936+(a=e<<2)>>2]|0,A=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24,i[t+4|0]=A,i[t+5|0]=A>>>8,i[t+6|0]=A>>>16,i[t+7|0]=A>>>24,a=e-o[a+3968>>2]|0):(e=b[a+4|0]|b[a+5|0]<<8|b[a+6|0]<<16|b[a+7|0]<<24,A=b[0|a]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,i[0|t]=A,i[t+1|0]=A>>>8,i[t+2|0]=A>>>16,i[t+3|0]=A>>>24,i[t+4|0]=e,i[t+5|0]=e>>>8,i[t+6|0]=e>>>16,i[t+7|0]=e>>>24),e=t+8|0,u=a+8|0,r>>>0<=n>>>0){if(n=e+l|0,(e-u|0)<=15)for(;;)if(t=b[u+4|0]|b[u+5|0]<<8|b[u+6|0]<<16|b[u+7|0]<<24,r=b[0|u]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,i[0|e]=r,i[e+1|0]=r>>>8,i[e+2|0]=r>>>16,i[e+3|0]=r>>>24,i[e+4|0]=t,i[e+5|0]=t>>>8,i[e+6|0]=t>>>16,i[e+7|0]=t>>>24,u=u+8|0,!(n>>>0>(e=e+8|0)>>>0))break t;if(r=b[u+4|0]|b[u+5|0]<<8|b[u+6|0]<<16|b[u+7|0]<<24,A=b[0|u]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,i[0|e]=A,i[e+1|0]=A>>>8,i[e+2|0]=A>>>16,i[e+3|0]=A>>>24,i[e+4|0]=r,i[e+5|0]=r>>>8,i[e+6|0]=r>>>16,i[e+7|0]=r>>>24,r=b[u+12|0]|b[u+13|0]<<8|b[u+14|0]<<16|b[u+15|0]<<24,A=b[u+8|0]|b[u+9|0]<<8|b[u+10|0]<<16|b[u+11|0]<<24,i[e+8|0]=A,i[e+9|0]=A>>>8,i[e+10|0]=A>>>16,i[e+11|0]=A>>>24,i[e+12|0]=r,i[e+13|0]=r>>>8,i[e+14|0]=r>>>16,i[e+15|0]=r>>>24,(0|l)<17)break t;for(e=t+24|0;t=b[u+20|0]|b[u+21|0]<<8|b[u+22|0]<<16|b[u+23|0]<<24,r=b[u+16|0]|b[u+17|0]<<8|b[u+18|0]<<16|b[u+19|0]<<24,i[0|e]=r,i[e+1|0]=r>>>8,i[e+2|0]=r>>>16,i[e+3|0]=r>>>24,i[e+4|0]=t,i[e+5|0]=t>>>8,i[e+6|0]=t>>>16,i[e+7|0]=t>>>24,t=b[u+28|0]|b[u+29|0]<<8|b[u+30|0]<<16|b[u+31|0]<<24,r=b[u+24|0]|b[u+25|0]<<8|b[u+26|0]<<16|b[u+27|0]<<24,i[e+8|0]=r,i[e+9|0]=r>>>8,i[e+10|0]=r>>>16,i[e+11|0]=r>>>24,i[e+12|0]=t,i[e+13|0]=t>>>8,i[e+14|0]=t>>>16,i[e+15|0]=t>>>24,t=b[u+36|0]|b[u+37|0]<<8|b[u+38|0]<<16|b[u+39|0]<<24,r=b[u+32|0]|b[u+33|0]<<8|b[u+34|0]<<16|b[u+35|0]<<24,i[e+16|0]=r,i[e+17|0]=r>>>8,i[e+18|0]=r>>>16,i[e+19|0]=r>>>24,i[e+20|0]=t,i[e+21|0]=t>>>8,i[e+22|0]=t>>>16,i[e+23|0]=t>>>24,t=b[u+44|0]|b[u+45|0]<<8|b[u+46|0]<<16|b[u+47|0]<<24,r=b[u+40|0]|b[u+41|0]<<8|b[u+42|0]<<16|b[u+43|0]<<24,i[e+24|0]=r,i[e+25|0]=r>>>8,i[e+26|0]=r>>>16,i[e+27|0]=r>>>24,i[e+28|0]=t,i[e+29|0]=t>>>8,i[e+30|0]=t>>>16,i[e+31|0]=t>>>24,u=u+32|0,n>>>0>(e=e+32|0)>>>0;);}else{if(e>>>0>n>>>0)n=e;else{if(c=n-e|0,(e-u|0)<=15)for(t=u;A=b[t+4|0]|b[t+5|0]<<8|b[t+6|0]<<16|b[t+7|0]<<24,a=b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24,i[0|e]=a,i[e+1|0]=a>>>8,i[e+2|0]=a>>>16,i[e+3|0]=a>>>24,i[e+4|0]=A,i[e+5|0]=A>>>8,i[e+6|0]=A>>>16,i[e+7|0]=A>>>24,t=t+8|0,n>>>0>(e=e+8|0)>>>0;);else if(A=b[u+4|0]|b[u+5|0]<<8|b[u+6|0]<<16|b[u+7|0]<<24,a=b[0|u]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,i[0|e]=a,i[e+1|0]=a>>>8,i[e+2|0]=a>>>16,i[e+3|0]=a>>>24,i[e+4|0]=A,i[e+5|0]=A>>>8,i[e+6|0]=A>>>16,i[e+7|0]=A>>>24,A=b[u+12|0]|b[u+13|0]<<8|b[u+14|0]<<16|b[u+15|0]<<24,a=b[u+8|0]|b[u+9|0]<<8|b[u+10|0]<<16|b[u+11|0]<<24,i[e+8|0]=a,i[e+9|0]=a>>>8,i[e+10|0]=a>>>16,i[e+11|0]=a>>>24,i[e+12|0]=A,i[e+13|0]=A>>>8,i[e+14|0]=A>>>16,i[e+15|0]=A>>>24,!((0|c)<17))for(e=t+24|0,t=u;A=b[t+20|0]|b[t+21|0]<<8|b[t+22|0]<<16|b[t+23|0]<<24,a=b[t+16|0]|b[t+17|0]<<8|b[t+18|0]<<16|b[t+19|0]<<24,i[0|e]=a,i[e+1|0]=a>>>8,i[e+2|0]=a>>>16,i[e+3|0]=a>>>24,i[e+4|0]=A,i[e+5|0]=A>>>8,i[e+6|0]=A>>>16,i[e+7|0]=A>>>24,A=b[t+28|0]|b[t+29|0]<<8|b[t+30|0]<<16|b[t+31|0]<<24,a=b[t+24|0]|b[t+25|0]<<8|b[t+26|0]<<16|b[t+27|0]<<24,i[e+8|0]=a,i[e+9|0]=a>>>8,i[e+10|0]=a>>>16,i[e+11|0]=a>>>24,i[e+12|0]=A,i[e+13|0]=A>>>8,i[e+14|0]=A>>>16,i[e+15|0]=A>>>24,A=b[t+36|0]|b[t+37|0]<<8|b[t+38|0]<<16|b[t+39|0]<<24,a=b[t+32|0]|b[t+33|0]<<8|b[t+34|0]<<16|b[t+35|0]<<24,i[e+16|0]=a,i[e+17|0]=a>>>8,i[e+18|0]=a>>>16,i[e+19|0]=a>>>24,i[e+20|0]=A,i[e+21|0]=A>>>8,i[e+22|0]=A>>>16,i[e+23|0]=A>>>24,A=b[t+44|0]|b[t+45|0]<<8|b[t+46|0]<<16|b[t+47|0]<<24,a=b[t+40|0]|b[t+41|0]<<8|b[t+42|0]<<16|b[t+43|0]<<24,i[e+24|0]=a,i[e+25|0]=a>>>8,i[e+26|0]=a>>>16,i[e+27|0]=a>>>24,i[e+28|0]=A,i[e+29|0]=A>>>8,i[e+30|0]=A>>>16,i[e+31|0]=A>>>24,t=t+32|0,n>>>0>(e=e+32|0)>>>0;);u=c+u|0}if(!(r>>>0<=n>>>0))for(;i[0|n]=b[0|u],u=u+1|0,(0|r)!=(0|(n=n+1|0)););}}p=s}return p}function P(e,t,r,A,n){var a=0,o=0,c=0,f=0,s=0,u=0,d=0,k=0,E=0,p=0,T=0,h=0,O=0,_=0,w=0,y=0,R=0,D=0,m=0,v=0,M=0,F=0,S=0,P=0,B=0,X=0,U=0,I=0,x=0,j=0,Q=0,C=0,Z=0;e:if(!(A>>>0<10||(E=(o=b[r+4|0]|b[r+5|0]<<8)+((s=6+(a=b[0|r]|b[r+1|0]<<8)|0)+(f=b[r+2|0]|b[r+3|0]<<8)|0)|0)>>>0>A>>>0)){if(!a)return-72;c=r+6|0,S=l[n+2>>1];t:{if(a>>>0>=4){if(w=-1,!(c=b[(a+c|0)-1|0]))break t;k=g(c)-23|0,u=b[0|(c=(a=a+2|0)+r|0)]|b[c+1|0]<<8|b[c+2|0]<<16|b[c+3|0]<<24}else{u=b[0|c];r:switch(a-2|0){case 1:u=b[r+8|0]<<16|u;case 0:u=(b[r+7|0]<<8)+u|0}if(!(c=b[(a+c|0)-1|0]))break e;k=9+(g(c)-(a<<3)|0)|0,a=6}if(!f)return-72;if(R=r+s|0,f>>>0>=4){if(w=-1,!(s=b[(f+R|0)-1|0]))break t;v=b[0|(c=R+(_=f-4|0)|0)]|b[c+1|0]<<8|b[c+2|0]<<16|b[c+3|0]<<24,c=g(s)-23|0}else{v=b[0|R];r:switch(f-2|0){case 1:v=b[R+2|0]<<16|v;case 0:v=(b[R+1|0]<<8)+v|0}if(!(c=b[(f+R|0)-1|0]))break e;c=9+(g(c)-(f<<3)|0)|0}if(!o)return-72;if(D=f+R|0,o>>>0>=4){if(w=-1,!(s=b[(o+D|0)-1|0]))break t;M=b[0|(f=(F=o-4|0)+D|0)]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24,f=g(s)-23|0}else{M=b[0|D];r:switch(o-2|0){case 1:M=b[D+2|0]<<16|M;case 0:M=(b[D+1|0]<<8)+M|0}if(!(f=b[(o+D|0)-1|0]))break e;f=9+(g(f)-(o<<3)|0)|0}if(w=-72,A=A-E|0){m=o+D|0;r:{A:{if(A>>>0>=4){if(w=-1,!(o=b[(A+m|0)-1|0]))break t;if(A>>>0<=4294967176)break A;return A}y=b[0|m];n:switch(A-2|0){case 1:y=b[m+2|0]<<16|y;case 0:y=(b[m+1|0]<<8)+y|0}if(!(o=b[(A+m|0)-1|0]))break e;A=9+(g(o)-(A<<3)|0)|0;break r}y=b[0|(A=m+(d=A-4|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,A=g(o)-23|0}if(h=n+4|0,(X=(B=(P=(n=t+3>>>2|0)+e|0)+n|0)+n|0)>>>0>=(x=(I=e+t|0)-3|0)>>>0)E=_,O=F,T=d,s=a,n=X,p=B,o=P;else for(w=0-S&31,o=P,p=B,n=X,j=1;E=b[1+(s=(u<<k>>>w<<1)+h|0)|0],i[0|e]=b[0|s],O=b[1+(s=(v<<c>>>w<<1)+h|0)|0],i[0|o]=b[0|s],T=b[1+(s=(M<<f>>>w<<1)+h|0)|0],i[0|p]=b[0|s],U=b[1+(s=(y<<A>>>w<<1)+h|0)|0],i[0|n]=b[0|s],E=b[1+(s=(u<<(k=k+E|0)>>>w<<1)+h|0)|0],i[e+1|0]=b[0|s],O=b[1+(s=(v<<(c=c+O|0)>>>w<<1)+h|0)|0],i[o+1|0]=b[0|s],T=b[1+(s=(M<<(f=f+T|0)>>>w<<1)+h|0)|0],i[p+1|0]=b[0|s],Q=b[1+(A=(y<<(U=A+U|0)>>>w<<1)+h|0)|0],i[n+1|0]=b[0|A],k=k+E|0,(0|a)<10?s=a:(u=b[0|(A=(s=a-(k>>>3|0)|0)+r|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,k&=7),c=c+O|0,(0|_)<4?E=_:(v=b[0|(A=R+(E=_-(c>>>3|0)|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,c&=7),f=f+T|0,(0|F)<4?O=F:(M=b[0|(A=D+(O=F-(f>>>3|0)|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,f&=7),T=U+Q|0,n=n+2|0,(0|d)<4?(A=T,T=d):(A=7&T,y=b[0|(y=m+(T=d-(T>>>3|0)|0)|0)]|b[y+1|0]<<8|b[y+2|0]<<16|b[y+3|0]<<24),p=p+2|0,o=o+2|0,e=e+2|0,U=(0|_)>3,Q=(0|F)>3,C=(0|d)>3,Z=(0|a)>9,a=s,d=T,F=O,_=E,(j&=U&Q&C&Z)&n>>>0<x>>>0;);if(p>>>0>X>>>0|o>>>0>B>>>0)break e;if(w=-20,!(e>>>0>P>>>0)){r:{A:{n:if(k>>>0>=33)d=0-S&31;else{F=P-3|0,d=0-S&31;i:{a:{for(;;){if((0|s)>=10)u=k>>>3|0,_=0,a=7&k;else{if(6==(0|s)){a=k,s=6;break a}a=k-((u=(_=(s-(a=k>>>3|0)|0)<6)?s-6|0:a)<<3)|0}if(u=b[0|(k=(s=s-u|0)+r|0)]|b[k+1|0]<<8|b[k+2|0]<<16|b[k+3|0]<<24,!(_|e>>>0>=F>>>0)){if(_=b[1+(k=(u<<a>>>d<<1)+h|0)|0],i[0|e]=b[0|k],_=b[1+(k=(u<<(a=a+_|0)>>>d<<1)+h|0)|0],i[e+1|0]=b[0|k],e=e+2|0,(k=a+_|0)>>>0<=32)continue;break n}break}if(a>>>0>32)break i}for(;;){if((0|s)>=10)u=a>>>3|0,_=0,k=7&a;else{if(6==(0|s)){s=6;break i}k=a-((u=(_=(s-(k=a>>>3|0)|0)<6)?s-6|0:k)<<3)|0}if(u=b[0|(a=(s=s-u|0)+r|0)]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,a=e>>>0>=P>>>0,_||a){if(a)break r;break A}if(_=b[1+(a=(u<<k>>>d<<1)+h|0)|0],i[0|e]=b[0|a],e=e+1|0,!((a=k+_|0)>>>0<=32))break}}k=a}if(e>>>0>=P>>>0)break r}for(;a=b[1+(r=(u<<k>>>d<<1)+h|0)|0],i[0|e]=b[0|r],k=a+k|0,(0|P)!=(0|(e=e+1|0)););}r:{A:{n:if(c>>>0>=33)r=0-S&31,a=E+R|0;else{_=B-3|0,r=0-S&31;i:{a:{o:{for(;;){if((0|E)>=4)d=c>>>3|0,u=0,e=7&c;else{if(!E){E=0,a=R,e=c;break a}e=c-((d=(u=(E+R|0)-(e=c>>>3|0)>>>0<R>>>0)?E:e)<<3)|0}if(v=b[0|(a=R+(E=E-d|0)|0)]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,u|o>>>0>=_>>>0)break o;if(c=b[1+(a=(v<<e>>>r<<1)+h|0)|0],i[0|o]=b[0|a],c=b[1+(a=(v<<(e=e+c|0)>>>r<<1)+h|0)|0],i[o+1|0]=b[0|a],o=o+2|0,!((c=e+c|0)>>>0<33))break}a=E+R|0;break n}if(e>>>0>32)break i}for(;;){if((0|E)>=4)d=e>>>3|0,u=0,c=7&e;else{if(!E)break i;c=e-((d=(u=(c=a)-(a=e>>>3|0)>>>0<R>>>0)?E:a)<<3)|0}if(v=b[0|(a=R+(E=E-d|0)|0)]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,e=o>>>0>=B>>>0,u||e){if(e)break r;break A}if(d=b[1+(e=(v<<c>>>r<<1)+h|0)|0],i[0|o]=b[0|e],o=o+1|0,!((e=c+d|0)>>>0<=32))break}}c=e}if(o>>>0>=B>>>0)break r}for(;d=b[1+(e=(v<<c>>>r<<1)+h|0)|0],i[0|o]=b[0|e],c=c+d|0,(0|B)!=(0|(o=o+1|0)););}r:{A:{n:if(f>>>0>=33)r=0-S&31,u=O+D|0;else{E=X-3|0,r=0-S&31;i:{a:{o:{for(;;){if((0|O)>=4)d=f>>>3|0,o=0,e=7&f;else{if(!O){O=0,u=D,e=f;break a}e=f-((d=(o=(O+D|0)-(e=f>>>3|0)>>>0<D>>>0)?O:e)<<3)|0}if(M=b[0|(u=D+(O=O-d|0)|0)]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,o|p>>>0>=E>>>0)break o;if(o=b[1+(f=(M<<e>>>r<<1)+h|0)|0],i[0|p]=b[0|f],o=b[1+(f=(M<<(e=e+o|0)>>>r<<1)+h|0)|0],i[p+1|0]=b[0|f],p=p+2|0,!((f=e+o|0)>>>0<33))break}u=O+D|0;break n}if(e>>>0>32)break i}for(;;){if((0|O)>=4)d=e>>>3|0,o=0,f=7&e;else{if(!O)break i;f=e-((d=(o=u-(f=e>>>3|0)>>>0<D>>>0)?O:f)<<3)|0}if(M=b[0|(u=D+(O=O-d|0)|0)]|b[u+1|0]<<8|b[u+2|0]<<16|b[u+3|0]<<24,e=p>>>0>=X>>>0,o||e){if(e)break r;break A}if(o=b[1+(e=(M<<f>>>r<<1)+h|0)|0],i[0|p]=b[0|e],p=p+1|0,!((e=o+f|0)>>>0<=32))break}}f=e}if(p>>>0>=X>>>0)break r}for(;o=b[1+(e=(M<<f>>>r<<1)+h|0)|0],i[0|p]=b[0|e],f=o+f|0,(0|X)!=(0|(p=p+1|0)););}r=0-S&31;r:{A:{n:{i:{a:{if(A>>>0<=32)for(;;){if((0|T)>=4)d=A>>>3|0,p=0,e=7&A;else{if(!T){T=0,o=m,e=A;break i}e=A-((d=(p=(T+m|0)-(e=A>>>3|0)>>>0<m>>>0)?T:e)<<3)|0}if(y=b[0|(o=m+(T=T-d|0)|0)]|b[o+1|0]<<8|b[o+2|0]<<16|b[o+3|0]<<24,p|n>>>0>=x>>>0)break a;if(o=b[1+(A=(y<<e>>>r<<1)+h|0)|0],i[0|n]=b[0|A],o=b[1+(A=(y<<(e=e+o|0)>>>r<<1)+h|0)|0],i[n+1|0]=b[0|A],n=n+2|0,!((A=e+o|0)>>>0<33))break}o=T+m|0;break n}if(!(e>>>0<=32)){A=e;break n}}for(A=e;;){if((0|T)>=4)d=A>>>3|0,p=0,A&=7;else{if(!T)break n;A=A-((d=(p=o-(e=A>>>3|0)>>>0<m>>>0)?T:e)<<3)|0}if(y=b[0|(o=m+(T=T-d|0)|0)]|b[o+1|0]<<8|b[o+2|0]<<16|b[o+3|0]<<24,e=n>>>0>=I>>>0,p||e){if(e)break r;break A}if(d=b[1+(e=(y<<A>>>r<<1)+h|0)|0],i[0|n]=b[0|e],n=n+1|0,!((A=A+d|0)>>>0<=32))break}}if(n>>>0>=I>>>0)break r}for(;d=b[1+(e=(y<<A>>>r<<1)+h|0)|0],i[0|n]=b[0|e],A=A+d|0,(0|I)!=(0|(n=n+1|0)););}w=6==(0|s)&&32==(0|k)&&32==(0|c)&&(0|a)==(0|R)&&32==(0|f)&&(0|u)==(0|D)&&32==(0|A)&&(0|o)==(0|m)?t:-20}}}return w}return-20}function B(e,t){var r,A=0,n=0,a=0,c=0;y=r=y-32|0;e:{t:{r:{A:{n:{i:{a:{o:{c:{f:{s:{u:{b:{l:{d:{k:{E:switch(((n=(0|(n=o[e>>2]))!=o[e+4>>2]?i[0|n]:0)<<24>>24)-97|0){case 12:break f;case 11:break s;case 8:break u;case 6:break b;case 4:break l;case 3:break d;case 2:break k;case 0:break E;case 21:break r;case 18:break A;case 17:break n;case 16:break i;case 15:break a;case 14:break o;case 13:break c;default:break e}E:{p:{T:{g:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-97|0){case 1:case 2:break e;case 0:break t;case 3:break T}if(78==(0|t))break p;if(83==(0|t))break E;if(110!=(0|t))break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9619);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9629);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9640);break e}k:{E:{p:{T:switch(n=o[e>>2],(n=(n=o[e+4>>2]-n>>>0>1?i[n+1|0]:0)<<24>>24)-108|0){case 2:break e;case 3:break E;case 1:break p;case 0:break T;default:break k}o[e>>2]=o[e>>2]+2,A=Vr(e,9650);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9661);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9671);break e}if(118!=(0|n))break e;o[e>>2]=o[e>>2]+2,n=Nt(r+24|0,e+388|0,0),a=Nt(r+16|0,e+389|0,0!=(b[e+389|0]|t)),c=X(e),o[r+12>>2]=c,c&&(t&&(i[0|t]=1),A=Pr(e,r+12|0)),b[a+5|0]&&(i[o[a>>2]]=b[a+4|0]),b[n+5|0]&&(i[o[n>>2]]=b[n+4|0]);break e}d:{k:{E:{p:{T:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-97|0){case 1:case 2:case 3:break e;case 4:break E;case 0:break p}if(86==(0|t))break d;if(108==(0|t))break k;if(118!=(0|t))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,9725);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9681);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9699);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9709);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9735);break e}l:{d:{k:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-111|0){case 1:break e;case 2:break l;case 0:break k;default:break d}o[e>>2]=o[e>>2]+2,A=Vr(e,9746);break e}if(79!=(0|t))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,9756);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9767);break e}if(t=o[e>>2],116!=(0|(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24))){if(101!=(0|t))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,9778);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9789);break e}if(t=o[e>>2],120!=(255&(t=o[e+4>>2]-t>>>0>1?b[t+1|0]:0)))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,9799);break e}s:{u:{b:{l:{d:{k:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-101|0){case 1:case 2:case 3:break e;case 4:break l;case 0:break d}switch(t-115|0){case 1:break s;case 0:break b;default:break u}}o[e>>2]=o[e>>2]+2,A=Vr(e,9810);break e}if(o[e>>2]=o[e>>2]+2,t=Ce(e),o[r+24>>2]=t,!t)break e;A=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=19,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=10236,e}(e+416|0,r+24|0);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9821);break e}if(83!=(0|t))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,9832);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9844);break e}f:{s:{u:{b:{l:{d:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-105|0){case 1:case 2:break e;case 4:break f;case 3:break u;case 0:break l}switch(t-73|0){case 3:break s;case 0:break b;default:break e}}o[e>>2]=o[e>>2]+2,A=Vr(e,9854);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9864);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9699);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9875);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9886);break e}c:{f:{s:{u:{b:{l:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-97|0){case 1:case 2:case 3:case 5:break e;case 6:break s;case 4:break u;case 0:break b}switch(t-116|0){case 3:break c;case 0:break f;default:break e}}o[e>>2]=o[e>>2]+2,A=Vr(e,9897);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9912);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9854);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9923);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9933);break e}o:{c:{f:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-111|0){case 1:case 2:break e;case 3:break c;case 0:break f;default:break o}o[e>>2]=o[e>>2]+2,A=Vr(e,9946);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9957);break e}if(82!=(0|t))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,9967);break e}a:{o:{c:{f:{s:{u:switch(t=o[e>>2],(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-108|0){case 2:case 3:case 5:case 6:break e;case 8:break a;case 7:break o;case 4:break c;case 0:break s;case 1:break u;default:break f}o[e>>2]=o[e>>2]+2,A=Vr(e,9978);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9990);break e}if(76!=(0|t))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,1e4);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,10011);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9990);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,10022);break e}if(t=o[e>>2],117!=(255&(t=o[e+4>>2]-t>>>0>1?b[t+1|0]:0)))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,10033);break e}n:{i:{if(t=o[e>>2],77!=(0|(t=(t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24))){if(83==(0|t))break n;if(115==(0|t))break i;if(109!=(0|t))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,10043);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,10053);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,10064);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,10075);break e}if(t=o[e>>2],115!=(255&(t=o[e+4>>2]-t>>>0>1?b[t+1|0]:0)))break e;o[e>>2]=o[e>>2]+2,A=Vr(e,10087);break e}if(t=o[e>>2],((t=o[e+4>>2]-t>>>0>1?i[t+1|0]:0)<<24>>24)-48>>>0>9)break e;if(o[e>>2]=o[e>>2]+2,t=Ce(e),o[r+24>>2]=t,!t)break e;A=Pr(e,r+24|0);break e}o[e>>2]=o[e>>2]+2,A=Vr(e,9608)}return y=r+32|0,A}function X(e){var t,r,A=0,n=0,a=0,c=0,f=0,s=0,u=0;y=t=y-32|0,o[t+28>>2]=0,r=t;e:{t:{r:{A:{n:{i:{a:{o:{c:{f:{s:{u:{b:{l:{d:{k:{E:{p:{T:{g:{h:{O:{_:{w:{y:{R:{D:{m:{v:{M:{F:{S:{P:{B:{X:{U:{I:{x:{j:{Q:{C:{Z:switch((A=(A=(0|(A=o[e>>2]))!=o[e+4>>2]?i[0|A]:0)<<24>>24)-65|0){case 6:break f;case 2:break s;case 14:break u;case 17:break b;case 15:break l;case 19:break d;case 12:break k;case 0:break E;case 5:break p;case 3:break T;case 52:break g;case 57:break h;case 38:break O;case 36:break _;case 35:break w;case 37:break y;case 46:break R;case 45:break D;case 56:break m;case 55:break v;case 44:break M;case 43:break F;case 41:break S;case 40:break P;case 51:break B;case 50:break X;case 39:break U;case 32:break I;case 34:break x;case 33:break j;case 54:break Q;case 53:break C;case 10:case 21:case 49:break Z;case 20:break n;case 18:break c;default:break o}c=(A=114==(0|A))?2:1,f=A,a=o[e>>2],a=A=86==(255&(A=A>>>0<o[e+4>>2]-a>>>0?b[A+a|0]:0))?c:f,f=A,c=o[e>>2],a=A=f+(75==(255&(A=A>>>0<o[e+4>>2]-c>>>0?b[a+c|0]:0)))|0;Z:switch(c=o[e>>2],((a=A>>>0<o[e+4>>2]-c>>>0?i[a+c|0]:0)<<24>>24&255)-68|0){case 2:break A;case 0:break Z;default:break n}if(A=A+1|0,a=o[e>>2],(a=(A=(A=A>>>0<o[e+4>>2]-a>>>0?i[A+a|0]:0)<<24>>24&255)-111|0)>>>0>9|!(1<<a&769))break i;break A}o[e>>2]=o[e>>2]+1,n=Vr(e,13984);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6283);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,13989);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6299);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6304);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6316);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6330);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6336);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,13994);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,13998);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,14011);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,14016);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,14030);break e}o[e>>2]=o[e>>2]+1,n=Ct(e+416|0,14040);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6366);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,6375);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,14059);break e}o[e>>2]=o[e>>2]+1,n=Ct(e+416|0,14065);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,14072);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,14084);break e}o[e>>2]=o[e>>2]+1,n=Vr(e,7916);break e}if(o[e>>2]=o[e>>2]+1,ze(t+16|0,e),o[t+16>>2]==o[t+20>>2])break a;s=t,u=Yr(e,t+16|0),o[s+28>>2]=u;break t}T:{g:{h:{O:{_:{w:{y:{R:{D:{m:{v:{M:{F:{S:switch(A=o[e>>2],((A=o[e+4>>2]-A>>>0>1?i[A+1|0]:0)<<24>>24)-79|0){case 0:case 32:case 40:case 41:break T;case 33:break g;case 39:break h;case 5:case 37:break O;case 31:break _;case 20:break w;case 18:break y;case 38:break R;case 36:break D;case 26:break m;case 25:break v;case 23:break M;case 22:break F;case 21:break S;default:break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14095);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14105);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14116);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14126);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14136);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14145);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14154);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,5907);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14162);break e}o[e>>2]=o[e>>2]+2,n=Vr(e,14177);break e}A=lt(e);break r}A=function(e){var t=0,r=0,A=0,n=0,a=0,c=0;y=t=y-32|0,A=dr(t+24|0,14924),n=o[A+4>>2],o[t>>2]=o[A>>2],o[t+4>>2]=n;h:if(rt(e,t)){O:{if((((A=(0|(A=o[e>>2]))!=o[e+4>>2]?i[0|A]:0)<<24>>24)-49&255)>>>0<=8){if(et(t+8|0,e,0),a=t,c=Yr(e,t+8|0),o[a+20>>2]=c,!ir(e,95))break h;if(ir(e,112)){r=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=26,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=14936,e}(e+416|0,t+20|0);break h}if(r=X(e),o[t+8>>2]=r,!r)break O;r=wr(e,t+8|0,t+20|0);break h}if(!ir(e,95)){if(A=v(e),o[t+8>>2]=A,!A)break h;if(!ir(e,95))break h;if(r=X(e),o[t+20>>2]=r,!r)break O;r=wr(e,t+20|0,t+8|0);break h}if(r=X(e),o[t+8>>2]=r,r){o[t+20>>2]=0,r=Lt(kt(e+416|0,16),o[t+8>>2],0);break h}}r=0}return y=t+32|0,r}(e);break r}if(o[e>>2]=o[e>>2]+2,A=X(e),o[t+16>>2]=A,!A)break e;s=t,u=Mr(e,t+16|0),o[s+28>>2]=u;break t}A=$(e);break r}A=$(e);break r}A=function(e){var t=0,r=0,A=0,n=0,a=0;y=r=y-16|0;E:if(ir(e,65)){o[r+12>>2]=0;p:{if(((t=(0|(t=o[e>>2]))!=o[e+4>>2]?i[0|t]:0)<<24>>24)-48>>>0<=9){if(et(r,e,0),n=r,a=Yr(e,r),o[n+12>>2]=a,ir(e,95))break p;break E}if(!ir(e,95)){if(!(t=v(e)))break E;if(!ir(e,95))break E;o[r+12>>2]=t}}t=X(e),o[r>>2]=t,t&&(A=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=0,i[e+5|0]=0,i[e+4|0]=14,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=15176,e}(kt(e+416|0,16),o[r>>2],o[r+12>>2]))}return y=r+16|0,A}(e);break r}A=function(e){var t=0,r=0;return y=r=y-16|0,t=0,ir(e,77)&&(t=X(e),o[r+12>>2]=t,t=t&&(t=X(e),o[r+8>>2]=t,t)?function(e,t,r){var A=0;return A=b[r+5|0],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=A,i[e+4|0]=13,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=15284,e}(kt(e+416|0,16),o[r+12>>2],o[r+8>>2]):0),y=r+16|0,t}(e);break r}d:{k:{E:switch(A=o[e>>2],(A=(A=o[e+4>>2]-A>>>0>1?i[A+1|0]:0)<<24>>24&255)-115|0){case 1:break d;case 0:case 2:break k}if(101!=(0|A))break d}A=ge(e);break r}if(A=Te(e),o[t+28>>2]=A,!A)break a;if(!b[e+388|0])break t;if(73!=(255&(A=(0|(A=o[e>>2]))!=o[e+4>>2]?b[0|A]:0)))break t;if(A=_e(e,0),o[t+16>>2]=A,!A)break a;s=t,u=Or(e,t+28|0,t+16|0),o[s+28>>2]=u;break t}if(o[e>>2]=o[e>>2]+1,A=X(e),o[t+16>>2]=A,!A)break a;s=t,u=function(e,t){var r=0;return e=kt(e,12),t=o[t>>2],r=b[t+5|0],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=r,i[e+4|0]=11,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=15544,e}(e+416|0,t+16|0),o[s+28>>2]=u;break t}if(o[e>>2]=o[e>>2]+1,A=X(e),o[t+16>>2]=A,!A)break a;o[t+12>>2]=0,s=t,u=hr(e,t+16|0,t+12|0),o[s+28>>2]=u;break t}if(o[e>>2]=o[e>>2]+1,A=X(e),o[t+16>>2]=A,!A)break a;o[t+12>>2]=1,s=t,u=hr(e,t+16|0,t+12|0),o[s+28>>2]=u;break t}if(o[e>>2]=o[e>>2]+1,A=X(e),o[t+16>>2]=A,!A)break e;s=t,u=It(e+416|0,t+16|0,14192),o[s+28>>2]=u;break t}if(o[e>>2]=o[e>>2]+1,A=X(e),o[t+16>>2]=A,!A)break a;s=t,u=It(e+416|0,t+16|0,14201),o[s+28>>2]=u;break t}if(A=o[e>>2],!(!(A=(A=o[e+4>>2]-A>>>0>1?i[A+1|0]:0)<<24>>24)|116==(255&A))){if(n=ae(e),o[t+16>>2]=n,!n|!b[e+388|0])break e;if(73!=(255&(A=(0|(A=o[e>>2]))!=o[e+4>>2]?b[0|A]:0)))break e;if(A=_e(e,0),o[t+12>>2]=A,!A)break a;s=t,u=Or(e,t+16|0,t+12|0),o[s+28>>2]=u;break t}}A=ge(e);break r}n=0;break e}if(79==(0|A))break A}A=ue(e);break r}A=$(e)}if(o[r+28>>2]=A,!A)break e}Gt(e+148|0,t+28|0),n=o[t+28>>2]}return y=t+32|0,n}function U(e,t,r,A){var n,a,c=0,f=0,s=0,u=0,l=0,d=0,k=0,E=0,T=0,g=0,h=0,O=0,_=0,w=0,R=0;y=n=y-16|0,o[n+12>>2]=0,o[n+8>>2]=0,f=-44;e:{if(!((a=te(g=A+740|0,A,n+8|0,n+12|0,t,r,A+128|0))>>>0>4294967176)){if((_=o[n+12>>2])>>>0>1+(255&(t=o[e>>2]))>>>0)break e;if(o[e>>2]=-16776961&t|_<<16&16711680,O=o[n+8>>2],f=0,!((0|_)<0)){if(l=A- -64|0,E=3&(r=_+1|0),t=0,_>>>0>=3)for(k=-4&r;c=o[(u=f<<2)+A>>2],o[u+l>>2]=t,s=o[(r=4|u)+A>>2],d=r+l|0,r=t+c|0,o[d>>2]=r,c=o[(t=8|u)+A>>2],s=r+s|0,o[t+l>>2]=s,r=o[(t=12|u)+A>>2],u=t+l|0,t=c+s|0,o[u>>2]=t,t=t+r|0,f=f+4|0,k=k-4|0;);if(E)for(;r=o[(s=f<<2)+A>>2],o[s+l>>2]=t,f=f+1|0,t=t+r|0,E=E-1|0;);}if(f=0,(0|O)>=4)for(s=O-3|0,u=A- -64|0,E=A+484|0;t=u+(b[f+g|0]<<2)|0,r=o[t>>2],o[t>>2]=r+1,i[r+E|0]=f,t=u+(b[(r=1|f)+g|0]<<2)|0,c=o[t>>2],o[t>>2]=c+1,i[c+E|0]=r,t=u+(b[(r=2|f)+g|0]<<2)|0,c=o[t>>2],o[t>>2]=c+1,i[c+E|0]=r,t=u+(b[(r=3|f)+g|0]<<2)|0,c=o[t>>2],o[t>>2]=c+1,i[c+E|0]=r,(0|s)>(0|(f=f+4|0)););if(!((0|f)>=(0|O))&&(t=f+1|0,E=A- -64|0,c=A+484|0,O-f&1&&(r=E+(b[f+g|0]<<2)|0,s=o[r>>2],o[r>>2]=s+1,i[c+s|0]=f,f=t),(0|t)!=(0|O)))for(;t=E+(b[f+g|0]<<2)|0,r=o[t>>2],o[t>>2]=r+1,i[r+c|0]=f,t=E+(b[(r=f+1|0)+g|0]<<2)|0,s=o[t>>2],o[t>>2]=s+1,i[c+s|0]=r,(0|O)!=(0|(f=f+2|0)););if(!((R=_+1|0)>>>0<2))for(h=e+4|0,T=A+484|0,k=o[A>>2],t=0,f=1;;){E=f,u=R-f|0,d=o[(f<<2)+A>>2];t:{r:{A:{n:{i:{a:switch((w=(e=1<<f)>>1)-1|0){case 0:break A;case 1:break n;case 3:break i;case 7:break a;default:break r}if((0|d)<1)break t;if(l=u<<8&65280,f=0,r=t,1!=(0|d))for(s=-2&d;c=h+(r<<1)|0,u=e=Qr(l|b[T+(f+k|0)|0],0,65537,65537),i[c+8|0]=u,i[c+9|0]=u>>>8,i[c+10|0]=u>>>16,i[c+11|0]=u>>>24,u=D,i[c+12|0]=u,i[c+13|0]=u>>>8,i[c+14|0]=u>>>16,i[c+15|0]=u>>>24,i[0|c]=e,i[c+1|0]=e>>>8,i[c+2|0]=e>>>16,i[c+3|0]=e>>>24,i[c+4|0]=u,i[c+5|0]=u>>>8,i[c+6|0]=u>>>16,i[c+7|0]=u>>>24,u=e=Qr(l|b[T+((1|f)+k|0)|0],0,65537,65537),i[c+24|0]=u,i[c+25|0]=u>>>8,i[c+26|0]=u>>>16,i[c+27|0]=u>>>24,u=D,i[c+28|0]=u,i[c+29|0]=u>>>8,i[c+30|0]=u>>>16,i[c+31|0]=u>>>24,i[c+16|0]=e,i[c+17|0]=e>>>8,i[c+18|0]=e>>>16,i[c+19|0]=e>>>24,i[c+20|0]=u,i[c+21|0]=u>>>8,i[c+22|0]=u>>>16,i[c+23|0]=u>>>24,f=f+2|0,r=r+16|0,s=s-2|0;);if(!(1&d))break t;s=h+(r<<1)|0,r=e=Qr(l|b[T+(f+k|0)|0],0,65537,65537),i[s+8|0]=r,i[s+9|0]=r>>>8,i[s+10|0]=r>>>16,i[s+11|0]=r>>>24,r=D,i[s+12|0]=r,i[s+13|0]=r>>>8,i[s+14|0]=r>>>16,i[s+15|0]=r>>>24,i[0|s]=e,i[s+1|0]=e>>>8,i[s+2|0]=e>>>16,i[s+3|0]=e>>>24,i[s+4|0]=r,i[s+5|0]=r>>>8,i[s+6|0]=r>>>16,i[s+7|0]=r>>>24;break t}if((0|d)<1)break t;if(c=u<<8&65280,f=0,u=t,1!=(0|d))for(r=-2&d;l=e=h+(u<<1)|0,s=Qr(c|b[T+(f+k|0)|0],0,65537,65537),i[0|l]=s,i[l+1|0]=s>>>8,i[l+2|0]=s>>>16,i[l+3|0]=s>>>24,s=D,i[l+4|0]=s,i[l+5|0]=s>>>8,i[l+6|0]=s>>>16,i[l+7|0]=s>>>24,s=Qr(c|b[T+((1|f)+k|0)|0],0,65537,65537),i[l+8|0]=s,i[l+9|0]=s>>>8,i[l+10|0]=s>>>16,i[l+11|0]=s>>>24,s=D,i[l+12|0]=s,i[l+13|0]=s>>>8,i[l+14|0]=s>>>16,i[l+15|0]=s>>>24,f=f+2|0,u=u+8|0,r=r-2|0;);if(!(1&d))break t;r=h+(u<<1)|0,e=Qr(c|b[T+(f+k|0)|0],0,65537,65537),i[0|r]=e,i[r+1|0]=e>>>8,i[r+2|0]=e>>>16,i[r+3|0]=e>>>24,e=D,i[r+4|0]=e,i[r+5|0]=e>>>8,i[r+6|0]=e>>>16,i[r+7|0]=e>>>24;break t}if((0|d)<1)break t;if(r=0,s=t,1!=(0|d))for(e=-2&d;c=h+(s<<1)|0,f=b[T+(r+k|0)|0],i[c+2|0]=f,i[c+3|0]=u,i[c+1|0]=u,i[0|c]=f,f=b[T+((1|r)+k|0)|0],i[c+5|0]=u,i[c+4|0]=f,i[c+6|0]=f,i[c+7|0]=u,r=r+2|0,s=s+4|0,e=e-2|0;);if(!(1&d))break t;e=b[T+(r+k|0)|0],i[3+(r=h+(s<<1)|0)|0]=u,i[r+1|0]=u,i[0|r]=e,i[r+2|0]=e;break t}if((0|d)<1)break t;if(f=0,s=t,1!=(0|d))for(e=-2&d;r=b[T+(f+k|0)|0],i[1+(c=h+(s<<1)|0)|0]=u,i[0|c]=r,r=b[T+((1|f)+k|0)|0],i[c+3|0]=u,i[c+2|0]=r,f=f+2|0,s=s+2|0,e=e-2|0;);if(!(1&d))break t;r=b[T+(f+k|0)|0],i[1+(e=h+(s<<1)|0)|0]=u,i[0|e]=r;break t}if(!((0|d)<1|(0|e)<2))for(g=u<<8&65280,s=0,e=t;;){for(O=h+(e<<1)|0,r=Qr(g|b[T+(s+k|0)|0],0,65537,65537),f=D,u=0;c=r,i[24+(l=O+(u<<1)|0)|0]=c,i[l+25|0]=c>>>8,i[l+26|0]=c>>>16,i[l+27|0]=c>>>24,i[l+28|0]=f,i[l+29|0]=f>>>8,i[l+30|0]=f>>>16,i[l+31|0]=f>>>24,i[l+16|0]=c,i[l+17|0]=c>>>8,i[l+18|0]=c>>>16,i[l+19|0]=c>>>24,i[l+20|0]=f,i[l+21|0]=f>>>8,i[l+22|0]=f>>>16,i[l+23|0]=f>>>24,i[l+8|0]=c,i[l+9|0]=c>>>8,i[l+10|0]=c>>>16,i[l+11|0]=c>>>24,i[l+12|0]=f,i[l+13|0]=f>>>8,i[l+14|0]=f>>>16,i[l+15|0]=f>>>24,i[0|l]=c,i[l+1|0]=c>>>8,i[l+2|0]=c>>>16,i[l+3|0]=c>>>24,i[l+4|0]=f,i[l+5|0]=f>>>8,i[l+6|0]=f>>>16,i[l+7|0]=f>>>24,(0|w)>(0|(u=u+16|0)););if(e=e+w|0,(0|d)==(0|(s=s+1|0)))break}}if(f=E+1|0,k=d+k|0,t=p(d,w)+t|0,(0|E)==(0|_))break}}f=a}return y=n+16|0,f}function I(e,t,r,A,n){var c,f,s,u=0,l=0,d=0,k=0,T=0,g=0,h=0,O=0,_=0,w=0,R=0,D=0;y=c=y-80|0,o[c+76>>2]=t,s=c+55|0,f=c+56|0,t=0;e:{t:for(;;){(0|O)<0||((2147483647-O|0)<(0|t)?(o[4041]=61,O=-1):O=t+O|0);r:{A:{n:{if(d=o[c+76>>2],u=b[0|(t=d)])for(;;){i:{a:if(u&=255){if(37!=(0|u))break i;for(u=t;;){if(37!=b[t+1|0])break a;if(l=t+2|0,o[c+76>>2]=l,u=u+1|0,k=b[t+2|0],t=l,37!=(0|k))break}}else u=t;if(t=u-d|0,e&&Ir(e,d,t),t)continue t;u=c,t=o[c+76>>2],36!=b[t+2|0]|i[o[c+76>>2]+1|0]-48>>>0>=10?(w=-1,t=t+1|0):(w=i[t+1|0]-48|0,R=1,t=t+3|0),o[u+76>>2]=t,T=0;a:if((l=(_=i[0|t])-32|0)>>>0>31)u=t;else if(u=t,75913&(l=1<<l))for(;;){if(u=t+1|0,o[c+76>>2]=u,T|=l,(l=(_=i[t+1|0])-32|0)>>>0>=32)break a;if(t=u,!(75913&(l=1<<l)))break}a:if(42!=(0|_)){if((0|(g=wt(c+76|0)))<0)break n;t=o[c+76>>2]}else{if(l=c,i[u+1|0]-48>>>0>=10||(t=o[c+76>>2],36!=b[t+2|0])){if(R)break n;R=0,g=0,e&&(t=o[r>>2],o[r>>2]=t+4,g=o[t>>2]),t=o[c+76>>2]+1|0}else o[((i[t+1|0]<<2)+n|0)-192>>2]=10,g=o[((i[t+1|0]<<3)+A|0)-384>>2],R=1,t=t+3|0;if(o[l+76>>2]=t,(0|g)>-1)break a;g=0-g|0,T|=8192}k=-1;a:if(46==b[0|t])if(42!=b[t+1|0])o[c+76>>2]=t+1,k=wt(c+76|0),t=o[c+76>>2];else{if(!(i[t+2|0]-48>>>0>=10)&&(t=o[c+76>>2],36==b[t+3|0])){o[((i[t+2|0]<<2)+n|0)-192>>2]=10,k=o[((i[t+2|0]<<3)+A|0)-384>>2],t=t+4|0,o[c+76>>2]=t;break a}if(R)break n;e?(t=o[r>>2],o[r>>2]=t+4,k=o[t>>2]):k=0,t=o[c+76>>2]+2|0,o[c+76>>2]=t}for(u=0;;){if(D=u,h=-1,i[0|t]-65>>>0>57)break e;if(_=t+1|0,o[c+76>>2]=_,u=i[0|t],t=_,!((u=b[4015+(u+p(D,58)|0)|0])-1>>>0<8))break}a:{o:{if(19!=(0|u)){if(!u)break e;if((0|w)>=0){o[(w<<2)+n>>2]=u,u=o[4+(t=(w<<3)+A|0)>>2],o[c+64>>2]=o[t>>2],o[c+68>>2]=u;break o}if(!e)break r;Ee(c- -64|0,u,r),_=o[c+76>>2];break a}if((0|w)>-1)break e}if(t=0,!e)continue t}l=-65537&T,u=8192&T?l:T,h=0,w=4052,T=f;a:{o:{c:{f:{s:{u:{b:{l:{d:{k:{E:{p:{T:{g:{h:{O:switch(t=i[_-1|0],(t=D&&3==(15&t)?-33&t:t)-88|0){case 11:break a;case 9:case 13:case 14:case 15:break o;case 27:break b;case 12:case 17:break k;case 23:break E;case 0:case 32:break p;case 24:break T;case 22:break g;case 29:break h;case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 10:case 16:case 18:case 19:case 20:case 21:case 25:case 26:case 28:case 30:case 31:break A}O:switch(t-65|0){case 0:case 4:case 5:case 6:break o;case 2:break s;case 1:case 3:break A}if(83==(0|t))break u;break A}t=o[c+64>>2],d=o[c+68>>2],w=4052;break d}t=0;g:switch(255&D){case 0:case 1:o[o[c+64>>2]>>2]=O;continue t;case 2:u=o[c+64>>2],o[u>>2]=O,o[u+4>>2]=O>>31;continue t;case 3:a[o[c+64>>2]>>1]=O;continue t;case 4:i[o[c+64>>2]]=O;continue t;case 6:o[o[c+64>>2]>>2]=O;continue t;case 7:break g;default:continue t}u=o[c+64>>2],o[u>>2]=O,o[u+4>>2]=O>>31;continue t}k=k>>>0>8?k:8,u|=8,t=120}if(d=Zt(o[c+64>>2],o[c+68>>2],f,32&t),!(8&u)|!(o[c+64>>2]|o[c+68>>2]))break l;w=4052+(t>>>4|0)|0,h=2;break l}if(d=zt(o[c+64>>2],o[c+68>>2],f),!(8&u))break l;k=(0|(t=f-d|0))<(0|k)?k:t+1|0;break l}d=l=o[c+68>>2],t=o[c+64>>2],(0|l)<-1||(0|l)<=-1?(d=0-(d+(0!=(0|t))|0)|0,t=0-t|0,o[c+64>>2]=t,o[c+68>>2]=d,h=1,w=4052):2048&u?(h=1,w=4053):w=(h=1&u)?4054:4052}d=Le(t,d,f)}if(u=(0|k)>-1?-65537&u:u,!(!!((t=o[c+64>>2])|(l=o[c+68>>2]))|k)){k=0,d=f;break A}k=(0|(t=!(t|l)+(f-d|0)|0))<(0|k)?k:t;break A}T=(t=me(d=(t=o[c+64>>2])||4062,0,k))||k+d|0,u=l,k=t?t-d|0:k;break A}if(l=o[c+64>>2],k)break f;t=0,dt(e,32,g,0,u);break c}o[c+12>>2]=0,o[c+8>>2]=o[c+64>>2],o[c+64>>2]=c+8,k=-1,l=c+8|0}t=0;f:{for(;;){if(!(d=o[l>>2]))break f;if(!((T=(0|(d=Ur(c+4|0,d)))<0)|d>>>0>k-t>>>0)){if(l=l+4|0,k>>>0>(t=t+d|0)>>>0)continue;break f}break}if(h=-1,T)break e}if(dt(e,32,g,t,u),t)for(l=0,_=o[c+64>>2];;){if(!(d=o[_>>2]))break c;if((0|(l=(d=Ur(c+4|0,d))+l|0))>(0|t))break c;if(Ir(e,c+4|0,d),_=_+4|0,!(t>>>0>l>>>0))break}else t=0}dt(e,32,g,t,8192^u),t=(0|t)<(0|g)?g:t;continue t}t=0|aA[2](e,E[c+64>>3],g,k,u,t);continue t}i[c+55|0]=o[c+64>>2],k=1,d=s,u=l;break A}l=t+1|0,o[c+76>>2]=l,u=b[t+1|0],t=l}if(h=O,e)break e;if(!R)break r;for(t=1;;){if(e=o[(t<<2)+n>>2]){if(Ee((t<<3)+A|0,e,r),h=1,10!=(0|(t=t+1|0)))continue;break e}break}if(h=1,t>>>0>=10)break e;for(;;){if(o[(t<<2)+n>>2])break n;if(10==(0|(t=t+1|0)))break}break e}h=-1;break e}dt(e,32,t=(0|(l=(k=(0|k)<(0|(T=T-d|0))?T:k)+h|0))>(0|g)?l:g,l,u),Ir(e,w,h),dt(e,48,t,l,65536^u),dt(e,48,k,T,0),Ir(e,d,T),dt(e,32,t,l,8192^u);continue}break}h=0}return y=c+80|0,h}function x(e,t,r,A,n){var c,f=0,s=0,u=0,l=0,d=0,k=0,E=0,T=0,g=0,h=0,O=0,_=0,w=0,R=0,D=0,m=0,v=0,M=0,F=0,S=0,P=0,B=0,X=0;y=c=y-448|0,f=-44;e:if(!(n>>>0<1500||(n=o[e>>2],T=ke(A+624|0,0,108),(_=255&n)>>>0>12))){if((M=te(k=A+1244|0,T,c+8|0,c+12|0,t,r,c+80|0))>>>0<=4294967176){if((h=o[c+12>>2])>>>0>_>>>0)break e;for(D=A+732|0,P=A+676|0,m=e+4|0,B=-16777216&n,r=A+680|0,t=h;n=t,t=t-1|0,!o[T+(n<<2)>>2];);if(t=1,!((O=n+1|0)>>>0<2)){if(u=3&n,n-1>>>0>=3)for(f=-4&n;E=o[(s=t<<2)+T>>2],o[r+s>>2]=g,w=o[(d=s+4|0)+T>>2],g=E+g|0,o[r+d>>2]=g,d=o[(E=s+8|0)+T>>2],g=g+w|0,o[r+E>>2]=g,E=o[(s=s+12|0)+T>>2],l=r+s|0,s=d+g|0,o[l>>2]=s,g=s+E|0,t=t+4|0,f=f-4|0;);if(u)for(;s=o[(f=t<<2)+T>>2],o[r+f>>2]=g,t=t+1|0,g=s+g|0,u=u-1|0;);}if(o[r>>2]=g,f=o[c+8>>2]){if(t=0,1!=(0|f))for(s=-2&f;E=b[t+k|0],u=o[(d=r+(E<<2)|0)>>2],o[d>>2]=u+1,i[1+(u=(u<<1)+D|0)|0]=E,i[0|u]=t,d=b[(E=1|t)+k|0],u=o[(w=r+(d<<2)|0)>>2],o[w>>2]=u+1,i[1+(u=(u<<1)+D|0)|0]=d,i[0|u]=E,t=t+2|0,s=s-2|0;);1&f&&(f=b[t+k|0],r=o[(s=r+(f<<2)|0)>>2],o[s>>2]=r+1,i[1+(r=(r<<1)+D|0)|0]=f,i[0|r]=t)}r=0,o[A+680>>2]=0;t:if(O>>>0>=2){if(t=1,1!=(0|n))for(f=(-1^h)+_|0,k=-2&n;u=o[(s=t<<2)+T>>2],o[A+s>>2]=r,d=o[(E=(s=t+1|0)<<2)+T>>2],r=(u<<t+f)+r|0,o[A+E>>2]=r,r=r+(d<<f+s)|0,t=t+2|0,k=k-2|0;);if(1&n&&(o[(t<<2)+A>>2]=r),h=(E=h+1|0)-n|0,O>>>0<2)break t;if((u=1+(_-h|0)|0)>>>0<=h>>>0)break t;for(r=-4&n,k=3&n,O=n-1>>>0<3,n=h;;){if(f=1,s=r,!O)for(;T=p(n,52)+A|0,o[T+(t=f<<2)>>2]=o[t+A>>2]>>>n,o[(d=t+4|0)+T>>2]=o[A+d>>2]>>>n,o[(d=t+8|0)+T>>2]=o[A+d>>2]>>>n,o[(t=t+12|0)+T>>2]=o[t+A>>2]>>>n,f=f+4|0,s=s-4|0;);if(t=k)for(;o[(s=f<<2)+(p(n,52)+A|0)>>2]=o[A+s>>2]>>>n,f=f+1|0,t=t-1|0;);if(!(u>>>0>(n=n+1|0)>>>0))break}}else h=(E=h+1|0)-n|0;if(o[c- -64>>2]=o[A+48>>2],t=o[A+44>>2],o[c+56>>2]=o[A+40>>2],o[c+60>>2]=t,t=o[A+36>>2],o[c+48>>2]=o[A+32>>2],o[c+52>>2]=t,t=o[A+28>>2],o[c+40>>2]=o[A+24>>2],o[c+44>>2]=t,t=o[A+20>>2],o[c+32>>2]=o[A+16>>2],o[c+36>>2]=t,t=o[A+4>>2],o[c+16>>2]=o[A>>2],o[c+20>>2]=t,t=o[A+12>>2],o[c+24>>2]=o[A+8>>2],o[c+28>>2]=t,g)for(X=E-_|0,d=0;;){if(w=1<<(O=_-(T=E-(r=b[1+(t=(d<<1)+D|0)|0])|0)|0),v=b[0|t],k=o[(F=(c+16|0)+(r<<2)|0)>>2],h>>>0<=O>>>0){if(R=o[(n=((0|(r=T+X|0))>1?r:1)<<2)+P>>2],t=p(T,52)+A|0,o[c+128>>2]=o[t+48>>2],f=o[t+44>>2],o[c+120>>2]=o[t+40>>2],o[c+124>>2]=f,f=o[t+36>>2],o[c+112>>2]=o[t+32>>2],o[c+116>>2]=f,f=o[t+28>>2],o[c+104>>2]=o[t+24>>2],o[c+108>>2]=f,f=o[t+20>>2],o[c+96>>2]=o[t+16>>2],o[c+100>>2]=f,f=o[t+12>>2],o[c+88>>2]=o[t+8>>2],o[c+92>>2]=f,f=o[t+4>>2],o[c+80>>2]=o[t>>2],o[c+84>>2]=f,S=g-R|0,f=(k<<2)+m|0,!((0|r)<2)&&(n=o[n+(c+80|0)>>2])){if(s=7&n,t=T<<16&16711680|v|16777216,r=0,n-1>>>0>=7)for(u=-8&n;a[(l=(n=r<<2)+f|0)>>1]=t,a[l+2>>1]=t>>>16,a[(l=f+(4|n)|0)>>1]=t,a[l+2>>1]=t>>>16,a[(l=f+(8|n)|0)>>1]=t,a[l+2>>1]=t>>>16,a[(l=f+(12|n)|0)>>1]=t,a[l+2>>1]=t>>>16,a[(l=f+(16|n)|0)>>1]=t,a[l+2>>1]=t>>>16,a[(l=f+(20|n)|0)>>1]=t,a[l+2>>1]=t>>>16,a[(l=f+(24|n)|0)>>1]=t,a[l+2>>1]=t>>>16,a[(n=f+(28|n)|0)>>1]=t,a[n+2>>1]=t>>>16,r=r+8|0,u=u-8|0;);if(s)for(;a[(n=f+(r<<2)|0)>>1]=t,a[n+2>>1]=t>>>16,r=r+1|0,s=s-1|0;);}if(S)for(u=(R<<1)+D|0,s=0;;){for(t=E-(r=b[1+(R=u+(s<<1)|0)|0])|0,r=(n=o[(l=(c+80|0)+(r<<2)|0)>>2])+(1<<O-t)|0,t=t+T<<16&16711680|b[0|R]<<8|v|33554432;a[(R=f+(n<<2)|0)>>1]=t,a[R+2>>1]=t>>>16,r>>>0>(n=n+1|0)>>>0;);if(o[l>>2]=r,(0|S)==(0|(s=s+1|0)))break}t=k+w|0}else if(!(k>>>0>=(t=k+w|0)>>>0)){if(r=T<<16&16711680|v|16777216,O>>>0<=2)for(f=7&w;a[(n=(k<<2)+m|0)>>1]=r,a[n+2>>1]=r>>>16,k=k+1|0,f=f-1|0;);if(!(O>>>0<3))for(;a[(n=(k<<2)+m|0)>>1]=r,a[n+2>>1]=r>>>16,a[n+28>>1]=r,a[n+30>>1]=r>>>16,a[n+24>>1]=r,a[n+26>>1]=r>>>16,a[n+20>>1]=r,a[n+22>>1]=r>>>16,a[n+16>>1]=r,a[n+18>>1]=r>>>16,a[n+12>>1]=r,a[n+14>>1]=r>>>16,a[n+8>>1]=r,a[n+10>>1]=r>>>16,a[n+4>>1]=r,a[n+6>>1]=r>>>16,(0|(k=k+8|0))!=(0|t););}if(o[F>>2]=t,(0|g)==(0|(d=d+1|0)))break}o[e>>2]=_<<16|B|_|256}f=M}return y=c+448|0,f}function j(e,t,r,A,n,i){var a=0,c=0,f=0,s=0,u=0,l=0,d=0,k=0,E=0,p=0,T=0,g=0,h=0,O=0,_=0,w=0,y=0,R=0,D=0,v=0,M=0,F=0,S=0,P=0,B=0;i&&(O=o[i+8>>2],_=o[i+4>>2]);e:{t:{r:if((f=(a=o[e+29532>>2])?1:5)>>>0>n>>>0)u=t;else for(T=e+29440|0,w=e+29376|0,y=i+10276|0,R=i+4116|0,D=i+6172|0,v=i+12|0,M=e+4120|0,F=e+6176|0,S=e+16|0,k=e+26668|0,P=e+10280|0,g=e+29356|0,h=d=e+29480|0,u=t;;){if(407710288==(-16&(b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24)))for(f=a?1:5;;){if(n>>>0<8)return-72;if((a=b[A+4|0]|b[A+5|0]<<8|b[A+6|0]<<16|b[A+7|0]<<24)>>>0>4294967287)return-14;if((a=n>>>0<(a=a+8|0)>>>0?-72:a)>>>0>4294967176)break t;if(f>>>0>(n=n-a|0)>>>0)break r;if(407710288!=(-16&(b[0|(A=A+a|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24)))break}A:if(i){if(c=o[i+8>>2],a=o[i+4>>2],o[e+29428>>2]=0,o[e+29372>>2]=f,o[e+29416>>2]=0,o[e+29420>>2]=0,o[e+29588>>2]=o[e+29368>>2]!=(a+c|0),o[(a=g)+8>>2]=0,o[a+12>>2]=0,o[a>>2]=0,o[a+4>>2]=0,o[e+10280>>2]=201326604,o[e+29432>>2]=0,o[e+29436>>2]=0,o[e+29424>>2]=3,o[k+8>>2]=o[610],a=o[609],o[k>>2]=o[608],o[k+4>>2]=a,o[e+12>>2]=P,o[e+8>>2]=M,o[e+4>>2]=F,o[e>>2]=S,o[e+29584>>2]=o[i+27304>>2],a=o[i+4>>2],o[e+29364>>2]=a,o[e+29360>>2]=a,a=a+o[i+8>>2]|0,o[e+29356>>2]=a,o[e+29368>>2]=a,o[i+27308>>2]){o[e+29432>>2]=1,o[e+29436>>2]=1,o[e+12>>2]=y,o[e+8>>2]=R,o[e+4>>2]=D,o[e>>2]=v,o[e+26668>>2]=o[i+26664>>2],o[e+26672>>2]=o[i+26668>>2],o[e+26676>>2]=o[i+26672>>2];break A}o[e+29432>>2]=0,o[e+29436>>2]=0}else{if((a=fe(e,_,O))>>>0>4294967176)break t;a=o[g>>2]}if((0|a)!=(0|u)&&(o[e+29368>>2]=a,o[e+29356>>2]=u,c=o[e+29360>>2],o[e+29360>>2]=u,o[e+29364>>2]=(c-a|0)+u),((s=o[e+29532>>2])?5:9)>>>0>n>>>0){a=-72;break e}if(f=b[((a=s?1:5)+A|0)-1|0],(a=((l=o[3920+((c=f>>>6|0)<<2)>>2]+(a+o[3904+((3&f)<<2)>>2]|0)|0)+!(a=32&f)|0)+(!c&a>>>5)|0)>>>0>4294967176)break e;if(a+3>>>0>n>>>0){a=-72;break e}if((c=L(w,A,a,s))>>>0>4294967176){a=c;break e}if(c){a=-72;break e}if(!(!(c=o[e+29404>>2])|(0|c)==o[e+29584>>2])){a=-32;break e}for(o[e+29408>>2]?(c=o[e+29536>>2],o[e+29540>>2]=!c,c||(o[e+29472>>2]=2048144761,o[e+29476>>2]=1640531534,o[e+29464>>2]=0,o[e+29468>>2]=0,o[e+29456>>2]=668265295,o[e+29460>>2]=-1028477379,o[e+29448>>2]=-1379879466,o[e+29452>>2]=1625958382,o[e+29440>>2]=0,o[e+29444>>2]=0,o[d+32>>2]=0,o[d+36>>2]=0,o[h+24>>2]=0,o[h+28>>2]=0,o[(c=d)+16>>2]=0,o[c+20>>2]=0,o[c+8>>2]=0,o[c+12>>2]=0,o[c>>2]=0,o[c+4>>2]=0)):o[e+29540>>2]=0,E=r+u|0,n=n-a|0,A=A+a|0,s=u;;){if(n>>>0<3){a=-72;break e}c=((p=b[0|A]|b[A+1|0]<<8)|b[A+2|0]<<16)>>>3|0,a=-20;A:{n:switch((f=l=p>>>1&3)-1|0){case 2:break e;case 0:break A}f=c}if((n=n-3|0)>>>0<f>>>0){a=-72;break e}A=A+3|0;A:{n:switch(0|l){case 0:if(E-s>>>0<f>>>0){a=-70;break e}if(!s){if(a=0,!f)break A;a=-74;break e}re(s,A,f),a=f;break A;case 1:if(c>>>0>E-s>>>0){a=-70;break e}if(!s){if(a=0,!c)break A;a=-74;break e}ke(s,b[0|A],c),a=c;break A;case 2:break n;default:break e}if((a=m(e,s,E-s|0,A,f))>>>0>4294967176)break e}if(o[e+29540>>2]&&W(T,s,a),n=n-f|0,A=A+f|0,s=a+s|0,1&p)break}if(f=a=o[e+29380>>2],-1!=(0|(c=o[e+29376>>2]))|-1!=(0|a)&&(a=-20,(0|(l=c))!=(0|(c=s-u|0))|c>>31!=(0|f)))break e;if(o[e+29408>>2]){if(a=-22,n>>>0<4)break e;if(!o[e+29536>>2]&&(b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24)!=(0|Y(T)))break e;n=n-4|0,A=A+4|0}if((a=s-u|0)>>>0>=4294967177)break e;if(u=a?s:u,B=1,r=r-a|0,!((f=(a=o[e+29532>>2])?1:5)>>>0<=n>>>0))break}a=-72,n||(a=u-t|0)}return a}return B&&-10==(0|a)?-72:a}function Q(e,t,r,A,n,o){var c=0,f=0,s=0,u=0,d=0,k=0,E=0,p=0,T=0,h=0,O=0,_=0,w=0,y=0,R=0;u=-46;e:if(!(r>>>0>255||(p=o,s=31&(o=A),(63&o)>>>0>=32?(o=1<<s,s=0):(o=(1<<s)-1&1>>>32-s,s=1<<s),o=(f=s+((c=r+1|0)<<1)|0)>>>0<s>>>0?o+1|0:o,(s=f+8|0)>>>0<8&&(o=o+1|0),!o&s>>>0>p>>>0|o||(u=-44,A>>>0>12)))){O=e+4|0,p=(T=1<<A)-1|0,R=2+((r<<1)+n|0)|0;t:{r:if(c){if(h=1,y=1&c,_=65536<<A-1>>16,r)for(s=-2&c,u=p,o=0;65535!=(0|(d=l[(f=o<<1)+t>>1]))?h=(0|_)>d<<16>>16?h:0:(i[2+((u<<2)+O|0)|0]=o,u=u-1|0,d=1),a[n+f>>1]=d,65535==(0|(d=l[(f=(w=1|o)<<1)+t>>1]))?(i[2+((u<<2)+O|0)|0]=w,u=u-1|0,d=1):h=(0|_)>d<<16>>16?h:0,a[n+f>>1]=d,o=o+2|0,s=s-2|0;);else o=0,u=p;if(y&&(65535==(0|(d=l[(o<<1)+t>>1]))?(i[2+((u<<2)+O|0)|0]=o,u=u-1|0,d=1):h=(0|_)>d<<16>>16?h:0,a[(o<<1)+n>>1]=d),a[e+2>>1]=h,a[e>>1]=A,(0|u)!=(0|p)){if(!c)break t;for(f=3+((T>>>3)+(T>>>1)|0)|0,o=0,s=0;;){if(e=s,!((0|(s=(E=l[(s<<1)+t>>1])<<16>>16))<1)){if(E=1&(k=E>>>0>1?E:1),1!=(0|s))for(s=65534&k;;){for(i[2+((o<<2)+O|0)|0]=e;u>>>0<(o=p&o+f)>>>0;);for(i[2+((o<<2)+O|0)|0]=e;u>>>0<(o=p&o+f)>>>0;);if(!(s=s-2|0))break}if(E)for(i[2+((o<<2)+O|0)|0]=e;u>>>0<(o=p&o+f)>>>0;);}if(s=e+1|0,(0|e)==(0|r))break}if(u=-1,o)break e;break t}if(h=3+((T>>>3)+(T>>>1)|0)|0,!c)break r;for(_=0,o=0;;){if(e=o,y=a[(o<<1)+t>>1],o=E,i[0|(u=w=_+R|0)]=o,i[u+1|0]=o>>>8,i[u+2|0]=o>>>16,i[u+3|0]=o>>>24,i[u+4|0]=k,i[u+5|0]=k>>>8,i[u+6|0]=k>>>16,i[u+7|0]=k>>>24,!((0|y)<9)){if(d=7&(o=1+((s=y-9|0)>>>3|0)|0),u=8,s>>>0>=56)for(s=1073741816&o;o=E,i[0|(c=u+w|0)]=o,i[c+1|0]=o>>>8,i[c+2|0]=o>>>16,i[c+3|0]=o>>>24,f=k,i[c+4|0]=f,i[c+5|0]=f>>>8,i[c+6|0]=f>>>16,i[c+7|0]=f>>>24,i[c+56|0]=o,i[c+57|0]=o>>>8,i[c+58|0]=o>>>16,i[c+59|0]=o>>>24,i[c+60|0]=f,i[c+61|0]=f>>>8,i[c+62|0]=f>>>16,i[c+63|0]=f>>>24,i[c+48|0]=o,i[c+49|0]=o>>>8,i[c+50|0]=o>>>16,i[c+51|0]=o>>>24,i[c+52|0]=f,i[c+53|0]=f>>>8,i[c+54|0]=f>>>16,i[c+55|0]=f>>>24,i[c+40|0]=o,i[c+41|0]=o>>>8,i[c+42|0]=o>>>16,i[c+43|0]=o>>>24,i[c+44|0]=f,i[c+45|0]=f>>>8,i[c+46|0]=f>>>16,i[c+47|0]=f>>>24,i[c+32|0]=o,i[c+33|0]=o>>>8,i[c+34|0]=o>>>16,i[c+35|0]=o>>>24,i[c+36|0]=f,i[c+37|0]=f>>>8,i[c+38|0]=f>>>16,i[c+39|0]=f>>>24,i[c+24|0]=o,i[c+25|0]=o>>>8,i[c+26|0]=o>>>16,i[c+27|0]=o>>>24,i[c+28|0]=f,i[c+29|0]=f>>>8,i[c+30|0]=f>>>16,i[c+31|0]=f>>>24,i[c+16|0]=o,i[c+17|0]=o>>>8,i[c+18|0]=o>>>16,i[c+19|0]=o>>>24,i[c+20|0]=f,i[c+21|0]=f>>>8,i[c+22|0]=f>>>16,i[c+23|0]=f>>>24,i[c+8|0]=o,i[c+9|0]=o>>>8,i[c+10|0]=o>>>16,i[c+11|0]=o>>>24,i[c+12|0]=f,i[c+13|0]=f>>>8,i[c+14|0]=f>>>16,i[c+15|0]=f>>>24,u=u- -64|0,s=s-8|0;);if(d)for(;o=E,i[0|(s=u+w|0)]=o,i[s+1|0]=o>>>8,i[s+2|0]=o>>>16,i[s+3|0]=o>>>24,i[s+4|0]=k,i[s+5|0]=k>>>8,i[s+6|0]=k>>>16,i[s+7|0]=k>>>24,u=u+8|0,d=d-1|0;);}if(k=k+16843009|0,k=(o=E+16843009|0)>>>0<16843009?k+1|0:k,E=o,o=e+1|0,_=_+y|0,(0|e)==(0|r))break}}else a[e+2>>1]=1,a[e>>1]=A,h=3+((T>>>3)+(T>>>1)|0)|0;for(e=h<<1,o=0,u=0;i[2+(((u&p)<<2)+O|0)|0]=b[o+R|0],i[2+(((p&u+h)<<2)+O|0)|0]=b[(1|o)+R|0],u=p&e+u,T>>>0>(o=o+2|0)>>>0;);}for(r=T>>>0>1?T:1,t=A+1|0,u=0,o=0;e=(b[2+(A=(o<<2)+O|0)|0]<<1)+n|0,E=l[e>>1],a[e>>1]=E+1,e=t+(-32^g(E))|0,i[A+3|0]=e,a[A>>1]=(E<<e)-T,(0|r)!=(0|(o=o+1|0)););}return u}function C(e,t,r,A,n){var i=0,a=0,c=0;c=o[e+29428>>2],a=o[e+29372>>2],c-3>>>0>=2||(a=o[e+29372>>2],o[e+29424>>2]||(a=(i=n>>>0>1?n:1)>>>0<(a=o[e+29372>>2])>>>0?i:a)),i=-72;e:{t:{r:if((0|n)==(0|a)){r&&(0|(i=o[e+29356>>2]))!=(0|t)&&(o[e+29368>>2]=i,o[e+29356>>2]=t,a=o[e+29360>>2],o[e+29360>>2]=t,o[e+29364>>2]=(a-i|0)+t),i=-1;A:{n:{i:{a:{o:switch(0|c){case 0:if(i=1,!o[e+29532>>2]&&(i=5,407710288==(-16&(b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24)))){re(e+160776|0,A,n),o[e+29428>>2]=6,o[e+29372>>2]=8-n;break e}if(n>>>0<i>>>0)return o[e+29528>>2]=-72,-72;if(t=b[(A+i|0)-1|0],i=((i=o[3920+((r=t>>>6|0)<<2)>>2]+(o[3904+((3&t)<<2)>>2]+i|0)|0)+!(t&=32)|0)+(!r&t>>>5)|0,o[e+29528>>2]=i,i>>>0>4294967176)break r;re(e+160776|0,A,n),o[e+29428>>2]=1,o[e+29372>>2]=i-n;break e;case 1:if(re((t=e+160776|0)+(o[e+29528>>2]-n|0)|0,A,n),(t=L(e+29376|0,t,o[e+29528>>2],o[e+29532>>2]))>>>0>4294967176)return t;if(i=-72,t)break r;if((t=o[e+29404>>2])&&(i=-32,(0|t)!=o[e+29584>>2]))break r;o[e+29408>>2]?(t=o[e+29536>>2],o[e+29540>>2]=!t,t||(o[e+29440>>2]=0,o[e+29444>>2]=0,o[(t=e+29480|0)>>2]=0,o[t+4>>2]=0,o[(t=e+29472|0)>>2]=2048144761,o[t+4>>2]=1640531534,o[(t=e+29464|0)>>2]=0,o[t+4>>2]=0,o[(t=e+29456|0)>>2]=668265295,o[t+4>>2]=-1028477379,o[(t=e+29448|0)>>2]=-1379879466,o[t+4>>2]=1625958382,o[(t=e+29488|0)>>2]=0,o[t+4>>2]=0,o[(t=e+29496|0)>>2]=0,o[t+4>>2]=0,o[(t=e+29504|0)>>2]=0,o[t+4>>2]=0,o[(t=e+29512|0)>>2]=0,o[t+4>>2]=0)):o[e+29540>>2]=0;break t;case 2:t=((r=b[0|A]|b[A+1|0]<<8)|b[A+2|0]<<16)>>>3|0,i=-20;c:{f:switch((n=A=r>>>1&3)-1|0){case 0:break c;case 2:break r}n=t}if(d[e+29392>>2]<n>>>0)break r;if(r&=1,o[e+29564>>2]=t,o[e+29424>>2]=A,o[e+29372>>2]=n,n){o[e+29428>>2]=r?4:3;break e}if(r){if(!o[e+29408>>2])break a;o[e+29428>>2]=5,o[e+29372>>2]=4;break e}break t;case 3:case 4:i=-20;c:{f:switch(o[e+29424>>2]){case 2:i=m(e,t,r,A,n);break c;case 0:if(i=-70,r>>>0<n>>>0)break r;if(!t){if(i=-74,n)break r;break n}if(re(t,A,n),n>>>0<=4294967176)break n;return n;case 1:break f;default:break r}i=-70,(n=r)>>>0<(r=o[e+29564>>2])>>>0||(i=r?-74:0,t&&(ke(t,b[0|A],r),i=r))}if(a=0,o[e+29372>>2]=0,i>>>0>4294967176)break r;break A;case 6:break i;case 7:break a;case 5:break o;default:break r}if(o[e+29540>>2]&&(i=-22,(b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24)!=(0|Y(e+29440|0))))break r}i=0,o[e+29428>>2]=0,o[e+29372>>2]=0;break r}re(160784+(e-n|0)|0,A,n),o[e+29428>>2]=7,t=e,e=e+160780|0,o[t+29372>>2]=b[0|e]|b[e+1|0]<<8|b[e+2|0]<<16|b[e+3|0]<<24;break e}a=o[e+29372>>2]-n|0,o[e+29372>>2]=a,i=n}if((r=i)>>>0>d[e+29392>>2])return-20;if(i=o[e+29420>>2],i=(c=(n=r)+o[e+29416>>2]|0)>>>0<n>>>0?i+1|0:i,o[e+29416>>2]=c,o[e+29420>>2]=i,o[e+29540>>2]&&(W(e+29440|0,t,r),a=o[e+29372>>2]),o[e+29356>>2]=t+r,a)return r;if(4==o[e+29428>>2]){if(A=t=o[e+29380>>2],-1!=(0|(n=o[e+29376>>2]))|-1!=(0|t)&&(i=-20,(0|n)!=o[e+29416>>2]|(0|A)!=o[e+29420>>2]))break r;return o[e+29408>>2]?(o[e+29428>>2]=5,o[e+29372>>2]=4,r):(o[e+29428>>2]=0,o[e+29372>>2]=0,r)}return o[e+29372>>2]=3,o[e+29428>>2]=2,r}return i}o[e+29428>>2]=2,o[e+29372>>2]=3}return 0}function Z(e,t,r,A,n,c,f){var s,u,d,k,E=0,p=0,T=0,h=0,O=0,_=0,w=0,y=0,R=0,D=0,m=0,v=0;k=f+106|0,u=e+8|0,w=1,d=(s=1<<c)-1|0;e:{t:{if(-1!=(0|r)){if(p=1&(T=r+1|0),D=65536<<c-1>>16,r)for(_=-2&T,T=d;65535!=(0|(O=l[(R=E<<1)+t>>1]))?w=O<<16>>16<(0|D)?w:0:(o[4+((T<<3)+u|0)>>2]=E,T=T-1|0,O=1),a[f+R>>1]=O,65535==(0|(O=l[(m=(R=1|E)<<1)+t>>1]))?(o[4+((T<<3)+u|0)>>2]=R,T=T-1|0,O=1):w=O<<16>>16<(0|D)?w:0,a[f+m>>1]=O,E=E+2|0,_=_-2|0;);else T=d;if(p&&(65535==(0|(O=l[(E<<1)+t>>1]))?(o[4+((T<<3)+u|0)>>2]=E,T=T-1|0,O=1):w=O<<16>>16<(0|D)?w:0,a[(E<<1)+f>>1]=O),o[e+4>>2]=c,o[e>>2]=w,m=s>>>3|0,(0|T)!=(0|d))break t;for(w=0,E=0;;){if(D=E,R=a[(E<<1)+t>>1],e=y,i[0|(T=v=w+k|0)]=e,i[T+1|0]=e>>>8,i[T+2|0]=e>>>16,i[T+3|0]=e>>>24,i[T+4|0]=h,i[T+5|0]=h>>>8,i[T+6|0]=h>>>16,i[T+7|0]=h>>>24,!((0|R)<9)){if(O=7&(E=1+((e=R-9|0)>>>3|0)|0),T=8,e>>>0>=56)for(_=1073741816&E;e=y,i[0|(p=T+v|0)]=e,i[p+1|0]=e>>>8,i[p+2|0]=e>>>16,i[p+3|0]=e>>>24,e=h,i[p+4|0]=e,i[p+5|0]=e>>>8,i[p+6|0]=e>>>16,i[p+7|0]=e>>>24,E=y,i[p+56|0]=E,i[p+57|0]=E>>>8,i[p+58|0]=E>>>16,i[p+59|0]=E>>>24,i[p+60|0]=e,i[p+61|0]=e>>>8,i[p+62|0]=e>>>16,i[p+63|0]=e>>>24,i[p+48|0]=E,i[p+49|0]=E>>>8,i[p+50|0]=E>>>16,i[p+51|0]=E>>>24,i[p+52|0]=e,i[p+53|0]=e>>>8,i[p+54|0]=e>>>16,i[p+55|0]=e>>>24,i[p+40|0]=E,i[p+41|0]=E>>>8,i[p+42|0]=E>>>16,i[p+43|0]=E>>>24,i[p+44|0]=e,i[p+45|0]=e>>>8,i[p+46|0]=e>>>16,i[p+47|0]=e>>>24,i[p+32|0]=E,i[p+33|0]=E>>>8,i[p+34|0]=E>>>16,i[p+35|0]=E>>>24,i[p+36|0]=e,i[p+37|0]=e>>>8,i[p+38|0]=e>>>16,i[p+39|0]=e>>>24,i[p+24|0]=E,i[p+25|0]=E>>>8,i[p+26|0]=E>>>16,i[p+27|0]=E>>>24,i[p+28|0]=e,i[p+29|0]=e>>>8,i[p+30|0]=e>>>16,i[p+31|0]=e>>>24,i[p+16|0]=E,i[p+17|0]=E>>>8,i[p+18|0]=E>>>16,i[p+19|0]=E>>>24,i[p+20|0]=e,i[p+21|0]=e>>>8,i[p+22|0]=e>>>16,i[p+23|0]=e>>>24,i[p+8|0]=E,i[p+9|0]=E>>>8,i[p+10|0]=E>>>16,i[p+11|0]=E>>>24,i[p+12|0]=e,i[p+13|0]=e>>>8,i[p+14|0]=e>>>16,i[p+15|0]=e>>>24,T=T- -64|0,_=_-8|0;);if(O)for(;e=y,i[0|(E=T+v|0)]=e,i[E+1|0]=e>>>8,i[E+2|0]=e>>>16,i[E+3|0]=e>>>24,i[E+4|0]=h,i[E+5|0]=h>>>8,i[E+6|0]=h>>>16,i[E+7|0]=h>>>24,T=T+8|0,O=O-1|0;);}if(h=h+16843009|0,h=(e=y+16843009|0)>>>0<16843009?h+1|0:h,y=e,E=D+1|0,w=w+R|0,(0|r)==(0|D))break}e=3+(m+(s>>>1|0)|0)|0}else o[e+4>>2]=c,o[e>>2]=1,e=3+((s>>>3)+(s>>>1)|0)|0;for(t=e<<1,E=0,T=0;o[4+(((T&d)<<3)+u|0)>>2]=b[E+k|0],o[4+(((d&e+T)<<3)+u|0)>>2]=b[(1|E)+k|0],T=d&t+T,s>>>0>(E=E+2|0)>>>0;);break e}for(y=3+(m+(s>>>1|0)|0)|0,E=0,_=0;;){if(!((0|(h=a[((e=_)<<1)+t>>1]))<1)){if(1!=(0|h))for(_=-2&h;;){for(o[4+((E<<3)+u|0)>>2]=e;T>>>0<(E=d&E+y)>>>0;);for(o[4+((E<<3)+u|0)>>2]=e;T>>>0<(E=d&E+y)>>>0;);if(!(_=_-2|0))break}if(1&h)for(o[4+((E<<3)+u|0)>>2]=e;T>>>0<(E=d&E+y)>>>0;);}if(_=e+1|0,(0|e)==(0|r))break}}for(y=s>>>0>1?s:1,c=c+1|0,t=0;T=o[4+(e=(t<<3)+u|0)>>2],r=l[(h=(T<<1)+f|0)>>1],a[h>>1]=r+1,h=c+(-32^g(r))|0,i[e+3|0]=h,a[e>>1]=(r<<h)-s,r=T<<2,i[e+2|0]=o[r+n>>2],o[e+4>>2]=o[r+A>>2],(0|y)!=(0|(t=t+1|0)););}function G(e){var t,r=0,A=0,n=0;y=t=y-304|0;e:if(ir(e,76)){t:{r:{A:{n:{i:{a:{o:{c:{f:{s:{u:{b:{l:{d:{k:{E:{p:{T:{g:{h:{O:{_:{w:switch(((A=(0|(A=o[e>>2]))!=o[e+4>>2]?i[0|A]:0)<<24>>24)-65|0){case 19:break e;case 46:break f;case 45:break s;case 56:break u;case 55:break b;case 44:break l;case 43:break d;case 41:break k;case 40:break E;case 51:break p;case 50:break T;case 39:break g;case 32:break h;case 34:break O;case 33:break _;case 54:break w;case 20:break r;case 3:break A;case 0:break n;case 30:break i;case 36:break a;case 35:break o;case 37:break c;default:break t}o[e>>2]=o[e>>2]+1,r=dr(t+296|0,6283),A=o[r+4>>2],o[t>>2]=o[r>>2],o[t+4>>2]=A,r=Pt(e,t);break e}if(A=dr(t+288|0,6291),n=o[A+4>>2],o[t+16>>2]=o[A>>2],o[t+20>>2]=n,rt(e,t+16|0)){o[t+144>>2]=0,r=mr(e,t+144|0);break e}if(A=dr(t+280|0,6295),n=o[A+4>>2],o[t+8>>2]=o[A>>2],o[t+12>>2]=n,!rt(e,t+8|0))break e;o[t+144>>2]=1,r=mr(e,t+144|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+272|0,6299),A=o[r+4>>2],o[t+24>>2]=o[r>>2],o[t+28>>2]=A,r=Pt(e,t+24|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+264|0,6304),A=o[r+4>>2],o[t+32>>2]=o[r>>2],o[t+36>>2]=A,r=Pt(e,t+32|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+256|0,6316),A=o[r+4>>2],o[t+40>>2]=o[r>>2],o[t+44>>2]=A,r=Pt(e,t+40|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+248|0,6330),A=o[r+4>>2],o[t+48>>2]=o[r>>2],o[t+52>>2]=A,r=Pt(e,t+48|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+240|0,6336),A=o[r+4>>2],o[t+56>>2]=o[r>>2],o[t+60>>2]=A,r=Pt(e,t+56|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+232|0,6351),A=o[r+4>>2],o[t+64>>2]=o[r>>2],o[t+68>>2]=A,r=Pt(e,t- -64|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+224|0,6352),A=o[r+4>>2],o[t+72>>2]=o[r>>2],o[t+76>>2]=A,r=Pt(e,t+72|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+216|0,6354),A=o[r+4>>2],o[t+80>>2]=o[r>>2],o[t+84>>2]=A,r=Pt(e,t+80|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+208|0,6356),A=o[r+4>>2],o[t+88>>2]=o[r>>2],o[t+92>>2]=A,r=Pt(e,t+88|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+200|0,6359),A=o[r+4>>2],o[t+96>>2]=o[r>>2],o[t+100>>2]=A,r=Pt(e,t+96|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+192|0,6362),A=o[r+4>>2],o[t+104>>2]=o[r>>2],o[t+108>>2]=A,r=Pt(e,t+104|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+184|0,6366),A=o[r+4>>2],o[t+112>>2]=o[r>>2],o[t+116>>2]=A,r=Pt(e,t+112|0);break e}o[e>>2]=o[e>>2]+1,r=dr(t+176|0,6375),A=o[r+4>>2],o[t+120>>2]=o[r>>2],o[t+124>>2]=A,r=Pt(e,t+120|0);break e}o[e>>2]=o[e>>2]+1,r=function(e){var t=0,r=0,A=0,n=0,a=0;y=A=y-16|0,t=0;c:if(!(o[e+4>>2]-o[e>>2]>>>0<9)){r=A+8|0,t=o[e>>2],o[r+4>>2]=t+8,o[r>>2]=t,t=o[r>>2],a=o[r+4>>2];f:{for(;;){if((0|t)==(0|a))break f;if(n=i[0|t],t=t+1|0,!(n-48>>>0<10|(32|n)-97>>>0<6))break}t=0;break c}o[e>>2]=o[e>>2]+8,t=0,ir(e,69)&&(t=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=70,o[r>>2]=5508,o[r>>2]=6640,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,r))}return y=A+16|0,t}(e);break e}o[e>>2]=o[e>>2]+1,r=function(e){var t=0,r=0,A=0,n=0,a=0;y=A=y-16|0,t=0;o:if(!(o[e+4>>2]-o[e>>2]>>>0<17)){r=A+8|0,t=o[e>>2],o[r+4>>2]=t+16,o[r>>2]=t,t=o[r>>2],a=o[r+4>>2];c:{for(;;){if((0|t)==(0|a))break c;if(n=i[0|t],t=t+1|0,!(n-48>>>0<10|(32|n)-97>>>0<6))break}t=0;break o}o[e>>2]=o[e>>2]+16,t=0,ir(e,69)&&(t=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=71,o[r>>2]=5508,o[r>>2]=6756,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,r))}return y=A+16|0,t}(e);break e}o[e>>2]=o[e>>2]+1,r=function(e){var t=0,r=0,A=0,n=0,a=0;y=A=y-16|0,t=0;a:if(!(o[e+4>>2]-o[e>>2]>>>0<33)){r=A+8|0,t=o[e>>2],o[r+4>>2]=t+32,o[r>>2]=t,t=o[r>>2],a=o[r+4>>2];o:{for(;;){if((0|t)==(0|a))break o;if(n=i[0|t],t=t+1|0,!(n-48>>>0<10|(32|n)-97>>>0<6))break}t=0;break a}o[e>>2]=o[e>>2]+32,t=0,ir(e,69)&&(t=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=72,o[r>>2]=5508,o[r>>2]=6872,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,r))}return y=A+16|0,t}(e);break e}if(r=dr(t+168|0,5024),A=o[r+4>>2],o[t+128>>2]=o[r>>2],o[t+132>>2]=A,rt(e,t+128|0)&&(r=oe(e))&&ir(e,69))break e;r=0;break e}if(A=X(e),o[t+144>>2]=A,!A)break e;if(!ir(e,69))break e;r=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=66,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=6992,e}(e+416|0,t+144|0);break e}if(A=dr(t+160|0,6393),n=o[A+4>>2],o[t+136>>2]=o[A>>2],o[t+140>>2]=n,!rt(e,t+136|0))break e;r=Vr(e,6397);break e}if(A=o[e>>2],108!=(255&(A=o[e+4>>2]-A>>>0>1?b[A+1|0]:0)))break e;if(A=ee(e,0),o[t+144>>2]=A,!A)break e;if(!ir(e,69))break e;r=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=67,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=8044,e}(e+416|0,t+144|0);break e}A=X(e),o[t+156>>2]=A,A&&(et(t+144|0,e,0),o[t+144>>2]!=o[t+148>>2]&&ir(e,69)&&(r=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=68,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=8160,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}(e+416|0,t+156|0,t+144|0)))}return y=t+304|0,r}function N(e){var t=0,r=0,A=0,n=0,i=0,a=0,c=0,f=0,s=0;e:if(e|=0){i=(A=e-8|0)+(e=-8&(t=o[e-4>>2]))|0;t:if(!(1&t)){if(!(3&t))break e;if((A=A-(t=o[A>>2])|0)>>>0<d[4046])break e;if(e=e+t|0,o[4047]==(0|A)){if(3==(3&(t=o[i+4>>2])))return o[4044]=e,o[i+4>>2]=-2&t,o[A+4>>2]=1|e,void(o[e+A>>2]=e)}else{if(t>>>0<=255){if(n=o[A+8>>2],t=t>>>3|0,(0|(r=o[A+12>>2]))==(0|n)){f=16168,s=o[4042]&Er(t),o[f>>2]=s;break t}o[n+12>>2]=r,o[r+8>>2]=n;break t}if(c=o[A+24>>2],(0|A)==(0|(t=o[A+12>>2])))if((r=o[(n=A+20|0)>>2])||(r=o[(n=A+16|0)>>2])){for(;a=n,(r=o[(n=(t=r)+20|0)>>2])||(n=t+16|0,r=o[t+16>>2]););o[a>>2]=0}else t=0;else r=o[A+8>>2],o[r+12>>2]=t,o[t+8>>2]=r;if(!c)break t;n=o[A+28>>2];r:{if(o[(r=16472+(n<<2)|0)>>2]==(0|A)){if(o[r>>2]=t,t)break r;f=16172,s=o[4043]&Er(n),o[f>>2]=s;break t}if(o[c+(o[c+16>>2]==(0|A)?16:20)>>2]=t,!t)break t}if(o[t+24>>2]=c,(r=o[A+16>>2])&&(o[t+16>>2]=r,o[r+24>>2]=t),!(r=o[A+20>>2]))break t;o[t+20>>2]=r,o[r+24>>2]=t}}if(!(A>>>0>=i>>>0)&&1&(t=o[i+4>>2])){t:{if(!(2&t)){if(o[4048]==(0|i)){if(o[4048]=A,e=o[4045]+e|0,o[4045]=e,o[A+4>>2]=1|e,o[4047]!=(0|A))break e;return o[4044]=0,void(o[4047]=0)}if(o[4047]==(0|i))return o[4047]=A,e=o[4044]+e|0,o[4044]=e,o[A+4>>2]=1|e,void(o[e+A>>2]=e);e=(-8&t)+e|0;r:if(t>>>0<=255){if(t=t>>>3|0,(0|(r=o[i+8>>2]))==(0|(n=o[i+12>>2]))){f=16168,s=o[4042]&Er(t),o[f>>2]=s;break r}o[r+12>>2]=n,o[n+8>>2]=r}else{if(c=o[i+24>>2],(0|i)==(0|(t=o[i+12>>2])))if((r=o[(n=i+20|0)>>2])||(r=o[(n=i+16|0)>>2])){for(;a=n,(r=o[(n=(t=r)+20|0)>>2])||(n=t+16|0,r=o[t+16>>2]););o[a>>2]=0}else t=0;else r=o[i+8>>2],o[r+12>>2]=t,o[t+8>>2]=r;if(c){n=o[i+28>>2];A:{if(o[(r=16472+(n<<2)|0)>>2]==(0|i)){if(o[r>>2]=t,t)break A;f=16172,s=o[4043]&Er(n),o[f>>2]=s;break r}if(o[c+(o[c+16>>2]==(0|i)?16:20)>>2]=t,!t)break r}o[t+24>>2]=c,(r=o[i+16>>2])&&(o[t+16>>2]=r,o[r+24>>2]=t),(r=o[i+20>>2])&&(o[t+20>>2]=r,o[r+24>>2]=t)}}if(o[A+4>>2]=1|e,o[e+A>>2]=e,o[4047]!=(0|A))break t;return void(o[4044]=e)}o[i+4>>2]=-2&t,o[A+4>>2]=1|e,o[e+A>>2]=e}if(e>>>0<=255)return t=16208+((e=e>>>3|0)<<3)|0,(r=o[4042])&(e=1<<e)?e=o[t+8>>2]:(o[4042]=e|r,e=t),o[t+8>>2]=A,o[e+12>>2]=A,o[A+12>>2]=t,void(o[A+8>>2]=e);n=31,o[A+16>>2]=0,o[A+20>>2]=0,e>>>0<=16777215&&(t=e>>>8|0,t<<=a=t+1048320>>>16&8,n=28+((t=((t<<=n=t+520192>>>16&4)<<(r=t+245760>>>16&2)>>>15|0)-(r|n|a)|0)<<1|e>>>t+21&1)|0),o[A+28>>2]=n,a=16472+(n<<2)|0;t:{r:{if((r=o[4043])&(t=1<<n)){for(n=e<<(31==(0|n)?0:25-(n>>>1|0)|0),t=o[a>>2];;){if(r=t,(-8&o[t+4>>2])==(0|e))break r;if(t=n>>>29|0,n<<=1,!(t=o[16+(a=r+(4&t)|0)>>2]))break}o[a+16>>2]=A,o[A+24>>2]=r}else o[4043]=t|r,o[a>>2]=A,o[A+24>>2]=a;o[A+12>>2]=A,o[A+8>>2]=A;break t}e=o[r+8>>2],o[e+12>>2]=A,o[r+8>>2]=A,o[A+24>>2]=0,o[A+12>>2]=r,o[A+8>>2]=e}e=o[4050]-1|0,o[4050]=e||-1}}}function H(e,t){var r,A=0,n=0,i=0,a=0,c=0,f=0,s=0;r=e+t|0;e:{t:if(!(1&(A=o[e+4>>2]))){if(!(3&A))break e;if(t=(A=o[e>>2])+t|0,(0|(e=e-A|0))==o[4047]){if(3==(3&(A=o[r+4>>2])))return o[4044]=t,o[r+4>>2]=-2&A,o[e+4>>2]=1|t,void(o[r>>2]=t)}else{if(A>>>0<=255){if(i=o[e+8>>2],A=A>>>3|0,(0|(n=o[e+12>>2]))==(0|i)){f=16168,s=o[4042]&Er(A),o[f>>2]=s;break t}o[i+12>>2]=n,o[n+8>>2]=i;break t}if(c=o[e+24>>2],(0|(A=o[e+12>>2]))==(0|e))if((n=o[(i=e+20|0)>>2])||(n=o[(i=e+16|0)>>2])){for(;a=i,(n=o[(i=(A=n)+20|0)>>2])||(i=A+16|0,n=o[A+16>>2]););o[a>>2]=0}else A=0;else n=o[e+8>>2],o[n+12>>2]=A,o[A+8>>2]=n;if(!c)break t;i=o[e+28>>2];r:{if(o[(n=16472+(i<<2)|0)>>2]==(0|e)){if(o[n>>2]=A,A)break r;f=16172,s=o[4043]&Er(i),o[f>>2]=s;break t}if(o[c+(o[c+16>>2]==(0|e)?16:20)>>2]=A,!A)break t}if(o[A+24>>2]=c,(n=o[e+16>>2])&&(o[A+16>>2]=n,o[n+24>>2]=A),!(n=o[e+20>>2]))break t;o[A+20>>2]=n,o[n+24>>2]=A}}t:{if(!(2&(A=o[r+4>>2]))){if(o[4048]==(0|r)){if(o[4048]=e,t=o[4045]+t|0,o[4045]=t,o[e+4>>2]=1|t,o[4047]!=(0|e))break e;return o[4044]=0,void(o[4047]=0)}if(o[4047]==(0|r))return o[4047]=e,t=o[4044]+t|0,o[4044]=t,o[e+4>>2]=1|t,void(o[e+t>>2]=t);t=(-8&A)+t|0;r:if(A>>>0<=255){if(i=o[r+8>>2],A=A>>>3|0,(0|(n=o[r+12>>2]))==(0|i)){f=16168,s=o[4042]&Er(A),o[f>>2]=s;break r}o[i+12>>2]=n,o[n+8>>2]=i}else{if(c=o[r+24>>2],(0|r)==(0|(A=o[r+12>>2])))if((i=o[(n=r+20|0)>>2])||(i=o[(n=r+16|0)>>2])){for(;a=n,(i=o[(n=(A=i)+20|0)>>2])||(n=A+16|0,i=o[A+16>>2]););o[a>>2]=0}else A=0;else n=o[r+8>>2],o[n+12>>2]=A,o[A+8>>2]=n;if(c){i=o[r+28>>2];A:{if(o[(n=16472+(i<<2)|0)>>2]==(0|r)){if(o[n>>2]=A,A)break A;f=16172,s=o[4043]&Er(i),o[f>>2]=s;break r}if(o[c+(o[c+16>>2]==(0|r)?16:20)>>2]=A,!A)break r}o[A+24>>2]=c,(n=o[r+16>>2])&&(o[A+16>>2]=n,o[n+24>>2]=A),(n=o[r+20>>2])&&(o[A+20>>2]=n,o[n+24>>2]=A)}}if(o[e+4>>2]=1|t,o[e+t>>2]=t,o[4047]!=(0|e))break t;return void(o[4044]=t)}o[r+4>>2]=-2&A,o[e+4>>2]=1|t,o[e+t>>2]=t}if(t>>>0<=255)return A=16208+((t=t>>>3|0)<<3)|0,(n=o[4042])&(t=1<<t)?t=o[A+8>>2]:(o[4042]=t|n,t=A),o[A+8>>2]=e,o[t+12>>2]=e,o[e+12>>2]=A,void(o[e+8>>2]=t);i=31,o[e+16>>2]=0,o[e+20>>2]=0,t>>>0<=16777215&&(A=t>>>8|0,A<<=a=A+1048320>>>16&8,i=28+((A=((A<<=i=A+520192>>>16&4)<<(n=A+245760>>>16&2)>>>15|0)-(n|i|a)|0)<<1|t>>>A+21&1)|0),o[e+28>>2]=i,a=16472+(i<<2)|0;t:{if((n=o[4043])&(A=1<<i)){for(i=t<<(31==(0|i)?0:25-(i>>>1|0)|0),A=o[a>>2];;){if(n=A,(-8&o[A+4>>2])==(0|t))break t;if(A=i>>>29|0,i<<=1,!(A=o[16+(a=n+(4&A)|0)>>2]))break}o[a+16>>2]=e,o[e+24>>2]=n}else o[4043]=A|n,o[a>>2]=e,o[e+24>>2]=a;return o[e+12>>2]=e,void(o[e+8>>2]=e)}t=o[n+8>>2],o[t+12>>2]=e,o[n+8>>2]=e,o[e+24>>2]=0,o[e+12>>2]=n,o[e+8>>2]=t}}function W(e,t,r){var A=0,n=0,i=0,a=0,c=0,f=0,s=0,u=0,l=0,d=0,k=0,E=0,p=0;n=o[e+4>>2],c=r+o[e>>2]|0,o[e>>2]=c,o[e+4>>2]=r>>>0>c>>>0?n+1|0:n;e:{t:if((i=(A=o[e+72>>2])+r|0)>>>0<=31){if(!t)break t;re(40+(e+A|0)|0,t,r),i=o[e+72>>2]+r|0}else{if(s=t+r|0,A&&(re(A+(e+40|0)|0,t,32-A|0),c=o[e+72>>2],o[e+72>>2]=0,i=o[e+12>>2],r=o[e+8>>2],f=Qr(b[e+40|0]|b[e+41|0]<<8|b[e+42|0]<<16|b[e+43|0]<<24,b[e+44|0]|b[e+45|0]<<8|b[e+46|0]<<16|b[e+47|0]<<24,668265295,-1028477379),n=D+i|0,E=e,p=Qr(Fe(A=r+f|0,A>>>0<f>>>0?n+1|0:n,31),D,-2048144761,-1640531535),o[E+8>>2]=p,o[e+12>>2]=D,A=o[e+20>>2],r=o[e+16>>2],n=Qr(b[e+48|0]|b[e+49|0]<<8|b[e+50|0]<<16|b[e+51|0]<<24,b[e+52|0]|b[e+53|0]<<8|b[e+54|0]<<16|b[e+55|0]<<24,668265295,-1028477379),A=D+A|0,E=e,p=Qr(Fe(i=r+n|0,n>>>0>i>>>0?A+1|0:A,31),D,-2048144761,-1640531535),o[E+16>>2]=p,o[e+20>>2]=D,A=o[e+28>>2],r=o[e+24>>2],n=Qr(b[e+56|0]|b[e+57|0]<<8|b[e+58|0]<<16|b[e+59|0]<<24,b[e+60|0]|b[e+61|0]<<8|b[e+62|0]<<16|b[e+63|0]<<24,668265295,-1028477379),A=D+A|0,E=e,p=Qr(Fe(i=r+n|0,n>>>0>i>>>0?A+1|0:A,31),D,-2048144761,-1640531535),o[E+24>>2]=p,o[e+28>>2]=D,i=o[e+36>>2],r=o[e+32>>2],f=Qr(b[0|(A=e- -64|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,b[A+4|0]|b[A+5|0]<<8|b[A+6|0]<<16|b[A+7|0]<<24,668265295,-1028477379),n=D+i|0,E=e,p=Qr(Fe(A=r+f|0,A>>>0<f>>>0?n+1|0:n,31),D,-2048144761,-1640531535),o[E+32>>2]=p,o[e+36>>2]=D,t=32+(t-c|0)|0),s>>>0>=t+32>>>0){for(k=s-32|0,r=o[e+32>>2],f=o[e+36>>2],A=o[e+24>>2],u=o[e+28>>2],i=o[e+16>>2],l=o[e+20>>2],c=o[e+8>>2],d=o[e+12>>2];a=Qr(b[t+24|0]|b[t+25|0]<<8|b[t+26|0]<<16|b[t+27|0]<<24,b[t+28|0]|b[t+29|0]<<8|b[t+30|0]<<16|b[t+31|0]<<24,668265295,-1028477379)+r|0,n=f+D|0,r=Qr(Fe(a,r>>>0>a>>>0?n+1|0:n,31),D,-2048144761,-1640531535),f=D,a=Qr(b[t+16|0]|b[t+17|0]<<8|b[t+18|0]<<16|b[t+19|0]<<24,b[t+20|0]|b[t+21|0]<<8|b[t+22|0]<<16|b[t+23|0]<<24,668265295,-1028477379)+A|0,n=u+D|0,A=Qr(Fe(a,A>>>0>a>>>0?n+1|0:n,31),D,-2048144761,-1640531535),u=D,a=Qr(b[t+8|0]|b[t+9|0]<<8|b[t+10|0]<<16|b[t+11|0]<<24,b[t+12|0]|b[t+13|0]<<8|b[t+14|0]<<16|b[t+15|0]<<24,668265295,-1028477379)+i|0,n=l+D|0,i=Qr(Fe(a,i>>>0>a>>>0?n+1|0:n,31),D,-2048144761,-1640531535),l=D,a=Qr(b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24,b[t+4|0]|b[t+5|0]<<8|b[t+6|0]<<16|b[t+7|0]<<24,668265295,-1028477379)+c|0,n=d+D|0,c=Qr(Fe(a,a>>>0<c>>>0?n+1|0:n,31),D,-2048144761,-1640531535),d=D,k>>>0>=(t=t+32|0)>>>0;);o[e+32>>2]=r,o[e+36>>2]=f,o[e+24>>2]=A,o[e+28>>2]=u,o[e+16>>2]=i,o[e+20>>2]=l,o[e+8>>2]=c,o[e+12>>2]=d}if(t>>>0>=s>>>0)break e;re(e+40|0,t,i=s-t|0)}o[e+72>>2]=i}}function Y(e){var t,r,A,n=0,i=0,a=0,c=0,f=0,s=0,u=0,l=0,d=0,k=0,E=0,p=0,T=0;if(t=(a=e+40|0)+(r=o[e+72>>2])|0,A=n=o[e+4>>2],!n&(c=o[e>>2])>>>0>=32|n?(E=n=o[e+20>>2],i=Fe(l=o[e+16>>2],n,7),f=D,p=n=o[e+12>>2],s=i,i=Fe(d=o[e+8>>2],n,1),n=D+f|0,n=i>>>0>(s=s+i|0)>>>0?n+1|0:n,f=s,T=i=o[e+28>>2],i=Fe(k=o[e+24>>2],i,12),n=D+n|0,n=i>>>0>(f=i+f|0)>>>0?n+1|0:n,s=i=o[e+36>>2],u=(u=f)+(i=Fe(f=o[e+32>>2],i,18))|0,n=D+n|0,i=(n=Qr(Qr(Fe(Qr(d,p,668265295,-1028477379),D,31),D,-2048144761,-1640531535)^u,D^(i>>>0>u>>>0?n+1|0:n),-2048144761,-1640531535))-1028477341|0,n=D-((n>>>0<1028477341)+2048144776|0)|0,i=(n=Qr(Qr(Fe(Qr(l,E,668265295,-1028477379),D,31),D,-2048144761,-1640531535)^i,D^n,-2048144761,-1640531535))-1028477341|0,n=D-((n>>>0<1028477341)+2048144776|0)|0,i=(n=Qr(Qr(Fe(Qr(k,T,668265295,-1028477379),D,31),D,-2048144761,-1640531535)^i,D^n,-2048144761,-1640531535))-1028477341|0,n=D-((n>>>0<1028477341)+2048144776|0)|0,i=(n=Qr(Qr(Fe(Qr(f,s,668265295,-1028477379),D,31),D,-2048144761,-1640531535)^i,D^n,-2048144761,-1640531535))-1028477341|0,n=D-((n>>>0<1028477341)+2048144776|0)|0):(n=o[e+28>>2]+668265263|0,n=(i=o[e+24>>2]+374761413|0)>>>0<374761413?n+1|0:n),n=n+A|0,n=(i=i+c|0)>>>0<c>>>0?n+1|0:n,f=i,(i=e+48|0)>>>0>t>>>0)c=a;else for(;f=(c=Qr(Fe(Qr(Fe(Qr(b[0|a]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24,b[a+4|0]|b[a+5|0]<<8|b[a+6|0]<<16|b[a+7|0]<<24,668265295,-1028477379),D,31),D,-2048144761,-1640531535)^f,n^D,27),D,-2048144761,-1640531535))-1028477341|0,n=D-((c>>>0<1028477341)+2048144776|0)|0,c=i,a=i,(i=i+8|0)>>>0<=t>>>0;);if(t>>>0<(a=c+4|0)>>>0?a=c:(c=Qr(Fe(Qr(b[0|c]|b[c+1|0]<<8|b[c+2|0]<<16|b[c+3|0]<<24,0,-2048144761,-1640531535)^f,n^D,23),D,668265295,-1028477379)-1640531463|0,n=D+374761393|0,n=c>>>0<2654435833?n+1|0:n,f=c),a>>>0<t>>>0)for(e=40+(e+r|0)|0;f=Qr(Fe(Qr(b[0|a],0,374761413,668265263)^f,n^D,11),D,-2048144761,-1640531535),n=D,(0|e)!=(0|(a=a+1|0)););return c=Qr(f^n>>>1,n,668265295,-1028477379),e=Qr(((536870911&(i=D))<<3|(e=c)>>>29)^e,i^i>>>29,-1640531463,374761393),D=a=D,e^a}function V(e){var t,r=0,A=0;y=t=y-16|0;e:{t:{r:{if(71!=(0|(r=(r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24))){if(84!=(0|r))break e;A:{n:{i:{a:{o:{c:{f:{s:{u:{b:switch(r=o[e>>2],(r=(r=o[e+4>>2]-r>>>0>1?i[r+1|0]:0)<<24>>24)-83|0){case 4:break a;case 0:break c;case 1:break s;case 3:break u;case 2:break A}switch(r-67|0){case 0:break o;case 6:break f;case 1:case 2:case 3:case 4:break A;case 5:break i;default:break n}}if(o[e>>2]=o[e>>2]+2,A=X(e),o[t>>2]=A,!A)break t;A=Xt(e+416|0,5104,t);break e}if(o[e>>2]=o[e>>2]+2,A=X(e),o[t>>2]=A,!A)break t;A=Xt(e+416|0,5116,t);break e}if(o[e>>2]=o[e>>2]+2,A=X(e),o[t>>2]=A,!A)break t;A=Xt(e+416|0,5125,t);break e}if(o[e>>2]=o[e>>2]+2,A=X(e),o[t>>2]=A,!A)break t;A=Xt(e+416|0,5139,t);break e}if(o[e>>2]=o[e>>2]+2,r=X(e),o[t+12>>2]=r,!r)break e;if(et(t,e,1),o[t>>2]==o[t+4>>2])break e;if(!ir(e,95))break e;if(A=X(e),o[t>>2]=A,!A)break t;A=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=21,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=5552,e}(kt(e+416|0,16),o[t>>2],o[t+12>>2]);break e}if(o[e>>2]=o[e>>2]+2,r=be(e,0),o[t>>2]=r,!r)break e;A=Nr(e,5185,t);break e}if(o[e>>2]=o[e>>2]+2,r=be(e,0),o[t>>2]=r,!r)break e;A=Xt(e+416|0,5219,t);break e}if(99==(0|r))break r}if(o[e>>2]=o[e>>2]+1,r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0,He(e))break e;if(A=oe(e),o[t>>2]=A,!A)break t;if(118==(255&r)){A=Xt(e+416|0,5260,t);break e}A=Xt(e+416|0,5278,t);break e}A:{n:switch(r=o[e>>2],((r=o[e+4>>2]-r>>>0>1?i[r+1|0]:0)<<24>>24)-82|0){case 0:break A;case 4:break n;default:break e}if(o[e>>2]=o[e>>2]+2,r=be(e,0),o[t>>2]=r,!r)break e;A=Xt(e+416|0,5300,t);break e}if(o[e>>2]=o[e>>2]+2,r=be(e,0),o[t>>2]=r,!r)break e;if(A=Be(e,t+12|0),r=ir(e,95),!A&&(A=0,!r))break e;A=Xt(e+416|0,5320,t);break e}if(o[e>>2]=o[e>>2]+2,He(e))break e;if(He(e))break e;if(A=oe(e),o[t>>2]=A,A){A=Xt(e+416|0,5158,t);break e}}A=0}return y=t+16|0,A}function L(e,t,r,A){var n=0,i=0,a=0,c=0,f=0,s=0,u=0;if(o[e>>2]=0,o[e+4>>2]=0,o[e+32>>2]=0,o[e+36>>2]=0,o[e+24>>2]=0,o[e+28>>2]=0,o[e+16>>2]=0,o[e+20>>2]=0,o[e+8>>2]=0,o[e+12>>2]=0,(i=A?1:5)>>>0>r>>>0)return i;if(!t)return-1;n=1;e:{t:{r:{A:{n:{i:{a:{o:{if(1==(0|A)){if(A=t,r)break o;A=-72;break a}if(-47205080!=(0|(n=b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24)))break i;n=i,A=(t+i|0)-1|0}A=b[0|A],A=((n=o[3920+((a=A>>>6|0)<<2)>>2]+(o[3904+((3&A)<<2)>>2]+n|0)|0)+!(A&=32)|0)+(!a&A>>>5)|0}if(A>>>0>r>>>0)break e;if(o[e+24>>2]=A,A=-14,8&(a=b[(r=t+i|0)-1|0]))break e;if(!(f=32&a)){if(A=-16,(r=b[0|r])>>>0>167)break e;A=7&r,c=31&(r=10+(r>>>3|0)|0),(63&r)>>>0>=32?(n=1<<c,r=0):(n=(1<<c)-1&1>>>32-c,r=1<<c),A=Qr(A,0,(7&n)<<29|r>>>3,n>>>3|0)+r|0,c=n+D|0,c=r>>>0>A>>>0?c+1|0:c,s=A,i=i+1|0}switch(u=a>>>6|0,n=a>>>2|0,A=0,(3&a)-1|0){case 2:break r;case 1:break A;case 0:break n;default:break t}}if(A=-10,407710288!=(-16&n))break e;if(A=8,r>>>0<8)break e;return o[e>>2]=0,o[e+4>>2]=0,o[e+32>>2]=0,o[e+36>>2]=0,o[e+24>>2]=0,o[e+28>>2]=0,o[e+16>>2]=0,o[e+20>>2]=0,o[e+8>>2]=0,o[e+12>>2]=0,t=b[t+4|0]|b[t+5|0]<<8|b[t+6|0]<<16|b[t+7|0]<<24,o[e+20>>2]=1,o[e>>2]=t,o[e+4>>2]=0,0}A=b[t+i|0],i=i+1|0;break t}A=b[0|(r=t+i|0)]|b[r+1|0]<<8,i=i+2|0;break t}A=b[0|(r=t+i|0)]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24,i=i+4|0}a=1&n;t:{r:switch(u-1|0){default:if(n=-1,r=-1,!f)break t;n=b[t+i|0],r=0;break t;case 0:r=0,r=(t=256+(b[0|(t=t+i|0)]|b[t+1|0]<<8)|0)>>>0<256?1:r,n=t;break t;case 1:n=b[0|(t=t+i|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24,r=0;break t;case 2:}n=b[0|(t=t+i|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24,r=b[t+4|0]|b[t+5|0]<<8|b[t+6|0]<<16|b[t+7|0]<<24}o[e+32>>2]=a,o[e+28>>2]=A,t=n,o[e>>2]=t,o[e+4>>2]=r,A=0,o[e+20>>2]=0,n=f?t:s,o[e+8>>2]=n,t=f?r:c,o[e+12>>2]=t,o[e+16>>2]=!t&n>>>0<131072?n:131072}return A}function z(e,t,r,A,n){var a=0,o=0,c=0,f=0,s=0,u=0,d=0,k=0;if(!A)return-72;e:{t:{r:{if(A>>>0>=4){if(a=-1,!(o=b[(r+A|0)-1|0]))break e;if(A>>>0<=4294967176)break r;return A}c=b[0|r];A:switch(A-2|0){case 1:c=b[r+2|0]<<16|c;case 0:c=(b[r+1|0]<<8)+c|0}if(!(a=b[(r+A|0)-1|0]))return-20;o=9+(g(a)-(A<<3)|0)|0,A=0;break t}o=g(o)-23|0,c=b[0|(a=(A=A-4|0)+r|0)]|b[a+1|0]<<8|b[a+2|0]<<16|b[a+3|0]<<24}u=n+4|0,d=e+t|0,n=l[n+2>>1];t:{r:{A:{if(o>>>0>=33)n=0-n&31;else for(k=d-3|0,n=0-n&31;;){if((0|A)>=4)c=o>>>3|0,s=0,a=7&o;else{if(!A){s=d-2|0,A=0,f=r,a=o;break r}a=o-((c=(s=(r+A|0)-(a=o>>>3|0)>>>0<r>>>0)?A:a)<<3)|0}if(c=b[0|(f=(A=A-c|0)+r|0)]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24,s|e>>>0>=k>>>0)break A;if(f=l[(o=(c<<a>>>n<<2)+u|0)>>1],i[0|e]=f,i[e+1|0]=f>>>8,e=b[o+3|0]+e|0,o=b[o+2|0]+a|0,f=l[(a=(c<<o>>>n<<2)+u|0)>>1],i[0|e]=f,i[e+1|0]=f>>>8,e=e+b[a+3|0]|0,!((o=o+b[a+2|0]|0)>>>0<33))break}f=r+A|0,s=d-2|0;break t}if(s=d-2|0,!(a>>>0<=32)){o=a;break t}}for(;;){if((0|A)>=4)o=7&a,c=a>>>3|0,a=0;else{if(!A){o=a;break t}o=(o=a)-((c=(a=(c=f)-(f=a>>>3|0)>>>0<r>>>0)?A:f)<<3)|0}if(c=b[0|(f=(A=A-c|0)+r|0)]|b[f+1|0]<<8|b[f+2|0]<<16|b[f+3|0]<<24,a|e>>>0>s>>>0)break t;if(k=l[(a=(c<<o>>>n<<2)+u|0)>>1],i[0|e]=k,i[e+1|0]=k>>>8,e=b[a+3|0]+e|0,!((a=b[a+2|0]+o|0)>>>0<=32))break}o=a}if(e>>>0<=s>>>0)for(;a=l[(A=(c<<o>>>n<<2)+u|0)>>1],i[0|e]=a,i[e+1|0]=a>>>8,o=b[A+2|0]+o|0,s>>>0>=(e=b[A+3|0]+e|0)>>>0;);e>>>0>=d>>>0||(a=e,e=((A=c<<o>>>n|0)<<2)+u|0,i[0|a]=b[0|e],1!=b[e+3|0]?o>>>0>31||(o=(e=b[2+((A<<2)+u|0)|0]+o|0)>>>0<32?e:32):o=b[e+2|0]+o|0),a=(0|r)!=(0|f)||32!=(0|o)?-20:t}return a}function J(e,t,r,A,n){var i,c=0,f=0,s=0,u=0,l=0,d=0,k=0,E=0,T=0,h=0,O=0,_=0;if(y=i=y-16|0,n>>>0<=7)o[i+8>>2]=0,o[i+12>>2]=0,re(i+8|0,A,n),s=(e=J(e,t,r,i+8|0,8))>>>0<4294967177&&e>>>0>n>>>0?-20:e;else if(_=ke(e,0,(k=o[t>>2]+1|0)<<1),s=-44,!((f=15&(u=b[0|A]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24))>>>0>10)){o[r>>2]=f+5,s=(e=A+n|0)-4|0,E=e-7|0,T=f+6|0,e=4,u=u>>>4|0,l=1|(d=32<<f),r=0,n=A;e:{for(;;){if(c){if((c=vr(-1^u|-2147483648))>>>0>=24)for(;c=n-E|0,r=r+36|0,e=(f=n>>>0>E>>>0)?(c<<3)+e&31:e,(c=vr(-1^(u=(b[0|(n=f?s:n+3|0)]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24)>>>e|0)|-2147483648))>>>0>23;);if(e=2+((f=30&c)+e|0)|0,k>>>0<=(r=(p(c>>>1|0,3)+r|0)+(u>>>f&3)|0)>>>0)break e;f=7&e,s>>>0<(c=(e>>3)+n|0)>>>0&&n>>>0>E>>>0&&(f=(n-(c=s)<<3)+e&31),e=f,u=(b[0|c]|b[c+1|0]<<8|b[c+2|0]<<16|b[c+3|0]<<24)>>>e|0}else c=n;h=T-1|0,(n=d-1&u)>>>0<(f=(O=(d<<1)-1|0)-l|0)>>>0||(n=(n=u&O)-((0|n)<(0|d)?0:f)|0,h=T),f=h,u=n-1|0,a[(r<<1)+_>>1]=u,r=r+1|0,e=e+f|0;t:{if((0|(l=((0|n)>0?1-n|0:u)+l|0))<(0|d)){if((0|l)<2)break t;T=32-(n=g(l))|0,d=1<<(31^n)}if(!(r>>>0>=k>>>0)){f=7&e,s>>>0<(n=(e>>3)+c|0)>>>0&&c>>>0>E>>>0&&(f=(c-(n=s)<<3)+e&31),c=!u,e=f,u=(b[0|n]|b[n+1|0]<<8|b[n+2|0]<<16|b[n+3|0]<<24)>>>e|0;continue}}break}n=c}s=-20,1==(0|l)&&(s=-48,r>>>0>k>>>0||(s=-20,(0|e)>32||(o[t>>2]=r-1,s=((e+7>>3)+n|0)-A|0)))}return y=i+16|0,c=s}function K(e,t,r,A,n){var a=0,o=0,c=0,f=0,s=0,u=0,d=0,k=0;if(!A)return-72;f=l[n+2>>1];e:{t:{r:{if(A>>>0>=4){if(a=-1,!(o=b[(r+A|0)-1|0]))break e;if(A>>>0<=4294967176)break r;return A}c=b[0|r];A:switch(A-2|0){case 1:c=b[r+2|0]<<16|c;case 0:c=(b[r+1|0]<<8)+c|0}if(!(a=b[(r+A|0)-1|0]))return-20;a=9+(g(a)-(A<<3)|0)|0,A=0;break t}a=g(o)-23|0,c=b[0|(o=(A=A-4|0)+r|0)]|b[o+1|0]<<8|b[o+2|0]<<16|b[o+3|0]<<24}u=n+4|0,d=e+t|0;t:{r:{A:if(a>>>0>=33)s=0-f&31,o=r+A|0;else{k=d-3|0,s=0-f&31;n:{i:{a:{for(;;){if((0|A)>=4)c=a>>>3|0,f=0,n=7&a;else{if(!A){A=0,o=r,n=a;break i}n=a-((c=(f=(r+A|0)-(n=a>>>3|0)>>>0<r>>>0)?A:n)<<3)|0}if(c=b[0|(o=(A=A-c|0)+r|0)]|b[o+1|0]<<8|b[o+2|0]<<16|b[o+3|0]<<24,f|e>>>0>=k>>>0)break a;if(o=b[1+(a=(c<<n>>>s<<1)+u|0)|0],i[0|e]=b[0|a],o=b[1+(a=(c<<(n=n+o|0)>>>s<<1)+u|0)|0],i[e+1|0]=b[0|a],e=e+2|0,!((a=n+o|0)>>>0<33))break}o=r+A|0;break A}if(n>>>0>32)break n}for(;;){if((0|A)>=4)c=n>>>3|0,f=0,a=7&n;else{if(!A)break n;a=n-((c=(f=o-(a=n>>>3|0)>>>0<r>>>0)?A:a)<<3)|0}if(c=b[0|(o=(A=A-c|0)+r|0)]|b[o+1|0]<<8|b[o+2|0]<<16|b[o+3|0]<<24,n=e>>>0>=d>>>0,f||n){if(n)break t;break r}if(f=b[1+(n=(c<<a>>>s<<1)+u|0)|0],i[0|e]=b[0|n],e=e+1|0,!((n=a+f|0)>>>0<=32))break}}a=n}if(e>>>0>=d>>>0)break t}for(;n=b[1+(A=(c<<a>>>s<<1)+u|0)|0],i[0|e]=b[0|A],a=n+a|0,(0|d)!=(0|(e=e+1|0)););}a=(0|r)!=(0|o)||32!=(0|a)?-20:t}return a}function q(e){var t,r=0,A=0,n=0,a=0,c=0,f=0;y=t=y+-64|0,o[t+60>>2]=0,r=dr(t+48|0,9351),n=o[r+4>>2],o[t+16>>2]=o[r>>2],o[t+20>>2]=n;e:{t:{if(rt(e,t+16|0)){if(r=We(e),o[t+60>>2]=r,!r)break e;if(73==(255&(r=(0|(r=o[e>>2]))!=o[e+4>>2]?b[0|r]:0))){if(r=_e(e,0),o[t+44>>2]=r,!r)break t;c=t,f=Or(e,t+60|0,t+44|0),o[c+60>>2]=f}for(;!ir(e,69);){if(r=it(e),o[t+44>>2]=r,!r)break t;c=t,f=_r(e,t+60|0,t+44|0),o[c+60>>2]=f}if(r=ye(e),o[t+44>>2]=r,!r)break t;A=_r(e,t+60|0,t+44|0);break e}if(r=dr(t+32|0,6044),n=o[r+4>>2],o[t+8>>2]=o[r>>2],o[t+12>>2]=n,n=rt(e,t+8|0),r=dr(t+24|0,9355),a=o[r+4>>2],o[t>>2]=o[r>>2],o[t+4>>2]=a,!rt(e,t)){if(A=ye(e),o[t+60>>2]=A,!A|1^n)break e;A=Br(e,t+60|0);break e}if(((r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24)-48>>>0<=9)for(r=e;;){if(a=it(r),o[t+44>>2]=a,!a)break e;if(o[t+60>>2]?(c=t,f=_r(e,t+60|0,t+44|0),o[c+60>>2]=f):n?(c=t,f=Br(e,t+44|0),o[c+60>>2]=f):o[t+60>>2]=a,ir(e,69))break}else{if(r=e,n=We(e),o[t+60>>2]=n,!n)break e;if(73==(255&(A=(0|(A=o[e>>2]))!=o[e+4>>2]?b[0|A]:0))){if(A=_e(r,0),o[t+44>>2]=A,!A)break t;c=t,f=Or(e,t+60|0,t+44|0),o[c+60>>2]=f}}if(r=ye(r),o[t+44>>2]=r,r){A=_r(e,t+60|0,t+44|0);break e}}A=0}return y=t- -64|0,A}function $(e){var t,r=0,A=0,n=0,a=0,c=0,f=0;y=t=y-128|0,c=t,f=pt(e),o[c+124>>2]=f,o[t+120>>2]=0,r=dr(t+112|0,14212),A=o[r+4>>2],o[t+48>>2]=o[r>>2],o[t+52>>2]=A;e:{t:{if(rt(e,t+48|0))c=t,f=Vr(e,14215),o[c+120>>2]=f;else if(r=dr(t+104|0,14224),A=o[r+4>>2],o[t+40>>2]=o[r>>2],o[t+44>>2]=A,rt(e,t+40|0)){if(r=v(e),o[t+88>>2]=r,!r)break t;if(!ir(e,69))break t;c=t,f=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=16,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=14248,e}(e+416|0,t+88|0),o[c+120>>2]=f}else if(r=dr(t+96|0,14227),A=o[r+4>>2],o[t+32>>2]=o[r>>2],o[t+36>>2]=A,rt(e,t+32|0)){for(A=o[4+(r=e+8|0)>>2]-o[r>>2]>>2;!ir(e,69);){if(n=X(e),o[t+88>>2]=n,!n)break t;Gt(r,t+88|0)}or(t+88|0,e,A),c=t,f=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=17,o[r>>2]=5508,o[r>>2]=14368,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,t+88|0),o[c+120>>2]=f}if(r=dr(t+80|0,14230),A=o[r+4>>2],o[t+24>>2]=o[r>>2],o[t+28>>2]=A,rt(e,t+24|0),r=0,!ir(e,70))break e;if(ir(e,89),r=X(e),o[t+76>>2]=r,r)for(i[t+75|0]=0,A=o[4+(r=e+8|0)>>2]-o[r>>2]>>2;;){r:{if(!ir(e,69)){if(ir(e,118))continue;if(n=dr(t- -64|0,14233),a=o[n+4>>2],o[t+16>>2]=o[n>>2],o[t+20>>2]=a,rt(e,t+16|0))i[t+75|0]=1;else{if(n=dr(t+56|0,14236),a=o[n+4>>2],o[t+8>>2]=o[n>>2],o[t+12>>2]=a,!rt(e,t+8|0))break r;i[t+75|0]=2}}or(t+88|0,e,A),r=bt(e+416|0,t+76|0,t+88|0,t+124|0,t+75|0,t+120|0);break e}if(n=X(e),o[t+88>>2]=n,!n)break t;Gt(r,t+88|0)}}r=0}return y=t+128|0,r}function ee(e,t){var r,A=0,n=0,a=0,c=0,f=0;y=r=y-160|0,t&&(o[e+336>>2]=o[e+332>>2]),t=dr(r+152|0,7098),n=o[t+4>>2],o[r+24>>2]=o[t>>2],o[r+28>>2]=n;e:if(rt(e,r+24|0)){if(t=0,et(r+72|0,e,0),!ir(e,95))break e;t=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=44,o[r>>2]=5508,o[r>>2]=7140,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,r+72|0)}else if(t=dr(r+144|0,7101),n=o[t+4>>2],o[r+16>>2]=o[t>>2],o[r+20>>2]=n,rt(e,r+16|0)){n=jt(r+128|0,e+392|0,o[e+336>>2]-o[e+332>>2]>>2),f=Mt(r+72|0,e),c=o[4+(t=e+8|0)>>2]-o[t>>2]>>2;t:{r:{for(;84==(255&(A=(0|(A=o[e>>2]))!=o[e+4>>2]?b[0|A]:0))&&(a=dr(r- -64|0,7104),A=o[e>>2],-1!=(0|Ye(a,(A=o[e+4>>2]-A>>>0>1?i[A+1|0]:0)<<24>>24)));){if(A=ne(e),o[r+64>>2]=A,!A)break r;Gt(t,r- -64|0)}if(or(r- -64|0,e,c),o[4+(r- -64|0)>>2]||(o[e+336>>2]=o[e+336>>2]-4),A=dr(r+56|0,7109),a=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=a,!rt(e,r))for(;;){if(A=X(e),o[r+48>>2]=A,!A)break r;if(Gt(t,r+48|0),ir(e,69))break}if(or(r+48|0,e,c),et(r+40|0,e,0),t=0,!ir(e,95))break t;t=function(e,t,r,A){var n=0,a=0,c=0,f=0,s=0;return y=n=y-48|0,s=kt(e,32),a=o[t+4>>2],c=o[t>>2],o[n+40>>2]=c,o[n+44>>2]=a,f=o[r+4>>2],t=o[r>>2],o[n+32>>2]=t,o[n+36>>2]=f,r=o[A+4>>2],e=o[A>>2],o[n+24>>2]=e,o[n+28>>2]=r,o[n+16>>2]=c,o[n+20>>2]=a,o[n+8>>2]=t,o[n+12>>2]=f,o[n>>2]=e,o[n+4>>2]=r,e=function(e,t,r,A){var n=0;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=45,o[e>>2]=5508,o[e>>2]=7928,n=o[t+4>>2],o[e+8>>2]=o[t>>2],o[e+12>>2]=n,t=o[r+4>>2],o[e+16>>2]=o[r>>2],o[e+20>>2]=t,t=o[A+4>>2],o[e+24>>2]=o[A>>2],o[e+28>>2]=t,e}(s,n+16|0,n+8|0,n),y=n+48|0,e}(e+416|0,r- -64|0,r+48|0,r+40|0);break t}t=0}sr(f),b[n+8|0]&&(o[o[n>>2]>>2]=o[n+4>>2])}else t=dr(r+32|0,7112),n=o[t+4>>2],o[r+8>>2]=o[t>>2],o[r+12>>2]=n,t=0,rt(e,r+8|0)&&(et(r+72|0,e,0),ir(e,95)&&(t=Vr(e,7115)));return y=r+160|0,t}function te(e,t,r,A,n,a,c){var f=0,s=0,u=0,d=0;u=-72;e:{t:if(a){if(s=255&(f=i[0|n]),(0|f)<=-1){if((c=s-126>>>1|0)>>>0>=a>>>0)break t;if(u=-20,(f=s-127|0)>>>0>=256)break t;if(!f)break e;for(n=n+1|0,a=0;s=n+(a>>>1|0)|0,i[e+a|0]=b[0|s]>>>4,i[(1|a)+e|0]=15&b[0|s],f>>>0>(a=a+2|0)>>>0;);s=c}else{if(a>>>0<=s>>>0)break t;if(f=function(e,t,r,A){var n,a=0,c=0,f=0,s=0,u=0,d=0,k=0,E=0,p=0,T=0,h=0,O=0,_=0;y=n=y-528|0,o[n+8>>2]=255;r:if((u=J(n+16|0,n+8|0,n+12|0,t,r))>>>0>4294967176)s=u;else if(s=-44,!((f=o[n+12>>2])>>>0>6||(k=31&(a=f),(63&a)>>>0>=32?(a=1<<k,E=0):(a=(1<<k)-1&1>>>32-k,E=1<<k),a=a+(c=(c=(k=1+(1<<f)|0)>>31)<<2|(d=k)>>>30)|0,a=(d=E+(p=d<<2)|0)>>>0<p>>>0?a+1|0:a,a=(c=d)>>>0>(d=c+(2+((E=o[n+8>>2])<<1)|0)|0)>>>0?a+1|0:a,(d=d+11|0)>>>0<11&&(a=a+1|0),!a&(d&=-4)>>>0>356|a||(s=Q(A,n+16|0,E,f,(a=k<<2)+A|0,356-a|0))>>>0>4294967176))){r=r-u|0,c=t+u|0,h=(E=e+255|0)-3|0;A:{n:{if(l[A+2>>1]){if(!r){s=-72;break r}i:{a:{if(r>>>0>=4){if(s=-1,!(t=b[(r+c|0)-1|0]))break r;if(r>>>0<=4294967176)break a;s=r;break r}switch(f=b[0|c],r-2|0){case 1:f=b[c+2|0]<<16|f;case 0:f=(b[c+1|0]<<8)+f|0}if(!(t=b[(r+c|0)-1|0])){s=-20;break r}s=9+(g(t)-(r<<3)|0)|0,r=0;break i}s=g(t)-23|0,f=b[0|(t=c+(r=r-4|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24}if(k=f>>>0-(t=(a=l[A>>1])+s|0)|0,s=o[3776+(a<<2)>>2],t>>>0>32?u=t:(0|r)>=4?(u=7&t,f=b[0|(t=c+(r=r-(t>>>3|0)|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24):r?(u=(u=t)-((t=(r+c|0)-(t=t>>>3|0)>>>0<c>>>0?r:t)<<3)|0,f=b[0|(t=c+(r=r-t|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24):(r=0,u=t),d=s&k,p=A+4|0,u=s&f>>>0-(t=a+u|0),t>>>0>32){a=t,t=e;break A}if((0|r)>=4){a=7&t,f=b[0|(t=c+(r=r-(t>>>3|0)|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24;break n}if(!r){r=0,a=t;break n}if(f=b[0|(A=c+(r=r-(a=(r+c|0)-(A=t>>>3|0)>>>0<c>>>0?r:A)|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,(a=t-(a<<3)|0)>>>0<=32)break n;t=e;break A}if(!r){s=-72;break r}i:{a:{if(r>>>0>=4){if(s=-1,!(t=b[(r+c|0)-1|0]))break r;if(r>>>0<=4294967176)break a;s=r;break r}switch(f=b[0|c],r-2|0){case 1:f=b[c+2|0]<<16|f;case 0:f=(b[c+1|0]<<8)+f|0}if(!(t=b[(r+c|0)-1|0])){s=-20;break r}s=9+(g(t)-(r<<3)|0)|0,r=0;break i}s=g(t)-23|0,f=b[0|(t=c+(r=r-4|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24}k=f>>>0-(t=(a=l[A>>1])+s|0)|0,s=o[3776+(a<<2)>>2],t>>>0>32?u=t:(0|r)>=4?(u=7&t,f=b[0|(t=c+(r=r-(t>>>3|0)|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24):r?(u=(u=t)-((t=(r+c|0)-(t=t>>>3|0)>>>0<c>>>0?r:t)<<3)|0,f=b[0|(t=c+(r=r-t|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24):(r=0,u=t),d=s&k,p=A+4|0,u=s&f>>>0-(t=a+u|0);i:if(t>>>0>32)a=t,t=e;else{if((0|r)>=4)a=7&t,f=b[0|(t=c+(r=r-(t>>>3|0)|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24;else if(r){if(f=b[0|(A=c+(r=r-(a=(r+c|0)-(A=t>>>3|0)>>>0<c>>>0?r:A)|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,!((a=t-(a<<3)|0)>>>0<=32)){t=e;break i}}else r=0,a=t;for(t=e;;){if((0|r)>=4)s=a>>>3|0,k=0,a&=7;else{if(!r){r=0;break i}a=a-((s=(k=(r+c|0)-(A=a>>>3|0)>>>0<c>>>0)?r:A)<<3)|0}if(f=b[0|(s=c+(r=r-s|0)|0)]|b[s+1|0]<<8|b[s+2|0]<<16|b[s+3|0]<<24,k|t>>>0>=h>>>0)break i;d=l[(A=p+(d<<2)|0)>>1],k=b[A+3|0],i[0|t]=b[A+2|0],T=l[(A=p+(u<<2)|0)>>1],u=b[A+3|0],i[t+1|0]=b[A+2|0],A=a+k|0,d=d+(o[3776+(k<<2)>>2]&f>>>0-A)|0,A=A+u|0,u=T+(o[3776+(u<<2)>>2]&f>>>0-A)|0;a:{if(A>>>0>32)a=A;else{if((0|r)>=4){a=7&A,f=b[0|(A=c+(r=r-(A>>>3|0)|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24;break a}if(r){if(a=(f=A)-((A=c>>>0>(s=s-(a=A>>>3|0)|0)>>>0?r:a)<<3)|0,f=b[0|(A=c+(r=r-A|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,c>>>0<=s>>>0)break a}else r=0,a=A}t=t+2|0;break i}if(k=l[(A=p+(d<<2)|0)>>1],s=b[A+3|0],i[t+2|0]=b[A+2|0],T=l[(A=p+(u<<2)|0)>>1],u=b[A+3|0],i[t+3|0]=b[A+2|0],A=a+s|0,d=k+(o[3776+(s<<2)>>2]&f>>>0-A)|0,a=A+u|0,u=T+(o[3776+(u<<2)>>2]&f>>>0-a)|0,t=t+4|0,!(a>>>0<33))break}}if(s=-70,(T=E-2|0)>>>0<t>>>0)break r;for(h=2;;){_=l[(A=p+(d<<2)|0)>>1],O=b[A+3|0],i[0|t]=b[A+2|0],E=t+1|0;i:{if((a=a+O|0)>>>0>32)d=u;else{a:{if((0|r)>=4)d=a>>>3|0,k=7&a;else{if(!r){r=0,k=a,A=f;break a}k=a-((d=(r+c|0)-(A=a>>>3|0)>>>0<c>>>0?r:A)<<3)|0}A=b[0|(A=c+(r=r-d|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24}if(E>>>0>T>>>0)break r;if(d=(o[3776+(O<<2)>>2]&f>>>0-a)+_|0,O=l[(a=p+(u<<2)|0)>>1],u=b[a+3|0],i[t+1|0]=b[a+2|0],E=t+2|0,(a=u+k|0)>>>0<=32)break i;h=3}i[0|E]=b[2+(p+(d<<2)|0)|0],s=(t+h|0)-e|0;break r}u=o[3776+(u<<2)>>2]&A>>>0-a;i:{if((0|r)>=4)A=a>>>3|0,a&=7;else{if(!r){r=0;break i}a=a-((A=(r+c|0)-(t=a>>>3|0)>>>0<c>>>0?r:t)<<3)|0}A=b[0|(t=c+(r=r-A|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24}if(f=A,u=u+O|0,!(T>>>0>=(t=E)>>>0))break}break r}for(t=e;;){if((0|r)>=4)f=a>>>3|0,A=0,a&=7;else{if(!r){r=0;break A}a=a-((f=(A=(r+c|0)-(f=a>>>3|0)>>>0<c>>>0)?r:f)<<3)|0}if(f=b[0|(s=c+(r=r-f|0)|0)]|b[s+1|0]<<8|b[s+2|0]<<16|b[s+3|0]<<24,A|t>>>0>=h>>>0)break A;d=l[(A=p+(d<<2)|0)>>1],k=b[A+3|0],i[0|t]=b[A+2|0],u=l[(A=p+(u<<2)|0)>>1],T=b[A+3|0],i[t+1|0]=b[A+2|0],d=d+(f<<a>>>0-k|0)|0,u=u+(f<<(A=a+k|0)>>>0-T|0)|0;n:{if((A=A+T|0)>>>0>32)a=A;else{if((0|r)>=4){a=7&A,f=b[0|(A=c+(r=r-(A>>>3|0)|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24;break n}if(r){if(a=(f=A)-((A=c>>>0>(s=s-(a=A>>>3|0)|0)>>>0?r:a)<<3)|0,f=b[0|(A=c+(r=r-A|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24,c>>>0<=s>>>0)break n}else r=0,a=A}t=t+2|0;break A}if(d=l[(A=p+(d<<2)|0)>>1],s=b[A+3|0],i[t+2|0]=b[A+2|0],u=l[(A=p+(u<<2)|0)>>1],k=b[A+3|0],i[t+3|0]=b[A+2|0],d=d+(f<<a>>>0-s|0)|0,u=u+(f<<(A=a+s|0)>>>0-k|0)|0,t=t+4|0,!((a=A+k|0)>>>0<33))break}}if(s=-70,!((T=E-2|0)>>>0<t>>>0))for(h=2;;){O=l[(A=p+(d<<2)|0)>>1],d=b[A+3|0],i[0|t]=b[A+2|0],E=t+1|0;A:{if((A=a+d|0)>>>0>32)d=u;else{n:{if((0|r)>=4)k=7&A,A=A>>>3|0;else{if(!r){r=0,k=A,A=f;break n}k=(k=A)-((A=(r+c|0)-(A=A>>>3|0)>>>0<c>>>0?r:A)<<3)|0}A=b[0|(A=c+(r=r-A|0)|0)]|b[A+1|0]<<8|b[A+2|0]<<16|b[A+3|0]<<24}if(E>>>0>T>>>0)break r;if(d=O+(f<<a>>>0-d|0)|0,O=l[(a=p+(u<<2)|0)>>1],u=b[a+3|0],i[t+1|0]=b[a+2|0],E=t+2|0,(a=u+k|0)>>>0<=32)break A;h=3}i[0|E]=b[2+(p+(d<<2)|0)|0],s=(t+h|0)-e|0;break r}u=A<<k>>>0-u|0;A:{if((0|r)>=4)A=a>>>3|0,a&=7;else{if(!r){r=0;break A}a=a-((A=(r+c|0)-(t=a>>>3|0)>>>0<c>>>0?r:t)<<3)|0}A=b[0|(t=c+(r=r-A|0)|0)]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24}if(f=A,u=u+O|0,!(T>>>0>=(t=E)>>>0))break}}return y=n+528|0,s}(e,n+1|0,s,c),u=f,f>>>0>4294967176)break t}if(o[(a=t)>>2]=0,o[a+4>>2]=0,n=0,o[a+48>>2]=0,o[a+40>>2]=0,o[a+44>>2]=0,o[a+32>>2]=0,o[a+36>>2]=0,o[a+24>>2]=0,o[a+28>>2]=0,o[a+16>>2]=0,o[a+20>>2]=0,o[a+8>>2]=0,o[a+12>>2]=0,u=-20,f){for(a=0;;){if((d=b[0|(c=e+a|0)])>>>0>11)break t;if(o[(d=(d<<2)+t|0)>>2]=o[d>>2]+1,n=(1<<b[0|c]>>1)+n|0,(0|f)==(0|(a=a+1|0)))break}!n|n>>>0>4095||(a=A,A=32-g(n)|0,o[a>>2]=A,1<<(n=31^g(A=(1<<A)-n|0))==(0|A)&&(A=e+f|0,e=n+1|0,i[0|A]=e,o[(e=(e<<2)+t|0)>>2]=o[e>>2]+1,(e=o[t+4>>2])>>>0<2|1&e||(o[r>>2]=f+1,u=s+1|0)))}}return u}return o[t>>2]=0,o[t+4>>2]=0,o[t+48>>2]=0,o[t+40>>2]=0,o[t+44>>2]=0,o[t+32>>2]=0,o[t+36>>2]=0,o[t+24>>2]=0,o[t+28>>2]=0,o[t+16>>2]=0,o[t+20>>2]=0,o[t+8>>2]=0,o[t+12>>2]=0,-20}function re(e,t,r){var A,n=0,a=0;if(r>>>0>=512)return _(0|e,0|t,0|r),e;A=e+r|0;e:if(3&(e^t))if(A>>>0<4)r=e;else if((n=A-4|0)>>>0<e>>>0)r=e;else for(r=e;i[0|r]=b[0|t],i[r+1|0]=b[t+1|0],i[r+2|0]=b[t+2|0],i[r+3|0]=b[t+3|0],t=t+4|0,n>>>0>=(r=r+4|0)>>>0;);else{t:if((0|r)<1)r=e;else if(3&e)for(r=e;;){if(i[0|r]=b[0|t],t=t+1|0,A>>>0<=(r=r+1|0)>>>0)break t;if(!(3&r))break}else r=e;if(!((n=-4&A)>>>0<64||(a=n+-64|0)>>>0<r>>>0))for(;o[r>>2]=o[t>>2],o[r+4>>2]=o[t+4>>2],o[r+8>>2]=o[t+8>>2],o[r+12>>2]=o[t+12>>2],o[r+16>>2]=o[t+16>>2],o[r+20>>2]=o[t+20>>2],o[r+24>>2]=o[t+24>>2],o[r+28>>2]=o[t+28>>2],o[r+32>>2]=o[t+32>>2],o[r+36>>2]=o[t+36>>2],o[r+40>>2]=o[t+40>>2],o[r+44>>2]=o[t+44>>2],o[r+48>>2]=o[t+48>>2],o[r+52>>2]=o[t+52>>2],o[r+56>>2]=o[t+56>>2],o[r+60>>2]=o[t+60>>2],t=t- -64|0,a>>>0>=(r=r- -64|0)>>>0;);if(r>>>0>=n>>>0)break e;for(;o[r>>2]=o[t>>2],t=t+4|0,n>>>0>(r=r+4|0)>>>0;);}if(r>>>0<A>>>0)for(;i[0|r]=b[0|t],t=t+1|0,(0|A)!=(0|(r=r+1|0)););return e}function Ae(e){var t,r=0,A=0,n=0,a=0,c=0;y=t=y-96|0,r=dr(t+88|0,5024),n=o[r+4>>2],o[t+32>>2]=o[r>>2],o[t+36>>2]=n;e:if(rt(e,t+32|0)||(r=dr(t+80|0,5027),n=o[r+4>>2],o[t+24>>2]=o[r>>2],o[t+28>>2]=n,rt(e,t+24|0))){if(A=oe(e),o[t+76>>2]=A,!A){A=0;break e}46==(255&(r=(0|(r=o[e>>2]))!=o[e+4>>2]?b[0|r]:0))&&(r=o[e>>2],o[4+(A=t- -64|0)>>2]=o[e+4>>2],o[A>>2]=r,A=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=1,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=13888,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}(e+416|0,t+76|0,A),o[t+76>>2]=A,o[e>>2]=o[e+4>>2]),A=o[e+4>>2]-o[e>>2]|0?0:A}else if(r=dr(t+56|0,5031),n=o[r+4>>2],o[t+16>>2]=o[r>>2],o[t+20>>2]=n,rt(e,t+16|0)||(r=dr(t+48|0,5036),n=o[r+4>>2],o[t+8>>2]=o[r>>2],o[t+12>>2]=n,rt(e,t+8|0))){if(r=oe(e),o[t+76>>2]=r,!r)break e;if(r=dr(t+40|0,5042),n=o[r+4>>2],o[t>>2]=o[r>>2],o[t+4>>2]=n,!rt(e,t))break e;if(r=ir(e,95),et(t- -64|0,e,0),n=r,o[(r=t- -64|0)>>2]==o[r+4>>2]&&n)break e;if(46==(255&(r=(0|(r=o[e>>2]))!=o[e+4>>2]?b[0|r]:0))&&(o[e>>2]=o[e+4>>2]),o[e+4>>2]-o[e>>2]|0)break e;A=Nr(e,5056,t+76|0)}else a=0,c=X(e),A=o[e+4>>2]-o[e>>2]|0?a:c;return y=t+96|0,A}function ne(e){var t,r=0,A=0,n=0,a=0,c=0;y=t=y-160|0,o[t+152>>2]=e,A=dr(t+144|0,7251),n=o[A+4>>2],o[t+40>>2]=o[A>>2],o[t+44>>2]=n;e:if(rt(e,t+40|0)){if(A=ot(t+152|0,0),o[t+72>>2]=A,!A)break e;r=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=0,i[e+4|0]=28,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=7408,e}(e+416|0,t+72|0)}else{A=dr(t+136|0,7254),n=o[A+4>>2],o[t+32>>2]=o[A>>2],o[t+36>>2]=n;t:{if(rt(e,t+32|0)){if(r=ot(t+152|0,1),o[t+72>>2]=r,!r)break t;if(r=X(e),o[t+56>>2]=r,!r)break t;r=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=0,i[e+4|0]=29,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=7536,e}(kt(e+416|0,16),o[t+72>>2],o[t+56>>2]);break e}if(A=dr(t+128|0,7257),n=o[A+4>>2],o[t+24>>2]=o[A>>2],o[t+28>>2]=n,rt(e,t+24|0)){if(r=ot(t+152|0,2),o[t+124>>2]=r,!r)break t;A=o[4+(r=e+8|0)>>2]-o[r>>2]>>2,n=Mt(t+72|0,e);r:{A:{for(;a=dr(t- -64|0,7260),c=o[a+4>>2],o[t+8>>2]=o[a>>2],o[t+12>>2]=c,!rt(e,t+8|0);){if(a=ne(e),o[t+56>>2]=a,!a)break A;Gt(r,t+56|0)}or(t+56|0,e,A),r=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=0,i[e+4|0]=30,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=7660,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}(e+416|0,t+124|0,t+56|0);break r}r=0}sr(n);break e}if(A=dr(t+48|0,7262),n=o[A+4>>2],o[t+16>>2]=o[A>>2],o[t+20>>2]=n,!rt(e,t+16|0))break e;if(r=ne(e),o[t+72>>2]=r,r){r=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=0,i[e+4|0]=31,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=7808,e}(e+416|0,t+72|0);break e}}r=0}return y=t+160|0,r}function ie(e,t,r,A){var n,i,a=0,c=0,u=0;y=n=y-32|0,u=a=2147483647&A,i=r;e:if((0|(c=a-1006698496|0))==(0|(a=a-1140785152|0))&r>>>0>r>>>0|a>>>0>c>>>0){if(a=A<<4|r>>>28,r=r<<4|t>>>28,u=t&=268435455,134217728==(0|t)&e>>>0>=1|t>>>0>134217728){a=a+1073741824|0,a=(e=r+1|0)>>>0<1?a+1|0:a,c=e;break e}if(c=r,a=a+1073741824|0,e|134217728^u)break e;a=(e=(t=1&r)+c|0)>>>0<t>>>0?a+1|0:a,c=e}else(!i&2147418112==(0|u)?!(e|t):u>>>0<2147418112)?(c=0,a=2146435072,u>>>0>1140785151||(a=0,(u=u>>>16|0)>>>0<15249||(function(e,t,r,A,n,i){var a=0,c=0,f=0,s=0;64&i?(A=t,t=31&(n=i+-64|0),(63&n)>>>0>=32?(n=A<<t,A=0):(n=(1<<t)-1&A>>>32-t|r<<t,A<<=t),t=0,r=0):i&&(a=A,f=i,A=31&i,(63&i)>>>0>=32?(c=a<<A,s=0):(c=(1<<A)-1&a>>>32-A|n<<A,s=a<<A),A=r,a=t,n=31&(i=64-i|0),(63&i)>>>0>=32?(i=0,A=A>>>n|0):(i=A>>>n|0,A=((1<<n)-1&A)<<32-n|a>>>n),A|=s,n=i|c,i=t,t=31&f,(63&f)>>>0>=32?(c=i<<t,t=0):(c=(1<<t)-1&i>>>32-t|r<<t,t=i<<t),r=c),o[e>>2]=t,o[e+4>>2]=r,o[e+8>>2]=A,o[e+12>>2]=n}(n+16|0,e,t,r,a=65535&A|65536,u-15233|0),function(e,t,r,A,n,i){var a=0,c=0,f=0,s=0;64&i?(t=31&(r=i+-64|0),(63&r)>>>0>=32?(r=0,t=n>>>t|0):(r=n>>>t|0,t=((1<<t)-1&n)<<32-t|A>>>t),A=0,n=0):i&&(c=n,f=A,a=31&(s=64-i|0),(63&s)>>>0>=32?(c=f<<a,s=0):(c=(1<<a)-1&f>>>32-a|c<<a,s=f<<a),f=t,t=31&(a=i),(63&a)>>>0>=32?(a=0,t=r>>>t|0):(a=r>>>t|0,t=((1<<t)-1&r)<<32-t|f>>>t),t|=s,r=a|c,a=A,A=31&i,(63&i)>>>0>=32?(c=0,A=n>>>A|0):(c=n>>>A|0,A=((1<<A)-1&n)<<32-A|a>>>A),n=c),o[e>>2]=t,o[e+4>>2]=r,o[e+8>>2]=A,o[e+12>>2]=n}(n,e,t,r,a,15361-u|0),u=a=o[n+4>>2],c=(e=o[n+8>>2])<<4|a>>>28,a=r=o[n+12>>2]<<4|e>>>28,r=e=268435455&u,134217728==(0|e)&(t=o[n>>2]|0!=(o[n+16>>2]|o[n+24>>2])|0!=(o[n+20>>2]|o[n+28>>2]))>>>0>=1|e>>>0>134217728?(a=(e=c+1|0)>>>0<1?a+1|0:a,c=e):t|134217728^r||(a=(e=c+(1&c)|0)>>>0<c>>>0?a+1|0:a,c=e)))):(c=(c=r)<<4|t>>>28,a=524287&(r=A<<4|r>>>28)|2146959360);return y=n+32|0,f(0,0|c),f(1,-2147483648&A|a),+s()}function ae(e){var t,r=0,A=0,n=0,a=0;y=t=y-16|0;e:if(ir(e,83))if((r=(n=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24)-97>>>0<26){a=t,n=e;t:{r:{A:{n:{i:{a:switch(r-97|0){default:switch(r-111|0){case 0:break A;case 4:break i;default:break e}case 0:o[e>>2]=o[e>>2]+1,o[t+12>>2]=0,A=Fr(e,t+12|0);break t;case 2:case 4:case 5:case 6:case 7:break e;case 1:break a;case 3:break r;case 8:break n}o[e>>2]=o[e>>2]+1,o[t+12>>2]=1,A=Fr(e,t+12|0);break t}o[e>>2]=o[e>>2]+1,o[t+12>>2]=2,A=Fr(e,t+12|0);break t}o[e>>2]=o[e>>2]+1,o[t+12>>2]=3,A=Fr(e,t+12|0);break t}o[e>>2]=o[e>>2]+1,o[t+12>>2]=4,A=Fr(e,t+12|0);break t}o[e>>2]=o[e>>2]+1,o[t+12>>2]=5,A=Fr(e,t+12|0)}if(r=at(n,A),o[a+12>>2]=r,(0|r)==(0|A))break e;Gt(e+148|0,t+12|0),A=r}else if(ir(e,95)){if(o[(e=e+148|0)>>2]==o[e+4>>2])break e;A=o[o[e>>2]>>2]}else o[t+12>>2]=0,Be(e,t+12|0)||(r=o[t+12>>2],ir(e,95)&&((r=r+1|0)>>>0>=o[4+(e=e+148|0)>>2]-o[e>>2]>>2>>>0||(A=o[o[e>>2]+(r<<2)>>2])));return y=t+16|0,A}function oe(e){var t,r=0,A=0,n=0,c=0,f=0,s=0;y=t=y+-64|0;e:if(71!=(255&(r=(r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24))&&84!=(0|r)){if(o[t+56>>2]=e,i[8+(A=t+40|0)|0]=0,o[A+4>>2]=0,a[A>>1]=0,r=e,o[A+12>>2]=o[r+364>>2]-o[r+360>>2]>>2,n=be(r,A),o[t+36>>2]=n,e=0,n&&!function(e,t){var r=0,A=0,n=0,i=0,a=0,c=0,f=0;i=o[4+(r=A=e+360|0)>>2]-o[r>>2]>>2,t=o[t+12>>2],f=t>>>0<i>>>0?i:t,r=e+332|0,e=t;t:{for(;(0|e)!=(0|f);){if(o[r>>2]==o[r+4>>2])break t;if(!(n=o[o[r>>2]>>2]))break t;if(a=o[o[A>>2]+(e<<2)>>2],(c=o[a+8>>2])>>>0>=o[n+4>>2]-o[n>>2]>>2>>>0)break t;o[a+12>>2]=o[o[n>>2]+(c<<2)>>2],e=e+1|0}o[A+4>>2]=o[A>>2]+(t<<2)}return e>>>0<i>>>0}(r,A)&&(e=n,!qe(t+56|0))){o[t+32>>2]=0,e=dr(t+24|0,5090),n=o[e+4>>2],o[t>>2]=o[e>>2],o[t+4>>2]=n;t:{if(rt(r,t)){for(n=o[4+(e=r+8|0)>>2]-o[e>>2]>>2;!ir(r,69);){if(c=Oe(r),o[t+16>>2]=c,!c)break t;Gt(e,t+16|0)}or(t+16|0,r,n),f=t,s=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=9,o[r>>2]=5508,o[r>>2]=13616,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(r+416|0,t+16|0),o[f+32>>2]=s}if(o[t+12>>2]=0,b[t+40|0]|!b[A+1|0]||(e=X(r),o[t+12>>2]=e,e)){if(ir(r,118)){o[(e=t+16|0)>>2]=0,o[e+4>>2]=0,e=Rr(r,t+12|0,t+36|0,e,t+32|0,A+4|0,A+8|0);break e}for(n=o[4+(e=r+8|0)>>2]-o[e>>2]>>2;;){if(c=X(r),o[t+16>>2]=c,!c)break t;if(Gt(e,t+16|0),qe(t+56|0))break}or(t+16|0,r,n),e=Rr(r,t+12|0,t+36|0,t+16|0,t+32|0,A+4|0,A+8|0);break e}}e=0}}else e=V(e);return y=t- -64|0,e}function ce(e,t,r){var A,n=0,i=0,a=0,c=0,f=0,s=0;if(y=A=y-48|0,407710288!=(-16&(b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24))|r>>>0<8){c=e;e:if((n=L(A+8|0,t,r,0))>>>0>=4294967177)o[e>>2]=n,e=-2,t=-1;else if(n)o[e>>2]=-72,e=-2,t=-1;else{r=r-(n=o[A+32>>2])|0,n=t+n|0;t:{for(;;){if(r>>>0<3){i=-72;break t}i=-20;r:{A:switch((f=(a=b[0|n]|b[n+1|0]<<8)>>>1&3)-1|0){case 2:break t;case 0:break r}f=(b[n+2|0]<<16|a)>>>3|0}if((i=f+3|0)>>>0>r>>>0){o[e>>2]=-72,e=-2,t=-1;break e}if(s=s+1|0,r=r-i|0,n=n+i|0,1&a)break}if(o[A+40>>2]){if(r>>>0<=3){o[e>>2]=-72,e=-2,t=-1;break e}n=n+4|0}a=o[A+24>>2],r=o[A+8>>2],i=o[A+12>>2],o[e+4>>2]=0,o[e>>2]=n-t,e=(t=-1==(0|r)&-1==(0|i))?p(a,s):r,t=t?0:i;break e}o[e>>2]=i,e=-2,t=-1}o[c+8>>2]=e,o[c+12>>2]=t}else t=b[t+4|0]|b[t+5|0]<<8|b[t+6|0]<<16|b[t+7|0]<<24,o[e+8>>2]=0,o[e+12>>2]=0,o[e+4>>2]=0,c=e,e=t+8|0,o[c>>2]=t>>>0>4294967287?-14:e>>>0>r>>>0?-72:e;y=A+48|0}function fe(e,t,r){var A=0,n=0,i=0;o[e+29416>>2]=0,o[e+29420>>2]=0,o[e+29356>>2]=0,o[e+29360>>2]=0,o[e+29584>>2]=0,o[e+29432>>2]=0,o[e+29436>>2]=0,o[e+29424>>2]=3,o[e+29428>>2]=0,o[(A=e+29364|0)>>2]=0,o[A+4>>2]=0,o[(A=e+10280|0)>>2]=201326604,o[e+29372>>2]=o[e+29532>>2]?1:5,o[e+12>>2]=A,o[e+8>>2]=e+4120,o[e+4>>2]=e+6176,o[e>>2]=e+16,i=o[609],o[(A=e+26668|0)>>2]=o[608],o[A+4>>2]=i,o[e+26676>>2]=o[610];e:{if(!(!t|!r)){if(-332356553!=(b[0|t]|b[t+1|0]<<8|b[t+2|0]<<16|b[t+3|0]<<24)|r>>>0<=7)break e;o[e+29584>>2]=b[t+4|0]|b[t+5|0]<<8|b[t+6|0]<<16|b[t+7|0]<<24,n=-30,(A=function(e,t,r){var A,n=0,i=0,a=0,c=0,f=0,s=0,u=0;return y=A=y-128|0,s=-30,r>>>0<9||(n=x(e+10264|0,i=t+8|0,r-8|0,e,10264))>>>0>4294967176||(o[A+124>>2]=31,(r=J(A,A+124|0,A+120|0,n=n>>>0<4294967177?n+i|0:i,(i=t+r|0)-n|0))>>>0>4294967176||(a=o[A+124>>2])>>>0>31||(c=o[A+120>>2])>>>0>=9||(Z(e+4104|0,A,f=a,1440,1568,c,a=e+26664|0),o[A+124>>2]=52,(n=J(A,A+124|0,A+120|0,r=r+n|0,i-r|0))>>>0>4294967176||(c=o[A+124>>2])>>>0>52||(f=o[A+120>>2])>>>0>=10||(Z(e+6160|0,A,c,1696,1920,f,a),o[A+124>>2]=35,(u=(n=J(A,A+124|0,A+120|0,r=r+n|0,i-r|0))>>>0>4294967176|(c=o[A+124>>2])>>>0>35|(f=o[A+120>>2])>>>0>9)||(Z(e,A,c,2144,2288,f,a),r=r+n|0),(n=r+12|0)>>>0>i>>>0|u||(i=i-n|0)>>>0<=(a=b[0|r]|b[r+1|0]<<8|b[r+2|0]<<16|b[r+3|0]<<24)-1>>>0||(o[e+26652>>2]=a,i>>>0<=(n=b[r+4|0]|b[r+5|0]<<8|b[r+6|0]<<16|b[r+7|0]<<24)-1>>>0||(o[e+26656>>2]=n,(a=i)>>>0<=(i=b[r+8|0]|b[r+9|0]<<8|b[r+10|0]<<16|b[r+11|0]<<24)-1>>>0||(o[e+26660>>2]=i,s=12+(r-t|0)|0)))))),y=A+128|0,s}(e+16|0,t,r))>>>0>4294967176||(o[e+29432>>2]=1,o[e+29436>>2]=1,n=o[e+29356>>2],o[e+29368>>2]=n,i=o[e+29360>>2],t=A>>>0<4294967177?t+A|0:t,o[e+29360>>2]=t,o[e+29356>>2]=t+(r-A|0),o[e+29364>>2]=t+(i-n|0),n=0)}return n}return o[e+29364>>2]=t,o[e+29368>>2]=0,o[e+29360>>2]=t,o[e+29356>>2]=t+r,0}function se(e,t,r){var A,n=0,a=0,c=0,f=0,s=0,u=0;y=A=y-32|0,n=o[t>>2],41==b[n+4|0]&&(n=o[n+8>>2],o[A+28>>2]=n,n-2>>>0>3||(s=t,u=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=40,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=11944,e}(e+416|0,A+28|0),o[s>>2]=u));e:if(ir(e,67)){if(f=ir(e,73),(((a=(0|(n=o[e>>2]))!=o[e+4>>2]?i[0|n]:0)<<24>>24)-49&255)>>>0>4)break e;if(a=A,n=(0|(n=o[e>>2]))!=o[e+4>>2]?i[0|n]:0,o[a+24>>2]=(n<<24>>24)-48,o[e>>2]=o[e>>2]+1,r&&(i[0|r]=1),f&&!be(e,r))break e;i[A+23|0]=0,c=pr(e,t,A+23|0,A+24|0)}else 68==(255&(a=(0|(n=o[e>>2]))!=o[e+4>>2]?b[0|n]:0))&&(n=o[e>>2],(n=((a=o[e+4>>2]-n>>>0>1?i[n+1|0]:0)<<24>>24&255)-48|0)>>>0>5|3==(0|n)||(a=A,c=o[e>>2],n=o[e+4>>2]-c>>>0>1?i[c+1|0]:0,o[a+16>>2]=(n<<24>>24)-48,o[e>>2]=o[e>>2]+2,r&&(i[0|r]=1),i[A+15|0]=1,c=pr(e,t,A+15|0,A+16|0)));return y=A+32|0,c}function ue(e){var t,r=0,A=0,n=0,a=0;y=t=y-80|0;e:{t:{if(ir(e,85)){if(ze(t+72|0,e),o[t+72>>2]==o[t+76>>2])break e;if(r=dr(t- -64|0,14592),A=o[r+4>>2],o[t>>2]=o[r>>2],o[t+4>>2]=A,$t(t+72|0,t)){if(rr(t+56|0,t+72|0,9),o[(A=t+48|0)>>2]=0,o[A+4>>2]=0,r=jt(t+32|0,e,o[t+56>>2]),n=jt(t+16|0,e+4|0,o[t+60>>2]),ze(t+8|0,e),a=o[t+12>>2],o[t+48>>2]=o[t+8>>2],o[t+52>>2]=a,b[n+8|0]&&(o[o[n>>2]>>2]=o[n+4>>2]),b[r+8|0]&&(o[o[r>>2]>>2]=o[r+4>>2]),r=0,o[A>>2]==o[A+4>>2])break e;if(r=ue(e),o[t+32>>2]=r,!r)break t;r=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=10,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=14612,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}(e+416|0,t+32|0,A);break e}if(r=ue(e),o[t+32>>2]=r,!r)break t;r=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=2,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=14720,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}(e+416|0,t+32|0,t+72|0);break e}if(A=pt(e),o[t+32>>2]=A,r=X(e),o[t+16>>2]=r,r){if(!A)break e;r=function(e,t,r){var A=0,n=0;return A=b[t+5|0],n=b[t+6|0],i[e+7|0]=b[t+7|0],i[e+6|0]=n,i[e+5|0]=A,i[e+4|0]=3,o[e>>2]=5508,o[e+12>>2]=t,o[e+8>>2]=r,o[e>>2]=14832,e}(kt(e+416|0,16),o[t+16>>2],o[t+32>>2]);break e}}r=0}return y=t+80|0,r}function be(e,t){var r,A=0,n=0;y=r=y-16|0,ir(e,76);e:{t:{r:{A:{if(90!=(0|(A=(A=(0|(A=o[e>>2]))!=o[e+4>>2]?i[0|A]:0)<<24>>24))){if(83==(0|(A&=255)))break A;if(78!=(0|A))break r;A=function(e,t){var r,A=0,n=0,a=0,c=0;y=r=y-48|0,o[r+44>>2]=t;n:if(ir(e,78)){A=pt(e),t&&(o[t+4>>2]=A);i:if(ir(e,79)){if(!t)break i;i[t+8|0]=2}else if(ir(e,82)){if(!t)break i;i[t+8|0]=1}else t&&(i[t+8|0]=0);o[r+40>>2]=0,o[r+28>>2]=e,o[r+32>>2]=r+44,o[r+24>>2]=r+40,t=dr(r+16|0,5690),A=o[t+4>>2],o[r>>2]=o[t>>2],o[r+4>>2]=A,rt(e,r)&&(a=r,c=Vr(e,5693),o[a+40>>2]=c),t=e+148|0;i:{for(;!ir(e,69);){if(ir(e,76),ir(e,77)){if(o[r+40>>2])continue;break i}a:{o:{c:{f:{s:{switch((A=(A=(0|(A=o[e>>2]))!=o[e+4>>2]?i[0|A]:0)<<24>>24&255)-67|0){case 2:case 3:case 4:case 5:break a;case 0:break o;case 1:break f;case 6:break s}u:switch(A-83|0){case 0:break c;case 1:break u;default:break a}if(!tt(r+24|0,Te(e)))break i;Gt(t,r+40|0);continue}if(A=_e(e,0!=o[r+44>>2]),o[r+12>>2]=A,!A|!o[r+40>>2])break i;a=r,c=Or(e,r+40|0,r+12|0),o[a+40>>2]=c,(A=o[r+44>>2])&&(i[A+1|0]=1),Gt(t,r+40|0);continue}if(A=o[e>>2],67==(0|(A=(A=o[e+4>>2]-A>>>0>1?i[A+1|0]:0)<<24>>24)))break a;if(84!=(0|(A&=255))&&116!=(0|A))break o;if(!tt(r+24|0,lt(e)))break i;Gt(t,r+40|0);continue}if(A=o[e>>2],116==(255&(A=o[e+4>>2]-A>>>0>1?b[A+1|0]:0)))break a;if(A=ae(e),o[r+12>>2]=A,!tt(r+24|0,A))break i;if((0|A)==o[r+40>>2])continue;Gt(t,r+12|0);continue}if(A=0,!o[r+40>>2])break n;if(!tt(r+24|0,se(e,r+40|0,o[r+44>>2])))break n;if(n=at(e,o[r+40>>2]),o[r+40>>2]=n,!n)break n;Gt(t,r+40|0);continue}if(!tt(r+24|0,Re(e,o[r+44>>2])))break i;Gt(t,r+40|0)}if(A=0,!o[r+40>>2]|o[t>>2]==o[t+4>>2])break n;o[t+4>>2]=o[t+4>>2]-4,A=o[r+40>>2];break n}A=0}return y=r+48|0,A}(e,t);break e}A=function(e,t){var r=0,A=0,n=0,i=0,a=0;y=r=y-16|0;n:if(ir(e,90)&&(n=oe(e),o[r+12>>2]=n,n&&ir(e,69)))if(ir(e,115))i=e,a=Se(o[e>>2],o[e+4>>2]),o[i>>2]=a,i=r,a=Vr(e,12720),o[i>>2]=a,A=yr(e,r+12|0,r);else i:{if(ir(e,100)){if(et(r,e,1),!ir(e,95))break n;if(t=be(e,t),o[r>>2]=t,!t)break i;A=yr(e,r+12|0,r);break n}t=be(e,t),o[r>>2]=t,t&&(i=e,a=Se(o[e>>2],o[e+4>>2]),o[i>>2]=a,A=yr(e,r+12|0,r))}return y=r+16|0,A}(e,t);break e}if(A=o[e>>2],116!=(255&(A=o[e+4>>2]-A>>>0>1?b[A+1|0]:0))){if(A=ae(e),o[r+12>>2]=A,!A)break t;if(A=0,73!=(255&(n=(0|(n=o[e>>2]))!=o[e+4>>2]?b[0|n]:0)))break e;if(n=_e(e,0!=(0|t)),o[r+8>>2]=n,!n)break e;t&&(i[t+1|0]=1),A=Or(e,r+12|0,r+8|0);break e}}if(A=function(e,t){var r=0,A=0,n=0;y=r=y-48|0,A=dr(r+40|0,13368),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n;r:if(rt(e,r+16|0)||(A=dr(r+32|0,5690),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,rt(e,r+8|0))){if(A=Re(e,t),o[r+28>>2]=A,t=0,!A)break r;t=function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=39,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=13380,e}(e+416|0,r+28|0)}else t=Re(e,t);return y=r+48|0,t}(e,t),o[r+12>>2]=A,A){if(73!=(255&(n=(0|(n=o[e>>2]))!=o[e+4>>2]?b[0|n]:0)))break e;Gt(e+148|0,r+12|0),A=_e(e,0!=(0|t)),o[r+8>>2]=A,A?(t&&(i[t+1|0]=1),A=Or(e,r+12|0,r+8|0)):A=0;break e}}A=0}return y=r+16|0,A}function le(e,t,r){var A=0;e:if((0|e)!=(0|t)){if((t-e|0)-r>>>0<=0-(r<<1)>>>0)return re(e,t,r);if(A=3&(e^t),e>>>0<t>>>0){if(A)A=e;else{if(3&e)for(A=e;;){if(!r)break e;if(i[0|A]=b[0|t],t=t+1|0,r=r-1|0,!(3&(A=A+1|0)))break}else A=e;if(!(r>>>0<=3))for(;o[A>>2]=o[t>>2],t=t+4|0,A=A+4|0,(r=r-4|0)>>>0>3;);}if(r)for(;i[0|A]=b[0|t],A=A+1|0,t=t+1|0,r=r-1|0;);}else{if(!A){if(e+r&3)for(;;){if(!r)break e;if(i[0|(A=(r=r-1|0)+e|0)]=b[t+r|0],!(3&A))break}if(!(r>>>0<=3))for(;o[(r=r-4|0)+e>>2]=o[t+r>>2],r>>>0>3;);}if(!r)break e;for(;i[(r=r-1|0)+e|0]=b[t+r|0],r;);}}return e}function de(e){var t,r=0;y=t=y-16|0;e:{t:{r:if(100==(255&(r=(0|(r=o[e>>2]))!=o[e+4>>2]?b[0|r]:0))){if(r=o[e>>2],88!=(0|(r=(r=o[e+4>>2]-r>>>0>1?i[r+1|0]:0)<<24>>24))){if(120!=(0|r)){if(105!=(0|r))break r;if(o[e>>2]=o[e>>2]+2,r=Ce(e),o[t+12>>2]=r,!r)break t;if(r=de(e),o[t+8>>2]=r,!r)break t;i[t+4|0]=0,e=Tr(e,t+12|0,t+8|0,t+4|0);break e}if(o[e>>2]=o[e>>2]+2,r=v(e),o[t+12>>2]=r,!r)break t;if(r=de(e),o[t+8>>2]=r,!r)break t;i[t+4|0]=1,e=Tr(e,t+12|0,t+8|0,t+4|0);break e}if(o[e>>2]=o[e>>2]+2,r=v(e),o[t+12>>2]=r,!r)break t;if(r=v(e),o[t+8>>2]=r,!r)break t;if(r=de(e),o[t+4>>2]=r,!r)break t;e=function(e,t,r,A){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=74,o[e>>2]=5508,o[e+16>>2]=A,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=10800,e}(kt(e+416|0,20),o[t+12>>2],o[t+8>>2],o[t+4>>2]);break e}e=v(e);break e}e=0}return y=t+16|0,e}function ke(e,t,r){var A=0,n=0,a=0;if(r&&(i[(a=e+r|0)-1|0]=t,i[0|e]=t,!(r>>>0<3||(i[a-2|0]=t,i[e+1|0]=t,i[a-3|0]=t,i[e+2|0]=t,r>>>0<7||(i[a-4|0]=t,i[e+3|0]=t,r>>>0<9||(n=(a=0-e&3)+e|0,A=p(255&t,16843009),o[n>>2]=A,o[(r=(t=r-a&-4)+n|0)-4>>2]=A,t>>>0<9||(o[n+8>>2]=A,o[n+4>>2]=A,o[r-8>>2]=A,o[r-12>>2]=A,t>>>0<25||(o[n+24>>2]=A,o[n+20>>2]=A,o[n+16>>2]=A,o[n+12>>2]=A,o[r-16>>2]=A,o[r-20>>2]=A,o[r-24>>2]=A,o[r-28>>2]=A,(r=(r=t)-(t=4&n|24)|0)>>>0<32))))))))for(A=Qr(A,0,1,1),a=D,t=t+n|0;o[t+24>>2]=A,n=a,o[t+28>>2]=n,o[t+16>>2]=A,o[t+20>>2]=n,o[t+8>>2]=A,o[t+12>>2]=n,o[t>>2]=A,o[t+4>>2]=n,t=t+32|0,(r=r-32|0)>>>0>31;);return e}function Ee(e,t,r){e:if(!(t>>>0>20)){t:switch(t-9|0){case 0:return t=o[r>>2],o[r>>2]=t+4,void(o[e>>2]=o[t>>2]);case 1:return t=o[r>>2],o[r>>2]=t+4,t=o[t>>2],o[e>>2]=t,void(o[e+4>>2]=t>>31);case 2:return t=o[r>>2],o[r>>2]=t+4,o[e>>2]=o[t>>2],void(o[e+4>>2]=0);case 3:return t=o[r>>2]+7&-8,o[r>>2]=t+8,r=o[t+4>>2],o[e>>2]=o[t>>2],void(o[e+4>>2]=r);case 4:return t=o[r>>2],o[r>>2]=t+4,t=a[t>>1],o[e>>2]=t,void(o[e+4>>2]=t>>31);case 5:return t=o[r>>2],o[r>>2]=t+4,o[e>>2]=l[t>>1],void(o[e+4>>2]=0);case 6:return t=o[r>>2],o[r>>2]=t+4,t=i[0|t],o[e>>2]=t,void(o[e+4>>2]=t>>31);case 7:return t=o[r>>2],o[r>>2]=t+4,o[e>>2]=b[0|t],void(o[e+4>>2]=0);case 8:return t=o[r>>2]+7&-8,o[r>>2]=t+8,void(E[e>>3]=E[t>>3]);case 9:break t;default:break e}aA[3](e,r)}}function pe(e,t){e|=0;var r,A,n,i=0,a=0,c=0,f=0;y=r=y+-64|0,A=Qt(r+48|0,12+(t|=0)|0),n=Qt(r+32|0,t+16|0),a=o[t+4>>2],i=o[e+8>>2],aA[o[o[i>>2]+16>>2]](i,t),1!=b[i+5|0]&&aA[o[o[i>>2]+20>>2]](i,t);e:{t:{r:{A:switch((i=o[t+16>>2])+1|0){case 0:break t;case 1:break A;default:break r}o[t+4>>2]=a;break e}for(c=i>>>0>1?i:1,i=1;;){if((0|i)==(0|c))break e;a=dr(r+16|0,7794),f=o[a+4>>2],o[r>>2]=o[a>>2],o[r+4>>2]=f,Kt(t,r),o[t+12>>2]=i,a=o[e+8>>2],aA[o[o[a>>2]+16>>2]](a,t),1!=b[a+5|0]&&aA[o[o[a>>2]+20>>2]](a,t),i=i+1|0}}e=dr(r+24|0,7916),i=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=i,Kt(t,r+8|0)}b[n+8|0]&&(o[o[n>>2]>>2]=o[n+4>>2]),b[A+8|0]&&(o[o[A>>2]>>2]=o[A+4>>2]),y=r- -64|0}function Te(e){var t,r=0,A=0,n=0,a=0,c=0;y=t=y-16|0;e:if(ir(e,84)){if(o[t+12>>2]=0,ir(e,76)){if(Ne(e,t+12|0))break e;if(A=o[t+12>>2],!ir(e,95))break e;A=A+1|0}if(o[t+8>>2]=0,!ir(e,95)){if(Ne(e,t+8|0))break e;if(n=o[t+8>>2]+1|0,o[t+8>>2]=n,!ir(e,95))break e}if(A|!b[e+389|0])if(!((c=o[4+(r=a=e+332|0)>>2]-o[r>>2]>>2)>>>0<=A>>>0)&&(r=o[o[a>>2]+(A<<2)>>2])&&o[r+4>>2]-o[r>>2]>>2>>>0>n>>>0)r=o[o[r>>2]+(n<<2)>>2];else{if(r=0,o[e+392>>2]!=(0|A)|A>>>0>c>>>0)break e;(0|A)==(0|c)&&(o[t+4>>2]=0,Gt(a,t+4|0)),r=Vr(e,5907)}else r=function(e,t){return i[e+7|0]=2,i[e+6|0]=2,i[e+5|0]=2,i[e+4|0]=36,o[e>>2]=5508,i[e+16|0]=0,o[e+12>>2]=0,o[e+8>>2]=t,o[e>>2]=5920,e}(kt(e+416|0,20),o[t+8>>2]),o[t+4>>2]=r,Gt(e+360|0,t+4|0)}return y=t+16|0,r}function ge(e){var t,r,A=0,n=0;return y=t=y+-64|0,o[(A=r=t+56|0)>>2]=0,o[A+4>>2]=0,A=dr(t+48|0,15392),n=o[A+4>>2],o[t+16>>2]=o[A>>2],o[t+20>>2]=n,rt(e,t+16|0)?(dr(t+40|0,15395),A=o[t+44>>2],o[t+56>>2]=o[t+40>>2],o[t+60>>2]=A):(A=dr(t+32|0,15402),n=o[A+4>>2],o[t+8>>2]=o[A>>2],o[t+12>>2]=n,rt(e,t+8|0)?(dr(t+40|0,15405),A=o[t+44>>2],o[t+56>>2]=o[t+40>>2],o[t+60>>2]=A):(A=dr(t+24|0,15411),n=o[A+4>>2],o[t>>2]=o[A>>2],o[t+4>>2]=n,rt(e,t)&&(dr(t+40|0,15414),A=o[t+44>>2],o[t+56>>2]=o[t+40>>2],o[t+60>>2]=A))),n=be(e,0),o[t+40>>2]=n,A=0,n&&(A=n,o[r>>2]!=o[r+4>>2]&&(A=function(e,t,r){var A=0,n=0;return y=A=y-16|0,n=kt(e,20),e=o[t+4>>2],t=o[t>>2],o[A+8>>2]=t,o[A+12>>2]=e,r=o[r>>2],o[A>>2]=t,o[A+4>>2]=e,e=function(e,t,r){var A=0;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=6,o[e>>2]=5508,o[e>>2]=15428,A=o[t+4>>2],t=o[t>>2],o[e+16>>2]=r,o[e+8>>2]=t,o[e+12>>2]=A,e}(n,A,r),y=A+16|0,e}(e+416|0,r,t+40|0))),y=t- -64|0,A}function he(e,t,r,A,n,c,f,s,u,l,d,k,E,p){var T,g=0;y=T=y-128|0,o[T+124>>2]=A,g=-1;e:{t:switch(0|r){case 1:if(!f){g=-72;break e}if(g=-20,(r=b[0|c])>>>0>A>>>0)break e;A=o[(r<<=2)+u>>2],r=o[r+s>>2],i[e+11|0]=0,o[e>>2]=0,o[e+4>>2]=0,o[e+12>>2]=r,i[e+10|0]=A,a[e+8>>1]=0,o[t>>2]=e,g=1;break e;case 0:o[t>>2]=l,g=0;break e;case 3:if(!d){g=-20;break e}if(g=0,!k|(0|E)<25)break e;if(!(e=8+(8<<n)|0))break e;for(A=0;e>>>0>(A=A- -64|0)>>>0;);break e;case 2:break t;default:break e}g=-20,(r=J(T,T+124|0,T+120|0,c,f))>>>0>4294967176||(A=o[T+120>>2])>>>0>n>>>0||(Z(e,T,o[T+124>>2],s,u,A,p),o[t>>2]=e,g=r)}return y=T+128|0,g}function Oe(e){var t,r=0,A=0,n=0;y=t=y-16|0;e:{t:{r:{A:{n:{i:switch((r=(r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24)-74|0){case 1:break r;case 2:break A;case 0:break n}if(88!=(0|r))break r;if(o[e>>2]=o[e>>2]+1,!(r=v(e)))break t;return e=ir(e,69),y=t+16|0,e?r:0}for(o[e>>2]=o[e>>2]+1,n=o[4+(r=e+8|0)>>2]-o[r>>2]>>2;!ir(e,69);){if(A=Oe(e),o[t+12>>2]=A,!A)break t;Gt(r,t+12|0)}or(t,e,n),e=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=33,o[r>>2]=5508,o[r>>2]=13500,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,t);break e}if(r=o[e>>2],90==(255&(r=o[e+4>>2]-r>>>0>1?b[r+1|0]:0))){if(o[e>>2]=o[e>>2]+2,!(r=oe(e)))break t;e=ir(e,69)?r:0;break e}e=G(e);break e}e=X(e);break e}e=0}return y=t+16|0,e}function _e(e,t){var r,A=0,n=0,a=0,c=0,f=0,s=0,u=0,l=0,d=0;y=r=y-48|0;e:if(ir(e,73)){t&&(o[4+(A=n=e+332|0)>>2]=o[A>>2],A=e+288|0,o[r+16>>2]=A,Gt(n,r+16|0),o[A+4>>2]=o[A>>2]),n=e+332|0,f=o[4+(A=a=e+8|0)>>2]-o[A>>2]>>2;t:{for(;!ir(e,69);)if(t){if(c=Ke(r+16|0,n),A=Oe(e),o[r+12>>2]=A,De(n,c),!A)break t;Gt(a,r+12|0),o[r+8>>2]=A,33==b[A+4|0]&&(s=o[A+12>>2],o[r>>2]=o[A+8>>2],o[r+4>>2]=s,l=r,d=Bt(e+416|0,r),o[l+8>>2]=d),Gt(o[o[n+4>>2]-4>>2],r+8|0),Xr(c)}else{if(A=Oe(e),o[r+16>>2]=A,!A)break e;Gt(a,r+16|0)}or(r+16|0,e,f),u=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=35,o[r>>2]=5508,o[r>>2]=13152,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,r+16|0);break e}Xr(c)}return y=r+48|0,u}function we(e){var t=0,r=0,A=0,n=0,i=0,a=0;e:{t:if(e){if(t=-64,o[e+29568>>2])break e;n=o[e+29556>>2],r=o[e+29552>>2];r:if(t=o[e+29576>>2]){i=o[t+27320>>2],A=o[t+27316>>2];A:{n:{if(a=o[t>>2]){if(!A)break n;aA[0|A](i,a),aA[0|A](i,t);break r}if(!A)break A;aA[0|A](i,t);break r}N(a)}N(t)}o[e+29592>>2]=0,o[e+29576>>2]=0,o[e+29580>>2]=0;r:{A:{if(t=o[e+29600>>2]){if(!r)break A;aA[0|r](n,t),o[e+29600>>2]=0,aA[0|r](n,e);break t}if(o[e+29600>>2]=0,!r)break r;aA[0|r](n,e);break t}N(t),o[e+29600>>2]=0}N(e)}t=0}return t}function ye(e){var t,r=0,A=0;return y=t=y-48|0,((r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24)-48>>>0<=9?r=it(e):(r=dr(t+40|0,9500),A=o[r+4>>2],o[t+16>>2]=o[r>>2],o[t+20>>2]=A,rt(e,t+16|0)?r=function(e){var t=0,r=0,A=0;return y=r=y-16|0,A=r,t=((t=(0|(t=o[e>>2]))!=o[e+4>>2]?i[0|t]:0)<<24>>24)-48>>>0<=9?it(e):We(e),o[A+12>>2]=t,e=t?function(e,t){return e=kt(e,12),t=o[t>>2],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=43,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=9516,e}(e+416|0,r+12|0):0,y=r+16|0,e}(e):(r=dr(t+32|0,9503),A=o[r+4>>2],o[t+8>>2]=o[r>>2],o[t+12>>2]=A,rt(e,t+8|0),A=B(e,0),o[t+28>>2]=A,r=0,A&&(r=A,73==(255&(A=(0|(A=o[e>>2]))!=o[e+4>>2]?b[0|A]:0))&&(r=_e(e,0),o[t+24>>2]=r,r=r?Or(e,t+28|0,t+24|0):0)))),y=t+48|0,r}function Re(e,t){var r,A=0,n=0;y=r=y-32|0;e:{t:{r:{if(85!=(0|(A=(A=(0|(A=o[e>>2]))!=o[e+4>>2]?i[0|A]:0)<<24>>24)))if((A-49&255)>>>0<=8)t=Ce(e);else{if(A=dr(r+24|0,12598),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,rt(e,r+8|0)){for(A=o[4+(A=t=e+8|0)>>2]-o[A>>2]>>2;;){if(n=Ce(e),o[r+16>>2]=n,!n)break r;if(Gt(t,r+16|0),ir(e,69))break}or(r+16|0,e,A),t=function(e,t){var r=0,A=0,n=0;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=46,o[r>>2]=5508,o[r>>2]=12612,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,r+16|0);break t}t=B(e,t)}else t=ee(e,t);if(t)break t}e=0;break e}e=at(e,t)}return y=r+32|0,e}function De(e,t){var r=0;if(r=o[e>>2]==(e+12|0),o[t>>2]!=(t+12|0)){if(r)return o[e>>2]=o[t>>2],o[e+4>>2]=o[t+4>>2],o[e+8>>2]=o[t+8>>2],o[t+8>>2]=t+28,e=t+12|0,o[t+4>>2]=e,void(o[t>>2]=e);r=o[e>>2],o[e>>2]=o[t>>2],o[t>>2]=r,r=o[e+4>>2],o[e+4>>2]=o[t+4>>2],o[t+4>>2]=r,r=o[e+8>>2],o[e+8>>2]=o[t+8>>2],o[t+8>>2]=r}else r||(N(o[e>>2]),o[e+8>>2]=e+28,r=e+12|0,o[e+4>>2]=r,o[e>>2]=r),jr(o[t>>2],o[t+4>>2],o[e>>2]),o[e+4>>2]=o[e>>2]+(o[t+4>>2]-o[t>>2]>>2<<2);o[t+4>>2]=o[t>>2]}function me(e,t,r){var A=0,n=0;A=0!=(0|r);e:{t:{r:if(!(!r|!(3&e)))for(n=255&t;;){if((0|n)==b[0|e])break t;if(e=e+1|0,A=0!=(0|(r=r-1|0)),!r)break r;if(!(3&e))break}if(!A)break e}t:if(!(b[0|e]==(255&t)|r>>>0<4))for(A=p(255&t,16843009);;){if((-1^(n=A^o[e>>2]))&n-16843009&-2139062144)break t;if(e=e+4|0,!((r=r-4|0)>>>0>3))break}if(r)for(t&=255;;){if((0|t)==b[0|e])return e;if(e=e+1|0,!(r=r-1|0))break}}return 0}function ve(e,t,r,A,n,i){var a,c,f=0;if(!r)return-70;if(!n)return-20;a=r>>>8|0,f=p(r>>>0>n>>>0?(n<<4>>>0)/(r>>>0)|0:15,24);e:{if((c=p(a,o[f+1068>>2])+o[f+1064>>2]|0)+(c>>>3|0)>>>0<o[f+1056>>2]+p(o[f+1060>>2],a)>>>0){if((i=x(e,A,n,i,2048))>>>0>4294967176)return i;if(f=-72,n>>>0<=i>>>0)break e;return F(t,r,A+i|0,n-i|0,e)}if((i=U(e,A,n,i))>>>0>4294967176)return i;f=-72,n>>>0<=i>>>0||(f=P(t,r,A+i|0,n-i|0,e))}return f}function Me(e){var t,r=0,A=0,n=0;y=t=y-48|0,r=dr(t+40|0,8260),A=o[r+4>>2],o[t+16>>2]=o[r>>2],o[t+20>>2]=A;e:if(rt(e,t+16|0)){if(pt(e),et(t+32|0,e,0),!ir(e,95))break e;n=zr(e,t+32|0)}else r=dr(t+24|0,8263),A=o[r+4>>2],o[t+8>>2]=o[r>>2],o[t+12>>2]=A,rt(e,t+8|0)&&(et(t+32|0,e,0),o[t+32>>2]!=o[t+36>>2]&&ir(e,112)&&(pt(e),et(t+32|0,e,0),ir(e,95)&&(n=zr(e,t+32|0))));return y=t+48|0,n}function Fe(e,t,r){var A=0,n=0,i=0,a=0;return A=31&(i=a=63&r),i>>>0>=32?A=-1>>>A|0:(n=-1>>>A|0,A=(1<<A)-1<<32-A|-1>>>A),i=A&e,A=t&n,n=31&a,a>>>0>=32?(A=i<<n,a=0):(A=(1<<n)-1&i>>>32-n|A<<n,a=i<<n),i=A,r=31&(A=n=0-r&63),A>>>0>=32?(A=-1<<r,r=0):(A=(1<<r)-1&-1>>>32-r|-1<<r,r=-1<<r),e&=r,A&=t,t=31&n,n>>>0>=32?(r=0,e=A>>>t|0):(r=A>>>t|0,e=((1<<t)-1&A)<<32-t|e>>>t),D=r|i,e|=a}function Se(e,t){var r=0,A=0;e:if((0|e)!=(0|t)){if(95==(0|(r=i[0|e]))){if((e+1|0)==(0|t))break e;if((r=i[e+1|0])-48>>>0<=9)return e+2|0;if(95!=(0|r))break e;for(r=e+2|0;;){if((0|t)==(0|r))break e;if(!((A=i[0|r])-48>>>0<=9))break;r=r+1|0}return 95==(0|A)?r+1|0:e}if(!(r-48>>>0>9))for(r=e;;){if((0|(r=r+1|0))==(0|t))return t;if(!(i[0|r]-48>>>0<10))break}}return e}function Pe(e,t){var r,A=0,n=0,i=0;y=r=y+-64|0,o[4+(A=e+8|0)>>2]&&(n=dr(r+56|0,6130),i=o[n+4>>2],o[r+24>>2]=o[n>>2],o[r+28>>2]=i,Kt(t,r+24|0),xe(A,t),A=dr(r+48|0,6118),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0)),A=dr(r+40|0,6516),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),xe(e+16|0,t),e=dr(r+32|0,6042),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r- -64|0}function Be(e,t){var r=0,A=0,n=0;A=1;e:if(!((0|(r=(r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24))<48|((r-65&255)>>>0>25?(0|r)>=58:0)))for(A=0;;){t:{if((0|(r=(r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24))>=48){if(n=-48,(0|r)<58)break t;if(n=-55,(r-65&255)>>>0<26)break t}o[t>>2]=A,A=0;break e}o[e>>2]=o[e>>2]+1,A=(p(A,36)+n|0)+(255&r)|0}return A}function Xe(e,t){e|=0,t|=0;var r,A=0,n=0;y=r=y-48|0,A=dr(r+40|0,6516),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),A=dr(r+32|0,6042),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),A=o[e+16>>2],e=o[e+12>>2],o[r>>2]=e,o[r+4>>2]=A,o[r+24>>2]=e,o[r+28>>2]=A,Kt(t,r),y=r+48|0}function Ue(e,t){var r,A,n;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=32,o[e>>2]=5508,o[e>>2]=13044,r=o[t+4>>2],t=o[t>>2],i[e+7|0]=2,o[e+8>>2]=t,o[e+12>>2]=r,i[e+5|0]=2,i[e+6|0]=2,A=e,n=function(e,t){for(var r=0,A=0;!(r=(0|e)==(0|t))&&(A=o[e>>2],e=e+4|0,1==b[A+6|0]););return r}(o[(t=e+8|0)>>2],o[t>>2]+(o[t+4>>2]<<2)|0)?1:2,i[A+6|0]=n,function(e,t){for(var r=0,A=0;!(r=(0|e)==(0|t))&&(A=o[e>>2],e=e+4|0,1==b[A+7|0]););return r}(o[t>>2],o[t>>2]+(o[t+4>>2]<<2)|0)&&(i[e+7|0]=1),function(e,t){for(var r=0,A=0;!(r=(0|e)==(0|t))&&(A=o[e>>2],e=e+4|0,1==b[A+5|0]););return r}(o[t>>2],o[t>>2]+(o[t+4>>2]<<2)|0)&&(i[e+5|0]=1),e}function Ie(e,t,r){var A=0,n=0,a=0;e:{if(A=t,!(n=o[r+16>>2])){if(function(e){var t=0;return t=b[e+74|0],i[e+74|0]=t-1|t,8&(t=o[e>>2])?(o[e>>2]=32|t,-1):(o[e+4>>2]=0,o[e+8>>2]=0,t=o[e+44>>2],o[e+28>>2]=t,o[e+20>>2]=t,o[e+16>>2]=t+o[e+48>>2],0)}(r))break e;n=o[r+16>>2]}if(n-(a=o[r+20>>2])>>>0<A>>>0)return void aA[o[r+36>>2]](r,e,t);t:if(!(i[r+75|0]<0)){for(n=t;;){if(!(A=n))break t;if(10==b[(n=A-1|0)+e|0])break}if(aA[o[r+36>>2]](r,e,A)>>>0<A>>>0)break e;e=e+A|0,t=t-A|0,a=o[r+20>>2]}re(a,e,t),o[r+20>>2]=o[r+20>>2]+t}}function xe(e,t){var r,A=0,n=0,i=0,a=0,c=0;for(y=r=y-16|0,a=1;o[e+4>>2]!=(0|n);)c=o[t+4>>2],a||(A=dr(r+8|0,7794),i=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=i,Kt(t,r)),i=o[t+4>>2],A=o[o[e>>2]+(n<<2)>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),n=n+1|0,o[t+4>>2]==(0|i)?o[t+4>>2]=c:a=0;y=r+16|0}function je(e,t,r,A){i[e+53|0]=1;e:if(o[e+4>>2]==(0|r)){if(i[e+52|0]=1,!(r=o[e+16>>2])){if(o[e+36>>2]=1,o[e+24>>2]=A,o[e+16>>2]=t,1!=(0|A)|1!=o[e+48>>2])break e;return void(i[e+54|0]=1)}if((0|t)==(0|r)){if(2==(0|(r=o[e+24>>2]))&&(o[e+24>>2]=A,r=A),1!=o[e+48>>2]|1!=(0|r))break e;return void(i[e+54|0]=1)}i[e+54|0]=1,o[e+36>>2]=o[e+36>>2]+1}}function Qe(e,t,r,A){var n,a=0;y=n=y-160|0,re(n+8|0,4600,144);e:{t:{if(t-1>>>0>=2147483647){if(t)break t;t=1,e=n+159|0}if(o[n+52>>2]=e,o[n+28>>2]=e,t=t>>>0>(a=-2-e|0)>>>0?a:t,o[n+56>>2]=t,e=e+t|0,o[n+36>>2]=e,o[n+24>>2]=e,e=function(e,t,r){var A,n=0,a=0;return y=A=y-208|0,o[A+204>>2]=r,ke(A+160|0,0,40),o[A+200>>2]=o[A+204>>2],(0|I(0,t,A+200|0,A+80|0,A+160|0))<0?t=-1:(o[e+76>>2],r=o[e>>2],i[e+74|0]<=0&&(o[e>>2]=-33&r),a=32&r,o[e+48>>2]?t=I(e,t,A+200|0,A+80|0,A+160|0):(o[e+48>>2]=80,o[e+16>>2]=A+80,o[e+28>>2]=A,o[e+20>>2]=A,r=o[e+44>>2],o[e+44>>2]=A,t=n=I(e,t,A+200|0,A+80|0,A+160|0),r&&(aA[o[e+36>>2]](e,0,0),o[e+48>>2]=0,o[e+44>>2]=r,o[e+28>>2]=0,o[e+16>>2]=0,t=o[e+20>>2],o[e+20>>2]=0,t=t?n:-1)),r=e,e=o[e>>2],o[r>>2]=e|a,t=32&e?-1:t),y=A+208|0,t}(n+8|0,r,A),!t)break e;t=o[n+28>>2],i[t-((0|t)==o[n+24>>2])|0]=0;break e}o[4041]=61,e=-1}return y=n+160|0,e}function Ce(e){var t,r=0,A=0,n=0;return y=t=y-32|0,o[t+28>>2]=0,Ne(e,t+28|0)||(A=o[t+28>>2])-1>>>0>=o[e+4>>2]-o[e>>2]>>>0||(r=t+16|0,n=o[e>>2],o[r+4>>2]=n+A,o[r>>2]=n,o[e>>2]=o[e>>2]+A,A=dr(t+8|0,9358),n=o[A+4>>2],o[t>>2]=o[A>>2],o[t+4>>2]=n,r=$t(r,t)?Ct(e+416|0,9369):Yr(e,r)),y=t+32|0,r}function Ze(e){var t=0,r=0,A=0;e:{t:if(3&(t=e)){if(!b[0|e])return 0;for(;;){if(!(3&(t=t+1|0)))break t;if(!b[0|t])break}break e}for(;r=t,t=t+4|0,!((-1^(A=o[r>>2]))&A-16843009&-2139062144););if(!(255&A))return r-e|0;for(;A=b[r+1|0],r=t=r+1|0,A;);}return t-e|0}function Ge(e,t,r,A,n,a){var c,f;return y=c=y-32|0,e=kt(e,32),f=o[t+4>>2],o[c+24>>2]=o[t>>2],o[c+28>>2]=f,t=o[r>>2],r=o[A+4>>2],o[c+16>>2]=o[A>>2],o[c+20>>2]=r,r=b[0|n],A=b[0|a],n=o[c+28>>2],o[c+8>>2]=o[c+24>>2],o[c+12>>2]=n,n=o[c+20>>2],o[c>>2]=o[c+16>>2],o[c+4>>2]=n,e=function(e,t,r,A,n,a){var c=0;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=56,o[e>>2]=5508,o[e>>2]=11140,c=o[t+4>>2],t=o[t>>2],o[e+16>>2]=r,o[e+8>>2]=t,o[e+12>>2]=c,t=o[A+4>>2],r=o[A>>2],i[e+29|0]=a,i[e+28|0]=n,o[e+20>>2]=r,o[e+24>>2]=t,e}(e,c+8|0,t,c,r,A),y=c+32|0,e}function Ne(e,t){var r=0,A=0,n=0;o[t>>2]=0;e:if(!(A=(((r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24)-48&255)>>>0>9))for(;;){if((((r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24)-48&255)>>>0>9)break e;o[t>>2]=p(n,10),n=(tr(e)+o[t>>2]|0)-48|0,o[t>>2]=n}return A}function He(e){var t,r=0;y=t=y-16|0;e:if(ir(e,104)){if(r=1,et(t+8|0,e,1),o[t+8>>2]==o[t+12>>2])break e;r=1^ir(e,95)}else r=1,ir(e,118)&&(et(t+8|0,e,1),o[t+8>>2]!=o[t+12>>2]&&ir(e,95)&&(et(t,e,1),o[t>>2]!=o[t+4>>2]&&(r=1^ir(e,95))));return y=t+16|0,r}function We(e){var t,r=0;y=t=y-16|0;e:{t:{if(68!=(0|(r=(r=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24))){if(84!=(255&r))break t;if(r=Te(e),o[t+12>>2]=r,!r)break e;Gt(e+148|0,t+12|0);break e}if(r=lt(e),o[t+8>>2]=r,!r)break e;Gt(e+148|0,t+8|0);break e}r=ae(e)}return y=t+16|0,r}function Ye(e,t){var r,A,n,i=0,a=0;return y=r=y-16|0,o[r+12>>2]=0,o[r+8>>2]=o[e+4>>2]-o[e>>2],y=a=y-16|0,y=a+16|0,a=-1,n=r+12|0,(A=o[e+4>>2]-o[e>>2]|0)>>>0<=(i=o[(d[(i=r+8|0)>>2]<d[n>>2]?i:n)>>2])>>>0||(t=me(i+o[e>>2]|0,t,A-i|0))&&(a=t-o[e>>2]|0),y=r+16|0,a}function Ve(e,t){t|=0;var r,A,n=0;y=r=y-16|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=dr(r+8|0,5904),A=o[n+4>>2],o[r>>2]=o[n>>2],o[r+4>>2]=A,Kt(t,r),e=o[e+12>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0}function Le(e,t,r){var A=0,n=0,a=0;if(t>>>0<1)A=e;else for(;A=qr(e,t,10),a=n=D,n=Qr(A,n,10,0),i[0|(r=r-1|0)]=e-n|48,n=t>>>0>9,e=A,t=a,n;);if(A)for(;e=(A>>>0)/10|0,i[0|(r=r-1|0)]=A-p(e,10)|48,t=A>>>0>9,A=e,t;);return r}function ze(e,t){var r,A=0,n=0;y=r=y-16|0,o[r+12>>2]=0,!Ne(t,r+12|0)&&(A=o[r+12>>2])>>>0<=o[t+4>>2]-o[t>>2]>>>0?(n=o[t>>2],o[r+4>>2]=n+A,o[r>>2]=n,o[t>>2]=o[t>>2]+A,t=o[r+4>>2],o[e>>2]=o[r>>2],o[e+4>>2]=t):(o[e>>2]=0,o[e+4>>2]=0),y=r+16|0}function Je(e){var t,r=0;return d[e+20>>2]<=d[e+28>>2]||(aA[o[e+36>>2]](e,0,0),o[e+20>>2])?((r=o[e+4>>2])>>>0<(t=o[e+8>>2])>>>0&&(r=r-t|0,aA[o[e+40>>2]](e,r,r>>31,1)),o[e+28>>2]=0,o[e+16>>2]=0,o[e+20>>2]=0,o[e+4>>2]=0,o[e+8>>2]=0,0):-1}function Ke(e,t){var r;return e=Ht(e),o[t>>2]==(t+12|0)?(jr(o[t>>2],o[t+4>>2],o[e>>2]),o[e+4>>2]=o[e>>2]+(o[t+4>>2]-o[t>>2]>>2<<2),o[t+4>>2]=o[t>>2],e):(o[e>>2]=o[t>>2],o[e+4>>2]=o[t+4>>2],o[e+8>>2]=o[t+8>>2],o[t+8>>2]=t+28,r=t+12|0,o[t+4>>2]=r,o[t>>2]=r,e)}function qe(e){var t=0,r=0;return e=o[e>>2],!(t=o[e+4>>2]-o[e>>2]|0)||(255&(e=((e=(0|(r=o[e>>2]))!=o[e+4>>2]?i[0|r]:0)<<24>>24)-46|0))>>>0>49?!t:(t=31&e,1&((63&e)>>>0>=32?131072>>>t|0:((1<<t)-1&131072)<<32-t|8388609>>>t))}function $e(e,t){var r,A,n=0;if(u(+e),n=0|c(1),r=0|c(0),A=n,2047!=(0|(n=n>>>20&2047))){if(!n)return n=t,0==e?t=0:(e=$e(0x10000000000000000*e,t),t=o[t>>2]+-64|0),o[n>>2]=t,e;o[t>>2]=n-1022,f(0,0|r),f(1,-2146435073&A|1071644672),e=+s()}return e}function et(e,t,r){var A;if(A=o[t>>2],r&&ir(t,110),o[t+4>>2]!=o[t>>2]&&(r=o[t>>2],!(i[0|r]-48>>>0>=10))){for(;!(o[t+4>>2]==o[t>>2]|i[0|r]-48>>>0>9);)r=r+1|0,o[t>>2]=r;return o[e+4>>2]=r,void(o[e>>2]=A)}o[e>>2]=0,o[e+4>>2]=0}function tt(e,t){var r,A=0;return y=r=y-16|0,o[r+12>>2]=t,t?(A=o[e>>2],o[A>>2]?(t=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=23,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=5808,e}(kt(o[e+4>>2]+416|0,16),o[A>>2],o[r+12>>2]),o[o[e>>2]>>2]=t):o[A>>2]=t,(t=o[o[e+8>>2]>>2])&&(i[t+1|0]=0),e=0!=o[o[e>>2]>>2]):e=0,y=r+16|0,e}function rt(e,t){var r,A,n=0,i=0;return y=r=y-32|0,i=o[e>>2],o[4+(n=r+24|0)>>2]=o[e+4>>2],o[n>>2]=i,i=o[t+4>>2],A=o[t>>2],o[r+8>>2]=A,o[r+12>>2]=i,o[r+16>>2]=A,o[r+20>>2]=i,(n=$t(n,r+8|0))&&(o[e>>2]=o[e>>2]+(o[t+4>>2]-o[t>>2]|0)),y=r+32|0,n}function At(e,t,r){var A,n,a,c;return y=A=y-32|0,c=kt(e,24),n=o[t+4>>2],t=o[t>>2],o[A+24>>2]=t,o[A+28>>2]=n,a=o[r+4>>2],e=o[r>>2],o[A+16>>2]=e,o[A+20>>2]=a,o[A+8>>2]=t,o[A+12>>2]=n,o[A>>2]=e,o[A+4>>2]=a,e=function(e,t,r){var A=0;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=69,o[e>>2]=5508,o[e>>2]=6416,A=o[t+4>>2],o[e+8>>2]=o[t>>2],o[e+12>>2]=A,t=o[r+4>>2],o[e+16>>2]=o[r>>2],o[e+20>>2]=t,e}(c,A+8|0,A),y=A+32|0,e}function nt(e,t){var r=0,A=0;return e?t>>>0>=4294967232?(o[4041]=48,0):(r=function(e,t){var r,A,n=0,i=0,a=0,c=0,f=0,s=0,u=0,b=0,l=0;r=(i=-8&(A=o[e+4>>2]))+e|0;e:{if(!(n=3&A)){if(n=0,t>>>0<256)break e;if(i>>>0>=t+4>>>0&&(n=e,i-t>>>0<=o[4162]<<1>>>0))break e;return 0}t:if(t>>>0<=i>>>0){if((n=i-t|0)>>>0<16)break t;o[e+4>>2]=1&A|t|2,o[4+(t=e+t|0)>>2]=3|n,o[r+4>>2]=1|o[r+4>>2],H(t,n)}else if(n=0,o[4048]!=(0|r))if(o[4047]!=(0|r)){if(2&(a=o[r+4>>2]))break e;if((c=i+(-8&a)|0)>>>0<t>>>0)break e;s=c-t|0;r:if(a>>>0<=255){if(n=o[r+8>>2],a=a>>>3|0,(0|(i=o[r+12>>2]))==(0|n)){b=16168,l=o[4042]&Er(a),o[b>>2]=l;break r}o[n+12>>2]=i,o[i+8>>2]=n}else{if(f=o[r+24>>2],(0|r)==(0|(i=o[r+12>>2])))if((a=o[(n=r+20|0)>>2])||(a=o[(n=r+16|0)>>2])){for(;u=n,(a=o[(n=(i=a)+20|0)>>2])||(n=i+16|0,a=o[i+16>>2]););o[u>>2]=0}else i=0;else n=o[r+8>>2],o[n+12>>2]=i,o[i+8>>2]=n;if(f){n=o[r+28>>2];A:{if(o[(a=16472+(n<<2)|0)>>2]==(0|r)){if(o[a>>2]=i,i)break A;b=16172,l=o[4043]&Er(n),o[b>>2]=l;break r}if(o[f+(o[f+16>>2]==(0|r)?16:20)>>2]=i,!i)break r}o[i+24>>2]=f,(n=o[r+16>>2])&&(o[i+16>>2]=n,o[n+24>>2]=i),(n=o[r+20>>2])&&(o[i+20>>2]=n,o[n+24>>2]=i)}}s>>>0<=15?(o[e+4>>2]=1&A|c|2,o[4+(t=e+c|0)>>2]=1|o[t+4>>2]):(o[e+4>>2]=1&A|t|2,o[4+(t=e+t|0)>>2]=3|s,o[4+(n=e+c|0)>>2]=1|o[n+4>>2],H(t,s))}else{if((i=i+o[4044]|0)>>>0<t>>>0)break e;(n=i-t|0)>>>0>=16?(o[e+4>>2]=1&A|t|2,o[4+(t=e+t|0)>>2]=1|n,o[(i=e+i|0)>>2]=n,o[i+4>>2]=-2&o[i+4>>2]):(o[e+4>>2]=i|1&A|2,o[4+(t=e+i|0)>>2]=1|o[t+4>>2],n=0,t=0),o[4047]=t,o[4044]=n}else{if((i=i+o[4045]|0)>>>0<=t>>>0)break e;o[e+4>>2]=1&A|t|2,n=e+t|0,t=i-t|0,o[n+4>>2]=1|t,o[4045]=t,o[4048]=n}n=e}return n}(e-8|0,t>>>0<11?16:t+11&-8))?r+8|0:(r=M(t))?(re(r,e,t>>>0>(A=(3&(A=o[e-4>>2])?-4:-8)+(-8&A)|0)>>>0?A:t),N(e),r):0:M(t)}function it(e){var t,r=0,A=0;return y=t=y-16|0,r=Ce(e),o[t+12>>2]=r,r?73==(255&(A=(0|(A=o[e>>2]))!=o[e+4>>2]?b[0|A]:0))&&(r=_e(e,0),o[t+8>>2]=r,r=r?Or(e,t+12|0,t+8|0):0):r=0,y=t+16|0,r}function at(e,t){var r,A=0,n=0;y=r=y-16|0,o[r+12>>2]=t;e:{t:{for(;;){if(!ir(e,66))break t;if(ze(r,e),o[r>>2]==o[r+4>>2])break;A=r,n=Rt(e+416|0,r+12|0,r),o[A+12>>2]=n}e=0;break e}e=o[r+12>>2]}return y=r+16|0,e}function ot(e,t){var r,A;return y=r=y-16|0,o[r+12>>2]=t,e=o[e>>2],t=o[396+(A=e+(t<<2)|0)>>2],o[A+396>>2]=t+1,o[r+8>>2]=t,t=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=27,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=7276,e}(kt(e+416|0,16),o[r+12>>2],o[r+8>>2]),o[r+4>>2]=t,Gt(o[o[e+336>>2]-4>>2],r+4|0),y=r+16|0,t}function ct(e,t,r){var A,n,a;return y=A=y-32|0,n=kt(e,28),e=dr(A+24|0,t),r=o[r>>2],t=dr(A+16|0,6042),a=o[e+4>>2],o[A+8>>2]=o[e>>2],o[A+12>>2]=a,e=o[t+4>>2],o[A>>2]=o[t>>2],o[A+4>>2]=e,e=function(e,t,r,A){var n=0;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=52,o[e>>2]=5508,o[e>>2]=11256,n=o[t+4>>2],t=o[t>>2],o[e+16>>2]=r,o[e+8>>2]=t,o[e+12>>2]=n,t=o[A+4>>2],o[e+20>>2]=o[A>>2],o[e+24>>2]=t,e}(n,A+8|0,r,A),y=A+32|0,e}function ft(e,t,r,A,n,a,c){var f,s;return y=f=y-16|0,s=kt(e,36),r=o[r>>2],t=o[t>>2],e=o[A+4>>2],A=o[A>>2],o[f+8>>2]=A,o[f+12>>2]=e,c=b[0|c],a=o[a>>2],n=o[n>>2],o[f>>2]=A,o[f+4>>2]=e,e=function(e,t,r,A,n,a,c){return i[e+7|0]=0,i[e+6|0]=1,i[e+5|0]=0,i[e+4|0]=18,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=13740,t=o[A+4>>2],r=o[A>>2],i[e+32|0]=c,o[e+28>>2]=a,o[e+24>>2]=n,o[e+16>>2]=r,o[e+20>>2]=t,e}(s,t,r,f,n,a,c),y=f+16|0,e}function st(e,t,r){var A=0;for(o[e>>2]=o[t+12>>2],o[e+4>>2]=o[t+8>>2],t=o[e+4>>2];A=0|aA[o[o[t>>2]+12>>2]](t,r),12==b[A+4|0];)t=o[A+8>>2],o[e+4>>2]=t,A=A+12|0,o[e>>2]=o[(o[A>>2]<o[e>>2]?A:e)>>2]}function ut(e,t,r){var A;if(!(A=o[e+16>>2]))return o[e+36>>2]=1,o[e+24>>2]=r,void(o[e+16>>2]=t);e:{if((0|t)==(0|A)){if(2!=o[e+24>>2])break e;return void(o[e+24>>2]=r)}i[e+54|0]=1,o[e+24>>2]=2,o[e+36>>2]=o[e+36>>2]+1}}function bt(e,t,r,A,n,a){var c,f;return y=c=y-16|0,f=kt(e,32),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[c+8>>2]=r,o[c+12>>2]=e,a=o[a>>2],n=b[0|n],A=o[A>>2],o[c>>2]=r,o[c+4>>2]=e,e=function(e,t,r,A,n,a){return i[e+7|0]=0,i[e+6|0]=1,i[e+5|0]=0,i[e+4|0]=15,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=14492,t=o[r+4>>2],r=o[r>>2],o[e+28>>2]=a,i[e+24|0]=n,o[e+20>>2]=A,o[e+12>>2]=r,o[e+16>>2]=t,e}(f,t,c,A,n,a),y=c+16|0,e}function lt(e){var t,r=0,A=0;return y=t=y-16|0,ir(e,68)&&(ir(e,116)||ir(e,84))&&(r=v(e),o[t+12>>2]=r,r&&ir(e,69)&&(A=Zr(e,6032,t+12|0))),y=t+16|0,A}function dt(e,t,r,A,n){var i;if(y=i=y-256|0,!(73728&n|(0|r)<=(0|A))){if(ke(i,255&t,(A=(r=r-A|0)>>>0<256)?r:256),!A)for(;Ir(e,i,256),(r=r-256|0)>>>0>255;);Ir(e,i,r)}y=i+256|0}function kt(e,t){var r=0,A=0,n=0;if(r=o[e+4096>>2],(n=(A=o[r+4>>2])+(t=t+15&-16)|0)>>>0>=4088){if(t>>>0>=4089)return function(e,t){var r=0;return(t=M(t+8|0))||(aA[o[3983]](),h()),e=o[e+4096>>2],r=o[e>>2],o[t+4>>2]=0,o[t>>2]=r,o[e>>2]=t,t+8|0}(e,t);!function(e){var t=0,r=0;(t=M(4096))||(aA[o[3983]](),h()),r=o[e+4096>>2],o[t+4>>2]=0,o[t>>2]=r,o[e+4096>>2]=t}(e),r=o[e+4096>>2],n=t+(A=o[r+4>>2])|0}return o[r+4>>2]=n,8+(r+A|0)|0}function Et(e){var t,r,A=0;y=t=y-16|0,A=o[e+4>>2],kr(o[e>>2],40),A=Ar(t,o[A+8>>2]),r=o[e>>2],aA[o[o[A>>2]+16>>2]](A,r),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,r),kr(o[e>>2],41),y=t+16|0}function pt(e){var t;return y=t=y-16|0,o[t+12>>2]=0,ir(e,114)&&(o[t+12>>2]=4|o[t+12>>2]),ir(e,86)&&(o[t+12>>2]=2|o[t+12>>2]),ir(e,75)&&(o[t+12>>2]=1|o[t+12>>2]),y=t+16|0,o[t+12>>2]}function Tt(e){var t;return o[e+12>>2]=0,o[e+16>>2]=0,o[e+8>>2]=e+44,t=e+12|0,o[e+4>>2]=t,o[e>>2]=t,o[e+20>>2]=0,o[e+24>>2]=0,o[e+28>>2]=0,o[e+32>>2]=0,o[e+36>>2]=0,o[e+40>>2]=0,e}function gt(e){var t,r;return(e=(t=o[3982])+(r=e+3&-4)|0)>>>0<=t>>>0&&(0|r)>=1||e>>>0>oA()<<16>>>0&&!(0|O(0|e))?(o[4041]=48,-1):(o[3982]=e,t)}function ht(e,t){var r=0;(t=o[e+4>>2]+t|0)>>>0<(r=o[e+8>>2])>>>0||(t=t>>>0>(r<<=1)>>>0?t:r,o[e+8>>2]=t,r=e,e=nt(o[e>>2],t),o[r>>2]=e,e||(aA[o[3983]](),h()))}function Ot(e,t,r,A){var n,a;return y=n=y-16|0,a=kt(e,24),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[n+8>>2]=r,o[n+12>>2]=e,A=o[A>>2],o[n>>2]=r,o[n+4>>2]=e,e=function(e,t,r,A){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=47,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=8708,t=o[r+4>>2],r=o[r>>2],o[e+20>>2]=A,o[e+12>>2]=r,o[e+16>>2]=t,e}(a,t,n,A),y=n+16|0,e}function _t(e,t){var r,A=0;return y=r=y-16|0,A=v(e),o[r+12>>2]=A,e=A&&(A=v(e),o[r+8>>2]=A,A)?Ot(e+416|0,r+12|0,t,r+8|0):0,y=r+16|0,e}function wt(e){var t=0,r=0,A=0;if(i[o[e>>2]]-48>>>0<10)for(;t=o[e>>2],A=i[0|t],o[e>>2]=t+1,r=(p(r,10)+A|0)-48|0,i[t+1|0]-48>>>0<10;);return r}function yt(e,t,r){var A,n;return y=A=y-16|0,n=kt(e,20),e=o[t+4>>2],t=o[t>>2],o[A+8>>2]=t,o[A+12>>2]=e,r=o[r>>2],o[A>>2]=t,o[A+4>>2]=e,e=function(e,t,r){var A;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=58,o[e>>2]=5508,o[e>>2]=8820,A=o[t+4>>2],t=o[t>>2],o[e+16>>2]=r,o[e+8>>2]=t,o[e+12>>2]=A,e}(n,A,r),y=A+16|0,e}function Rt(e,t,r){var A,n;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){var A,n;return A=b[t+5|0],n=b[t+6|0],i[e+7|0]=b[t+7|0],i[e+6|0]=n,i[e+5|0]=A,i[e+4|0]=8,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=12496,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}function Dt(e,t,r){var A,n;return y=A=y-16|0,n=kt(e,20),t=o[t>>2],e=o[r+4>>2],r=o[r>>2],o[A>>2]=r,o[A+4>>2]=e,o[A+8>>2]=r,o[A+12>>2]=e,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=60,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=9132,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(n,t,A),y=A+16|0,e}function mt(e,t,r,A){var n,a;return y=n=y-16|0,a=kt(e,24),t=o[t>>2],e=dr(n+8|0,r),r=o[A>>2],A=o[e+4>>2],o[n>>2]=o[e>>2],o[n+4>>2]=A,e=function(e,t,r,A){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=51,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=10468,t=o[r+4>>2],r=o[r>>2],o[e+20>>2]=A,o[e+12>>2]=r,o[e+16>>2]=t,e}(a,t,n,r),y=n+16|0,e}function vt(e,t,r,A){var n,a;return y=n=y-16|0,a=kt(e,24),e=dr(n+8|0,t),t=o[A>>2],r=o[r>>2],A=o[e+4>>2],o[n>>2]=o[e>>2],o[n+4>>2]=A,e=function(e,t,r,A){var n;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=53,o[e>>2]=5508,o[e>>2]=8924,n=o[t+4>>2],t=o[t>>2],o[e+20>>2]=A,o[e+16>>2]=r,o[e+8>>2]=t,o[e+12>>2]=n,e}(a,n,r,t),y=n+16|0,e}function Mt(e,t){var r,A,n;return y=r=y-16|0,o[e>>2]=t,t=t+332|0,o[e+4>>2]=o[t+4>>2]-o[t>>2]>>2,A=r,n=Tt(e+8|0),o[A+12>>2]=n,Gt(t,r+12|0),y=r+16|0,e}function Ft(e){var t,r=0;return y=t=y-16|0,e=o[e+8>>2],7==b[e+4|0]&&(r=o[e+12>>2],o[t+8>>2]=o[e+8>>2],o[t+12>>2]=r,r=qt(t+8|0,dr(t,15644))),y=t+16|0,r}function St(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=61,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=10916,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}function Pt(e,t){var r,A=0;return y=r=y-16|0,et(r+8|0,e,1),o[r+8>>2]!=o[r+12>>2]&&ir(e,69)&&(A=At(e+416|0,t,r+8|0)),y=r+16|0,A}function Bt(e,t){var r,A;return y=r=y-16|0,A=kt(e,16),e=o[t+4>>2],t=o[t>>2],o[r>>2]=t,o[r+4>>2]=e,o[r+8>>2]=t,o[r+12>>2]=e,e=Ue(A,r),y=r+16|0,e}function Xt(e,t,r){var A,n;return y=A=y-16|0,n=kt(e,20),e=dr(A+8|0,t),t=o[r>>2],r=o[e+4>>2],o[A>>2]=o[e>>2],o[A+4>>2]=r,e=function(e,t,r){var A;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=20,o[e>>2]=5508,o[e>>2]=5356,A=o[t+4>>2],t=o[t>>2],o[e+16>>2]=r,o[e+8>>2]=t,o[e+12>>2]=A,e}(n,A,t),y=A+16|0,e}function Ut(e,t,r){var A,n;return y=A=y-16|0,e=kt(e,20),t=o[t>>2],r=dr(A+8|0,r),n=o[r+4>>2],o[A>>2]=o[r>>2],o[A+4>>2]=n,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=49,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=11024,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(e,t,A),y=A+16|0,e}function It(e,t,r){var A,n;return y=A=y-16|0,e=kt(e,20),t=o[t>>2],r=dr(A+8|0,r),n=o[r+4>>2],o[A>>2]=o[r>>2],o[A+4>>2]=n,e=function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=5,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=15772,t=o[r+4>>2],o[e+12>>2]=o[r>>2],o[e+16>>2]=t,e}(e,t,A),y=A+16|0,e}function xt(e,t){var r;return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=7,o[e>>2]=5508,o[e>>2]=5708,r=o[t+4>>2],o[e+8>>2]=o[t>>2],o[e+12>>2]=r,e}function jt(e,t,r){var A;return y=A=y-16|0,o[A+12>>2]=r,o[e>>2]=t,t=o[t>>2],i[e+8|0]=1,o[e+4>>2]=t,o[o[e>>2]>>2]=o[A+12>>2],y=A+16|0,e}function Qt(e,t){var r;return y=r=y-16|0,o[r+12>>2]=-1,o[e>>2]=t,t=o[t>>2],i[e+8|0]=1,o[e+4>>2]=t,o[o[e>>2]>>2]=o[r+12>>2],y=r+16|0,e}function Ct(e,t){var r,A;return y=r=y-16|0,e=kt(e,16),t=dr(r+8|0,t),A=o[t+4>>2],o[r>>2]=o[t>>2],o[r+4>>2]=A,e=xt(e,r),y=r+16|0,e}function Zt(e,t,r,A){if(e|t)for(;i[0|(r=r-1|0)]=b[4544+(15&e)|0]|A,(e=(15&t)<<28|e>>>4)|(t=t>>>4|0););return r}function Gt(e,t){var r=0;(0|(r=o[e+4>>2]))==o[e+8>>2]&&(function(e,t){var r,A=0;r=o[e+4>>2]-o[e>>2]>>2;e:{if(o[e>>2]!=(e+12|0)){if(A=nt(o[e>>2],t<<2),o[e>>2]=A,!A)break e}else{if(!(A=M(t<<2)))break e;jr(o[e>>2],o[e+4>>2],A),o[e>>2]=A}return o[e+8>>2]=(t<<2)+A,void(o[e+4>>2]=(r<<2)+A)}aA[o[3983]](),h()}(e,o[e+4>>2]-o[e>>2]>>2<<1),r=o[e+4>>2]),t=o[t>>2],o[e+4>>2]=r+4,o[r>>2]=t}function Nt(e,t,r){var A;return y=A=y-16|0,i[A+15|0]=r,o[e>>2]=t,t=b[0|t],i[e+5|0]=1,i[e+4|0]=t,i[o[e>>2]]=b[A+15|0],y=A+16|0,e}function Ht(e){var t;return o[e+12>>2]=0,o[e+16>>2]=0,o[e+8>>2]=e+28,t=e+12|0,o[e+4>>2]=t,o[e>>2]=t,o[e+20>>2]=0,o[e+24>>2]=0,e}function Wt(e,t){var r,A;y=r=y-16|0,A=o[t+4>>2],t=o[t>>2],o[r>>2]=t,o[r+4>>2]=A,o[r+8>>2]=t,o[r+12>>2]=A,Kt(e,r),y=r+16|0}function Yt(e,t){var r;return t|=0,e=o[12+(e|=0)>>2],0|(e=2==(0|(r=b[e+5|0]))?0|aA[o[o[e>>2]>>2]](e,t):!r)}function Vt(e,t){var r;return t|=0,e=o[8+(e|=0)>>2],0|(e=2==(0|(r=b[e+5|0]))?0|aA[o[o[e>>2]>>2]](e,t):!r)}function Lt(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=25,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=15060,e}function zt(e,t,r){if(e|t)for(;i[0|(r=r-1|0)]=7&e|48,(e=(7&t)<<29|e>>>3)|(t=t>>>3|0););return r}function Jt(e,t){var r,A;return y=r=y-16|0,A=v(e),o[r+12>>2]=A,e=A?yt(e+416|0,t,r+12|0):0,y=r+16|0,e}function Kt(e,t){var r;(r=o[t+4>>2]-o[t>>2]|0)&&(ht(e,r),le(o[e>>2]+o[e+4>>2]|0,o[t>>2],r),o[e+4>>2]=o[e+4>>2]+r)}function qt(e,t){return e=(o[e+4>>2]-o[e>>2]|0)==(o[t+4>>2]-o[t>>2]|0)?Kr(o[e>>2],o[e+4>>2],o[t>>2]):0}function $t(e,t){return e=o[t+4>>2]-o[t>>2]>>>0<=o[e+4>>2]-o[e>>2]>>>0?Kr(o[t>>2],o[t+4>>2],o[e>>2]):0}function er(e,t,r){return r?(0|e)==(0|t)?1:!function(e,t){var r=0,A=0;e:if(!(!(r=b[0|e])|(0|r)!=(0|(A=b[0|t]))))for(;;){if(A=b[t+1|0],!(r=b[e+1|0]))break e;if(t=t+1|0,e=e+1|0,(0|r)!=(0|A))break}return r-A|0}(o[e+4>>2],o[t+4>>2]):o[e+4>>2]==o[t+4>>2]}function tr(e){var t,r=0;return r=0,(0|(t=o[e>>2]))!=o[e+4>>2]&&(o[e>>2]=t+1,r=b[0|t]),r<<24>>24}function rr(e,t,r){var A;A=o[t+4>>2]-o[t>>2]|0,r=o[t>>2]+(r>>>0<A>>>0?r:A)|0,o[e+4>>2]=o[t+4>>2],o[e>>2]=r}function Ar(e,t){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=34,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=8592,e}function nr(e,t){t|=0,e=o[8+(e|=0)>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t)}function ir(e,t){var r,A=0;return(0|(r=o[e>>2]))==o[e+4>>2]|i[0|r]!=(255&t)||(o[e>>2]=r+1,A=1),A}function ar(e,t,r,A){jr(r,A,r=kt(t+416|0,(t=A-r>>2)<<2)),o[e+4>>2]=t,o[e>>2]=r}function or(e,t,r){ar(e,t,o[(e=t+8|0)>>2]+(r<<2)|0,o[e+4>>2]),o[e+4>>2]=o[e>>2]+(r<<2)}function cr(e,t){var r;y=r=y-16|0,i[r+15|0]=b[0|e],i[0|e]=b[0|t],i[0|t]=b[r+15|0],y=r+16|0}function fr(e,t,r,A){var n;return y=n=y-16|0,o[n+12>>2]=A,e=Qe(e,t,r,A),y=n+16|0,e}function sr(e){var t;t=o[e>>2],o[t+336>>2]=o[t+332>>2]+(o[e+4>>2]<<2),Xr(e+8|0)}function ur(e){var t;o[e+8>>2]=e+140,t=e+12|0,o[e+4>>2]=t,o[e>>2]=t,ke(t,0,128)}function br(e,t){var r;r=e|=0,e=o[12+(t|=0)>>2],aA[o[o[e>>2]+24>>2]](r,e)}function lr(e,t){var r;r=e|=0,e=o[8+(t|=0)>>2],aA[o[o[e>>2]+24>>2]](r,e)}function dr(e,t){var r,A;return o[e>>2]=t,r=e,A=Ze(t)+t|0,o[r+4>>2]=A,e}function kr(e,t){var r;ht(e,1),r=o[e+4>>2],o[e+4>>2]=r+1,i[o[e>>2]+r|0]=t}function Er(e){var t;return(-1>>>(t=31&e)&-2)<<t|(-1<<(e=0-e&31)&-2)>>>e}function pr(e,t,r,A){return function(e,t,r,A){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=42,o[e>>2]=5508,o[e+16>>2]=A,i[e+12|0]=r,o[e+8>>2]=t,o[e>>2]=12388,e}(kt(e+416|0,20),o[t>>2],b[0|r],o[A>>2])}function Tr(e,t,r,A){return function(e,t,r,A){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=73,o[e>>2]=5508,i[e+16|0]=A,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=10692,e}(kt(e+416|0,20),o[t>>2],o[r>>2],b[0|A])}function gr(e,t,r,A){return function(e,t,r,A){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=57,o[e>>2]=5508,i[e+13|0]=A,i[e+12|0]=r,o[e+8>>2]=t,o[e>>2]=9244,e}(kt(e+416|0,16),o[t>>2],b[0|r],b[0|A])}function hr(e,t,r){return function(e,t,r){var A;return A=b[t+5|0],i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=A,i[e+4|0]=12,o[e>>2]=5508,i[e+16|0]=0,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=15664,e}(kt(e+416|0,20),o[t>>2],o[r>>2])}function Or(e,t,r){return function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=37,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=13260,e}(kt(e+416|0,16),o[t>>2],o[r>>2])}function _r(e,t,r){return function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=22,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=9400,e}(kt(e+416|0,16),o[t>>2],o[r>>2])}function wr(e,t,r){return Lt(kt(e+416|0,16),o[t>>2],o[r>>2])}function yr(e,t,r){return function(e,t,r){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=24,o[e>>2]=5508,o[e+12>>2]=r,o[e+8>>2]=t,o[e>>2]=12744,e}(kt(e+416|0,16),o[t>>2],o[r>>2])}function Rr(e,t,r,A,n,i,a){return ft(e+416|0,t,r,A,n,i,a)}function Dr(e,t,r,A,n,i){return Ge(e+416|0,t,r,A,n,i)}function mr(e,t){return function(e,t){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=65,o[e>>2]=5508,i[e+8|0]=t,o[e>>2]=6528,e}(kt(e+416|0,12),0!=o[t>>2])}function vr(e){return e?31-g(e-1^e)|0:32}function Mr(e,t){return Ar(kt(e+416|0,12),o[t>>2])}function Fr(e,t){return function(e,t){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=41,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=12848,e}(kt(e+416|0,12),o[t>>2])}function Sr(e,t){return function(e,t){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=64,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=11828,e}(kt(e+416|0,12),o[t>>2])}function Pr(e,t){return function(e,t){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=4,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=10108,e}(kt(e+416|0,12),o[t>>2])}function Br(e,t){return function(e,t){return i[e+7|0]=1,i[e+6|0]=1,i[e+5|0]=1,i[e+4|0]=38,o[e>>2]=5508,o[e+8>>2]=t,o[e>>2]=10356,e}(kt(e+416|0,12),o[t>>2])}function Xr(e){o[e>>2]!=(e+12|0)&&N(o[e>>2])}function Ur(e,t){return e?function(e,t){e:{if(e){if(t>>>0<=127)break e;if(o[o[4027]>>2]){if(t>>>0<=2047)return i[e+1|0]=63&t|128,i[0|e]=t>>>6|192,2;if(!(57344!=(-8192&t)&&t>>>0>=55296))return i[e+2|0]=63&t|128,i[0|e]=t>>>12|224,i[e+1|0]=t>>>6&63|128,3;if(t-65536>>>0<=1048575)return i[e+3|0]=63&t|128,i[0|e]=t>>>18|240,i[e+2|0]=t>>>6&63|128,i[e+1|0]=t>>>12&63|128,4}else if(57216==(-128&t))break e;o[4041]=25,e=-1}else e=1;return e}return i[0|e]=t,1}(e,t):0}function Ir(e,t,r){32&b[0|e]||Ie(t,r,e)}function xr(e,t,r,A){return mt(e+416|0,t,r,A)}function jr(e,t,r){(t=t-e|0)&&le(r,e,t)}function Qr(e,t,r,A){return e=function(e,t,r,A){var n,i,a,o,c=0,f=0;return o=p(c=r>>>16|0,f=e>>>16|0),c=(65535&(f=((a=p(n=65535&r,i=65535&e))>>>16|0)+p(f,n)|0))+p(c,i)|0,e=(p(t,r)+o|0)+p(e,A)+(f>>>16)+(c>>>16)|0,D=e,65535&a|c<<16}(e,t,r,A)}function Cr(e,t){return ct(e+416|0,6232,t)}function Zr(e,t,r){return ct(e+416|0,t,r)}function Gr(e,t,r){return Dt(e+416|0,t,r)}function Nr(e,t,r){return Xt(e+416|0,t,r)}function Hr(e,t,r){return Ut(e+416|0,t,r)}function Wr(e,t){xe(8+(e|=0)|0,t|=0)}function Yr(e,t){return function(e,t){var r,A;return y=r=y-16|0,A=kt(e,16),e=o[t+4>>2],t=o[t>>2],o[r>>2]=t,o[r+4>>2]=e,o[r+8>>2]=t,o[r+12>>2]=e,e=xt(A,r),y=r+16|0,e}(e+416|0,t)}function Vr(e,t){return Ct(e+416|0,t)}function Lr(e,t){return 1}function zr(e,t){return function(e,t){var r,A,n;return y=A=y-16|0,r=kt(e,16),n=o[t+4>>2],e=o[t>>2],o[A>>2]=e,o[A+4>>2]=n,o[A+8>>2]=e,o[A+12>>2]=n,i[r+7|0]=1,i[r+6|0]=1,i[r+5|0]=1,i[r+4|0]=59,o[r>>2]=5508,o[r>>2]=8276,e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,y=A+16|0,r}(e+416|0,t)}function Jr(e,t){return 0}function Kr(e,t,r){return function(e,t,r){var A,n=0;for(y=A=y-16|0;!((n=(0|e)==(0|t))|b[0|e]!=b[0|r]);)r=r+1|0,e=e+1|0;return y=A+16|0,n}(e,t,r)}function qr(e,t,r){return function(e,t,r){var A=0,n=0,i=0,a=0,o=0,c=0,f=0,s=0,u=0;e:{t:{r:{A:{n:{i:{a:{o:{c:{if(A=t){if(!(n=r))break c;break o}return D=0,(e>>>0)/(r>>>0)|0}if(!e)break a;break i}if(!(n-1&n))break n;c=0-(o=(g(n)+33|0)-g(A)|0)|0;break r}return D=0,(A>>>0)/0|0}if((A=32-g(A)|0)>>>0<31)break A;break t}if(1==(0|n))break e;return r=31&(A=vr(n)),(63&A)>>>0>=32?(n=0,e=t>>>r|0):(n=t>>>r|0,e=((1<<r)-1&t)<<32-r|e>>>r),D=n,e}o=A+1|0,c=63-A|0}if(A=t,i=31&(n=63&o),n>>>0>=32?(n=0,i=A>>>i|0):(n=A>>>i|0,i=((1<<i)-1&A)<<32-i|e>>>i),A=31&(c&=63),c>>>0>=32?(t=e<<A,e=0):(t=(1<<A)-1&e>>>32-A|t<<A,e<<=A),o)for(c=-1!=(0|(A=r-1|0))?0:-1;i=(f=a=i<<1|t>>>31)-(s=r&(a=c-((n=n<<1|i>>>31)+(A>>>0<a>>>0)|0)>>31))|0,n=n-(f>>>0<s>>>0)|0,t=t<<1|e>>>31,e=u|e<<1,u=a&=1,o=o-1|0;);return D=t<<1|e>>>31,a|e<<1}e=0,t=0}return D=t,e}(e,t,r)}function $r(e){return 0|(e|=0)}function eA(e){N(e|=0)}function tA(e,t){!function(e,t){e:if((0|e)!=(0|t))for(;;){if((t=t-1|0)>>>0<=e>>>0)break e;cr(e,t),e=e+1|0}}(e,t)}function rA(){R=16752}function AA(e){}function nA(){h()}n(t=b,1024,"RXJyb3Igc3RyaW5ncyBzdHJpcHBlZA=="),n(t,1064,"AQAAAAEAAAACAAAAAg=="),n(t,1088,"AQAAAAEAAAACAAAAAgAAACYAAACCAAAAIQUAAEoAAABnCAAAJgAAAMABAACAAAAASQUAAEoAAAC+CAAAKQAAACwCAACAAAAASQUAAEoAAAC+CAAALwAAAMoCAACAAAAAigUAAEoAAACECQAANQAAAHMDAACAAAAAnQUAAEoAAACgCQAAPQAAAIEDAACAAAAA6wUAAEsAAAA+CgAARAAAAJ4DAACAAAAATQYAAEsAAACqCgAASwAAALMDAACAAAAAwQYAAE0AAAAfDQAATQAAAFMEAACAAAAAIwgAAFEAAACmDwAAVAAAAJkEAACAAAAASwkAAFcAAACxEgAAWAAAANoEAACAAAAAbwkAAF0AAAAjFAAAVAAAAEUFAACAAAAAVAoAAGoAAACMFAAAagAAAK8FAACAAAAAdgkAAHwAAABOEAAAfAAAANICAACAAAAAYwcAAJEAAACQBwAAkgAAAAAAAAABAAAAAQAAAAUAAAANAAAAHQAAAD0AAAB9AAAA/QAAAP0BAAD9AwAA/QcAAP0PAAD9HwAA/T8AAP1/AAD9/wAA/f8BAP3/AwD9/wcA/f8PAP3/HwD9/z8A/f9/AP3//wD9//8B/f//A/3//wf9//8P/f//H/3//z/9//9/AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABMAAAAUAAAAFQAAABYAAAAXAAAAGAAAABkAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABEAAAASAAAAEwAAABQAAAAVAAAAFgAAABcAAAAYAAAAGQAAABoAAAAbAAAAHAAAAB0AAAAeAAAAHwAAACAAAAAhAAAAIgAAACMAAAAlAAAAJwAAACkAAAArAAAALwAAADMAAAA7AAAAQwAAAFMAAABjAAAAgwAAAAMBAAADAgAAAwQAAAMIAAADEAAAAyAAAANAAAADgAAAAwAB"),n(t,2048,"AQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAwAAAAMAAAAEAAAABAAAAAUAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8AAAAQ"),n(t,2148,"AQAAAAIAAAADAAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAACgAAAAsAAAAMAAAADQAAAA4AAAAPAAAAEAAAABIAAAAUAAAAFgAAABgAAAAcAAAAIAAAACgAAAAwAAAAQAAAAIAAAAAAAQAAAAIAAAAEAAAACAAAABAAAAAgAAAAQAAAAIAAAAAAAQ=="),n(t,2352,"AQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAwAAAAMAAAAEAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAABAAAABAAAAAgAAAAAAAAAAQABAQYAAAAAAAAEAAAAABAAAAQAAAAAIAAABQEAAAAAAAAFAwAAAAAAAAUEAAAAAAAABQYAAAAAAAAFBwAAAAAAAAUJAAAAAAAABQoAAAAAAAAFDAAAAAAAAAYOAAAAAAABBRAAAAAAAAEFFAAAAAAAAQUWAAAAAAACBRwAAAAAAAMFIAAAAAAABAUwAAAAIAAGBUAAAAAAAAcFgAAAAAAACAYAAQAAAAAKBgAEAAAAAAwGABAAACAAAAQAAAAAAAAABAEAAAAAAAAFAgAAACAAAAUEAAAAAAAABQUAAAAgAAAFBwAAAAAAAAUIAAAAIAAABQoAAAAAAAAFCwAAAAAAAAYNAAAAIAABBRAAAAAAAAEFEgAAACAAAQUWAAAAAAACBRgAAAAgAAMFIAAAAAAAAwUoAAAAAAAGBEAAAAAQAAYEQAAAACAABwWAAAAAAAAJBgACAAAAAAsGAAgAADAAAAQAAAAAEAAABAEAAAAgAAAFAgAAACAAAAUDAAAAIAAABQUAAAAgAAAFBgAAACAAAAUIAAAAIAAABQkAAAAgAAAFCwAAACAAAAUMAAAAAAAABg8AAAAgAAEFEgAAACAAAQUUAAAAIAACBRgAAAAgAAIFHAAAACAAAwUoAAAAIAAEBTAAAAAAABAGAAABAAAADwYAgAAAAAAOBgBAAAAAAA0GACA="),n(t,2976,"AQABAQUAAAAAAAAFAAAAAAAABgQ9AAAAAAAJBf0BAAAAAA8F/X8AAAAAFQX9/x8AAAADBQUAAAAAAAcEfQAAAAAADAX9DwAAAAASBf3/AwAAABcF/f9/AAAABQUdAAAAAAAIBP0AAAAAAA4F/T8AAAAAFAX9/w8AAAACBQEAAAAQAAcEfQAAAAAACwX9BwAAAAARBf3/AQAAABYF/f8/AAAABAUNAAAAEAAIBP0AAAAAAA0F/R8AAAAAEwX9/wcAAAABBQEAAAAQAAYEPQAAAAAACgX9AwAAAAAQBf3/AAAAABwF/f//DwAAGwX9//8HAAAaBf3//wMAABkF/f//AQAAGAX9//8="),n(t,3248,"AQABAQYAAAAAAAAGAwAAAAAAAAQEAAAAIAAABQUAAAAAAAAFBgAAAAAAAAUIAAAAAAAABQkAAAAAAAAFCwAAAAAAAAYNAAAAAAAABhAAAAAAAAAGEwAAAAAAAAYWAAAAAAAABhkAAAAAAAAGHAAAAAAAAAYfAAAAAAAABiIAAAAAAAEGJQAAAAAAAQYpAAAAAAACBi8AAAAAAAMGOwAAAAAABAZTAAAAAAAHBoMAAAAAAAkGAwIAABAAAAQEAAAAAAAABAUAAAAgAAAFBgAAAAAAAAUHAAAAIAAABQkAAAAAAAAFCgAAAAAAAAYMAAAAAAAABg8AAAAAAAAGEgAAAAAAAAYVAAAAAAAABhgAAAAAAAAGGwAAAAAAAAYeAAAAAAAABiEAAAAAAAEGIwAAAAAAAQYnAAAAAAACBisAAAAAAAMGMwAAAAAABAZDAAAAAAAFBmMAAAAAAAgGAwEAACAAAAQEAAAAMAAABAQAAAAQAAAEBQAAACAAAAUHAAAAIAAABQgAAAAgAAAFCgAAACAAAAULAAAAAAAABg4AAAAAAAAGEQAAAAAAAAYUAAAAAAAABhcAAAAAAAAGGgAAAAAAAAYdAAAAAAAABiAAAAAAABAGAwABAAAADwYDgAAAAAAOBgNAAAAAAA0GAyAAAAAADAYDEAAAAAALBgMIAAAAAAoGAwQ="),n(t,3780,"AQAAAAMAAAAHAAAADwAAAB8AAAA/AAAAfwAAAP8AAAD/AQAA/wMAAP8HAAD/DwAA/x8AAP8/AAD/fwAA//8AAP//AQD//wMA//8HAP//DwD//x8A//8/AP//fwD///8A////Af///wP///8H////D////x////8/////fwAAAAABAAAAAgAAAAQAAAAAAAAAAgAAAAQAAAAIAAAAAAAAAAEAAAACAAAAAQAAAAQAAAAEAAAABAAAAAQAAAAIAAAACAAAAAgAAAAHAAAACAAAAAkAAAAKAAAACwAAAHRlcm1pbmF0aW5nAHRlcm1pbmF0ZV9oYW5kbGVyIHVuZXhwZWN0ZWRseSByZXR1cm5lZAAtKyAgIDBYMHgAKG51bGwp"),n(t,4080,"EQAKABEREQAAAAAFAAAAAAAACQAAAAALAAAAAAAAAAARAA8KERERAwoHAAEACQsLAAAJBgsAAAsABhEAAAARERE="),n(t,4161,"CwAAAAAAAAAAEQAKChEREQAKAAACAAkLAAAACQALAAAL"),n(t,4219,"DA=="),n(t,4231,"DAAAAAAMAAAAAAkMAAAAAAAMAAAM"),n(t,4277,"Dg=="),n(t,4289,"DQAAAAQNAAAAAAkOAAAAAAAOAAAO"),n(t,4335,"EA=="),n(t,4347,"DwAAAAAPAAAAAAkQAAAAAAAQAAAQAAASAAAAEhIS"),n(t,4402,"EgAAABISEgAAAAAAAAk="),n(t,4451,"Cw=="),n(t,4463,"CgAAAAAKAAAAAAkLAAAAAAALAAAL"),n(t,4509,"DA=="),n(t,4521,"DAAAAAAMAAAAAAkMAAAAAAAMAAAMAAAwMTIzNDU2Nzg5QUJDREVGLTBYKzBYIDBYLTB4KzB4IDB4AGluZgBJTkYAbmFuAE5BTgAu"),n(t,4636,"BA=="),n(t,4675,"//////8="),n(t,4744,"U3Q5dHlwZV9pbmZvAAAAACQTAACIEgAAUHVyZSB2aXJ0dWFsIGZ1bmN0aW9uIGNhbGxlZCEATjEwX19jeHhhYml2MTE2X19zaGltX3R5cGVfaW5mb0UAAEwTAAC+EgAAmBIAAE4xMF9fY3h4YWJpdjExN19fY2xhc3NfdHlwZV9pbmZvRQAAAEwTAADsEgAA4BIAAAAAAAAQEwAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAAAAAAAlBMAAAUAAAANAAAABwAAAAgAAAAJAAAADgAAAA8AAAAQAAAATjEwX19jeHhhYml2MTIwX19zaV9jbGFzc190eXBlX2luZm9FAAAAAEwTAABsEwAAEBMAAF9aAF9fWgBfX19aAF9fX19aAF9ibG9ja19pbnZva2UAaW52b2NhdGlvbiBmdW5jdGlvbiBmb3IgYmxvY2sgaW4gAFVhOWVuYWJsZV9pZkkAdnRhYmxlIGZvciAAVlRUIGZvciAAdHlwZWluZm8gZm9yIAB0eXBlaW5mbyBuYW1lIGZvciAAY292YXJpYW50IHJldHVybiB0aHVuayB0byAAdGhyZWFkLWxvY2FsIHdyYXBwZXIgcm91dGluZSBmb3IgAHRocmVhZC1sb2NhbCBpbml0aWFsaXphdGlvbiByb3V0aW5lIGZvciAAdmlydHVhbCB0aHVuayB0byAAbm9uLXZpcnR1YWwgdGh1bmsgdG8gAGd1YXJkIHZhcmlhYmxlIGZvciAAcmVmZXJlbmNlIHRlbXBvcmFyeSBmb3IgAAAAAAAAAABwFQAAEQAAABIAAAATAAAAFAAAABUAAAAWAAAAFwAAABgAAAAZAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTFTcGVjaWFsTmFtZUUATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlNE5vZGVFACQTAABAFQAATBMAABAVAABoFQAAAAAAAGgVAAARAAAAEgAAABMAAAAUAAAAGgAAABYAAAAXAAAAGAAAABsAAAAAAAAAEBYAABEAAAASAAAAEwAAABQAAAAcAAAAFgAAABcAAAAYAAAAHQAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTIxQ3RvclZ0YWJsZVNwZWNpYWxOYW1lRQAAAEwTAADUFQAAaBUAAGNvbnN0cnVjdGlvbiB2dGFibGUgZm9yIAAtaW4tAFN0AHN0ZAAAAAAAAAAAnBYAABEAAAASAAAAEwAAABQAAAAeAAAAFgAAAB8AAAAYAAAAIAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZThOYW1lVHlwZUUATBMAAHAWAABoFQAAAAAAAAQXAAARAAAAEgAAABMAAAAUAAAAIQAAABYAAAAiAAAAGAAAACMAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxME5lc3RlZE5hbWVFAABMEwAA1BYAAGgVAAA6OgBhdXRvAAAAAACEFwAAJAAAACUAAAAmAAAAJwAAACgAAAApAAAAFwAAABgAAAAqAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjRGb3J3YXJkVGVtcGxhdGVSZWZlcmVuY2VFAAAAAEwTAABEFwAAaBUAAGRlY2x0eXBlKAApAGdzACYmACYAJj0APQBhbGlnbm9mICgAY29uc3RfY2FzdAAsAH4AZHluYW1pY19jYXN0ACoALioALgAvAC89AF4AXj0APT0APj0APgA8PQA8PAA8PD0APAAtAC09ACo9AC0tACE9ACEAbm9leGNlcHQgKAB8fAB8AHw9AC0+KgArACs9ACsrAC0+AHJlaW50ZXJwcmV0X2Nhc3QAJQAlPQA+PgA+Pj0Ac3RhdGljX2Nhc3QAc2l6ZW9mICgAc2l6ZW9mLi4uICgAdHlwZWlkICgAdGhyb3cAdThfX3V1aWRvZnQAdThfX3V1aWRvZnoAd2NoYXJfdABiMEUAYjFFAGNoYXIAc2lnbmVkIGNoYXIAdW5zaWduZWQgY2hhcgBzaG9ydAB1bnNpZ25lZCBzaG9ydAAAdQBsAHVsAGxsAHVsbABfX2ludDEyOAB1bnNpZ25lZCBfX2ludDEyOABEbkUAbnVsbHB0cgAAAAAAAAAAaBkAABEAAAASAAAAEwAAABQAAAArAAAAFgAAABcAAAAYAAAALAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTE0SW50ZWdlckxpdGVyYWxFAABMEwAANBkAAGgVAAAoAAAAAAAAANAZAAARAAAAEgAAABMAAAAUAAAALQAAABYAAAAXAAAAGAAAAC4AAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGU4Qm9vbEV4cHJFAEwTAACkGQAAaBUAAHRydWUAZmFsc2UAAAAAAABMGgAAEQAAABIAAAATAAAAFAAAAC8AAAAWAAAAFwAAABgAAAAwAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTZGbG9hdExpdGVyYWxJbXBsSWZFRQBMEwAAFBoAAGgVAAAlYWYAAAAAAMAaAAARAAAAEgAAABMAAAAUAAAAMQAAABYAAAAXAAAAGAAAADIAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNkZsb2F0TGl0ZXJhbEltcGxJZEVFAEwTAACIGgAAaBUAACVhAAAAAAAANBsAABEAAAASAAAAEwAAABQAAAAzAAAAFgAAABcAAAAYAAAANAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTE2RmxvYXRMaXRlcmFsSW1wbEllRUUATBMAAPwaAABoFQAAJUxhTAAAAAAAAAAAqBsAABEAAAASAAAAEwAAABQAAAA1AAAAFgAAABcAAAAYAAAANgAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEzU3RyaW5nTGl0ZXJhbEUAAABMEwAAdBsAAGgVAAAiPAA+IgBVdABVbAB5cHRuAHZFAFViACdibG9jay1saXRlcmFsJwAAAAAAADwcAAARAAAAEgAAABMAAAAUAAAANwAAABYAAAAXAAAAGAAAADgAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNVVubmFtZWRUeXBlTmFtZUUATBMAAAgcAABoFQAAJ3VubmFtZWQAJwBUeQBUbgBUdABFAFRwAAAAAAAAAADQHAAAEQAAABIAAAATAAAAFAAAADkAAAAWAAAAFwAAABgAAAA6AAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjZTeW50aGV0aWNUZW1wbGF0ZVBhcmFtTmFtZUUAAEwTAACQHAAAaBUAACRUACROACRUVAAAAAAAAABQHQAAEQAAABIAAAATAAAAFAAAADsAAAA8AAAAFwAAABgAAAA9AAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjFUeXBlVGVtcGxhdGVQYXJhbURlY2xFAAAATBMAABQdAABoFQAAdHlwZW5hbWUgAAAAAAAAANQdAAARAAAAEgAAABMAAAAUAAAAPgAAAD8AAAAXAAAAGAAAAEAAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUyNE5vblR5cGVUZW1wbGF0ZVBhcmFtRGVjbEUAAAAATBMAAJQdAABoFQAAIAAAAAAAAABQHgAAEQAAABIAAAATAAAAFAAAAEEAAABCAAAAFwAAABgAAABDAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjVUZW1wbGF0ZVRlbXBsYXRlUGFyYW1EZWNsRQAAAEwTAAAQHgAAaBUAAHRlbXBsYXRlPAA+IHR5cGVuYW1lIAAsIAAAAAAAAAAA4B4AABEAAAASAAAAEwAAABQAAABEAAAARQAAABcAAAAYAAAARgAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTIxVGVtcGxhdGVQYXJhbVBhY2tEZWNsRQAAAEwTAACkHgAAaBUAAC4uLgAAAAAAUB8AABEAAAASAAAAEwAAABQAAABHAAAAFgAAABcAAAAYAAAASAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTE1Q2xvc3VyZVR5cGVOYW1lRQBMEwAAHB8AAGgVAAAnbGFtYmRhAAAAAADAHwAAEQAAABIAAAATAAAAFAAAAEkAAAAWAAAAFwAAABgAAABKAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTBMYW1iZGFFeHByRQAATBMAAJAfAABoFQAAW10Aey4uLn0AAAAAAAAAADggAAARAAAAEgAAABMAAAAUAAAASwAAABYAAAAXAAAAGAAAAEwAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNUludGVnZXJDYXN0RXhwckUATBMAAAQgAABoFQAAZnAAZkwAAAAAAAAArCAAABEAAAASAAAAEwAAABQAAABNAAAAFgAAABcAAAAYAAAATgAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEzRnVuY3Rpb25QYXJhbUUAAABMEwAAeCAAAGgVAABhYQBhbgBhTgBhUwBjbQBkcwBkdgBkVgBlbwBlTwBlcQBnZQBndABsZQBscwBsUwBsdABtaQBtSQBtbABtTABuZQBvbwBvcgBvUgBwbABwTABybQByTQBycwByUwAAAAAAAAAAcCEAABEAAAASAAAAEwAAABQAAABPAAAAFgAAABcAAAAYAAAAUAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZThGb2xkRXhwckUATBMAAEQhAABoFQAALi4uIAAgLi4uAAAAAAAAAPAhAAARAAAAEgAAABMAAAAUAAAAUQAAABYAAAAXAAAAGAAAAFIAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUyMlBhcmFtZXRlclBhY2tFeHBhbnNpb25FAABMEwAAtCEAAGgVAAAAAAAAWCIAABEAAAASAAAAEwAAABQAAABTAAAAFgAAABcAAAAYAAAAVAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEwQmluYXJ5RXhwckUAAEwTAAAoIgAAaBUAACkgACAoAAAAAAAAAMgiAAARAAAAEgAAABMAAAAUAAAAVQAAABYAAAAXAAAAGAAAAFYAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxMFByZWZpeEV4cHJFAABMEwAAmCIAAGgVAAAAAAAALCMAABEAAAASAAAAEwAAABQAAABXAAAAFgAAABcAAAAYAAAAWAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZThDYXN0RXhwckUATBMAAAAjAABoFQAAPigAAAAAAACUIwAAEQAAABIAAAATAAAAFAAAAFkAAAAWAAAAFwAAABgAAABaAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlOENhbGxFeHByRQBMEwAAaCMAAGgVAABjdgAAAAAAAAQkAAARAAAAEgAAABMAAAAUAAAAWwAAABYAAAAXAAAAGAAAAFwAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNENvbnZlcnNpb25FeHByRQAATBMAANAjAABoFQAAKSgAAAAAAABwJAAAEQAAABIAAAATAAAAFAAAAF0AAAAWAAAAFwAAABgAAABeAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTBEZWxldGVFeHByRQAATBMAAEAkAABoFQAAZGVsZXRlAFtdIABzck4Ac3IAX0dMT0JBTF9fTgAoYW5vbnltb3VzIG5hbWVzcGFjZSkAAAAAAAAQJQAAEQAAABIAAAATAAAAFAAAAF8AAAAWAAAAYAAAABgAAABhAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTNRdWFsaWZpZWROYW1lRQAAAEwTAADcJAAAaBUAAGRuAG9uAAAAAAAAAHwlAAARAAAAEgAAABMAAAAUAAAAYgAAABYAAAAXAAAAGAAAAGMAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGU4RHRvck5hbWVFAEwTAABQJQAAaBUAAG9wZXJhdG9yJiYAb3BlcmF0b3ImAG9wZXJhdG9yJj0Ab3BlcmF0b3I9AG9wZXJhdG9yKCkAb3BlcmF0b3IsAG9wZXJhdG9yfgBvcGVyYXRvciBkZWxldGVbXQBvcGVyYXRvcioAb3BlcmF0b3IgZGVsZXRlAG9wZXJhdG9yLwBvcGVyYXRvci89AG9wZXJhdG9yXgBvcGVyYXRvcl49AG9wZXJhdG9yPT0Ab3BlcmF0b3I+PQBvcGVyYXRvcj4Ab3BlcmF0b3JbXQBvcGVyYXRvcjw9AG9wZXJhdG9yPDwAb3BlcmF0b3I8PD0Ab3BlcmF0b3I8AG9wZXJhdG9yLQBvcGVyYXRvci09AG9wZXJhdG9yKj0Ab3BlcmF0b3ItLQBvcGVyYXRvciBuZXdbXQBvcGVyYXRvciE9AG9wZXJhdG9yIQBvcGVyYXRvciBuZXcAb3BlcmF0b3J8fABvcGVyYXRvcnwAb3BlcmF0b3J8PQBvcGVyYXRvci0+KgBvcGVyYXRvcisAb3BlcmF0b3IrPQBvcGVyYXRvcisrAG9wZXJhdG9yLT4Ab3BlcmF0b3I/AG9wZXJhdG9yJQBvcGVyYXRvciU9AG9wZXJhdG9yPj4Ab3BlcmF0b3I+Pj0Ab3BlcmF0b3I8PT4AAAAAAADcJwAAEQAAABIAAAATAAAAFAAAAGQAAAAWAAAAFwAAABgAAABlAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjJDb252ZXJzaW9uT3BlcmF0b3JUeXBlRQAATBMAAKAnAABoFQAAb3BlcmF0b3IgAAAAAAAAAFQoAAARAAAAEgAAABMAAAAUAAAAZgAAABYAAAAXAAAAGAAAAGcAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNUxpdGVyYWxPcGVyYXRvckUATBMAACAoAABoFQAAb3BlcmF0b3IiIiAAAAAAANAoAAARAAAAEgAAABMAAAAUAAAAaAAAABYAAABpAAAAGAAAAGoAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxOUdsb2JhbFF1YWxpZmllZE5hbWVFAEwTAACYKAAAaBUAAAAAAAA4KQAAEQAAABIAAAATAAAAFAAAAGsAAAAWAAAAFwAAABgAAABsAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTBNZW1iZXJFeHByRQAATBMAAAgpAABoFQAAAAAAAKgpAAARAAAAEgAAABMAAAAUAAAAbQAAABYAAAAXAAAAGAAAAG4AAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxOEFycmF5U3Vic2NyaXB0RXhwckUAAEwTAABwKQAAaBUAAClbAF0AAAAAAAAAABgqAAARAAAAEgAAABMAAAAUAAAAbwAAABYAAAAXAAAAGAAAAHAAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxMEJyYWNlZEV4cHJFAABMEwAA6CkAAGgVAAAgPSAAAAAAAIgqAAARAAAAEgAAABMAAAAUAAAAcQAAABYAAAAXAAAAGAAAAHIAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNUJyYWNlZFJhbmdlRXhwckUATBMAAFQqAABoFQAAIC4uLiAAAAAAAAAA/CoAABEAAAASAAAAEwAAABQAAABzAAAAFgAAABcAAAAYAAAAdAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEySW5pdExpc3RFeHByRQAAAABMEwAAyCoAAGgVAAAAAAAAZCsAABEAAAASAAAAEwAAABQAAAB1AAAAFgAAABcAAAAYAAAAdgAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTExUG9zdGZpeEV4cHJFAEwTAAA0KwAAaBUAAG53AG5hAHBpAAAAAAAAAADUKwAAEQAAABIAAAATAAAAFAAAAHcAAAAWAAAAFwAAABgAAAB4AAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlN05ld0V4cHJFAABMEwAAqCsAAGgVAAA6Om9wZXJhdG9yIABuZXcAAAAAAFAsAAARAAAAEgAAABMAAAAUAAAAeQAAABYAAAAXAAAAGAAAAHoAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxM0VuY2xvc2luZ0V4cHJFAAAATBMAABwsAABoFQAAAAAAALwsAAARAAAAEgAAABMAAAAUAAAAewAAABYAAAAXAAAAGAAAAHwAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNUNvbmRpdGlvbmFsRXhwckUATBMAAIgsAABoFQAAKSA/ICgAKSA6ICgAAAAAADgtAAARAAAAEgAAABMAAAAUAAAAfQAAABYAAAAXAAAAGAAAAH4AAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxOVNpemVvZlBhcmFtUGFja0V4cHJFAEwTAAAALQAAaBUAAHNpemVvZi4uLigAAAAAAACwLQAAEQAAABIAAAATAAAAFAAAAH8AAAAWAAAAFwAAABgAAACAAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTNOb2RlQXJyYXlOb2RlRQAAAEwTAAB8LQAAaBUAAAAAAAAYLgAAEQAAABIAAAATAAAAFAAAAIEAAAAWAAAAFwAAABgAAACCAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlOVRocm93RXhwckUAAAAATBMAAOgtAABoFQAAdGhyb3cgAAAAAAAAiC4AABEAAAASAAAAEwAAABQAAACDAAAAFgAAABcAAAAYAAAAhAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEwVVVJRE9mRXhwckUAAEwTAABYLgAAaBUAAF9fdXVpZG9mKAAAAAAAAAAMLwAAEQAAABIAAAATAAAAFAAAAIUAAAAWAAAAhgAAABgAAACHAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjdFeHBhbmRlZFNwZWNpYWxTdWJzdGl0dXRpb25FAEwTAADMLgAAaBUAAHN0ZDo6YWxsb2NhdG9yAHN0ZDo6YmFzaWNfc3RyaW5nAHN0ZDo6YmFzaWNfc3RyaW5nPGNoYXIsIHN0ZDo6Y2hhcl90cmFpdHM8Y2hhcj4sIHN0ZDo6YWxsb2NhdG9yPGNoYXI+ID4Ac3RkOjpiYXNpY19pc3RyZWFtPGNoYXIsIHN0ZDo6Y2hhcl90cmFpdHM8Y2hhcj4gPgBzdGQ6OmJhc2ljX29zdHJlYW08Y2hhciwgc3RkOjpjaGFyX3RyYWl0czxjaGFyPiA+AHN0ZDo6YmFzaWNfaW9zdHJlYW08Y2hhciwgc3RkOjpjaGFyX3RyYWl0czxjaGFyPiA+AGFsbG9jYXRvcgBiYXNpY19zdHJpbmcAYmFzaWNfaXN0cmVhbQBiYXNpY19vc3RyZWFtAGJhc2ljX2lvc3RyZWFtAAAAAAAAAAC8MAAAEQAAABIAAAATAAAAFAAAAIgAAAAWAAAAFwAAABgAAACJAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTJDdG9yRHRvck5hbWVFAAAAAEwTAACIMAAAaBUAAAAAAAAkMQAAEQAAABIAAAATAAAAFAAAAIoAAAAWAAAAFwAAABgAAACLAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTBBYmlUYWdBdHRyRQAATBMAAPQwAABoFQAAW2FiaToAREMAAAAAAAAAAKQxAAARAAAAEgAAABMAAAAUAAAAjAAAABYAAAAXAAAAGAAAAI0AAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUyMVN0cnVjdHVyZWRCaW5kaW5nTmFtZUUAAABMEwAAaDEAAGgVAABzdHJpbmcgbGl0ZXJhbAAAAAAAABwyAAARAAAAEgAAABMAAAAUAAAAjgAAABYAAAAXAAAAGAAAAI8AAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGU5TG9jYWxOYW1lRQAAAABMEwAA7DEAAGgVAAAAAAAAjDIAABEAAAASAAAAEwAAABQAAACQAAAAFgAAAJEAAAAYAAAAkgAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTE5U3BlY2lhbFN1YnN0aXR1dGlvbkUATBMAAFQyAABoFQAAc3RkOjpzdHJpbmcAc3RkOjppc3RyZWFtAHN0ZDo6b3N0cmVhbQBzdGQ6Omlvc3RyZWFtAHN0cmluZwBpc3RyZWFtAG9zdHJlYW0AaW9zdHJlYW0AAAAAAEwzAACTAAAAlAAAAJUAAACWAAAAlwAAAJgAAAAXAAAAGAAAAJkAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxM1BhcmFtZXRlclBhY2tFAAAATBMAABgzAABoFQAAAAAAALgzAAARAAAAEgAAABMAAAAUAAAAmgAAABYAAAAXAAAAGAAAAJsAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxMlRlbXBsYXRlQXJnc0UAAAAATBMAAIQzAABoFQAAAAAAACw0AAARAAAAEgAAABMAAAAUAAAAnAAAABYAAACdAAAAGAAAAJ4AAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUyME5hbWVXaXRoVGVtcGxhdGVBcmdzRQAAAABMEwAA8DMAAGgVAABTdEwAAAAAAKA0AAARAAAAEgAAABMAAAAUAAAAnwAAABYAAACgAAAAGAAAAKEAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNlN0ZFF1YWxpZmllZE5hbWVFAAAAAEwTAABoNAAAaBUAAHN0ZDo6AAAAAAAAABw1AAARAAAAEgAAABMAAAAUAAAAogAAABYAAAAXAAAAGAAAAKMAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUyMFRlbXBsYXRlQXJndW1lbnRQYWNrRQAAAABMEwAA4DQAAGgVAAAAAAAAiDUAABEAAAASAAAAEwAAABQAAACkAAAAFgAAABcAAAAYAAAApQAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEyRW5hYmxlSWZBdHRyRQAAAABMEwAAVDUAAGgVAAAgW2VuYWJsZV9pZjoAAAAAAAAAAAg2AACmAAAAEgAAAKcAAAAUAAAAqAAAAKkAAAAXAAAAGAAAAKoAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxNkZ1bmN0aW9uRW5jb2RpbmdFAAAAAEwTAADQNQAAaBUAACBjb25zdAAgdm9sYXRpbGUAIHJlc3RyaWN0ACAmACAmJgAAAAAAAACUNgAAEQAAABIAAAATAAAAFAAAAKsAAAAWAAAAFwAAABgAAACsAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlOURvdFN1ZmZpeEUAAAAATBMAAGQ2AABoFQAAdm9pZABib29sAGludAB1bnNpZ25lZCBpbnQAbG9uZwB1bnNpZ25lZCBsb25nAGxvbmcgbG9uZwB1bnNpZ25lZCBsb25nIGxvbmcAZmxvYXQAZG91YmxlAGxvbmcgZG91YmxlAF9fZmxvYXQxMjgAZGVjaW1hbDY0AGRlY2ltYWwxMjgAZGVjaW1hbDMyAGRlY2ltYWwxNgBjaGFyMzJfdABjaGFyMTZfdABjaGFyOF90AGRlY2x0eXBlKGF1dG8pAHN0ZDo6bnVsbHB0cl90ACBjb21wbGV4ACBpbWFnaW5hcnkARG8Abm9leGNlcHQARE8ARHcARHgAUkUAT0UAAAAAAAAAOAAAEQAAABIAAAATAAAAFAAAAK0AAAAWAAAAFwAAABgAAACuAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTJOb2V4Y2VwdFNwZWNFAAAAAEwTAADMNwAAaBUAAG5vZXhjZXB0KAAAAAAAAACAOAAAEQAAABIAAAATAAAAFAAAAK8AAAAWAAAAFwAAABgAAACwAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjBEeW5hbWljRXhjZXB0aW9uU3BlY0UAAAAATBMAAEQ4AABoFQAAdGhyb3coAAAAAAAA9DgAALEAAAASAAAAsgAAABQAAACzAAAAtAAAABcAAAAYAAAAtQAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEyRnVuY3Rpb25UeXBlRQAAAABMEwAAwDgAAGgVAABvYmpjcHJvdG8AAAAAAAAAbDkAABEAAAASAAAAEwAAABQAAAC2AAAAFgAAABcAAAAYAAAAtwAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTEzT2JqQ1Byb3RvTmFtZUUAAABMEwAAODkAAGgVAAAAAAAA3DkAABEAAAASAAAAEwAAABQAAAC4AAAAFgAAABcAAAAYAAAAuQAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTE3VmVuZG9yRXh0UXVhbFR5cGVFAAAATBMAAKQ5AABoFQAAAAAAAEA6AAC6AAAAuwAAALwAAAAUAAAAvQAAAL4AAAAXAAAAGAAAAL8AAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGU4UXVhbFR5cGVFAEwTAAAUOgAAaBUAAER2AAAAAAAAsDoAABEAAAASAAAAEwAAABQAAADAAAAAFgAAABcAAAAYAAAAwQAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTE1UGl4ZWxWZWN0b3JUeXBlRQBMEwAAfDoAAGgVAABwaXhlbCB2ZWN0b3JbAAAAAAAAACg7AAARAAAAEgAAABMAAAAUAAAAwgAAABYAAAAXAAAAGAAAAMMAAABOMTJfR0xPQkFMX19OXzExNml0YW5pdW1fZGVtYW5nbGUxMFZlY3RvclR5cGVFAABMEwAA+DoAAGgVAAAgdmVjdG9yWwAAAAAAAAAAnDsAAMQAAADFAAAAEwAAABQAAADGAAAAxwAAABcAAAAYAAAAyAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTlBcnJheVR5cGVFAAAAAEwTAABsOwAAaBUAAFsAAAAAAAAAEDwAAMkAAAASAAAAEwAAABQAAADKAAAAywAAABcAAAAYAAAAzAAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTE5UG9pbnRlclRvTWVtYmVyVHlwZUUATBMAANg7AABoFQAAOjoqAFRzAHN0cnVjdABUdQB1bmlvbgBUZQBlbnVtAAAAAAAApDwAABEAAAASAAAAEwAAABQAAADNAAAAFgAAABcAAAAYAAAAzgAAAE4xMl9HTE9CQUxfX05fMTE2aXRhbml1bV9kZW1hbmdsZTIyRWxhYm9yYXRlZFR5cGVTcGVmVHlwZUUAAEwTAABoPAAAaBUAAAAAAAAMPQAAzwAAABIAAAATAAAAFAAAANAAAADRAAAAFwAAABgAAADSAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTFQb2ludGVyVHlwZUUATBMAANw8AABoFQAAaWQ8AG9iamNfb2JqZWN0AAAAAACIPQAA0wAAABIAAAATAAAAFAAAANQAAADVAAAAFwAAABgAAADWAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMTNSZWZlcmVuY2VUeXBlRQAAAEwTAABUPQAAaBUAAAAAAAD8PQAAEQAAABIAAAATAAAAFAAAANcAAAAWAAAAFwAAABgAAADYAAAATjEyX0dMT0JBTF9fTl8xMTZpdGFuaXVtX2RlbWFuZ2xlMjBQb3N0Zml4UXVhbGlmaWVkVHlwZUUAAAAATBMAAMA9AABoFQAAFzAAACEwAAAhMAAALjAAADwwAABKMAAAFzAAACEwAADMMgAA0zIAANsyAADjMg=="),n(t,15928,"cEFQAAE="),n(t,16108,"QEE=");var iA,aA=((iA=[null,nA,function(e,t,r,A,n,a){e|=0,t=+t,r|=0,A|=0,n|=0,a|=0;var f,s=0,l=0,d=0,k=0,E=0,g=0,h=0,O=0,_=0,w=0,R=0,m=0,v=0,M=0,F=0,S=0,P=0,B=0,X=0;y=f=y-560|0,o[f+44>>2]=0,u(+t),(0|(s=0|c(1)))<-1||(0|s)<=-1?(S=1,u(+(t=-t)),s=0|c(1),R=4560):(S=1,R=4563,2048&n||(R=4566,1&n||(S=0,F=1,R=4561)));e:if(2146435072!=(2146435072&s)){m=f+16|0;t:{r:{A:{if(t=$e(t,f+44|0),0!=(t+=t)){if(s=o[f+44>>2],o[f+44>>2]=s-1,97!=(0|(P=32|a)))break A;break t}if(97==(0|(P=32|a)))break t;E=o[f+44>>2],g=(0|A)<0?6:A;break r}E=s-29|0,o[f+44>>2]=E,t*=268435456,g=(0|A)<0?6:A}for(k=h=(0|E)<0?f+48|0:f+336|0;s=t<4294967296&t>=0?~~t>>>0:0,o[(A=k)>>2]=s,k=k+4|0,0!=(t=1e9*(t-+(s>>>0))););if((0|E)<1)A=E,s=k,l=h;else for(l=h,A=E;;){if(O=(0|A)<29?A:29,!(l>>>0>(s=k-4|0)>>>0)){for(A=O,d=0;w=s,B=d,X=o[s>>2],_=31&A,(63&A)>>>0>=32?(d=X<<_,X=0):(d=(1<<_)-1&X>>>32-_,X<<=_),_=B+X|0,d=d+0|0,B=w,w=Qr(d=qr(_,d=_>>>0<X>>>0?d+1|0:d,1e9),D,1e9,0),o[B>>2]=_-w,l>>>0<=(s=s-4|0)>>>0;);d&&(o[(l=l-4|0)>>2]=d)}for(;l>>>0<(s=k)>>>0&&!o[(k=s-4|0)>>2];);if(A=o[f+44>>2]-O|0,o[f+44>>2]=A,k=s,!((0|A)>0))break}if((0|A)<=-1)for(v=1+((g+25|0)/9|0)|0,O=102==(0|P);;){if(d=(0|A)<-9?9:0-A|0,s>>>0<=l>>>0)l=o[l>>2]?l:l+4|0;else{for(w=1e9>>>d|0,_=-1<<d^-1,A=0,k=l;B=A,A=o[k>>2],o[k>>2]=B+(A>>>d|0),A=p(w,A&_),(k=k+4|0)>>>0<s>>>0;);l=o[l>>2]?l:l+4|0,A&&(o[s>>2]=A,s=s+4|0)}if(A=o[f+44>>2]+d|0,o[f+44>>2]=A,s=s-(k=O?h:l)>>2>(0|v)?k+(v<<2)|0:s,!((0|A)<0))break}if(k=0,!(s>>>0<=l>>>0||(k=p(h-l>>2,9),A=10,(d=o[l>>2])>>>0<10)))for(;k=k+1|0,d>>>0>=(A=p(A,10))>>>0;);if((0|(A=(g-(102==(0|P)?0:k)|0)-(103==(0|P)&0!=(0|g))|0))<(p(s-h>>2,9)-9|0)){if(E=(((O=(0|(d=A+9216|0))/9|0)<<2)+((0|E)<0?f+48|4:f+340|0)|0)-4096|0,A=10,(0|(d=d-p(O,9)|0))<=7)for(;A=p(A,10),8!=(0|(d=d+1|0)););if(v=E+4|0,((d=(O=o[E>>2])-p(A,w=(O>>>0)/(A>>>0)|0)|0)||(0|v)!=(0|s))&&(M=d>>>0<(_=A>>>1|0)>>>0?.5:(0|s)==(0|v)&&(0|_)==(0|d)?1:1.5,t=1&w?9007199254740994:9007199254740992,45!=b[0|R]|F||(M=-M,t=-t),d=O-d|0,o[E>>2]=d,t+M!=t)){if(A=A+d|0,o[E>>2]=A,A>>>0>=1e9)for(;o[E>>2]=0,(E=E-4|0)>>>0<l>>>0&&(o[(l=l-4|0)>>2]=0),A=o[E>>2]+1|0,o[E>>2]=A,A>>>0>999999999;);if(k=p(h-l>>2,9),A=10,!((d=o[l>>2])>>>0<10))for(;k=k+1|0,d>>>0>=(A=p(A,10))>>>0;);}s=(A=E+4|0)>>>0<s>>>0?A:s}for(;A=s,!(E=s>>>0<=l>>>0)&&!o[(s=A-4|0)>>2];);if(103==(0|P)){if(g=((d=(0|(s=g||1))>(0|k)&(0|k)>-5)?-1^k:-1)+s|0,a=(d?-1:-2)+a|0,!(F=8&n)){if(s=-9,!E&&(O=o[A-4>>2])&&(d=10,s=0,!((O>>>0)%10|0))){for(;E=s,s=s+1|0,!((O>>>0)%((d=p(d,10))>>>0)|0););s=-1^E}E=p(A-h>>2,9),70!=(-33&a)?(F=0,g=(0|(s=(0|(s=((k+E|0)+s|0)-9|0))>0?s:0))>(0|g)?g:s):(F=0,g=(0|(s=(0|(s=(s+E|0)-9|0))>0?s:0))>(0|g)?g:s)}}else F=8&n;if(w=0!=(0|(O=g|F)),B=e,d=r,s=(0|k)>0?k:0,70!=(0|(_=-33&a))){if((m-(s=Le((s=k>>31)+k^s,0,m))|0)<=1)for(;i[0|(s=s-1|0)]=48,(m-s|0)<2;);i[0|(v=s-2|0)]=a,i[s-1|0]=(0|k)<0?45:43,s=m-v|0}dt(B,32,d,E=1+(s+(w+(g+S|0)|0)|0)|0,n),Ir(e,R,S),dt(e,48,r,E,65536^n);r:{A:{n:{if(70==(0|_)){for(a=f+16|8,k=f+16|9,l=d=l>>>0>h>>>0?h:l;;){s=Le(o[l>>2],0,k);i:if((0|l)==(0|d))(0|s)==(0|k)&&(i[f+24|0]=48,s=a);else{if(f+16>>>0>=s>>>0)break i;for(;i[0|(s=s-1|0)]=48,f+16>>>0<s>>>0;);}if(Ir(e,s,k-s|0),!(h>>>0>=(l=l+4|0)>>>0))break}if(O&&Ir(e,4595,1),(0|g)<1|A>>>0<=l>>>0)break n;for(;;){if((s=Le(o[l>>2],0,k))>>>0>f+16>>>0)for(;i[0|(s=s-1|0)]=48,f+16>>>0<s>>>0;);if(Ir(e,s,(0|g)<9?g:9),s=g-9|0,A>>>0<=(l=l+4|0)>>>0)break A;if(a=(0|g)>9,g=s,!a)break}break A}i:if(!((0|g)<0))for(h=A>>>0>l>>>0?A:l+4|0,A=f+16|8,a=f+16|9,k=l;;){(0|a)==(0|(s=Le(o[k>>2],0,a)))&&(i[f+24|0]=48,s=A);a:if((0|l)==(0|k))Ir(e,s,1),s=s+1|0,(0|g)<1&&!F||Ir(e,4595,1);else{if(f+16>>>0>=s>>>0)break a;for(;i[0|(s=s-1|0)]=48,f+16>>>0<s>>>0;);}if(Ir(e,w=s,(0|(s=a-s|0))<(0|g)?s:g),g=g-s|0,h>>>0<=(k=k+4|0)>>>0)break i;if(!((0|g)>-1))break}dt(e,48,g+18|0,18,0),Ir(e,v,m-v|0);break r}s=g}dt(e,48,s+9|0,9,0)}break e}if(d=(g=32&a)?R+9|0:R,!(A>>>0>11)&&(s=12-A|0)){for(M=8;M*=16,s=s-1|0;);t=45!=b[0|d]?t+M-M:-(M+(-t-M))}for((0|m)==(0|(s=Le((s=(l=o[f+44>>2])>>31)^s+l,0,m)))&&(i[f+15|0]=48,s=f+15|0),k=2|S,l=o[f+44>>2],i[0|(h=s-2|0)]=a+15,i[s-1|0]=(0|l)<0?45:43,E=8&n,l=f+16|0;a=l,w=l,l=g,s=T(t)<2147483648?~~t:-2147483648,i[0|w]=l|b[s+4544|0],1!=((l=a+1|0)-(f+16|0)|0)|(0==(t=16*(t-+(0|s)))?!((0|A)>0|E):0)||(i[a+1|0]=46,l=a+2|0),0!=t;);dt(s=e,32,a=r,E=(A=g=!A|((l-f|0)-18|0)>=(0|A)?((m-(f+16|0)|0)-h|0)+l|0:2+((A+m|0)-h|0)|0)+k|0,n),Ir(e,d,k),dt(e,48,r,E,65536^n),Ir(e,f+16|0,a=l-(f+16|0)|0),dt(e,48,g-((A=m-h|0)+a|0)|0,0,0),Ir(e,h,A)}else dt(e,32,r,E=S+3|0,-65537&n),Ir(e,R,S),A=32&a,Ir(e,t!=t?A?4587:4591:A?4579:4583,3);return dt(e,32,r,E,8192^n),y=f+560|0,0|((0|r)>(0|E)?r:E)},function(e,t){var r,A,n;e|=0,r=t|=0,t=o[t>>2]+15&-16,o[r>>2]=t+16,A=e,n=ie(o[t>>2],o[t+4>>2],o[t+8>>2],o[t+12>>2]),E[A>>3]=n},function(e,t,r){t|=0,r|=0;var A,n=0;return re(A=o[20+(e|=0)>>2],t,n=r>>>0<(n=o[e+16>>2]-A|0)>>>0?r:n),o[e+20>>2]=n+o[e+20>>2],0|r},$r,eA,AA,AA,function(e,t,r){r|=0;var A,n=0;return y=A=y+-64|0,n=1,er(e|=0,t|=0,0)||(n=0,t&&(t=function(e){var t,r,A,n=0;y=t=y+-64|0,n=o[e>>2],r=o[n-4>>2],A=o[n-8>>2],o[t+20>>2]=0,o[t+16>>2]=4832,o[t+12>>2]=e,o[t+8>>2]=4880,n=0,ke(t+24|0,0,39),e=e+A|0;e:if(er(r,4880,0))o[t+56>>2]=1,aA[o[o[r>>2]+20>>2]](r,t+8|0,e,e,1,0),n=1==o[t+32>>2]?e:0;else{aA[o[o[r>>2]+24>>2]](r,t+8|0,e,1,0);t:switch(o[t+44>>2]){case 0:n=1==o[t+48>>2]&&1==o[t+36>>2]&&1==o[t+40>>2]?o[t+28>>2]:0;break e;case 1:break t;default:break e}1!=o[t+32>>2]&&o[t+48>>2]|1!=o[t+36>>2]|1!=o[t+40>>2]||(n=o[t+24>>2])}return y=t- -64|0,n}(t),n=0,t&&(ke(A+8|4,0,52),o[A+56>>2]=1,o[A+20>>2]=-1,o[A+16>>2]=e,o[A+8>>2]=t,aA[o[o[t>>2]+28>>2]](t,A+8|0,o[r>>2],1),1==(0|(e=o[A+32>>2]))&&(o[r>>2]=o[A+24>>2]),n=1==(0|e)))),y=A- -64|0,0|(e=n)},function(e,t,r,A,n,i){r|=0,A|=0,n|=0,i|=0,er(e|=0,o[8+(t|=0)>>2],i)&&je(t,r,A,n)},function(e,t,r,A,n){if(r|=0,A|=0,n|=0,er(e|=0,o[8+(t|=0)>>2],n))1==o[t+28>>2]|o[t+4>>2]!=(0|r)||(o[t+28>>2]=A);else e:if(er(e,o[t>>2],n)){if(o[t+20>>2]==(0|r)||o[t+16>>2]==(0|r)){if(1!=(0|A))break e;return void(o[t+32>>2]=1)}o[t+20>>2]=r,o[t+32>>2]=A,o[t+40>>2]=o[t+40>>2]+1,1!=o[t+36>>2]|2!=o[t+24>>2]||(i[t+54|0]=1),o[t+44>>2]=4}},function(e,t,r,A){r|=0,A|=0,er(e|=0,o[8+(t|=0)>>2],0)&&ut(t,r,A)},eA,function(e,t,r,A,n,i){r|=0,A|=0,n|=0,i|=0,er(e|=0,o[8+(t|=0)>>2],i)?je(t,r,A,n):(e=o[e+8>>2],aA[o[o[e>>2]+20>>2]](e,t,r,A,n,i))},function(e,t,r,A,n){if(r|=0,A|=0,n|=0,er(e|=0,o[8+(t|=0)>>2],n))1==o[t+28>>2]|o[t+4>>2]!=(0|r)||(o[t+28>>2]=A);else e:{if(er(e,o[t>>2],n)){if(o[t+20>>2]==(0|r)||o[t+16>>2]==(0|r)){if(1!=(0|A))break e;return void(o[t+32>>2]=1)}o[t+32>>2]=A;t:if(4!=o[t+44>>2]){if(a[t+52>>1]=0,e=o[e+8>>2],aA[o[o[e>>2]+20>>2]](e,t,r,r,1,n),b[t+53|0]){if(o[t+44>>2]=3,!b[t+52|0])break t;break e}o[t+44>>2]=4}if(o[t+20>>2]=r,o[t+40>>2]=o[t+40>>2]+1,1!=o[t+36>>2]|2!=o[t+24>>2])break e;return void(i[t+54|0]=1)}e=o[e+8>>2],aA[o[o[e>>2]+24>>2]](e,t,r,A,n)}},function(e,t,r,A){r|=0,A|=0,er(e|=0,o[8+(t|=0)>>2],0)?ut(t,r,A):(e=o[e+8>>2],aA[o[o[e>>2]+28>>2]](e,t,r,A))},Jr,Jr,Jr,function(e,t){return 0|(e|=0)},function(e,t){var r,A,n;t|=0,y=r=y-16|0,A=o[12+(e|=0)>>2],n=o[e+8>>2],o[r>>2]=n,o[r+4>>2]=A,o[r+8>>2]=n,o[r+12>>2]=A,Kt(t,r),e=o[e+16>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},function(e,t){},function(e,t){o[(e|=0)>>2]=0,o[e+4>>2]=0},$r,eA,nA,function(e){h()},function(e,t){e|=0,t|=0;var r,A=0,n=0;y=r=y-32|0,A=dr(r+24|0,5660),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),A=dr(r+16|0,5685),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+12>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+32|0},eA,function(e,t){var r,A;t|=0,y=r=y-16|0,A=o[12+(e|=0)>>2],e=o[e+8>>2],o[r>>2]=e,o[r+4>>2]=A,o[r+8>>2]=e,o[r+12>>2]=A,Kt(t,r),y=r+16|0},function(e,t){var r;e|=0,r=o[12+(t|=0)>>2],o[e>>2]=o[t+8>>2],o[e+4>>2]=r},eA,Ve,br,eA,function(e,t){t|=0;var r,A=0,n=0;return y=r=y-16|0,b[16+(e|=0)|0]||(n=Nt(r+8|0,e+16|0,1),A=o[e+12>>2],A=2==(0|(e=b[A+5|0]))?0|aA[o[o[A>>2]>>2]](A,t):!e,b[n+5|0]&&(i[o[n>>2]]=b[n+4|0])),y=r+16|0,0|A},function(e,t){t|=0;var r,A=0,n=0;return y=r=y-16|0,b[16+(e|=0)|0]||(n=Nt(r+8|0,e+16|0,1),A=o[e+12>>2],A=2==(0|(e=b[A+6|0]))?0|aA[o[o[A>>2]+4>>2]](A,t):!e,b[n+5|0]&&(i[o[n>>2]]=b[n+4|0])),y=r+16|0,0|A},function(e,t){t|=0;var r,A=0,n=0;return y=r=y-16|0,b[16+(e|=0)|0]||(n=Nt(r+8|0,e+16|0,1),A=o[e+12>>2],A=2==(0|(e=b[A+7|0]))?0|aA[o[o[A>>2]+8>>2]](A,t):!e,b[n+5|0]&&(i[o[n>>2]]=b[n+4|0])),y=r+16|0,0|A},function(e,t){t|=0;var r,A=0;return y=r=y-16|0,b[16+(e|=0)|0]||(A=Nt(r+8|0,e+16|0,1),e=o[e+12>>2],e=0|aA[o[o[e>>2]+12>>2]](e,t),b[A+5|0]&&(i[o[A>>2]]=b[A+4|0])),y=r+16|0,0|e},function(e,t){t|=0;var r,A=0;y=r=y-16|0,b[16+(e|=0)|0]||(A=Nt(r+8|0,e+16|0,1),e=o[e+12>>2],aA[o[o[e>>2]+16>>2]](e,t),b[A+5|0]&&(i[o[A>>2]]=b[A+4|0])),y=r+16|0},function(e,t){t|=0;var r,A=0;y=r=y-16|0,b[16+(e|=0)|0]||(A=Nt(r+8|0,e+16|0,1),e=o[e+12>>2],aA[o[o[e>>2]+20>>2]](e,t),b[A+5|0]&&(i[o[A>>2]]=b[A+4|0])),y=r+16|0},eA,function(e,t){t|=0;var r,A,n=0,i=0;y=r=y-112|0,o[12+(e|=0)>>2]-o[e+8>>2]>>>0>=4&&(i=dr(r+104|0,6516),n=o[i+4>>2],o[r+48>>2]=o[i>>2],o[r+52>>2]=n,Kt(t,r+48|0),i=o[e+12>>2],n=o[e+8>>2],o[r+40>>2]=n,o[r+44>>2]=i,o[r+96>>2]=n,o[r+100>>2]=i,Kt(t,r+40|0),i=dr(r+88|0,6042),n=o[i+4>>2],o[r+32>>2]=o[i>>2],o[r+36>>2]=n,Kt(t,r+32|0)),110!=b[o[(A=e+16|0)>>2]]?(i=o[A+4>>2],o[r+64>>2]=o[A>>2],o[r+68>>2]=i,n=r- -64|0):(n=dr(r+80|0,6132),i=o[n+4>>2],o[r+24>>2]=o[n>>2],o[r+28>>2]=i,Kt(t,r+24|0),rr(r+72|0,A,1),n=r+72|0),i=o[n+4>>2],o[r+16>>2]=o[n>>2],o[r+20>>2]=i,Kt(t,r+16|0),o[e+12>>2]-o[e+8>>2]>>>0<=3&&(n=o[e+12>>2],e=o[e+8>>2],o[r+8>>2]=e,o[r+12>>2]=n,o[r+56>>2]=e,o[r+60>>2]=n,Kt(t,r+8|0)),y=r+112|0},eA,function(e,t){var r,A;t|=0,y=r=y-16|0,e=dr(r+8|0,b[8+(e|=0)|0]?6620:6625),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r+16|0},eA,function(e,t){t|=0;var r,A,n=0,a=0,c=0,f=0,s=0;if(y=r=y+-64|0,A=o[(e=8+(e|=0)|0)>>2],1+(o[e+4>>2]-A|0)>>>0>=9){for(n=r+56|0,e=0;8!=(0|e);)c=((a=i[(1|e)+A|0])-48>>>0<10?-48:-87)+a|0,a=i[e+A|0],i[0|n]=c+((a-48>>>0<10?0:9)+a<<4),n=n+1|0,e=e+2|0;tA(r+56|0,n),o[r+48>>2]=0,o[r+52>>2]=0,o[r+40>>2]=0,o[r+44>>2]=0,o[r+32>>2]=0,o[r+36>>2]=0,E[r+16>>3]=k[r+56>>2],f=e=r+24|0,s=fr(r+32|0,24,6744,r+16|0)+(r+32|0)|0,o[f+4>>2]=s,o[e>>2]=r+32,n=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=n,Kt(t,r+8|0)}y=r- -64|0},eA,function(e,t){t|=0;var r,A,n=0,a=0,c=0,f=0,s=0;if(y=r=y-80|0,A=o[(e=8+(e|=0)|0)>>2],1+(o[e+4>>2]-A|0)>>>0>=17){for(n=r+72|0,e=0;16!=(0|e);)c=((a=i[(1|e)+A|0])-48>>>0<10?-48:-87)+a|0,a=i[e+A|0],i[0|n]=c+((a-48>>>0<10?0:9)+a<<4),n=n+1|0,e=e+2|0;tA(r+72|0,n),o[r+56>>2]=0,o[r+60>>2]=0,o[r+48>>2]=0,o[r+52>>2]=0,o[r+40>>2]=0,o[r+44>>2]=0,o[r+32>>2]=0,o[r+36>>2]=0,E[r+16>>3]=E[r+72>>3],f=e=r+24|0,s=fr(r+32|0,32,6860,r+16|0)+(r+32|0)|0,o[f+4>>2]=s,o[e>>2]=r+32,n=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=n,Kt(t,r+8|0)}y=r+80|0},eA,function(e,t){t|=0;var r,A,n=0,a=0,c=0,f=0,s=0;if(y=r=y-112|0,A=o[(e=8+(e|=0)|0)>>2],1+(o[e+4>>2]-A|0)>>>0>=33){for(n=r+96|0,e=0;32!=(0|e);)c=((a=i[(1|e)+A|0])-48>>>0<10?-48:-87)+a|0,a=i[e+A|0],i[0|n]=c+((a-48>>>0<10?0:9)+a<<4),n=n+1|0,e=e+2|0;tA(r+96|0,n),ke(r+48|0,0,40),e=o[r+100>>2],o[r+16>>2]=o[r+96>>2],o[r+20>>2]=e,e=o[r+108>>2],o[r+24>>2]=o[r+104>>2],o[r+28>>2]=e,f=e=r+40|0,s=fr(r+48|0,40,6976,r+16|0)+(r+48|0)|0,o[f+4>>2]=s,o[e>>2]=r+48,n=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=n,Kt(t,r+8|0)}y=r+112|0},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-32|0,n=dr(r+24|0,7092),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+16|0,7095),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+32|0},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-48|0,n=dr(r+40|0,7240),A=o[n+4>>2],o[r+16>>2]=o[n>>2],o[r+20>>2]=A,Kt(t,r+16|0),n=o[e+12>>2],e=o[e+8>>2],o[r+8>>2]=e,o[r+12>>2]=n,o[r+32>>2]=e,o[r+36>>2]=n,Kt(t,r+8|0),e=dr(r+24|0,7249),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+48|0},eA,function(e,t){t|=0;var r,A,n,a=0,c=0;y=r=y-32|0,A=r,n=r;e:{t:{r:switch(o[8+(e|=0)>>2]){case 0:a=dr(r+24|0,7388);break t;case 1:a=dr(r+16|0,7391);break t;case 2:break r;default:break e}a=dr(r+8|0,7394)}c=o[a+4>>2],o[n>>2]=o[a>>2],o[A+4>>2]=c,Kt(t,r)}(e=o[e+12>>2])&&function(e,t){var r=0,A=0,n=0,a=0,c=0,f=0;if(y=r=y-48|0,t){for(A=r+37|0;t|n;)c=A=A-1|0,f=(a=t)-Qr(t=qr(t,n,10),n=D,10,0)|48,i[0|c]=f;o[4+(t=r+8|0)>>2]=r+37,o[t>>2]=A,a=o[t+4>>2],o[r>>2]=o[t>>2],o[r+4>>2]=a,Wt(e,r)}else kr(e,48);y=r+48|0}(t,e-1|0),y=r+32|0},eA,function(e,t){var r,A;e|=0,t|=0,y=e=y-16|0,r=dr(e+8|0,7516),A=o[r+4>>2],o[e>>2]=o[r>>2],o[e+4>>2]=A,Kt(t,e),y=e+16|0},nr,eA,function(e,t){t|=0;var r,A=0;y=r=y-16|0,A=o[12+(e|=0)>>2],aA[o[o[A>>2]+16>>2]](A,t),e=o[e+12>>2],(e=2==(0|(A=b[e+5|0]))?0|aA[o[o[e>>2]>>2]](e,t):!A)||(e=dr(r+8|0,7648),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r)),y=r+16|0},function(e,t){var r;t|=0,r=o[8+(e|=0)>>2],aA[o[o[r>>2]+16>>2]](r,t),1!=b[r+5|0]&&aA[o[o[r>>2]+20>>2]](r,t),e=o[e+12>>2],aA[o[o[e>>2]+20>>2]](e,t)},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-32|0,n=dr(r+24|0,7772),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),xe(e+12|0,t),e=dr(r+16|0,7782),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+32|0},nr,eA,function(e,t){var r,A;t|=0,y=r=y-16|0,e=o[8+(e|=0)>>2],aA[o[o[e>>2]+16>>2]](e,t),e=dr(r+8|0,7916),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r+16|0},function(e,t){t|=0,e=o[8+(e|=0)>>2],aA[o[o[e>>2]+20>>2]](e,t)},eA,function(e,t){e|=0,t|=0;var r,A=0,n=0;y=r=y-48|0,A=dr(r+40|0,8028),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),A=o[e+28>>2],n=o[e+24>>2],o[r+8>>2]=n,o[r+12>>2]=A,o[r+32>>2]=n,o[r+36>>2]=A,Kt(t,r+8|0),A=dr(r+24|0,7249),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),Pe(e,t),y=r+48|0},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-32|0,n=dr(r+24|0,8140),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),e=o[e+8>>2],45==b[e+4|0]&&Pe(e,t),e=dr(r+16|0,8143),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+32|0},eA,Xe,eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-32|0,n=dr(r+24|0,8260),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),n=o[e+12>>2],e=o[e+8>>2],o[r>>2]=e,o[r+4>>2]=n,o[r+16>>2]=e,o[r+20>>2]=n,Kt(t,r),y=r+32|0},eA,function(e,t){e|=0,t|=0;var r,A=0,n=0;y=r=y-112|0,o[r+108>>2]=e,o[r+104>>2]=t,kr(t,40),b[e+24|0]?((A=o[e+12>>2])&&(aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),kr(t,32),A=o[e+20>>2],n=o[e+16>>2],o[r+48>>2]=n,o[r+52>>2]=A,o[r+96>>2]=n,o[r+100>>2]=A,Kt(t,r+48|0),kr(t,32)),A=dr(r+88|0,8572),n=o[A+4>>2],o[r+40>>2]=o[A>>2],o[r+44>>2]=n,Kt(t,r+40|0),A=o[e+20>>2],e=o[e+16>>2],o[r+32>>2]=e,o[r+36>>2]=A,o[r+80>>2]=e,o[r+84>>2]=A,Kt(t,r+32|0),kr(t,32),Et(r+104|0)):(Et(r+104|0),kr(t,32),A=o[e+20>>2],n=o[e+16>>2],o[r+24>>2]=n,o[r+28>>2]=A,o[r+72>>2]=n,o[r+76>>2]=A,Kt(t,r+24|0),A=dr(r- -64|0,8577),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),o[e+12>>2]&&(kr(t,32),A=o[e+20>>2],n=o[e+16>>2],o[r+8>>2]=n,o[r+12>>2]=A,o[r+56>>2]=n,o[r+60>>2]=A,Kt(t,r+8|0),kr(t,32),e=o[e+12>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t))),kr(t,41),y=r+112|0},eA,pe,eA,function(e,t){t|=0;var r,A,n=0,i=0;y=r=y-128|0,qt(A=12+(e|=0)|0,dr(r+120|0,6118))&&(n=dr(r+112|0,6516),i=o[n+4>>2],o[r+56>>2]=o[n>>2],o[r+60>>2]=i,Kt(t,r+56|0)),n=dr(r+104|0,6516),i=o[n+4>>2],o[r+48>>2]=o[n>>2],o[r+52>>2]=i,Kt(t,r+48|0),n=o[e+8>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=dr(r+96|0,8804),i=o[n+4>>2],o[r+40>>2]=o[n>>2],o[r+44>>2]=i,Kt(t,r+40|0),n=o[e+16>>2],i=o[e+12>>2],o[r+32>>2]=i,o[r+36>>2]=n,o[r+88>>2]=i,o[r+92>>2]=n,Kt(t,r+32|0),n=dr(r+80|0,8807),i=o[n+4>>2],o[r+24>>2]=o[n>>2],o[r+28>>2]=i,Kt(t,r+24|0),e=o[e+20>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+72|0,6042),n=o[e+4>>2],o[r+16>>2]=o[e>>2],o[r+20>>2]=n,Kt(t,r+16|0),qt(A,dr(r+120|0,6118))&&(e=dr(r- -64|0,6042),n=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=n,Kt(t,r+8|0)),y=r+128|0},eA,function(e,t){t|=0;var r,A=0,n=0;y=r=y-48|0,A=o[12+(e|=0)>>2],n=o[e+8>>2],o[r+16>>2]=n,o[r+20>>2]=A,o[r+40>>2]=n,o[r+44>>2]=A,Kt(t,r+16|0),A=dr(r+32|0,6516),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),e=o[e+16>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+24|0,6042),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r+48|0},eA,function(e,t){t|=0;var r,A=0,n=0;y=r=y+-64|0,A=o[12+(e|=0)>>2],n=o[e+8>>2],o[r+24>>2]=n,o[r+28>>2]=A,o[r+56>>2]=n,o[r+60>>2]=A,Kt(t,r+24|0),A=dr(r+48|0,6130),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),A=o[e+16>>2],aA[o[o[A>>2]+16>>2]](A,t),A=dr(r+40|0,9016),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),e=o[e+20>>2],aA[o[o[e>>2]+16>>2]](e,t),e=dr(r+32|0,6042),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r- -64|0},eA,function(e,t){t|=0;var r,A,n=0;y=r=y-32|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=dr(r+24|0,6516),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),xe(e+12|0,t),e=dr(r+16|0,6042),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+32|0},eA,function(e,t){e|=0,t|=0;var r,A=0,n=0;y=r=y-48|0,A=dr(r+40|0,6516),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),A=dr(r+32|0,9232),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),xe(e+12|0,t),e=dr(r+24|0,6042),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r+48|0},eA,function(e,t){t|=0;var r,A=0,n=0;y=r=y-48|0,b[12+(e|=0)|0]&&(A=dr(r+40|0,5904),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0)),A=dr(r+32|0,9340),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),b[e+13|0]&&(A=dr(r+24|0,9347),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r)),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+48|0},eA,Ve,br,eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,6080),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),y=r+16|0},eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,10216),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,10336),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,5904),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},lr,eA,function(e,t){t|=0;var r,A,n=0;y=r=y-16|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=o[e+16>>2],A=o[e+12>>2],o[r>>2]=A,o[r+4>>2]=n,o[r+8>>2]=A,o[r+12>>2]=n,Kt(t,r),e=o[e+20>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},eA,function(e,t){e|=0,t|=0;var r,A=0,n=0;y=r=y-48|0,A=dr(r+40|0,6516),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),A=dr(r+32|0,10676),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),e=o[e+12>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+24|0,10679),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r+48|0},eA,function(e,t){t|=0;var r,A=0,n=0;y=r=y-16|0,b[16+(e|=0)|0]?(kr(t,91),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),kr(t,93)):(kr(t,46),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t)),A=o[e+12>>2],(b[A+4|0]-73&255)>>>0>=2&&(A=dr(r+8|0,10788),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),A=o[e+12>>2]),aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),y=r+16|0},eA,function(e,t){e|=0;var r,A=0,n=0;y=r=y-32|0,kr(t|=0,91),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),A=dr(r+24|0,10900),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),A=o[e+12>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),kr(t,93),A=o[e+16>>2],(b[A+4|0]-73&255)>>>0>=2&&(A=dr(r+16|0,10788),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),A=o[e+16>>2]),aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),y=r+32|0},eA,function(e,t){var r;t|=0,(r=o[8+(e|=0)>>2])&&(aA[o[o[r>>2]+16>>2]](r,t),1!=b[r+5|0]&&aA[o[o[r>>2]+20>>2]](r,t)),kr(t,123),xe(e+12|0,t),kr(t,125)},eA,Xe,eA,function(e,t){t|=0;var r,A=0,n=0,i=0;y=r=y-112|0,b[28+(e|=0)|0]&&(A=dr(r+104|0,11232),n=o[A+4>>2],o[r+48>>2]=o[A>>2],o[r+52>>2]=n,Kt(t,r+48|0)),A=dr(r+96|0,11244),n=o[A+4>>2],o[r+40>>2]=o[A>>2],o[r+44>>2]=n,Kt(t,r+40|0),b[e+29|0]&&(A=dr(r+88|0,8140),n=o[A+4>>2],o[r+32>>2]=o[A>>2],o[r+36>>2]=n,Kt(t,r+32|0)),kr(t,32),o[4+(A=e+8|0)>>2]&&(n=dr(r+80|0,6516),i=o[n+4>>2],o[r+24>>2]=o[n>>2],o[r+28>>2]=i,Kt(t,r+24|0),xe(A,t),A=dr(r+72|0,6042),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0)),A=o[e+16>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),o[4+(e=e+20|0)>>2]&&(A=dr(r- -64|0,6516),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),xe(e,t),e=dr(r+56|0,6042),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r)),y=r+112|0},eA,function(e,t){t|=0;var r,A,n,i=0;y=r=y-32|0,A=o[12+(e|=0)>>2],i=r,n=o[e+8>>2],o[i+8>>2]=n,o[i+12>>2]=A,o[i+24>>2]=n,o[i+28>>2]=A,Kt(t,i+8|0),i=o[e+16>>2],aA[o[o[i>>2]+16>>2]](i,t),1!=b[i+5|0]&&aA[o[o[i>>2]+20>>2]](i,t),i=o[e+24>>2],e=o[e+20>>2],o[r>>2]=e,o[r+4>>2]=i,o[r+16>>2]=e,o[r+20>>2]=i,Kt(t,r),y=r+32|0},eA,function(e,t){e|=0,t|=0;var r,A=0,n=0;y=r=y+-64|0,A=dr(r+56|0,6516),n=o[A+4>>2],o[r+24>>2]=o[A>>2],o[r+28>>2]=n,Kt(t,r+24|0),A=o[e+8>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),A=dr(r+48|0,11464),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),A=o[e+12>>2],aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t),A=dr(r+40|0,11470),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),e=o[e+16>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+32|0,6042),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r- -64|0},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-48|0,n=dr(r+40|0,11588),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),pe(Ar(r+24|0,o[e+8>>2]),t),e=dr(r+16|0,6042),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+48|0},eA,Wr,eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,11812),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-32|0,n=dr(r+24|0,11924),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Wt(t,r+8|0),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+16|0,6042),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Wt(t,r),y=r+32|0},eA,function(e,t){t|=0;var r,A,n,i=0;y=r=y+-64|0,A=r,n=r;e:{t:{r:switch(o[8+(e|=0)>>2]){case 0:e=dr(r+56|0,12056);break t;case 1:e=dr(r+48|0,12071);break t;case 2:e=dr(r+40|0,12089);break t;case 3:e=dr(r+32|0,12160);break t;case 4:e=dr(r+24|0,12210);break t;case 5:break r;default:break e}e=dr(r+16|0,12260)}e=o[(i=e)+4>>2],o[n+8>>2]=o[i>>2],o[A+12>>2]=e,Kt(t,r+8|0)}y=r- -64|0},function(e,t){dr(e|=0,o[15880+(o[8+(t|=0)>>2]<<2)>>2])},eA,function(e,t){t|=0;var r,A=0,n=0;y=r=y-32|0,b[12+(e|=0)|0]&&(A=dr(r+24|0,6080),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0)),e=o[e+8>>2],aA[o[o[e>>2]+24>>2]](r+16|0,e),e=o[r+20>>2],o[r>>2]=o[r+16>>2],o[r+4>>2]=e,Kt(t,r),y=r+32|0},eA,function(e,t){t|=0;var r,A,n=0;y=r=y-48|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),n=dr(r+40|0,12592),A=o[n+4>>2],o[r+16>>2]=o[n>>2],o[r+20>>2]=A,Kt(t,r+16|0),n=o[e+16>>2],e=o[e+12>>2],o[r+8>>2]=e,o[r+12>>2]=n,o[r+32>>2]=e,o[r+36>>2]=n,Kt(t,r+8|0),e=dr(r+24|0,10679),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+48|0},eA,function(e,t){e|=0,kr(t|=0,91),xe(e+8|0,t),kr(t,93)},eA,Ve,eA,function(e,t){t|=0;var r,A,n,i=0;y=r=y+-64|0,A=r,n=r;e:{t:{r:switch(o[8+(e|=0)>>2]){case 0:e=dr(r+56|0,12056);break t;case 1:e=dr(r+48|0,12071);break t;case 2:e=dr(r+40|0,12952);break t;case 3:e=dr(r+32|0,12964);break t;case 4:e=dr(r+24|0,12977);break t;case 5:break r;default:break e}e=dr(r+16|0,12990)}e=o[(i=e)+4>>2],o[n+8>>2]=o[i>>2],o[A+12>>2]=e,Kt(t,r+8|0)}y=r- -64|0},function(e,t){dr(e|=0,o[15904+(o[8+(t|=0)>>2]<<2)>>2])},eA,function(e,t){e|=0;var r=0;return-1==o[16+(t|=0)>>2]&&(r=o[e+12>>2],o[t+12>>2]=0,o[t+16>>2]=r),(r=o[t+12>>2])>>>0<d[e+12>>2]?(e=o[o[e+8>>2]+(r<<2)>>2],e=2==(0|(r=b[e+5|0]))?0|aA[o[o[e>>2]>>2]](e,t):!r):e=0,0|e},function(e,t){e|=0;var r=0;return-1==o[16+(t|=0)>>2]&&(r=o[e+12>>2],o[t+12>>2]=0,o[t+16>>2]=r),(r=o[t+12>>2])>>>0<d[e+12>>2]?(e=o[o[e+8>>2]+(r<<2)>>2],e=2==(0|(r=b[e+6|0]))?0|aA[o[o[e>>2]+4>>2]](e,t):!r):e=0,0|e},function(e,t){e|=0;var r=0;return-1==o[16+(t|=0)>>2]&&(r=o[e+12>>2],o[t+12>>2]=0,o[t+16>>2]=r),(r=o[t+12>>2])>>>0<d[e+12>>2]?(e=o[o[e+8>>2]+(r<<2)>>2],e=2==(0|(r=b[e+7|0]))?0|aA[o[o[e>>2]+8>>2]](e,t):!r):e=0,0|e},function(e,t){e|=0;var r=0;return-1==o[16+(t|=0)>>2]&&(r=o[e+12>>2],o[t+12>>2]=0,o[t+16>>2]=r),(r=o[t+12>>2])>>>0<d[e+12>>2]&&(e=o[o[e+8>>2]+(r<<2)>>2],e=0|aA[o[o[e>>2]+12>>2]](e,t)),0|e},function(e,t){e|=0;var r=0;-1==o[16+(t|=0)>>2]&&(r=o[e+12>>2],o[t+12>>2]=0,o[t+16>>2]=r),(r=o[t+12>>2])>>>0<d[e+12>>2]&&(e=o[o[e+8>>2]+(r<<2)>>2],aA[o[o[e>>2]+16>>2]](e,t))},function(e,t){e|=0;var r=0;-1==o[16+(t|=0)>>2]&&(r=o[e+12>>2],o[t+12>>2]=0,o[t+16>>2]=r),(r=o[t+12>>2])>>>0<d[e+12>>2]&&(e=o[o[e+8>>2]+(r<<2)>>2],aA[o[o[e>>2]+20>>2]](e,t))},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-48|0,n=dr(r+40|0,6130),A=o[n+4>>2],o[r+16>>2]=o[n>>2],o[r+20>>2]=A,Kt(t,r+16|0),xe(e+8|0,t),e=0,(n=o[t+4>>2])&&(e=b[(n+o[t>>2]|0)-1|0]),62==(0|e)&&(e=dr(r+32|0,7648),n=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=n,Kt(t,r+8|0)),e=dr(r+24|0,6118),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+48|0},eA,function(e,t){var r;t|=0,r=o[8+(e|=0)>>2],aA[o[o[r>>2]+16>>2]](r,t),1!=b[r+5|0]&&aA[o[o[r>>2]+20>>2]](r,t),e=o[e+12>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t)},lr,eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,13484),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},lr,eA,Wr,eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,13716),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),xe(e+8|0,t),kr(t,93),y=r+16|0},eA,Lr,Lr,function(e,t){t|=0;var r,A=0,n=0;y=r=y-16|0,(A=o[8+(e|=0)>>2])&&(aA[o[o[A>>2]+16>>2]](A,t),A=o[e+8>>2],(A=2==(0|(n=b[A+5|0]))?0|aA[o[o[A>>2]>>2]](A,t):!n)||(A=dr(r+8|0,7648),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r))),e=o[e+12>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},function(e,t){e|=0,t|=0;var r,A,n=0,i=0,a=0;y=r=y-112|0,i=dr(r+104|0,6516),n=o[i+4>>2],o[r+48>>2]=o[i>>2],o[r+52>>2]=n,Kt(t,r+48|0),xe(e+16|0,t),i=dr(r+96|0,6042),n=o[i+4>>2],o[r+40>>2]=o[i>>2],o[r+44>>2]=n,Kt(t,r+40|0),(n=o[e+8>>2])&&aA[o[o[n>>2]+20>>2]](n,t),1&(n=o[e+28>>2])&&(i=dr(r+88|0,13844),n=o[i+4>>2],o[r+32>>2]=o[i>>2],o[r+36>>2]=n,Kt(t,r+32|0),n=o[e+28>>2]),2&n&&(i=dr(r+80|0,13851),n=o[i+4>>2],o[r+24>>2]=o[i>>2],o[r+28>>2]=n,Kt(t,r+24|0),n=o[e+28>>2]),4&n&&(i=dr(r+72|0,13861),n=o[i+4>>2],o[r+16>>2]=o[i>>2],o[r+20>>2]=n,Kt(t,r+16|0)),n=r,A=r;e:{t:{r:switch(b[e+32|0]-1|0){case 0:a=dr(r- -64|0,13871);break t;case 1:break r;default:break e}a=dr(r+56|0,13874)}i=o[a+4>>2],o[A+8>>2]=o[a>>2],o[n+12>>2]=i,Kt(t,r+8|0)}(e=o[e+24>>2])&&(aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t)),y=r+112|0},eA,function(e,t){t|=0;var r,A,n=0;y=r=y-48|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=dr(r+40|0,8807),A=o[n+4>>2],o[r+16>>2]=o[n>>2],o[r+20>>2]=A,Kt(t,r+16|0),n=o[e+16>>2],e=o[e+12>>2],o[r+8>>2]=e,o[r+12>>2]=n,o[r+32>>2]=e,o[r+36>>2]=n,Kt(t,r+8|0),e=dr(r+24|0,6042),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+48|0},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-32|0,n=dr(r+24|0,14348),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+16|0,6042),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+32|0},eA,function(e,t){var r,A,n;e|=0,t|=0,y=r=y-16|0,A=dr(r+8|0,14476),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),xe(e+8|0,t),kr(t,41),y=r+16|0},eA,Lr,Lr,function(e,t){var r,A;t|=0,y=r=y-16|0,e=o[8+(e|=0)>>2],aA[o[o[e>>2]+16>>2]](e,t),e=dr(r+8|0,7648),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),y=r+16|0},function(e,t){e|=0,t|=0;var r,A,n=0,i=0,a=0;y=r=y-112|0,i=dr(r+104|0,6516),n=o[i+4>>2],o[r+48>>2]=o[i>>2],o[r+52>>2]=n,Kt(t,r+48|0),xe(e+12|0,t),i=dr(r+96|0,6042),n=o[i+4>>2],o[r+40>>2]=o[i>>2],o[r+44>>2]=n,Kt(t,r+40|0),n=o[e+8>>2],aA[o[o[n>>2]+20>>2]](n,t),1&(n=o[e+20>>2])&&(i=dr(r+88|0,13844),n=o[i+4>>2],o[r+32>>2]=o[i>>2],o[r+36>>2]=n,Kt(t,r+32|0),n=o[e+20>>2]),2&n&&(i=dr(r+80|0,13851),n=o[i+4>>2],o[r+24>>2]=o[i>>2],o[r+28>>2]=n,Kt(t,r+24|0),n=o[e+20>>2]),4&n&&(i=dr(r+72|0,13861),n=o[i+4>>2],o[r+16>>2]=o[i>>2],o[r+20>>2]=n,Kt(t,r+16|0)),n=r,A=r;e:{t:{r:switch(b[e+24|0]-1|0){case 0:a=dr(r- -64|0,13871);break t;case 1:break r;default:break e}a=dr(r+56|0,13874)}i=o[a+4>>2],o[A+8>>2]=o[a>>2],o[n+12>>2]=i,Kt(t,r+8|0)}o[e+28>>2]&&(kr(t,32),e=o[e+28>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t)),y=r+112|0},eA,function(e,t){t|=0;var r,A,n=0;y=r=y-48|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=dr(r+40|0,6130),A=o[n+4>>2],o[r+16>>2]=o[n>>2],o[r+20>>2]=A,Kt(t,r+16|0),n=o[e+16>>2],e=o[e+12>>2],o[r+8>>2]=e,o[r+12>>2]=n,o[r+32>>2]=e,o[r+36>>2]=n,Kt(t,r+8|0),e=dr(r+24|0,6118),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+48|0},eA,function(e,t){t|=0;var r,A,n=0;y=r=y-32|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=dr(r+24|0,7648),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),n=o[e+16>>2],e=o[e+12>>2],o[r>>2]=e,o[r+4>>2]=n,o[r+16>>2]=e,o[r+20>>2]=n,Kt(t,r),y=r+32|0},eA,Yt,function(e,t){var r;return t|=0,e=o[12+(e|=0)>>2],0|(e=2==(0|(r=b[e+6|0]))?0|aA[o[o[e>>2]+4>>2]](e,t):!r)},function(e,t){var r;return t|=0,e=o[12+(e|=0)>>2],0|(e=2==(0|(r=b[e+7|0]))?0|aA[o[o[e>>2]+8>>2]](e,t):!r)},function(e,t){var r;t|=0,r=o[12+(e|=0)>>2],aA[o[o[r>>2]+16>>2]](r,t),function(e,t){var r,A=0,n=0;y=r=y-48|0,1&(A=o[e+8>>2])&&(A=dr(r+40|0,13844),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),A=o[e+8>>2]),2&A&&(A=dr(r+32|0,13851),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),A=o[e+8>>2]),4&A&&(e=dr(r+24|0,13861),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r)),y=r+48|0}(e,t)},function(e,t){t|=0,e=o[12+(e|=0)>>2],aA[o[o[e>>2]+20>>2]](e,t)},eA,function(e,t){e|=0,t|=0;var r,A,n=0;y=r=y-32|0,n=dr(r+24|0,15036),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),e=dr(r+16|0,10679),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+32|0},eA,function(e,t){t|=0;var r,A,n=0;y=r=y-32|0,n=o[8+(e|=0)>>2],aA[o[o[n>>2]+16>>2]](n,t),1!=b[n+5|0]&&aA[o[o[n>>2]+20>>2]](n,t),n=dr(r+24|0,15156),A=o[n+4>>2],o[r+8>>2]=o[n>>2],o[r+12>>2]=A,Kt(t,r+8|0),(e=o[e+12>>2])&&(aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t)),e=dr(r+16|0,10679),n=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=n,Kt(t,r),y=r+32|0},eA,Lr,Lr,function(e,t){t|=0,e=o[8+(e|=0)>>2],aA[o[o[e>>2]+16>>2]](e,t)},function(e,t){e|=0;var r,A=0,n=0;y=r=y-48|0,A=0,(n=o[4+(t|=0)>>2])&&(A=b[(n+o[t>>2]|0)-1|0]),93!=(0|A)&&(A=dr(r+40|0,7648),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0)),A=dr(r+32|0,15272),n=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=n,Kt(t,r+8|0),(A=o[e+12>>2])&&(aA[o[o[A>>2]+16>>2]](A,t),1!=b[A+5|0]&&aA[o[o[A>>2]+20>>2]](A,t)),A=dr(r+24|0,10679),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r),e=o[e+8>>2],aA[o[o[e>>2]+20>>2]](e,t),y=r+48|0},eA,Yt,function(e,t){t|=0;var r,A=0,n=0;y=r=y-48|0,A=o[12+(e|=0)>>2],aA[o[o[A>>2]+16>>2]](A,t),A=o[e+12>>2],A=(A=2==(0|(n=b[A+6|0]))?0|aA[o[o[A>>2]+4>>2]](A,t):!n)||(A=o[e+12>>2],A=2==(0|(n=b[A+7|0]))?0|aA[o[o[A>>2]+8>>2]](A,t):!n)?dr(r+40|0,6516):dr(r+32|0,7648),n=o[A+4>>2],o[r+16>>2]=o[A>>2],o[r+20>>2]=n,Kt(t,r+16|0),e=o[e+8>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),A=dr(r+24|0,15388),e=o[A+4>>2],o[r+8>>2]=o[A>>2],o[r+12>>2]=e,Kt(t,r+8|0),y=r+48|0},function(e,t){t|=0;var r,A=0,n=0;y=r=y-16|0,A=o[12+(e|=0)>>2],((A=2==(0|(n=b[A+6|0]))?0|aA[o[o[A>>2]+4>>2]](A,t):!n)||(A=o[e+12>>2],A=2==(0|(n=b[A+7|0]))?0|aA[o[o[A>>2]+8>>2]](A,t):!n))&&(A=dr(r+8|0,6042),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r)),e=o[e+12>>2],aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},eA,function(e,t){var r,A,n;t|=0,y=r=y-16|0,A=o[12+(e|=0)>>2],n=o[e+8>>2],o[r>>2]=n,o[r+4>>2]=A,o[r+8>>2]=n,o[r+12>>2]=A,Kt(t,r),kr(t,32),e=o[e+16>>2],aA[o[o[e>>2]+16>>2]](e,t),1!=b[e+5|0]&&aA[o[o[e>>2]+20>>2]](e,t),y=r+16|0},eA,Vt,function(e,t){t|=0;var r,A=0,n=0,i=0;y=r=y-96|0;e:{t:{if(A=o[8+(e|=0)>>2],10==b[A+4|0]){if(Ft(A))break t;A=o[e+8>>2]}aA[o[o[A>>2]+16>>2]](A,t),A=t,n=o[e+8>>2],(n=2==(0|(i=b[n+6|0]))?0|aA[o[o[n>>2]+4>>2]](n,A):!i)&&(n=dr(r+88|0,7648),i=o[n+4>>2],o[r+40>>2]=o[n>>2],o[r+44>>2]=i,Kt(A,r+40|0)),A=o[e+8>>2],((A=2==(0|(n=b[A+6|0]))?0|aA[o[o[A>>2]+4>>2]](A,t):!n)||(e=o[e+8>>2],e=2==(0|(A=b[e+7|0]))?0|aA[o[o[e>>2]+8>>2]](e,t):!A))&&(e=dr(r+80|0,6516),A=o[e+4>>2],o[r+32>>2]=o[e>>2],o[r+36>>2]=A,Kt(t,r+32|0)),e=dr(r+72|0,6095);break e}e=o[e+8>>2],A=dr(r- -64|0,15640),n=o[A+4>>2],o[r+24>>2]=o[A>>2],o[r+28>>2]=n,Kt(t,r+24|0),A=o[e+16>>2],e=o[e+12>>2],o[r+16>>2]=e,o[r+20>>2]=A,o[r+56>>2]=e,o[r+60>>2]=A,Kt(t,r+16|0),e=dr(r+48|0,6118)}A=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=A,Kt(t,r+8|0),y=r+96|0},function(e,t){t|=0;var r,A=0,n=0;y=r=y-16|0,A=o[8+(e|=0)>>2];e:{if(10==b[A+4|0]){if(Ft(A))break e;A=o[e+8>>2]}((A=2==(0|(n=b[A+6|0]))?0|aA[o[o[A>>2]+4>>2]](A,t):!n)||(A=o[e+8>>2],A=2==(0|(n=b[A+7|0]))?0|aA[o[o[A>>2]+8>>2]](A,t):!n))&&(A=dr(r+8|0,6042),n=o[A+4>>2],o[r>>2]=o[A>>2],o[r+4>>2]=n,Kt(t,r)),e=o[e+8>>2],aA[o[o[e>>2]+20>>2]](e,t)}y=r+16|0},eA,Vt,function(e,t){t|=0;var r,A=0,n=0;y=r=y+-64|0,b[16+(e|=0)|0]||(n=Nt(r+56|0,e+16|0,1),st(r+48|0,e,t),e=o[r+52>>2],aA[o[o[e>>2]+16>>2]](e,t),e=o[r+52>>2],(e=2==(0|(A=b[e+6|0]))?0|aA[o[o[e>>2]+4>>2]](e,t):!A)&&(e=dr(r+40|0,7648),A=o[e+4>>2],o[r+16>>2]=o[e>>2],o[r+20>>2]=A,Kt(t,r+16|0)),e=o[r+52>>2],((e=2==(0|(A=b[e+6|0]))?0|aA[o[o[e>>2]+4>>2]](e,t):!A)||(e=o[r+52>>2],e=2==(0|(A=b[e+7|0]))?0|aA[o[o[e>>2]+8>>2]](e,t):!A))&&(e=dr(r+32|0,6516),A=o[e+4>>2],o[r+8>>2]=o[e>>2],o[r+12>>2]=A,Kt(t,r+8|0)),e=dr(r+24|0,o[r+48>>2]?6047:6050),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r),b[n+5|0]&&(i[o[n>>2]]=b[n+4|0])),y=r- -64|0},function(e,t){t|=0;var r,A=0,n=0;y=r=y-32|0,b[16+(e|=0)|0]||(n=Nt(r+24|0,e+16|0,1),st(r+16|0,e,t),e=o[r+20>>2],((e=2==(0|(A=b[e+6|0]))?0|aA[o[o[e>>2]+4>>2]](e,t):!A)||(e=o[r+20>>2],e=2==(0|(A=b[e+7|0]))?0|aA[o[o[e>>2]+8>>2]](e,t):!A))&&(e=dr(r+8|0,6042),A=o[e+4>>2],o[r>>2]=o[e>>2],o[r+4>>2]=A,Kt(t,r)),e=o[r+20>>2],aA[o[o[e>>2]+20>>2]](e,t),b[n+5|0]&&(i[o[n>>2]]=b[n+4|0])),y=r+32|0},eA,function(e,t){t|=0;var r,A=0;y=r=y-16|0,A=o[8+(e|=0)>>2],aA[o[o[A>>2]+16>>2]](A,t),A=o[e+16>>2],e=o[e+12>>2],o[r>>2]=e,o[r+4>>2]=A,o[r+8>>2]=e,o[r+12>>2]=A,Kt(t,r),y=r+16|0},eA]).set=function(e,t){this[e]=t},iA.get=function(e){return this[e]},iA);function oA(){return A.byteLength/65536|0}return{__wasm_call_ctors:rA,malloc:M,free:N,ZSTD_isError:function(e){return(e|=0)>>>0>4294967176|0},ZSTD_getErrorName:function(e){return 1024},ZSTD_getFrameContentSize:function(e,t){return e=function(e,t){var r,A=0,n=0;return y=r=y-48|0,n=-1,A=-2,L(r+8|0,e,t,0)||(n=0,A=0,1!=o[r+28>>2]&&(n=o[r+12>>2],A=o[r+8>>2])),y=r+48|0,D=n,A}(e|=0,t|=0),w(0|D),0|e},ZSTD_decompress:function(e,t,r,A){e|=0,t|=0,r|=0,A|=0;var n,i=0;return(n=M(160800))?(o[n+29556>>2]=0,o[n+29548>>2]=0,o[n+29552>>2]=0,o[n+160796>>2]=0,o[n+29636>>2]=0,o[n+29640>>2]=0,o[n+29620>>2]=0,o[n+29368>>2]=0,o[n+29568>>2]=0,o[n+29572>>2]=0,o[n+29532>>2]=0,o[n+29588>>2]=0,o[n+29592>>2]=0,o[n+29652>>2]=0,o[n+29656>>2]=0,o[n+29612>>2]=134217729,o[n+29536>>2]=0,o[n+29540>>2]=1,o[(i=n+29576|0)>>2]=0,o[i+4>>2]=0,o[(i=n+29596|0)>>2]=0,o[i+4>>2]=0,o[n+29604>>2]=0,e=function(e,t,r,A,n){var i=0,a=0,c=0,f=0,s=0;i=e,a=t,c=r,f=A,s=n;e:{t:switch(o[e+29592>>2]+1|0){default:r:if(t=o[e+29576>>2]){A=o[t+27320>>2],r=o[t+27316>>2];A:{n:{if(n=o[t>>2]){if(!r)break n;aA[0|r](A,n),aA[0|r](A,t);break r}if(!r)break A;aA[0|r](A,t);break r}N(n)}N(t)}o[e+29592>>2]=0,o[e+29576>>2]=0,o[e+29580>>2]=0,e=0;break e;case 2:o[e+29592>>2]=0;break;case 0:}e=o[e+29580>>2]}return j(i,a,c,f,s,e)}(n,e,t,r,A),we(n),0|e):-64},ZSTD_createDStream:function(){var e,t=0;return(e=M(160800))?(o[e+29556>>2]=0,o[e+29548>>2]=0,o[e+29552>>2]=0,o[e+160796>>2]=0,o[e+29636>>2]=0,o[e+29640>>2]=0,o[e+29620>>2]=0,o[e+29368>>2]=0,o[e+29568>>2]=0,o[e+29572>>2]=0,o[e+29532>>2]=0,o[e+29588>>2]=0,o[e+29592>>2]=0,o[e+29652>>2]=0,o[e+29656>>2]=0,o[e+29612>>2]=134217729,o[e+29536>>2]=0,o[e+29540>>2]=1,o[(t=e+29576|0)>>2]=0,o[t+4>>2]=0,o[(t=e+29596|0)>>2]=0,o[t+4>>2]=0,o[e+29604>>2]=0,0|e):0},ZSTD_freeDStream:function(e){return 0|we(e|=0)},ZSTD_DStreamInSize:function(){return 131075},ZSTD_DStreamOutSize:function(){return 131072},ZSTD_initDStream:function(e){var t,r=0,A=0,n=0;o[29652+(e|=0)>>2]=0,o[e+29596>>2]=0;e:if(t=o[e+29576>>2]){A=o[t+27320>>2],r=o[t+27316>>2];t:{r:{if(n=o[t>>2]){if(!r)break r;aA[0|r](A,n),aA[0|r](A,t);break e}if(!r)break t;aA[0|r](A,t);break e}N(n)}N(t)}return o[e+29592>>2]=0,o[e+29576>>2]=0,o[e+29580>>2]=0,0|(o[e+29532>>2]?1:5)},ZSTD_decompressStream_simpleArgs:function(e,t,r,A,n,i,a){var c;return e|=0,t|=0,r|=0,A|=0,n|=0,i|=0,a|=0,y=c=y-32|0,o[c+20>>2]=r,o[c+16>>2]=t,o[c+24>>2]=o[A>>2],o[c+4>>2]=i,o[c>>2]=n,o[c+8>>2]=o[a>>2],e=function(e,t,r){var A,n=0,i=0,a=0,c=0,f=0,s=0,u=0,l=0,k=0,E=0,T=0,g=0,h=0,O=0,_=0,w=0,R=0,D=0,m=0,v=0,F=0,S=0,P=0,B=0,X=0,U=0,I=0,x=0,Q=0,Z=0;y=A=y-16|0;e:if((k=o[r+8>>2])>>>0>(E=o[r+4>>2])>>>0)n=-72;else if((i=o[t+8>>2])>>>0>(f=o[t+4>>2])>>>0)n=-70;else if(s=o[t>>2],T=o[r>>2],!o[e+29596>>2]|1!=o[e+29656>>2]||(n=-104,!((0|f)!=o[e+29664>>2]|(0|s)!=o[e+29660>>2]|(0|i)!=o[e+29668>>2]))){for(O=f+s|0,U=e+4120|0,I=e+6176|0,x=e+16|0,m=e+26668|0,Q=e+10280|0,Z=e+29356|0,F=(E=E+T|0)-(T=k+T|0)|0,_=e+160776|0,k=e+29376|0,w=e+29660|0,R=e+29428|0,D=e+29604|0,g=e+29620|0,S=e+160796|0,P=h=e+29480|0,f=T,s=v=i+s|0;;){t:{n=-1,i=k,a=_;r:{A:{n:{i:{a:{o:{c:{f:switch(o[e+29596>>2]){case 3:c=o[R>>2],i=o[e+29372>>2];break A;case 1:n=o[e+29632>>2];break c;case 4:break a;case 2:break o;case 0:break f;default:break e}o[e+29596>>2]=1,o[e+29624>>2]=0,o[e+29628>>2]=0,o[e+29644>>2]=0,o[e+29648>>2]=0,o[e+29632>>2]=0,o[e+29608>>2]=0,n=o[t+4>>2],o[w>>2]=o[t>>2],o[w+4>>2]=n,o[w+8>>2]=o[t+8>>2],n=0}if((n=L(i,a,n,o[e+29532>>2]))>>>0>4294967176)break e;if(n){if((c=n-(i=o[e+29632>>2])|0)>>>0>(a=E-f|0)>>>0){a&&(re(160776+(e+i|0)|0,f,a),i=a+o[e+29632>>2]|0,o[e+29632>>2]=i),o[r+8>>2]=o[r+4>>2],n=3+(((e=o[e+29532>>2]?2:6)>>>0>n>>>0?e:n)-i|0)|0;break e}re(160776+(e+i|0)|0,f,c),o[e+29632>>2]=n,f=c+f|0,i=1;break r}if(!(-1==(0|(a=o[k>>2]))&-1==(0|(i=o[k+4>>2]))|1==o[e+29396>>2]||!i&(n=a)>>>0>(a=O-s|0)>>>0|i||(ce(A,T,F),(i=o[A>>2])>>>0>F>>>0))){f=e,u=s,k=T,l=i;c:{switch(o[e+29592>>2]+1|0){default:f:if(h=o[e+29576>>2]){c=o[h+27320>>2],n=o[h+27316>>2];s:{u:{if(g=o[h>>2]){if(!n)break u;aA[0|n](c,g),aA[0|n](c,h);break f}if(!n)break s;aA[0|n](c,h);break f}N(g)}N(h)}o[e+29592>>2]=0,o[e+29576>>2]=0,o[e+29580>>2]=0,n=0;break c;case 2:o[e+29592>>2]=0;break;case 0:}n=o[e+29580>>2]}if((n=j(f,u,a,k,l,n))>>>0>4294967176)break e;o[e+29596>>2]=0,o[e+29372>>2]=0,s=n+s|0,f=i+T|0;break t}if(!(1==o[e+29396>>2]|1!=o[e+29656>>2]||-1==(0|(i=o[k>>2]))&-1==(0|(a=o[k+4>>2]))|!a&O-s>>>0>=i>>>0)){n=-70;break e}c:{switch(o[e+29592>>2]+1|0){default:f:if(i=o[e+29576>>2]){n=o[i+27320>>2],a=o[i+27316>>2];s:{u:{if(c=o[i>>2]){if(!a)break u;aA[0|a](n,c),aA[0|a](n,i);break f}if(!a)break s;aA[0|a](n,i);break f}N(c)}N(i)}n=0,o[e+29592>>2]=0,o[e+29576>>2]=0,o[e+29580>>2]=0,a=1;break c;case 2:o[e+29592>>2]=0;break;case 0:}(n=o[e+29580>>2])?(o[e+29588>>2]=o[e+29368>>2]!=(o[n+4>>2]+o[n+8>>2]|0),a=0):(n=0,a=1)}if(o[e+29416>>2]=0,o[e+29420>>2]=0,o[R>>2]=0,c=o[e+29532>>2],o[e+29372>>2]=c?1:5,o[(i=Z)+8>>2]=0,o[i+12>>2]=0,o[i>>2]=0,o[i+4>>2]=0,o[e+10280>>2]=201326604,o[e+29584>>2]=0,o[e+29432>>2]=0,o[e+29436>>2]=0,o[e+29424>>2]=3,o[m+8>>2]=o[610],i=o[609],o[m>>2]=o[608],o[m+4>>2]=i,o[e+12>>2]=Q,o[e+8>>2]=U,o[e+4>>2]=I,o[e>>2]=x,a||(o[e+29584>>2]=o[n+27304>>2],i=o[n+4>>2],o[e+29364>>2]=i,o[e+29360>>2]=i,i=i+o[n+8>>2]|0,o[e+29356>>2]=i,o[e+29368>>2]=i,o[n+27308>>2]?(o[e+29432>>2]=1,o[e+29436>>2]=1,o[e+12>>2]=n+10276,o[e+8>>2]=n+4116,o[e+4>>2]=n+6172,o[e>>2]=n+12,o[e+26668>>2]=o[n+26664>>2],o[e+26672>>2]=o[n+26668>>2],o[e+26676>>2]=o[n+26672>>2]):(o[e+29432>>2]=0,o[e+29436>>2]=0)),407710288!=(-16&(b[0|_]|b[_+1|0]<<8|b[_+2|0]<<16|b[_+3|0]<<24))){if((n=L(k,_,o[e+29632>>2],c))>>>0>4294967176)break e;if(n){n=-72;break e}if(!(!(i=o[e+29404>>2])|(0|i)==o[e+29584>>2])){n=-32;break e}o[e+29408>>2]?(i=o[e+29536>>2],o[e+29540>>2]=!i,c=2,n=3,i||(o[e+29472>>2]=2048144761,o[e+29476>>2]=1640531534,o[e+29464>>2]=0,o[e+29468>>2]=0,o[e+29456>>2]=668265295,o[e+29460>>2]=-1028477379,o[e+29448>>2]=-1379879466,o[e+29452>>2]=1625958382,o[e+29440>>2]=0,o[e+29444>>2]=0,o[P+32>>2]=0,o[P+36>>2]=0,o[(i=h)+24>>2]=0,o[i+28>>2]=0,o[i+16>>2]=0,o[i+20>>2]=0,o[i+8>>2]=0,o[i+12>>2]=0,o[i>>2]=0,o[i+4>>2]=0)):(o[e+29540>>2]=0,c=2,n=3)}else n=b[e+160780|0]|b[e+160781|0]<<8|b[e+160782|0]<<16|b[e+160783|0]<<24,c=7;if(o[e+29372>>2]=n,o[R>>2]=c,a=o[e+29384>>2],a=(n=!(i=o[e+29388>>2])&a>>>0>1024|0!=(0|i))?a:1024,n=i=n?i:0,o[e+29384>>2]=a,o[e+29388>>2]=i,!i&a>>>0>d[e+29612>>2]|i){n=-16;break e}if(u=(i=o[e+29392>>2])>>>0>4?i:4,i=0,c=0,o[e+29656>>2]||(l=a,X=(a=!n&a>>>0<131072)?l:131072,a=(a?n:0)+n|0,c=((n=(0|(c=o[k+4>>2]))==(0|(a=(a=(l=l+X|0)>>>0<X>>>0?a+1|0:a)-(((n=l)>>>0<4294967232)-1|0)|0))&(l=n- -64|0)>>>0>(B=o[k>>2])>>>0|a>>>0>c>>>0)?c:a)>>>0<1?n?B:l:-16),i=(a=o[D>>2])+(l=o[g>>2])>>>0>=p(n=c+u|0,3)>>>0?o[S>>2]+1|0:i,o[S>>2]=i,!(i>>>0<128)||a>>>0<u>>>0|c>>>0>l>>>0){c:{f:{if(i=o[e+29568>>2]){if(n>>>0<=i-160800>>>0)break f;n=-64;break e}if((i=o[e+29600>>2])&&((a=b[e+29552|0]|b[e+29553|0]<<8|b[e+29554|0]<<16|b[e+29555|0]<<24)?aA[0|a](b[e+29556|0]|b[e+29557|0]<<8|b[e+29558|0]<<16|b[e+29559|0]<<24,i):N(i)),o[g>>2]=0,o[D>>2]=0,a=e,i=(i=b[e+29548|0]|b[e+29549|0]<<8|b[e+29550|0]<<16|b[e+29551|0]<<24)?0|aA[0|i](b[e+29556|0]|b[e+29557|0]<<8|b[e+29558|0]<<16|b[e+29559|0]<<24,n):M(n),o[a+29600>>2]=i,i)break c;n=-64;break e}i=o[e+29600>>2]}o[g>>2]=c,o[e+29616>>2]=i+u,o[D>>2]=u}o[e+29596>>2]=2}if(n=E-f|0,c=o[R>>2],o[e+29424>>2]|c-3>>>0>=2)break i;a=n>>>0>1?n:1,a=(i=o[e+29372>>2])>>>0>a>>>0?a:i;break n}a=e,c=O-s|0,i=o[e+29624>>2],(n=(l=c>>>0<(u=o[e+29628>>2]-i|0)>>>0)?c:u)&&(re(s,i+o[e+29616>>2]|0,n),i=o[e+29624>>2]),i=n+i|0,o[a+29624>>2]=i,l||(o[e+29596>>2]=2,a=o[g>>2],!o[e+29380>>2]&a>>>0>=d[e+29376>>2]|a>>>0>=i+o[e+29392>>2]>>>0||(o[e+29624>>2]=0,o[e+29628>>2]=0)),s=n+s|0,i=c>>>0>=u>>>0;break r}a=i=o[e+29372>>2]}if(!a){o[e+29596>>2]=0;break t}if(n>>>0>=a>>>0){u=7==(0|c),i=e;n:{i:{if(!o[e+29656>>2]){if(7!=(0|c)?(n=o[e+29624>>2],c=o[g>>2]-n|0):(n=o[e+29624>>2],c=0),(n=C(e,o[e+29616>>2]+n|0,c,f,a))>>>0>4294967176)break e;if(!(n|u))break i;o[e+29628>>2]=o[e+29624>>2]+n,n=4;break n}if((n=C(e,s,u?0:O-s|0,f,a))>>>0>4294967176)break e;s=n+s|0}n=2}o[i+29596>>2]=n,f=a+f|0,i=1;break r}if((0|f)==(0|E)){f=E;break t}o[e+29596>>2]=3}if(a=i-(n=o[e+29608>>2])|0,7!=(0|c)){if(a>>>0>o[D>>2]-n>>>0){n=-20;break e}(c=a>>>0<(c=E-f|0)>>>0?a:c)?(re(o[e+29600>>2]+n|0,f,c),n=o[e+29608>>2]):c=0}else c=a>>>0<(c=E-f|0)>>>0?a:c;if(o[e+29608>>2]=n+c,f=c+f|0,a>>>0>c>>>0)break t;if(o[e+29608>>2]=0,a=7==(0|(c=o[R>>2])),n=o[e+29600>>2],o[e+29656>>2]){if((n=C(e,s,a?0:O-s|0,n,i))>>>0>4294967176)break e;o[e+29596>>2]=2,s=n+s|0,i=1}else{if(7!=(0|c)?(c=o[e+29624>>2],u=o[g>>2]-c|0):(c=o[e+29624>>2],u=0),(n=C(e,o[e+29616>>2]+c|0,u,n,i))>>>0>4294967176)break e;if(!(n|a)){o[e+29596>>2]=2,i=1;break r}o[e+29596>>2]=4,o[e+29628>>2]=o[e+29624>>2]+n,i=1}}if(i)continue}break}o[r+8>>2]=f-o[r>>2],i=s-o[t>>2]|0,o[t+8>>2]=i,o[w+8>>2]=i,i=o[t+4>>2],o[w>>2]=o[t>>2],o[w+4>>2]=i;t:{if(!((0|s)!=(0|v)|(0|f)!=(0|T))){if(t=o[e+29652>>2],o[e+29652>>2]=t+1,(0|t)<15)break t;if(n=-70,(0|O)==(0|v))break e;if(n=-72,(0|E)!=(0|T))break t;break e}o[e+29652>>2]=0}if(t=o[e+29372>>2])n=(t-o[e+29608>>2]|0)+(3==o[e+29428>>2]?3:0)|0;else{if(t=o[e+29648>>2],o[e+29628>>2]==o[e+29624>>2]){if(n=0,!t)break e;if((t=o[r+8>>2])>>>0>=d[r+4>>2]){o[e+29596>>2]=2,n=1;break e}o[r+8>>2]=t+1;break e}if(n=1,t)break e;o[r+8>>2]=o[r+8>>2]-1,o[e+29648>>2]=1}}return y=A+16|0,n}(e,c+16|0,c),o[A>>2]=o[c+24>>2],o[a>>2]=o[c+8>>2],y=c+32|0,0|e},__indirect_function_table:aA,__errno_location:function(){return 16164},fflush:function e(t){var r=0;if(t|=0)return o[t+76>>2],0|Je(t);if(o[4185]&&(r=e(o[4185])),t=o[4184])for(;d[t+20>>2]>d[t+28>>2]&&(r=Je(t)|r),t=o[t+56>>2];);return 0|r},stackSave:function(){return 0|y},stackRestore:function(e){y=e|=0},stackAlloc:function(e){return y=e=y-(e|=0)&-16,0|e},emscripten_stack_init:rA,emscripten_stack_get_free:function(){return y-R|0},emscripten_stack_get_end:function(){return 0|R},__cxa_demangle:function(e,t,r,A){e|=0,A|=0;var n,i=0,c=0,f=0,s=0;y=n=y-4560|0;e:if((r|=0)|!(t|=0)&&e)(function(e,t,r){o[e+4>>2]=r,o[e>>2]=t,ur(e+8|0),ur(e+148|0),Tt(e+288|0),Ht(e+332|0),Ht(e+360|0),o[e+396>>2]=0,o[e+400>>2]=0,o[e+392>>2]=-1,a[e+388>>1]=1,o[e+404>>2]=0,o[e+416>>2]=0,o[e+420>>2]=0,o[4096+(e=e+416|0)>>2]=e})(f=n+32|0,e,Ze(e)+e|0),e=0,o[8+(i=n+8|0)>>2]=0,o[i>>2]=0,o[i+4>>2]=0,o[i+12>>2]=-1,o[i+16>>2]=-1,(c=Ae(f))?(s=-1,function(e,t,r){var A=0;A=r;t:{if(!e){if(t=1024,e=M(1024))break t;return 0}t=o[t>>2]}return o[A+8>>2]=t,o[r>>2]=e,o[r+4>>2]=0,1}(t,r,i)&&(aA[o[o[c>>2]+16>>2]](c,i),1!=b[c+5|0]&&aA[o[o[c>>2]+20>>2]](c,i),s=0,kr(i,0),r&&(o[r>>2]=o[i+4>>2]),e=o[i>>2])):s=-2,A&&(o[A>>2]=s),function(e){(function(e){for(var t=0;t=o[e+4096>>2];)o[e+4096>>2]=o[t>>2],(0|e)!=(0|t)&&N(t);o[e>>2]=0,o[e+4>>2]=0,o[e+4096>>2]=e})(e+416|0),Xr(e+360|0),Xr(e+332|0),Xr(e+288|0),Xr(e+148|0),Xr(e+8|0)}(f);else{if(e=0,!A)break e;o[A>>2]=-3}return y=n+4560|0,0|e}}}(e)}(Te)},instantiate:function(e,t){return{then:function(t){var r=new w.Module(e);return t({instance:new w.Instance(r)}),{catch:function(){}}}}},RuntimeError:Error};h=[],"object"!=typeof w&&ee("no native wasm support detected");var y=!1;function R(e,t){e||ee("Assertion failed: "+t)}function D(e,r,A,n,i){var a={string:function(e){var t=0;if(null!=e&&0!==e){var r=1+(e.length<<2);x(e,t=we(r),r)}return t},array:function(e){var t=we(e.length);return function(e,t){R(e.length>=0,"writeArrayToMemory array must have a length (should be an array or typed array)"),v.set(e,t)}(e,t),t}},o=function(e){var r=t["_"+e];return R(r,"Cannot call unknown function "+e+", make sure it is exported"),r}(e),c=[],f=0;if(R("array"!==r,'Return type should not be "array".'),n)for(var s=0;s<n.length;s++){var u=a[A[s]];u?(0===f&&(f=Oe()),c[s]=u(n[s])):c[s]=n[s]}var b=o.apply(null,c);return b=function(e){return"string"===r?I(e):"boolean"===r?Boolean(e):e}(b),0!==f&&_e(f),b}var m,v,M,F,S,P,B,X,U="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function I(e,t){return e?function(e,t,r){for(var A=t+r,n=t;e[n]&&!(n>=A);)++n;if(n-t>16&&e.subarray&&U)return U.decode(e.subarray(t,n));for(var i="";t<n;){var a=e[t++];if(128&a){var o=63&e[t++];if(192!=(224&a)){var c=63&e[t++];if(224==(240&a)?a=(15&a)<<12|o<<6|c:(240!=(248&a)&&g("Invalid UTF-8 leading byte 0x"+a.toString(16)+" encountered when deserializing a UTF-8 string on the asm.js/wasm heap to a JS string!"),a=(7&a)<<18|o<<12|c<<6|63&e[t++]),a<65536)i+=String.fromCharCode(a);else{var f=a-65536;i+=String.fromCharCode(55296|f>>10,56320|1023&f)}}else i+=String.fromCharCode((31&a)<<6|o)}else i+=String.fromCharCode(a)}return i}(M,e,t):""}function x(e,t,r){return R("number"==typeof r,"stringToUTF8(str, outPtr, maxBytesToWrite) is missing the third parameter that specifies the length of the output buffer!"),function(e,t,r,A){if(!(A>0))return 0;for(var n=r,i=r+A-1,a=0;a<e.length;++a){var o=e.charCodeAt(a);if(o>=55296&&o<=57343&&(o=65536+((1023&o)<<10)|1023&e.charCodeAt(++a)),o<=127){if(r>=i)break;t[r++]=o}else if(o<=2047){if(r+1>=i)break;t[r++]=192|o>>6,t[r++]=128|63&o}else if(o<=65535){if(r+2>=i)break;t[r++]=224|o>>12,t[r++]=128|o>>6&63,t[r++]=128|63&o}else{if(r+3>=i)break;o>=2097152&&g("Invalid Unicode code point 0x"+o.toString(16)+" encountered when serializing a JS string to an UTF-8 string on the asm.js/wasm heap! (Valid unicode code points should be in range 0-0x1FFFFF)."),t[r++]=240|o>>18,t[r++]=128|o>>12&63,t[r++]=128|o>>6&63,t[r++]=128|63&o}}return t[r]=0,r-n}(e,M,t,r)}function j(e){m=e,t.HEAP8=v=new Int8Array(e),t.HEAP16=F=new Int16Array(e),t.HEAP32=S=new Int32Array(e),t.HEAPU8=M=new Uint8Array(e),t.HEAPU16=new Uint16Array(e),t.HEAPU32=P=new Uint32Array(e),t.HEAPF32=B=new Float32Array(e),t.HEAPF64=X=new Float64Array(e)}"undefined"!=typeof TextDecoder&&new TextDecoder("utf-16le");var Q=5242880;t.TOTAL_STACK&&R(Q===t.TOTAL_STACK,"the stack size can no longer be determined at runtime");var C,Z=t.INITIAL_MEMORY||16777216;function G(){var e=De();R(0==(3&e)),P[1+(e>>2)]=34821223,P[2+(e>>2)]=2310721022,S[0]=1668509029}function N(){if(!y){var e=De(),t=P[1+(e>>2)],r=P[2+(e>>2)];34821223==t&&2310721022==r||ee("Stack overflow! Stack cookie has been overwritten, expected hex dwords 0x89BACDFE and 0x2135467, but received 0x"+r.toString(16)+" "+t.toString(16)),1668509029!==S[0]&&ee("Runtime error: The application has corrupted its heap memory area (address zero)!")}}Object.getOwnPropertyDescriptor(t,"INITIAL_MEMORY")||Object.defineProperty(t,"INITIAL_MEMORY",{configurable:!0,get:function(){ee("Module.INITIAL_MEMORY has been replaced with plain INITIAL_MEMORY (the initial value can be provided on Module, but after startup the value is only looked for on a local variable of that name)")}}),R(Z>=Q,"INITIAL_MEMORY should be larger than TOTAL_STACK, was "+Z+"! (TOTAL_STACK=5242880)"),R("undefined"!=typeof Int32Array&&"undefined"!=typeof Float64Array&&void 0!==Int32Array.prototype.subarray&&void 0!==Int32Array.prototype.set,"JS engine does not provide full typed array support"),(_=t.wasmMemory?t.wasmMemory:new w.Memory({initial:Z/65536,maximum:32768}))&&(m=_.buffer),R((Z=m.byteLength)%65536==0),j(m),function(){var e=new Int16Array(1),t=new Int8Array(e.buffer);if(e[0]=25459,115!==t[0]||99!==t[1])throw"Runtime error: expected the system to be little-endian!"}();var H=[],W=[],Y=[],V=[],L=!1;if(W.push({func:function(){ge()}}),Math.imul&&-5===Math.imul(4294967295,5)||(Math.imul=function(e,t){var r=65535&e,A=65535&t;return r*A+((e>>>16)*A+r*(t>>>16)<<16)|0}),!Math.fround){var z=new Float32Array(1);Math.fround=function(e){return z[0]=e,z[0]}}Math.clz32||(Math.clz32=function(e){var t=32,r=e>>16;return r&&(t-=16,e=r),(r=e>>8)&&(t-=8,e=r),(r=e>>4)&&(t-=4,e=r),(r=e>>2)&&(t-=2,e=r),(r=e>>1)?t-2:t-e}),Math.trunc||(Math.trunc=function(e){return e<0?Math.ceil(e):Math.floor(e)}),R(Math.imul,"This browser does not support Math.imul(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),R(Math.fround,"This browser does not support Math.fround(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),R(Math.clz32,"This browser does not support Math.clz32(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill"),R(Math.trunc,"This browser does not support Math.trunc(), build with LEGACY_VM_SUPPORT or POLYFILL_OLD_MATH_FUNCTIONS to add in a polyfill");var J=0,K=null,q=null,$={};function ee(e){var r,A;t.onAbort&&t.onAbort(e),T(e+=""),y=!0,e="abort("+e+") at "+(A=function(){var e=new Error;if(!e.stack){try{throw new Error}catch(t){e=t}if(!e.stack)return"(no stack trace available)"}return e.stack.toString()}(),t.extraStackTrace&&(A+="\n"+t.extraStackTrace()),r=/\b_Z[\w\d_]+/g,A.replace(r,(function(e){var t=be(e);return e===t?e:t+" ["+e+"]"})));var i=new w.RuntimeError(e);throw n(i),i}t.preloadedImages={},t.preloadedAudios={};var te={error:function(){ee("Filesystem support (FS) was not included. The problem is that you are using files from JS, but files were not used from C/C++, so filesystem support was not auto-included. You can force-include filesystem support with -s FORCE_FILESYSTEM=1")},init:function(){te.error()},createDataFile:function(){te.error()},createPreloadedFile:function(){te.error()},createLazyFile:function(){te.error()},open:function(){te.error()},mkdev:function(){te.error()},registerDevice:function(){te.error()},analyzePath:function(){te.error()},loadFilesFromDB:function(){te.error()},ErrnoError:function(){te.error()}};function re(e,t){return String.prototype.startsWith?e.startsWith(t):0===e.indexOf(t)}t.FS_createDataFile=te.createDataFile,t.FS_createPreloadedFile=te.createPreloadedFile;var Ae="data:application/octet-stream;base64,";function ne(e){return re(e,Ae)}function ie(e){return re(e,"file://")}function ae(e,r){return function(){var A=e,n=r;return r||(n=t.asm),R(L,"native function `"+A+"` called before runtime initialization"),R(!0,"native function `"+A+"` called after runtime exit (use NO_EXIT_RUNTIME to keep it alive after main() exits)"),n[e]||R(n[e],"exported native function `"+A+"` not found"),n[e].apply(null,arguments)}}var oe,ce,fe="<<< WASM_BINARY_FILE >>>";function se(e){try{if(e==fe&&h)return new Uint8Array(h);var t=pe(e);if(t)return t;if(d)return d(e);throw"both async and sync fetching of the wasm failed"}catch(e){ee(e)}}function ue(e){for(;e.length>0;){var r=e.shift();if("function"!=typeof r){var A=r.func;"number"==typeof A?void 0===r.arg?C.get(A)():C.get(A)(r.arg):A(void 0===r.arg?null:r.arg)}else r(t)}}function be(e){if(be.recursionGuard=1+(0|be.recursionGuard),be.recursionGuard>1)return e;var r=t.___cxa_demangle||t.__cxa_demangle;R(r);var A=Oe();try{var n=e;n.startsWith("__Z")&&(n=n.substr(1));var i=function(e){for(var t=0,r=0;r<e.length;++r){var A=e.charCodeAt(r);A>=55296&&A<=57343&&(A=65536+((1023&A)<<10)|1023&e.charCodeAt(++r)),A<=127?++t:t+=A<=2047?2:A<=65535?3:4}return t}(n)+1,a=we(i);x(n,a,i);var o=we(4),c=r(a,0,0,o);if(0===S[o>>2]&&c)return I(c)}catch(e){}finally{he(c),_e(A),be.recursionGuard<2&&--be.recursionGuard}return e}ne(fe)||(fe=function(e){return t.locateFile?t.locateFile(e,p):p+e}(fe));var le=Uint8Array.prototype.copyWithin?function(e,t,r){M.copyWithin(e,t,t+r)}:function(e,t,r){M.set(M.subarray(t,t+r),e)};function de(e){try{return _.grow(e-m.byteLength+65535>>>16),j(_.buffer),1}catch(t){console.error("emscripten_realloc_buffer: Attempted to grow heap from "+m.byteLength+" bytes to "+e+" bytes, but got error: "+t)}}function ke(e){for(var t=[],r=0;r<e.length;r++){var A=e[r];A>255&&(R(!1,"Character code "+A+" ("+String.fromCharCode(A)+") at offset "+r+" not in 0x00-0xFF."),A&=255),t.push(String.fromCharCode(A))}return t.join("")}var Ee="function"==typeof atob?atob:function(e){var t,r,A,n,i,a,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",c="",f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");do{t=o.indexOf(e.charAt(f++))<<2|(n=o.indexOf(e.charAt(f++)))>>4,r=(15&n)<<4|(i=o.indexOf(e.charAt(f++)))>>2,A=(3&i)<<6|(a=o.indexOf(e.charAt(f++))),c+=String.fromCharCode(t),64!==i&&(c+=String.fromCharCode(r)),64!==a&&(c+=String.fromCharCode(A))}while(f<e.length);return c};function pe(e){if(ne(e))return function(e){if("boolean"==typeof f&&f){var t;try{t=Buffer.from(e,"base64")}catch(r){t=new Buffer(e,"base64")}return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}try{for(var r=Ee(e),A=new Uint8Array(r.length),n=0;n<r.length;++n)A[n]=r.charCodeAt(n);return A}catch(e){throw new Error("Converting base64 string to bytes failed.")}}(e.slice(Ae.length))}var Te={emscripten_memcpy_big:le,emscripten_resize_heap:function(e){var t=M.length;R(e>t);var r,A,n=2147483648;if(e>n)return T("Cannot enlarge memory, asked to go up to "+e+" bytes, but the limit is 2147483648 bytes!"),!1;for(var i=1;i<=4;i*=2){var a=t*(1+.2/i);a=Math.min(a,e+100663296);var o=Math.min(n,((r=Math.max(e,a))%(A=65536)>0&&(r+=A-r%A),r));if(de(o))return T("Warning: Enlarging memory arrays, this is not fast! "+[t,o]),!0}return T("Failed to grow the heap from "+t+" bytes to "+o+" bytes, not enough memory!"),!1},getTempRet0:function(){return O},memory:_,setTempRet0:function(e){O=e}};!function(){var e={env:Te,wasi_snapshot_preview1:Te};function r(e,r){var A=e.exports;t.asm=A,R(C=t.asm.__indirect_function_table,"table not found in wasm exports"),function(e){if(J--,t.monitorRunDependencies&&t.monitorRunDependencies(J),e?(R($[e]),delete $[e]):T("warning: run dependency removed without ID"),0==J&&(null!==K&&(clearInterval(K),K=null),q)){var r=q;q=null,r()}}("wasm-instantiate")}!function(e){J++,t.monitorRunDependencies&&t.monitorRunDependencies(J),e?(R(!$[e]),$[e]=1,null===K&&"undefined"!=typeof setInterval&&(K=setInterval((function(){if(y)return clearInterval(K),void(K=null);var e=!1;for(var t in $)e||(e=!0,T("still waiting on run dependencies:")),T("dependency: "+t);e&&T("(end of list)")}),1e4))):T("warning: run dependency added without ID")}("wasm-instantiate");var A=t;function a(e){R(t===A,"the Module object should not be replaced during async compilation - perhaps the order of HTML elements is wrong?"),A=null,r(e.instance)}function f(t){return function(){if(!h&&(o||c)){if("function"==typeof fetch&&!ie(fe))return fetch(fe,{credentials:"same-origin"}).then((function(e){if(!e.ok)throw"failed to load wasm binary file at '"+fe+"'";return e.arrayBuffer()})).catch((function(){return se(fe)}));if(l)return new i((function(e,t){l(fe,(function(t){e(new Uint8Array(t))}),t)}))}return i.resolve().then((function(){return se(fe)}))}().then((function(t){return w.instantiate(t,e)})).then(t,(function(e){T("failed to asynchronously prepare wasm: "+e),ie(fe)&&T("warning: Loading from a file URI ("+fe+") is not supported in most browsers. See https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-run-a-local-webserver-for-testing-why-does-my-program-stall-in-downloading-or-preparing"),ee(e)}))}if(t.instantiateWasm)try{return t.instantiateWasm(e,r)}catch(e){return T("Module.instantiateWasm callback failed with error: "+e),!1}(h||"function"!=typeof w.instantiateStreaming||ne(fe)||ie(fe)||"function"!=typeof fetch?f(a):fetch(fe,{credentials:"same-origin"}).then((function(t){return w.instantiateStreaming(t,e).then(a,(function(e){return T("wasm streaming compile failed: "+e),T("falling back to ArrayBuffer instantiation"),f(a)}))}))).catch(n)}();var ge=t.___wasm_call_ctors=ae("__wasm_call_ctors");t._malloc=ae("malloc");var he=t._free=ae("free");t._ZSTD_isError=ae("ZSTD_isError"),t._ZSTD_getErrorName=ae("ZSTD_getErrorName"),t._ZSTD_getFrameContentSize=ae("ZSTD_getFrameContentSize"),t._ZSTD_decompress=ae("ZSTD_decompress"),t._ZSTD_createDStream=ae("ZSTD_createDStream"),t._ZSTD_freeDStream=ae("ZSTD_freeDStream"),t._ZSTD_DStreamInSize=ae("ZSTD_DStreamInSize"),t._ZSTD_DStreamOutSize=ae("ZSTD_DStreamOutSize"),t._ZSTD_initDStream=ae("ZSTD_initDStream"),t._ZSTD_decompressStream_simpleArgs=ae("ZSTD_decompressStream_simpleArgs"),t.___errno_location=ae("__errno_location"),t._fflush=ae("fflush");var Oe=t.stackSave=ae("stackSave"),_e=t.stackRestore=ae("stackRestore"),we=t.stackAlloc=ae("stackAlloc"),ye=t._emscripten_stack_init=function(){return(ye=t._emscripten_stack_init=t.asm.emscripten_stack_init).apply(null,arguments)};t._emscripten_stack_get_free=function(){return(t._emscripten_stack_get_free=t.asm.emscripten_stack_get_free).apply(null,arguments)};var Re,De=t._emscripten_stack_get_end=function(){return(De=t._emscripten_stack_get_end=t.asm.emscripten_stack_get_end).apply(null,arguments)};function me(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}function ve(e){function r(){Re||(Re=!0,t.calledRun=!0,y||(N(),R(!L),L=!0,ue(W),N(),ue(Y),A(t),t.onRuntimeInitialized&&t.onRuntimeInitialized(),R(!t._main,'compiled without a main, but one is present. if you added it from JS, use Module["onRuntimeInitialized"]'),function(){if(N(),t.postRun)for("function"==typeof t.postRun&&(t.postRun=[t.postRun]);t.postRun.length;)e=t.postRun.shift(),V.unshift(e);var e;ue(V)}()))}J>0||(ye(),G(),function(){if(t.preRun)for("function"==typeof t.preRun&&(t.preRun=[t.preRun]);t.preRun.length;)e=t.preRun.shift(),H.unshift(e);var e;ue(H)}(),J>0||(t.setStatus?(t.setStatus("Running..."),setTimeout((function(){setTimeout((function(){t.setStatus("")}),1),r()}),1)):r(),N()))}if(t.___cxa_demangle=ae("__cxa_demangle"),Object.getOwnPropertyDescriptor(t,"intArrayFromString")||(t.intArrayFromString=function(){ee("'intArrayFromString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"intArrayToString")||(t.intArrayToString=function(){ee("'intArrayToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"ccall")||(t.ccall=function(){ee("'ccall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),t.cwrap=function(e,t,r,A){return function(){return D(e,t,r,arguments)}},t.setValue=function(e,t,r,A){switch("*"===(r=r||"i8").charAt(r.length-1)&&(r="i32"),r){case"i1":case"i8":v[e>>0]=t;break;case"i16":F[e>>1]=t;break;case"i32":S[e>>2]=t;break;case"i64":ce=[t>>>0,(oe=t,+Math.abs(oe)>=1?oe>0?(0|Math.min(+Math.floor(oe/4294967296),4294967295))>>>0:~~+Math.ceil((oe-+(~~oe>>>0))/4294967296)>>>0:0)],S[e>>2]=ce[0],S[e+4>>2]=ce[1];break;case"float":B[e>>2]=t;break;case"double":X[e>>3]=t;break;default:ee("invalid type for setValue: "+r)}},t.getValue=function(e,t,r){switch("*"===(t=t||"i8").charAt(t.length-1)&&(t="i32"),t){case"i1":case"i8":return v[e>>0];case"i16":return F[e>>1];case"i32":case"i64":return S[e>>2];case"float":return B[e>>2];case"double":return X[e>>3];default:ee("invalid type for getValue: "+t)}return null},Object.getOwnPropertyDescriptor(t,"allocate")||(t.allocate=function(){ee("'allocate' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"UTF8ArrayToString")||(t.UTF8ArrayToString=function(){ee("'UTF8ArrayToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"UTF8ToString")||(t.UTF8ToString=function(){ee("'UTF8ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stringToUTF8Array")||(t.stringToUTF8Array=function(){ee("'stringToUTF8Array' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stringToUTF8")||(t.stringToUTF8=function(){ee("'stringToUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"lengthBytesUTF8")||(t.lengthBytesUTF8=function(){ee("'lengthBytesUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stackTrace")||(t.stackTrace=function(){ee("'stackTrace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"addOnPreRun")||(t.addOnPreRun=function(){ee("'addOnPreRun' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),t.addOnInit=function(e){W.unshift(e)},Object.getOwnPropertyDescriptor(t,"addOnPreMain")||(t.addOnPreMain=function(){ee("'addOnPreMain' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"addOnExit")||(t.addOnExit=function(){ee("'addOnExit' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"addOnPostRun")||(t.addOnPostRun=function(){ee("'addOnPostRun' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeStringToMemory")||(t.writeStringToMemory=function(){ee("'writeStringToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeArrayToMemory")||(t.writeArrayToMemory=function(){ee("'writeArrayToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeAsciiToMemory")||(t.writeAsciiToMemory=function(){ee("'writeAsciiToMemory' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"addRunDependency")||(t.addRunDependency=function(){ee("'addRunDependency' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"removeRunDependency")||(t.removeRunDependency=function(){ee("'removeRunDependency' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"FS_createFolder")||(t.FS_createFolder=function(){ee("'FS_createFolder' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"FS_createPath")||(t.FS_createPath=function(){ee("'FS_createPath' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"FS_createDataFile")||(t.FS_createDataFile=function(){ee("'FS_createDataFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"FS_createPreloadedFile")||(t.FS_createPreloadedFile=function(){ee("'FS_createPreloadedFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"FS_createLazyFile")||(t.FS_createLazyFile=function(){ee("'FS_createLazyFile' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"FS_createLink")||(t.FS_createLink=function(){ee("'FS_createLink' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"FS_createDevice")||(t.FS_createDevice=function(){ee("'FS_createDevice' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"FS_unlink")||(t.FS_unlink=function(){ee("'FS_unlink' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ). Alternatively, forcing filesystem support (-s FORCE_FILESYSTEM=1) can export this for you")}),Object.getOwnPropertyDescriptor(t,"getLEB")||(t.getLEB=function(){ee("'getLEB' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getFunctionTables")||(t.getFunctionTables=function(){ee("'getFunctionTables' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"alignFunctionTables")||(t.alignFunctionTables=function(){ee("'alignFunctionTables' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerFunctions")||(t.registerFunctions=function(){ee("'registerFunctions' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"addFunction")||(t.addFunction=function(){ee("'addFunction' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"removeFunction")||(t.removeFunction=function(){ee("'removeFunction' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getFuncWrapper")||(t.getFuncWrapper=function(){ee("'getFuncWrapper' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"prettyPrint")||(t.prettyPrint=function(){ee("'prettyPrint' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"makeBigInt")||(t.makeBigInt=function(){ee("'makeBigInt' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"dynCall")||(t.dynCall=function(){ee("'dynCall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getCompilerSetting")||(t.getCompilerSetting=function(){ee("'getCompilerSetting' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"print")||(t.print=function(){ee("'print' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"printErr")||(t.printErr=function(){ee("'printErr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getTempRet0")||(t.getTempRet0=function(){ee("'getTempRet0' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"setTempRet0")||(t.setTempRet0=function(){ee("'setTempRet0' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"callMain")||(t.callMain=function(){ee("'callMain' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"abort")||(t.abort=function(){ee("'abort' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stringToNewUTF8")||(t.stringToNewUTF8=function(){ee("'stringToNewUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"setFileTime")||(t.setFileTime=function(){ee("'setFileTime' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"emscripten_realloc_buffer")||(t.emscripten_realloc_buffer=function(){ee("'emscripten_realloc_buffer' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"ENV")||(t.ENV=function(){ee("'ENV' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"ERRNO_CODES")||(t.ERRNO_CODES=function(){ee("'ERRNO_CODES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"ERRNO_MESSAGES")||(t.ERRNO_MESSAGES=function(){ee("'ERRNO_MESSAGES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"setErrNo")||(t.setErrNo=function(){ee("'setErrNo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"readSockaddr")||(t.readSockaddr=function(){ee("'readSockaddr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeSockaddr")||(t.writeSockaddr=function(){ee("'writeSockaddr' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"DNS")||(t.DNS=function(){ee("'DNS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getHostByName")||(t.getHostByName=function(){ee("'getHostByName' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"GAI_ERRNO_MESSAGES")||(t.GAI_ERRNO_MESSAGES=function(){ee("'GAI_ERRNO_MESSAGES' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"Protocols")||(t.Protocols=function(){ee("'Protocols' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"Sockets")||(t.Sockets=function(){ee("'Sockets' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getRandomDevice")||(t.getRandomDevice=function(){ee("'getRandomDevice' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"traverseStack")||(t.traverseStack=function(){ee("'traverseStack' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"UNWIND_CACHE")||(t.UNWIND_CACHE=function(){ee("'UNWIND_CACHE' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"withBuiltinMalloc")||(t.withBuiltinMalloc=function(){ee("'withBuiltinMalloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"readAsmConstArgsArray")||(t.readAsmConstArgsArray=function(){ee("'readAsmConstArgsArray' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"readAsmConstArgs")||(t.readAsmConstArgs=function(){ee("'readAsmConstArgs' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"mainThreadEM_ASM")||(t.mainThreadEM_ASM=function(){ee("'mainThreadEM_ASM' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"jstoi_q")||(t.jstoi_q=function(){ee("'jstoi_q' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"jstoi_s")||(t.jstoi_s=function(){ee("'jstoi_s' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getExecutableName")||(t.getExecutableName=function(){ee("'getExecutableName' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"listenOnce")||(t.listenOnce=function(){ee("'listenOnce' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"autoResumeAudioContext")||(t.autoResumeAudioContext=function(){ee("'autoResumeAudioContext' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"dynCallLegacy")||(t.dynCallLegacy=function(){ee("'dynCallLegacy' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getDynCaller")||(t.getDynCaller=function(){ee("'getDynCaller' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"dynCall")||(t.dynCall=function(){ee("'dynCall' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"callRuntimeCallbacks")||(t.callRuntimeCallbacks=function(){ee("'callRuntimeCallbacks' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"abortStackOverflow")||(t.abortStackOverflow=function(){ee("'abortStackOverflow' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"reallyNegative")||(t.reallyNegative=function(){ee("'reallyNegative' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"unSign")||(t.unSign=function(){ee("'unSign' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"reSign")||(t.reSign=function(){ee("'reSign' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"formatString")||(t.formatString=function(){ee("'formatString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"PATH")||(t.PATH=function(){ee("'PATH' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"PATH_FS")||(t.PATH_FS=function(){ee("'PATH_FS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"SYSCALLS")||(t.SYSCALLS=function(){ee("'SYSCALLS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"syscallMmap2")||(t.syscallMmap2=function(){ee("'syscallMmap2' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"syscallMunmap")||(t.syscallMunmap=function(){ee("'syscallMunmap' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getSocketFromFD")||(t.getSocketFromFD=function(){ee("'getSocketFromFD' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getSocketAddress")||(t.getSocketAddress=function(){ee("'getSocketAddress' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"JSEvents")||(t.JSEvents=function(){ee("'JSEvents' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerKeyEventCallback")||(t.registerKeyEventCallback=function(){ee("'registerKeyEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"specialHTMLTargets")||(t.specialHTMLTargets=function(){ee("'specialHTMLTargets' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"maybeCStringToJsString")||(t.maybeCStringToJsString=function(){ee("'maybeCStringToJsString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"findEventTarget")||(t.findEventTarget=function(){ee("'findEventTarget' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"findCanvasEventTarget")||(t.findCanvasEventTarget=function(){ee("'findCanvasEventTarget' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getBoundingClientRect")||(t.getBoundingClientRect=function(){ee("'getBoundingClientRect' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillMouseEventData")||(t.fillMouseEventData=function(){ee("'fillMouseEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerMouseEventCallback")||(t.registerMouseEventCallback=function(){ee("'registerMouseEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerWheelEventCallback")||(t.registerWheelEventCallback=function(){ee("'registerWheelEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerUiEventCallback")||(t.registerUiEventCallback=function(){ee("'registerUiEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerFocusEventCallback")||(t.registerFocusEventCallback=function(){ee("'registerFocusEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillDeviceOrientationEventData")||(t.fillDeviceOrientationEventData=function(){ee("'fillDeviceOrientationEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerDeviceOrientationEventCallback")||(t.registerDeviceOrientationEventCallback=function(){ee("'registerDeviceOrientationEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillDeviceMotionEventData")||(t.fillDeviceMotionEventData=function(){ee("'fillDeviceMotionEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerDeviceMotionEventCallback")||(t.registerDeviceMotionEventCallback=function(){ee("'registerDeviceMotionEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"screenOrientation")||(t.screenOrientation=function(){ee("'screenOrientation' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillOrientationChangeEventData")||(t.fillOrientationChangeEventData=function(){ee("'fillOrientationChangeEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerOrientationChangeEventCallback")||(t.registerOrientationChangeEventCallback=function(){ee("'registerOrientationChangeEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillFullscreenChangeEventData")||(t.fillFullscreenChangeEventData=function(){ee("'fillFullscreenChangeEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerFullscreenChangeEventCallback")||(t.registerFullscreenChangeEventCallback=function(){ee("'registerFullscreenChangeEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerRestoreOldStyle")||(t.registerRestoreOldStyle=function(){ee("'registerRestoreOldStyle' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"hideEverythingExceptGivenElement")||(t.hideEverythingExceptGivenElement=function(){ee("'hideEverythingExceptGivenElement' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"restoreHiddenElements")||(t.restoreHiddenElements=function(){ee("'restoreHiddenElements' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"setLetterbox")||(t.setLetterbox=function(){ee("'setLetterbox' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"currentFullscreenStrategy")||(t.currentFullscreenStrategy=function(){ee("'currentFullscreenStrategy' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"restoreOldWindowedStyle")||(t.restoreOldWindowedStyle=function(){ee("'restoreOldWindowedStyle' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"softFullscreenResizeWebGLRenderTarget")||(t.softFullscreenResizeWebGLRenderTarget=function(){ee("'softFullscreenResizeWebGLRenderTarget' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"doRequestFullscreen")||(t.doRequestFullscreen=function(){ee("'doRequestFullscreen' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillPointerlockChangeEventData")||(t.fillPointerlockChangeEventData=function(){ee("'fillPointerlockChangeEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerPointerlockChangeEventCallback")||(t.registerPointerlockChangeEventCallback=function(){ee("'registerPointerlockChangeEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerPointerlockErrorEventCallback")||(t.registerPointerlockErrorEventCallback=function(){ee("'registerPointerlockErrorEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"requestPointerLock")||(t.requestPointerLock=function(){ee("'requestPointerLock' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillVisibilityChangeEventData")||(t.fillVisibilityChangeEventData=function(){ee("'fillVisibilityChangeEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerVisibilityChangeEventCallback")||(t.registerVisibilityChangeEventCallback=function(){ee("'registerVisibilityChangeEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerTouchEventCallback")||(t.registerTouchEventCallback=function(){ee("'registerTouchEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillGamepadEventData")||(t.fillGamepadEventData=function(){ee("'fillGamepadEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerGamepadEventCallback")||(t.registerGamepadEventCallback=function(){ee("'registerGamepadEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerBeforeUnloadEventCallback")||(t.registerBeforeUnloadEventCallback=function(){ee("'registerBeforeUnloadEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"fillBatteryEventData")||(t.fillBatteryEventData=function(){ee("'fillBatteryEventData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"battery")||(t.battery=function(){ee("'battery' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"registerBatteryEventCallback")||(t.registerBatteryEventCallback=function(){ee("'registerBatteryEventCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"setCanvasElementSize")||(t.setCanvasElementSize=function(){ee("'setCanvasElementSize' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getCanvasElementSize")||(t.getCanvasElementSize=function(){ee("'getCanvasElementSize' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"polyfillSetImmediate")||(t.polyfillSetImmediate=function(){ee("'polyfillSetImmediate' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"demangle")||(t.demangle=function(){ee("'demangle' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"demangleAll")||(t.demangleAll=function(){ee("'demangleAll' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"jsStackTrace")||(t.jsStackTrace=function(){ee("'jsStackTrace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stackTrace")||(t.stackTrace=function(){ee("'stackTrace' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getEnvStrings")||(t.getEnvStrings=function(){ee("'getEnvStrings' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"checkWasiClock")||(t.checkWasiClock=function(){ee("'checkWasiClock' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"flush_NO_FILESYSTEM")||(t.flush_NO_FILESYSTEM=function(){ee("'flush_NO_FILESYSTEM' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeI53ToI64")||(t.writeI53ToI64=function(){ee("'writeI53ToI64' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeI53ToI64Clamped")||(t.writeI53ToI64Clamped=function(){ee("'writeI53ToI64Clamped' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeI53ToI64Signaling")||(t.writeI53ToI64Signaling=function(){ee("'writeI53ToI64Signaling' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeI53ToU64Clamped")||(t.writeI53ToU64Clamped=function(){ee("'writeI53ToU64Clamped' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeI53ToU64Signaling")||(t.writeI53ToU64Signaling=function(){ee("'writeI53ToU64Signaling' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"readI53FromI64")||(t.readI53FromI64=function(){ee("'readI53FromI64' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"readI53FromU64")||(t.readI53FromU64=function(){ee("'readI53FromU64' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"convertI32PairToI53")||(t.convertI32PairToI53=function(){ee("'convertI32PairToI53' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"convertU32PairToI53")||(t.convertU32PairToI53=function(){ee("'convertU32PairToI53' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"uncaughtExceptionCount")||(t.uncaughtExceptionCount=function(){ee("'uncaughtExceptionCount' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"exceptionLast")||(t.exceptionLast=function(){ee("'exceptionLast' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"exceptionCaught")||(t.exceptionCaught=function(){ee("'exceptionCaught' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"ExceptionInfoAttrs")||(t.ExceptionInfoAttrs=function(){ee("'ExceptionInfoAttrs' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"ExceptionInfo")||(t.ExceptionInfo=function(){ee("'ExceptionInfo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"CatchInfo")||(t.CatchInfo=function(){ee("'CatchInfo' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"exception_addRef")||(t.exception_addRef=function(){ee("'exception_addRef' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"exception_decRef")||(t.exception_decRef=function(){ee("'exception_decRef' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"Browser")||(t.Browser=function(){ee("'Browser' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"funcWrappers")||(t.funcWrappers=function(){ee("'funcWrappers' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"getFuncWrapper")||(t.getFuncWrapper=function(){ee("'getFuncWrapper' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"setMainLoop")||(t.setMainLoop=function(){ee("'setMainLoop' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"FS")||(t.FS=function(){ee("'FS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"mmapAlloc")||(t.mmapAlloc=function(){ee("'mmapAlloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"MEMFS")||(t.MEMFS=function(){ee("'MEMFS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"TTY")||(t.TTY=function(){ee("'TTY' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"PIPEFS")||(t.PIPEFS=function(){ee("'PIPEFS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"SOCKFS")||(t.SOCKFS=function(){ee("'SOCKFS' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"_setNetworkCallback")||(t._setNetworkCallback=function(){ee("'_setNetworkCallback' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"tempFixedLengthArray")||(t.tempFixedLengthArray=function(){ee("'tempFixedLengthArray' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"miniTempWebGLFloatBuffers")||(t.miniTempWebGLFloatBuffers=function(){ee("'miniTempWebGLFloatBuffers' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"heapObjectForWebGLType")||(t.heapObjectForWebGLType=function(){ee("'heapObjectForWebGLType' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"heapAccessShiftForWebGLHeap")||(t.heapAccessShiftForWebGLHeap=function(){ee("'heapAccessShiftForWebGLHeap' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"GL")||(t.GL=function(){ee("'GL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"emscriptenWebGLGet")||(t.emscriptenWebGLGet=function(){ee("'emscriptenWebGLGet' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"computeUnpackAlignedImageSize")||(t.computeUnpackAlignedImageSize=function(){ee("'computeUnpackAlignedImageSize' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"emscriptenWebGLGetTexPixelData")||(t.emscriptenWebGLGetTexPixelData=function(){ee("'emscriptenWebGLGetTexPixelData' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"emscriptenWebGLGetUniform")||(t.emscriptenWebGLGetUniform=function(){ee("'emscriptenWebGLGetUniform' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"emscriptenWebGLGetVertexAttrib")||(t.emscriptenWebGLGetVertexAttrib=function(){ee("'emscriptenWebGLGetVertexAttrib' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"writeGLArray")||(t.writeGLArray=function(){ee("'writeGLArray' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"AL")||(t.AL=function(){ee("'AL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"SDL_unicode")||(t.SDL_unicode=function(){ee("'SDL_unicode' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"SDL_ttfContext")||(t.SDL_ttfContext=function(){ee("'SDL_ttfContext' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"SDL_audio")||(t.SDL_audio=function(){ee("'SDL_audio' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"SDL")||(t.SDL=function(){ee("'SDL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"SDL_gfx")||(t.SDL_gfx=function(){ee("'SDL_gfx' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"GLUT")||(t.GLUT=function(){ee("'GLUT' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"EGL")||(t.EGL=function(){ee("'EGL' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"GLFW_Window")||(t.GLFW_Window=function(){ee("'GLFW_Window' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"GLFW")||(t.GLFW=function(){ee("'GLFW' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"GLEW")||(t.GLEW=function(){ee("'GLEW' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"IDBStore")||(t.IDBStore=function(){ee("'IDBStore' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"runAndAbortIfError")||(t.runAndAbortIfError=function(){ee("'runAndAbortIfError' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"warnOnce")||(t.warnOnce=function(){ee("'warnOnce' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stackSave")||(t.stackSave=function(){ee("'stackSave' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stackRestore")||(t.stackRestore=function(){ee("'stackRestore' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stackAlloc")||(t.stackAlloc=function(){ee("'stackAlloc' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"AsciiToString")||(t.AsciiToString=function(){ee("'AsciiToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stringToAscii")||(t.stringToAscii=function(){ee("'stringToAscii' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"UTF16ToString")||(t.UTF16ToString=function(){ee("'UTF16ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stringToUTF16")||(t.stringToUTF16=function(){ee("'stringToUTF16' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"lengthBytesUTF16")||(t.lengthBytesUTF16=function(){ee("'lengthBytesUTF16' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"UTF32ToString")||(t.UTF32ToString=function(){ee("'UTF32ToString' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"stringToUTF32")||(t.stringToUTF32=function(){ee("'stringToUTF32' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"lengthBytesUTF32")||(t.lengthBytesUTF32=function(){ee("'lengthBytesUTF32' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"allocateUTF8")||(t.allocateUTF8=function(){ee("'allocateUTF8' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"allocateUTF8OnStack")||(t.allocateUTF8OnStack=function(){ee("'allocateUTF8OnStack' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),t.writeStackCookie=G,t.checkStackCookie=N,Object.getOwnPropertyDescriptor(t,"intArrayFromBase64")||(t.intArrayFromBase64=function(){ee("'intArrayFromBase64' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),Object.getOwnPropertyDescriptor(t,"tryParseAsDataURI")||(t.tryParseAsDataURI=function(){ee("'tryParseAsDataURI' was not exported. add it to EXTRA_EXPORTED_RUNTIME_METHODS (see the FAQ)")}),t.ALLOC_NORMAL=0,t.ALLOC_STACK=1,q=function e(){Re||ve(),Re||(q=e)},t.run=ve,t.preInit)for("function"==typeof t.preInit&&(t.preInit=[t.preInit]);t.preInit.length>0;)t.preInit.pop()();return ve(),e.ready});e.exports=A})),An=Object.freeze(Object.assign(Object.create(null),rn,{default:rn})),nn=function(e,t,r){this.positionPtr=e,this.size=t,this.dataPtr=r},an=function(){function e(e){this.zstdGetErrorName=e.cwrap("ZSTD_getErrorName","string",["number"]),this.zstdIsError=e.cwrap("ZSTD_isError","number",["number"])}return e.prototype.checkError=function(e){if(e<0)throw new Error("ZSTD_ERROR: "+this.zstdGetErrorName(e)+", error code: "+e)},e}(),on=null,cn=null;var fn=function(){return!!on&&!!cn},sn=function(){function e(){}return e.decompress=function(t){if(!fn())throw new Error("Error: Zstd library not initialized. Please call the ZstdInit before usages");e.isDecompressInit||(e.initDecompressFunctions(),e.isDecompressInit=!0);var r,A=on._malloc(e.inputSize*t.BYTES_PER_ELEMENT),n=on._malloc(e.outputSize*t.BYTES_PER_ELEMENT),i=e.zstdCreateDStream();if(!i)throw on._free(A),on._free(n),new Error("ZSTD Stream decompress initialization failed.");r=e.zstdInitDStream(i);var a=0,o=on._malloc(e.positionSize),c=on._malloc(e.positionSize),f=new Uint8Array([]);try{for(;a<t.length;){var s=e.calculateReadBytes(a,r,t);on.HEAPU8.set(t.subarray(a,a+s),A),a+=s;for(var u=s<r,b=new nn(o,s,A);on.getValue(b.positionPtr,"i32")<b.size;){var l=new nn(c,e.outputSize,n);on.setValue(l.positionPtr,0,"i32"),on.setValue(b.positionPtr,0,"i32"),r=e.zstdDecompressStreamSimpleArgs(i,l.dataPtr,l.size,l.positionPtr,b.dataPtr,b.size,b.positionPtr),cn.checkError(r),f=e.getDataFromTransformation(l,f)}if(on.setValue(b.positionPtr,0,"i32"),u)break}}finally{e.zstdFreeDStream(i),on._free(A),on._free(n),on._free(o),on._free(c)}return f},e.getDataFromTransformation=function(e,t){var r=on.HEAPU8.subarray(e.dataPtr,e.dataPtr+on.getValue(e.positionPtr,"i32")),A=new Uint8Array(t.length+r.length);return A.set(t),A.set(r,t.length),A},e.initDecompressFunctions=function(){e.zstdDStreamInSize=on.cwrap("ZSTD_DStreamInSize","number",[]),e.zstdDStreamOutSize=on.cwrap("ZSTD_DStreamOutSize","number",[]),e.zstdCreateDStream=on.cwrap("ZSTD_createDStream","number",[]),e.zstdInitDStream=on.cwrap("ZSTD_initDStream","number",["number"]),e.zstdDecompressStreamSimpleArgs=on.cwrap("ZSTD_decompressStream_simpleArgs","number",["number","number","number","number","number","number"]),e.zstdFreeDStream=on.cwrap("ZSTD_freeDStream","number",["number"]),e.inputSize=e.zstdDStreamInSize(),e.outputSize=e.zstdDStreamOutSize()},e.positionSize=4,e.isDecompressInit=!1,e.calculateReadBytes=function(e,t,r){return Math.min(e+t,r.length)-e},e}();!function(e){function t(){return null!==e&&e.apply(this,arguments)||this}$A(t,e),t.compress=function(e,r,A){if(void 0===r&&(r=3),void 0===A&&(A=!1),!fn())throw new Error("Error: Zstd library not initialized. Please call the ZstdInit before usages");t.isCompressInit||(t.initCompressFunctions(),t.isCompressInit=!0);var n,i=on._malloc(t.inputSizeCo),a=on._malloc(t.outputSizeCo);try{n=t.initCompressStream()}catch(e){throw on._free(i),on._free(a),new Error(e)}t.setCompressionLevel(n,r,A);var o=on._malloc(t.positionSize),c=on._malloc(t.positionSize),f=new Uint8Array([]),s=t.inputSizeCo,u=0;try{for(;u<e.length;){var b=t.calculateReadBytes(u,s,e);on.HEAPU8.set(e.subarray(u,u+b),i),u+=b;var l=b<s,d=l?t.zstdEEnd:t.zstdEContinue,k=new nn(o,b,i),E=void 0;do{var p=new nn(c,t.outputSizeCo,a);on.setValue(p.positionPtr,0,"i32"),on.setValue(k.positionPtr,0,"i32");var T=t.zstdCompressStream2SimpleArgs(n,p.dataPtr,p.size,p.positionPtr,k.dataPtr,k.size,k.positionPtr,d);cn.checkError(T),f=t.getDataFromTransformation(p,f),E=l?0===T:on.getValue(k.positionPtr,"i32")===k.size}while(!E);if(on.setValue(k.positionPtr,0,"i32"),l)break}}finally{t.zstdFreeCStream(n),on._free(i),on._free(a),on._free(o),on._free(c)}return f},t.setCompressionLevel=function(e,r,A){cn.checkError(t.zstdCCtxSetParameter(e,t.zstdCCompressionLevel,r)),cn.checkError(t.zstdCCtxSetParameter(e,t.zstdCChecksumFlag,A)),t.zstdCCtxSetParameter(e,t.zstdCNbWorkers,4)},t.initCompressStream=function(){var e=t.zstdCreateCStream();if(t.zstdInitCStream(e),!e)throw new Error("ZSTD Stream compress initialization failed.");return e},t.initCompressFunctions=function(){t.zstdCStreamInSize=on.cwrap("ZSTD_CStreamInSize","number",[]),t.zstdCStreamOutSize=on.cwrap("ZSTD_CStreamOutSize","number",[]),t.zstdCreateCStream=on.cwrap("ZSTD_createCStream","number",[]),t.zstdInitCStream=on.cwrap("ZSTD_initCStream","number",["number"]),t.zstdCCtxSetParameter=on.cwrap("ZSTD_CCtx_setParameter","number",["number","number","number"]),t.zstdCompressStream2SimpleArgs=on.cwrap("ZSTD_compressStream2_simpleArgs","number",["number","number","number","number","number","number","number"]),t.zstdFreeCStream=on.cwrap("ZSTD_freeCStream","number",["number"]),t.inputSizeCo=t.zstdCStreamInSize(),t.outputSizeCo=t.zstdCStreamOutSize()},t.zstdEContinue=1,t.zstdEEnd=2,t.zstdCCompressionLevel=100,t.zstdCChecksumFlag=201,t.zstdCNbWorkers=400,t.isCompressInit=!1}(sn);var un=function(){function e(){}return e.decompress=function(t){if(!fn())throw new Error("Error: Zstd library not initialized. Please call the ZstdInit before usages");e.isDecompressInit||(e.initDecompressFunctions(),e.isDecompressInit=!0);var r=t.length,A=e.createArrayPointer(t,r),n=e.zstdGetFrameContentSize(A,r),i=on._malloc(n);cn.checkError(n);try{var a=e.zstdDecompress(i,n,A,r);return cn.checkError(a),new Uint8Array(on.HEAPU8.subarray(i,i+a-e.zstdFrameHeaderSizeMax))}finally{on._free(A),on._free(i)}},e.initDecompressFunctions=function(){e.zstdDecompress=on.cwrap("ZSTD_decompress","number",["number","number","number","number"]),e.zstdGetFrameContentSize=on.cwrap("ZSTD_getFrameContentSize","number",["number","number"])},e.createArrayPointer=function(e,t){var r=on._malloc(t);return on.HEAPU8.set(e,r),r},e.zstdFrameHeaderSizeMax=18,e.isDecompressInit=!1,e}();function bn(){return en(this,void 0,void 0,(function(){return tn(this,(function(e){return[2,(t=An,r={ZstdSimple:un,ZstdStream:sn},new Promise((function(e,A){on&&e({ZstdSimple:r.ZstdSimple,ZstdStream:r.ZstdStream}),("function"==typeof t?t:t.default)().then((function(t){cn=new an(on=t),e({ZstdSimple:r.ZstdSimple,ZstdStream:r.ZstdStream})})).catch((function(e){A(e)}))})))];var t,r}))}))}!function(e){function t(){return null!==e&&e.apply(this,arguments)||this}$A(t,e),t.compress=function(e,r){if(void 0===r&&(r=3),!fn())throw new Error("Error: Zstd library not initialized. Please call the ZstdInit before usages");if(t.isCompressInit||(t.initCompressFunctions(),t.isCompressInit=!0),e.length<=100)throw new Error("Length of the payload is too small. (Min length: >100)");var A=e.byteLength+t.zstdFrameHeaderSizeMax,n=t.createArrayPointer(e,A),i=on._malloc(t.zstdCompressBound(e.length));try{var a=t.zstdCompress(i,t.zstdCompressBound(e.length),n,A,r);return cn.checkError(a),new Uint8Array(on.HEAPU8.subarray(i,i+a))}finally{on._free(n),on._free(i)}},t.initCompressFunctions=function(){t.zstdCompress=on.cwrap("ZSTD_compress","number",["number","number","number","number","number"]),t.zstdCompressBound=on.cwrap("ZSTD_compressBound","number",["number"])},t.isCompressInit=!1}(un);export{bn as ZstdInit,un as ZstdSimple,sn as ZstdStream}; -//# sourceMappingURL=index.js.map diff --git a/js/modules/core.mjs b/js/modules/core.mjs deleted file mode 100644 index 0ce925b1f987b..0000000000000 --- a/js/modules/core.mjs +++ /dev/null @@ -1,2208 +0,0 @@ -/* eslint-disable @stylistic/js/indent */ - -/** @summary version id - * @desc For the JSROOT release the string in format 'major.minor.patch' like '7.0.0' */ -const version_id = '7.12.0', - -/** @summary version date - * @desc Release date in format day/month/year like '14/04/2022' */ -version_date = '28/10/2026', - -/** @summary version id and date - * @desc Produced by concatenation of {@link version_id} and {@link version_date} - * Like '7.0.0 14/04/2022' */ -version = version_id + ' ' + version_date, - -/** @summary Is node.js flag - * @private */ -nodejs = Boolean((typeof process === 'object') && process.versions?.node && process.versions.v8), - -/** @summary internal data - * @private */ -internals = { - /** @summary unique id counter, starts from 1 */ - id_counter: 1 -}, - -_src = import.meta?.url, - -_src_dir = '$jsrootsys'; - - -/** @summary Check if argument is a not-null Object - * @private */ -function isObject(arg) { return arg && typeof arg === 'object'; } - -/** @summary Check if argument is a Function - * @private */ -function isFunc(arg) { return typeof arg === 'function'; } - -/** @summary Check if argument is a String - * @private */ -function isStr(arg) { return typeof arg === 'string'; } - -/** @summary Check if object is a Promise - * @private */ -function isPromise(obj) { return isObject(obj) && isFunc(obj.then); } - -/** @summary Postpone func execution and return result in promise - * @private */ -function postponePromise(func, timeout) { - return new Promise(resolveFunc => { - setTimeout(() => { - const res = isFunc(func) ? func() : func; - resolveFunc(res); - }, timeout); - }); -} - -/** @summary Provide promise in any case - * @private */ -function getPromise(obj) { return isPromise(obj) ? obj : Promise.resolve(obj); } - - -/** @summary Location of JSROOT modules - * @desc Automatically detected and used to dynamically load other modules - * @private */ -let source_dir = ''; - -if (_src_dir[0] !== '$') - source_dir = _src_dir; -else if (_src && isStr(_src)) { - let pos = _src.indexOf('modules/core.mjs'); - if (pos < 0) - pos = _src.indexOf('build/jsroot.js'); - if (pos < 0) - pos = _src.indexOf('build/jsroot.min.js'); - if (pos >= 0) - source_dir = _src.slice(0, pos); - else - internals.ignore_v6 = true; -} - -if (!nodejs) { - if (source_dir) - console.log(`Set jsroot source_dir to ${source_dir}, ${version}`); - else - console.log(`jsroot bundle, ${version}`); -} - -/** @summary Is batch mode flag - * @private */ -let batch_mode = nodejs; - -/** @summary Indicates if running in batch mode */ -function isBatchMode() { return batch_mode; } - -/** @summary Set batch mode - * @private */ -function setBatchMode(on) { batch_mode = Boolean(on); } - -/** @summary Indicates if running inside Node.js */ -function isNodeJs() { return nodejs; } - -/** @summary atob function in all environments - * @private */ -const atob_func = isNodeJs() ? str => Buffer.from(str, 'base64').toString('latin1') : globalThis?.atob, - -/** @summary btoa function in all environments - * @private */ -btoa_func = isNodeJs() ? str => Buffer.from(str, 'latin1').toString('base64') : globalThis?.btoa, - -/** @summary browser detection flags - * @private */ -browser = { isFirefox: true, isSafari: false, isChrome: false, isWin: false, touches: false, screenWidth: 1200 }; - -if ((typeof document !== 'undefined') && (typeof window !== 'undefined') && (typeof navigator !== 'undefined')) { - navigator.userAgentData?.brands?.forEach(item => { - if (item.brand === 'HeadlessChrome') { - browser.isChromeHeadless = true; - browser.chromeVersion = parseInt(item.version); - } else if (item.brand === 'Chromium') { - browser.isChrome = true; - browser.chromeVersion = parseInt(item.version); - } - }); - - if (browser.chromeVersion) { - browser.isFirefox = false; - browser.isWin = navigator.userAgentData.platform === 'Windows'; - } else { - browser.isFirefox = navigator.userAgent.indexOf('Firefox') >= 0; - browser.isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; - browser.isChrome = Boolean(window.chrome); - browser.isChromeHeadless = navigator.userAgent.indexOf('HeadlessChrome') >= 0; - browser.chromeVersion = (browser.isChrome || browser.isChromeHeadless) ? (navigator.userAgent.indexOf('Chrom') > 0 ? parseInt(navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)[1]) : 134) : 0; - browser.isWin = navigator.userAgent.indexOf('Windows') >= 0; - } - browser.android = /android/i.test(navigator.userAgent); - browser.touches = ('ontouchend' in document); // identify if touch events are supported - browser.screenWidth = window.screen?.width ?? 1200; -} - -/** @summary Check if prototype string match to array (typed on untyped) - * @return {number} 0 - not array, 1 - regular array, 2 - typed array - * @private */ -function isArrayProto(proto) { - if ((proto.length < 14) || proto.indexOf('[object ')) - return 0; - const p = proto.indexOf('Array]'); - if ((p < 0) || (p !== proto.length - 6)) - return 0; - // plain array has only '[object Array]', typed array type name inside - return proto.length === 14 ? 1 : 2; -} - -/** @desc Specialized JSROOT constants, used in {@link settings} - * @namespace */ -const constants = { - /** @summary Kind of 3D rendering, used for {@link settings.Render3D} - * @namespace */ - Render3D: { - /** @summary Default 3D rendering, normally WebGL, if not supported - SVG */ - Default: 0, - /** @summary Use normal WebGL rendering and place as interactive Canvas element on HTML page */ - WebGL: 1, - /** @summary Use WebGL rendering, but convert into svg image, not interactive */ - WebGLImage: 2, - /** @summary Use SVG rendering, slow, imprecise and not interactive, not recommended */ - SVG: 3, - /** @summary Disable renderer, used for three.js model creation, only for internal use recommended */ - None: 4, - fromString(s) { - if ((s === 'webgl') || (s === 'gl')) - return this.WebGL; - if (s === 'img') - return this.WebGLImage; - if (s === 'svg') - return this.SVG; - if (s === 'none') - return this.None; - return this.Default; - } - }, - /** @summary Way to embed 3D into SVG, used for {@link settings.Embed3D} - * @namespace */ - Embed3D: { - /** @summary Do not embed 3D drawing, use complete space */ - NoEmbed: -1, - /** @summary Default embedding mode - on Firefox and latest Chrome is real ```Embed```, on all other ```Overlay``` */ - Default: 0, - /** @summary WebGL canvas not inserted into SVG, but just overlay. The only way how old Chrome browser can be used */ - Overlay: 1, - /** @summary Really embed WebGL Canvas into SVG */ - Embed: 2, - /** @summary Embedding, but when SVG rendering or SVG image conversion is used */ - EmbedSVG: 3, - /** @summary Convert string values into number */ - fromString(s) { - if (s === 'embed') - return this.Embed; - if (s === 'overlay') - return this.Overlay; - return this.Default; - } - }, - /** @summary How to use latex in text drawing, used for {@link settings.Latex} - * @namespace */ - Latex: { - /** @summary do not use Latex at all for text drawing */ - Off: 0, - /** @summary convert only known latex symbols */ - Symbols: 1, - /** @summary normal latex processing with svg */ - Normal: 2, - /** @summary use MathJax for complex cases, otherwise simple SVG text */ - MathJax: 3, - /** @summary always use MathJax for text rendering */ - AlwaysMathJax: 4, - /** @summary Convert string values into number */ - fromString(s) { - if (!s || !isStr(s)) - return this.Normal; - switch (s) { - case 'off': return this.Off; - case 'symbols': return this.Symbols; - case 'normal': - case 'latex': - case 'exp': - case 'experimental': return this.Normal; - case 'MathJax': - case 'mathjax': - case 'math': return this.MathJax; - case 'AlwaysMathJax': - case 'alwaysmath': - case 'alwaysmathjax': return this.AlwaysMathJax; - } - const code = parseInt(s); - return (Number.isInteger(code) && (code >= this.Off) && (code <= this.AlwaysMathJax)) ? code : this.Normal; - } - } -}, - -/** @desc Global JSROOT settings - * @namespace */ -settings = { - /** @summary Render of 3D drawing methods, see {@link constants.Render3D} for possible values */ - Render3D: constants.Render3D.Default, - /** @summary 3D drawing methods in batch mode, see {@link constants.Render3D} for possible values */ - Render3DBatch: constants.Render3D.Default, - /** @summary Way to embed 3D drawing in SVG, see {@link constants.Embed3D} for possible values */ - Embed3D: constants.Embed3D.Default, - /** @summary Use `resvg-js` backend for converting SVGs in node.js */ - UseResvgJs: true, - /** @summary Default canvas width */ - CanvasWidth: 1200, - /** @summary Default canvas height */ - CanvasHeight: 800, - /** @summary Canvas pixel ratio between viewport and display, default 1 */ - CanvasScale: 1, - /** @summary Enable or disable tooltips, default on */ - Tooltip: !nodejs, - /** @summary Time in msec for appearance of tooltips, 0 - no animation */ - TooltipAnimation: 500, - /** @summary Enables context menu usage */ - ContextMenu: !nodejs, - /** @summary Global zooming flag, enable/disable any kind of interactive zooming */ - Zooming: !nodejs, - /** @summary Zooming with the mouse events */ - ZoomMouse: !nodejs, - /** @summary Zooming with mouse wheel */ - ZoomWheel: !nodejs, - /** @summary Zooming on touch devices */ - ZoomTouch: !nodejs, - /** @summary Enables move and resize of elements like statistic box, title, pave, colz */ - MoveResize: !browser.touches && !nodejs, - /** @summary Configures keyboard key press handling - * @desc Can be disabled to prevent keys handling in complex HTML layouts - * @default true */ - HandleKeys: !nodejs, - /** @summary enables drag and drop functionality */ - DragAndDrop: !nodejs, - /** @summary Interactive dragging of TGraph points */ - DragGraphs: true, - /** @summary Value of user-select style in interactive drawings */ - UserSelect: 'none', - /** @summary Show progress box, can be false, true or 'modal' */ - ProgressBox: !nodejs, - /** @summary Show additional tool buttons on the canvas, false - disabled, true - enabled, 'popup' - only toggle button */ - ToolBar: nodejs ? false : 'popup', - /** @summary Position of toolbar 'left' left-bottom corner on canvas, 'right' - right-bottom corner on canvas, opposite on sub-pads */ - ToolBarSide: 'left', - /** @summary display tool bar vertical (default false) */ - ToolBarVert: false, - /** @summary if drawing inside particular div can be enlarged on full window */ - CanEnlarge: true, - /** @summary if frame position can be adjusted to let show axis or colz labels */ - CanAdjustFrame: false, - /** @summary calculation of text size consumes time and can be skipped to improve performance (but with side effects on text adjustments) */ - ApproxTextSize: false, - /** @summary Load symbol.ttf font to display greek labels. By default font file not loaded and unicode is used */ - LoadSymbolTtf: false, - /** @summary Histogram drawing optimization: 0 - disabled, 1 - only for large (>5000 1d bins, >50 2d bins) histograms, 2 - always */ - OptimizeDraw: 1, - /** @summary Automatically create stats box, default on */ - AutoStat: true, - /** @summary Default frame position in NFC - * @deprecated Use gStyle.fPad[Left/Right/Top/Bottom]Margin values instead, to be removed in v8 */ - FrameNDC: {}, - /** @summary size of pad, where many features will be deactivated like text draw or zooming */ - SmallPad: { width: 150, height: 100 }, - /** @summary Default color palette id */ - Palette: 57, - /** @summary Configures Latex usage, see {@link constants.Latex} for possible values */ - Latex: constants.Latex.Normal, - /** @summary Grads per segment in TGeo spherical shapes like tube */ - GeoGradPerSegm: 6, - /** @summary Enables faces compression after creation of composite shape */ - GeoCompressComp: true, - /** @summary if true, ignore all kind of URL options in the browser URL */ - IgnoreUrlOptions: false, - /** @summary how many items shown on one level of hierarchy */ - HierarchyLimit: 250, - /** @summary default display kind for the hierarchy painter */ - DisplayKind: 'simple', - /** @summary default left area width in browser layout */ - BrowserWidth: 250, - /** @summary custom format for all X values, when not specified {@link gStyle.fStatFormat} is used */ - XValuesFormat: undefined, - /** @summary custom format for all Y values, when not specified {@link gStyle.fStatFormat} is used */ - YValuesFormat: undefined, - /** @summary custom format for all Z values, when not specified {@link gStyle.fStatFormat} is used */ - ZValuesFormat: undefined, - /** @summary custom format for all float values like TH1F content */ - FloatFormat: '8.6g', - /** @summary custom format for all double values like TH1D content */ - DoubleFormat: '10.8g', - /** @summary Let detect and solve problem when server returns wrong Content-Length header - * @desc See [jsroot#189]{@link https://github.com/root-project/jsroot/issues/189} for more info - * Can be enabled by adding 'wrong_http_response' parameter to URL when using JSROOT UI - * @default false */ - HandleWrongHttpResponse: false, - /** @summary Tweak browser caching with stamp URL parameter - * @desc When specified, extra URL parameter like ```?stamp=unique_value``` append to each files loaded - * In such case browser will be forced to load file content disregards of browser or server cache settings - * Can be disabled by providing &usestamp=false in URL or via Settings/Files sub-menu - * Disabled by default on node.js, enabled in the web browsers */ - UseStamp: !nodejs, - /** @summary Maximal number of bytes ranges in http 'Range' header - * @desc Some http server has limitations for number of bytes ranges therefore let change maximal number via setting - * @default 200 */ - MaxRanges: 200, - /** @summary Number of bytes requested once by TTree::Draw processing - * @desc TTree can be very large and data from baskets read by portion specified by this variable - * @default 1e6 */ - TreeReadBunchSize: 1e6, - /** @summary File read timeout in ms - * @desc Configures timeout for each http operation for reading ROOT files - * @default 0 */ - FilesTimeout: 0, - /** @summary Default remap object for files loading - * @desc Allows to retry files reading if original URL fails - * @private */ - FilesRemap: { 'https://root.cern/': 'https://root-eos.web.cern.ch/' }, - /** @summary THttpServer read timeout in ms - * @desc Configures timeout for requests to THttpServer - * @default 0 */ - ServerTimeout: 0, - /** @summary Configure xhr.withCredentials = true when submitting http requests from JSROOT */ - WithCredentials: false, - /** @summary Skip streamer infos from the GUI */ - SkipStreamerInfos: false, - /** @summary Show only last cycle for objects in TFile */ - OnlyLastCycle: false, - /** @summary Configures dark mode for the GUI */ - DarkMode: false, - /** @summary Prefer to use saved points in TF1/TF2, avoids eval() and Function() when possible */ - PreferSavedPoints: false, - /** @summary Angle in degree for axis labels tilt when available space is not enough */ - AxisTiltAngle: 25, - /** @summary Strip axis labels trailing 0 or replace 10^0 by 1 */ - StripAxisLabels: true, - /** @summary If true exclude (cut off) axis labels which may exceed graphical range, also axis name can be specified */ - CutAxisLabels: false, - /** @summary Draw TF1 by default as curve or line */ - FuncAsCurve: false, - /** @summary Time zone used for date/time display, local by default, can be 'UTC' or 'Europe/Berlin' or any other valid value */ - TimeZone: '', - /** @summary Page URL which will be used to show item in new tab, jsroot main dir used by default */ - NewTabUrl: '', - /** @summary Extra parameters which will be append to the url when item shown in new tab */ - NewTabUrlPars: '', - /** @summary Export different settings in output URL */ - NewTabUrlExportSettings: false, - /** @summary Enable more debug output, also via 'WebGui.Debug: yes' on ROOT side */ - Debug: false -}, - -/** @namespace - * @summary Instance of TStyle object like in ROOT - * @desc Includes default draw styles, can be changed after loading of core.mjs - * or can be load from the file providing style=itemname in the URL - * See [TStyle docu]{@link https://root.cern/doc/master/classTStyle.html} 'Private attributes' section for more detailed info about each value */ -gStyle = { - fName: 'Modern', - /** @summary Default log x scale */ - fOptLogx: 0, - /** @summary Default log y scale */ - fOptLogy: 0, - /** @summary Default log z scale */ - fOptLogz: 0, - /** @summary Show date on canvas */ - fOptDate: 0, - /** @summary Show file name on canvas */ - fOptFile: 0, - /** @summary X position of date on canvas */ - fDateX: 0.01, - /** @summary Y position of date on canvas */ - fDateY: 0.01, - /** @summary Draw histogram title */ - fOptTitle: 1, - /** @summary Canvas fill color */ - fCanvasColor: 0, - /** @summary Pad fill color */ - fPadColor: 0, - /** @summary Pad bottom margin */ - fPadBottomMargin: 0.1, - /** @summary Pad top margin */ - fPadTopMargin: 0.1, - /** @summary Pad left margin */ - fPadLeftMargin: 0.1, - /** @summary Pad right margin */ - fPadRightMargin: 0.1, - /** @summary TPad.fGridx default value */ - fPadGridX: false, - /** @summary TPad.fGridy default value */ - fPadGridY: false, - /** @summary TPad.fTickx default value */ - fPadTickX: 0, - /** @summary TPad.fTicky default value */ - fPadTickY: 0, - /** @summary Pad border size */ - fPadBorderSize: 2, - /** @summary Pad border mode */ - fPadBorderMode: 0, - /** @summary Canvas border size */ - fCanvasBorderSize: 2, - /** @summary Canvas border mode */ - fCanvasBorderMode: 0, - /** @summary fill color for stat box */ - fStatColor: 0, - /** @summary fill style for stat box */ - fStatStyle: 1000, - /** @summary text color in stat box */ - fStatTextColor: 1, - /** @summary text size in stat box */ - fStatFontSize: 0, - /** @summary stat text font */ - fStatFont: 42, - /** @summary Stat border size */ - fStatBorderSize: 1, - /** @summary Printing format for stats */ - fStatFormat: '6.4g', - /** @summary Stat box X position - top right corner */ - fStatX: 0.98, - /** @summary Stat box Y position - top right corner */ - fStatY: 0.935, - /** @summary Stat box width */ - fStatW: 0.2, - /** @summary Stat box height */ - fStatH: 0.16, - /** @summary Title text align */ - fTitleAlign: 23, - /** @summary Title fill color */ - fTitleColor: 0, - /** @summary Title text color */ - fTitleTextColor: 1, - /** @summary Title border size */ - fTitleBorderSize: 0, - /** @summary Title text font */ - fTitleFont: 42, - /** @summary Title font size */ - fTitleFontSize: 0.05, - /** @summary Title fill style */ - fTitleStyle: 0, - /** @summary X position of top left corner of title box */ - fTitleX: 0.5, - /** @summary Y position of top left corner of title box */ - fTitleY: 0.995, - /** @summary Width of title box */ - fTitleW: 0, - /** @summary Height of title box */ - fTitleH: 0, - /** @summary Printing format for fit parameters */ - fFitFormat: '5.4g', - /** @summary Default optstat value */ - fOptStat: 1111, - /** @summary Default optfit value */ - fOptFit: 0, - /** @summary Default number of colors in contour */ - fNumberContours: 20, - /** @summary Grids color */ - fGridColor: 0, - /** @summary Grids line style */ - fGridStyle: 3, - /** @summary Grids line width */ - fGridWidth: 1, - /** @summary Frame fill color */ - fFrameFillColor: 0, - /** @summary Frame fill style */ - fFrameFillStyle: 1001, - /** @summary Frame line color */ - fFrameLineColor: 1, - /** @summary Frame line width */ - fFrameLineWidth: 1, - /** @summary Frame line style */ - fFrameLineStyle: 1, - /** @summary Frame border size */ - fFrameBorderSize: 1, - /** @summary Frame border mode */ - fFrameBorderMode: 0, - /** @summary size in pixels of end error for E1 draw options */ - fEndErrorSize: 2, - /** @summary X size of the error marks for the histogram drawings */ - fErrorX: 0.5, - /** @summary when true, BAR and LEGO drawing using base = 0 */ - fHistMinimumZero: false, - /** @summary Margin between histogram's top and pad's top */ - fHistTopMargin: 0.05, - /** @summary Histogram fill color */ - fHistFillColor: 0, - /** @summary Histogram fill style */ - fHistFillStyle: 1001, - /** @summary Histogram line color */ - fHistLineColor: 602, - /** @summary Histogram line style */ - fHistLineStyle: 1, - /** @summary Histogram line width */ - fHistLineWidth: 1, - /** @summary format for bin content */ - fPaintTextFormat: 'g', - /** @summary default time offset, UTC time at 01/01/95 */ - fTimeOffset: 788918400, - /** @summary Legend border size */ - fLegendBorderSize: 1, - /** @summary Legend text font */ - fLegendFont: 42, - /** @summary Legend font size */ - fLegendTextSize: 0, - /** @summary Legend fill color */ - fLegendFillColor: 0, - /** @summary Legend fill style */ - fLegendFillStyle: 1001, - /** @summary Hatches line width in fill styles */ - fHatchesLineWidth: 1, - fHatchesSpacing: 1, - fCandleWhiskerRange: 1.0, - fCandleBoxRange: 0.5, - fCandleScaled: false, - fViolinScaled: true, - fCandleCircleLineWidth: 1, - fCandleCrossLineWidth: 1, - fOrthoCamera: false, - fXAxisExpXOffset: 0, - fXAxisExpYOffset: 0, - fYAxisExpXOffset: 0, - fYAxisExpYOffset: 0, - fAxisMaxDigits: 5, - fStripDecimals: true, - /** @summary Width of bar for graphs */ - fBarWidth: 1, - /** @summary Offset of bar for graphs */ - fBarOffset: 0 -}; - -/** @summary Method returns current document in use - * @private */ -function getDocument() { - if (nodejs) - return internals.nodejs_document; - if (typeof document !== 'undefined') - return document; - if (typeof window === 'object') - return window.document; - return undefined; -} - -/** @summary Ensure global JSROOT and v6 support methods - * @private */ -let _ensureJSROOT = null; - - -/** @summary Generate mask for given bit - * @param {number} n bit number - * @return {number} produced mask - * @private */ -function BIT(n) { return 1 << n; } - -/** @summary Make deep clone of the object, including all sub-objects - * @return {object} cloned object - * @private */ -function clone(src, map, nofunc) { - if (!src) - return null; - - if (!map) - map = { obj: [], clones: [], nofunc }; - else { - const i = map.obj.indexOf(src); - if (i >= 0) - return map.clones[i]; - } - - const arr_kind = isArrayProto(Object.prototype.toString.apply(src)); - - // process normal array - if (arr_kind === 1) { - const tgt = []; - map.obj.push(src); - map.clones.push(tgt); - for (let i = 0; i < src.length; ++i) - tgt.push(isObject(src[i]) ? clone(src[i], map) : src[i]); - return tgt; - } - - // process typed array - if (arr_kind === 2) { - const tgt = []; - map.obj.push(src); - map.clones.push(tgt); - for (let i = 0; i < src.length; ++i) - tgt.push(src[i]); - - return tgt; - } - - const tgt = {}; - map.obj.push(src); - map.clones.push(tgt); - - for (const k in src) { - if (isObject(src[k])) - tgt[k] = clone(src[k], map); - else if (!map.nofunc || !isFunc(src[k])) - tgt[k] = src[k]; - } - - return tgt; -} - -// used very often - keep shortcut -const extend = Object.assign; - -/** @summary Adds specific methods to the object. - * @desc JSROOT implements some basic methods for different ROOT classes. - * @function - * @param {object} obj - object where methods are assigned - * @param {string} [typename] - optional typename, if not specified, obj._typename will be used - * @private */ -let addMethods = null; - -/** @summary Should be used to parse JSON string produced with TBufferJSON class - * @desc Replace all references inside object like { "$ref": "1" } - * @param {object|string} json object where references will be replaced - * @return {object} parsed object */ -function parse(json) { - if (!json) - return null; - - const obj = isStr(json) ? JSON.parse(json) : json, map = []; - let newfmt; - - const unref_value = value => { - if ((value === null) || (value === undefined)) - return; - - if (isStr(value)) { - if (newfmt || (value.length < 6) || value.indexOf('$ref:')) - return; - const ref = parseInt(value.slice(5)); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) - return; - newfmt = false; - return map[ref]; - } - - if (typeof value !== 'object') - return; - - const proto = Object.prototype.toString.apply(value); - - // scan array - it can contain other objects - if (isArrayProto(proto) > 0) { - for (let i = 0; i < value.length; ++i) { - const res = unref_value(value[i]); - if (res !== undefined) - value[i] = res; - } - return; - } - - const ks = Object.keys(value), len = ks.length; - - if ((newfmt !== false) && (len === 1) && (ks[0] === '$ref')) { - const ref = parseInt(value.$ref); - if (!Number.isInteger(ref) || (ref < 0) || (ref >= map.length)) - return; - newfmt = true; - return map[ref]; - } - - if ((newfmt !== false) && (len > 1) && (ks[0] === '$arr') && (ks[1] === 'len')) { - // this is ROOT-coded array - let arr; - switch (value.$arr) { - case 'Int8': arr = new Int8Array(value.len); break; - case 'Uint8': arr = new Uint8Array(value.len); break; - case 'Int16': arr = new Int16Array(value.len); break; - case 'Uint16': arr = new Uint16Array(value.len); break; - case 'Int32': arr = new Int32Array(value.len); break; - case 'Uint32': arr = new Uint32Array(value.len); break; - case 'Float32': arr = new Float32Array(value.len); break; - case 'Int64': - case 'Uint64': - case 'Float64': arr = new Float64Array(value.len); break; - default: arr = new Array(value.len); - } - - arr.fill((value.$arr === 'Bool') ? false : 0); - - if (value.b !== undefined) { - // base64 coding - const buf = atob_func(value.b); - if (arr.buffer) { - const dv = new DataView(arr.buffer, value.o || 0), - blen = Math.min(buf.length, dv.byteLength); - for (let k = 0; k < blen; ++k) - dv.setUint8(k, buf.charCodeAt(k)); - } else - throw new Error('base64 coding supported only for native arrays with binary data'); - } else { - // compressed coding - let nkey = 2, p = 0; - while (nkey < len) { - if (ks[nkey][0] === 'p') - p = value[ks[nkey++]]; // position - if (ks[nkey][0] !== 'v') - throw new Error(`Unexpected member ${ks[nkey]} in array decoding`); - const v = value[ks[nkey++]]; // value - if (typeof v === 'object') { - for (let k = 0; k < v.length; ++k) - arr[p++] = v[k]; - } else { - arr[p++] = v; - if ((nkey < len) && (ks[nkey][0] === 'n')) { - let cnt = value[ks[nkey++]]; // counter - while (--cnt) - arr[p++] = v; - } - } - } - } - - return arr; - } - - if ((newfmt !== false) && (len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { - newfmt = true; - const f1 = unref_value(value.first), - s1 = unref_value(value.second); - if (f1 !== undefined) - value.first = f1; - if (s1 !== undefined) - value.second = s1; - value._typename = value.$pair; - delete value.$pair; - return; // pair object is not counted in the objects map - } - - // prevent endless loop - if (map.indexOf(value) >= 0) - return; - - // add object to object map - map.push(value); - - // add methods to all objects, where _typename is specified - if (value._typename) - addMethods(value); - - for (let k = 0; k < len; ++k) { - const i = ks[k], res = unref_value(value[i]); - if (res !== undefined) - value[i] = res; - } - }; - - unref_value(obj); - - return obj; -} - -/** @summary Parse response from multi.json request - * @desc Method should be used to parse JSON code, produced by multi.json request of THttpServer - * @param {string} json string to parse - * @return {Array} array of parsed elements */ -function parseMulti(json) { - if (!json) - return null; - const arr = JSON.parse(json); - if (arr?.length) { - for (let i = 0; i < arr.length; ++i) - arr[i] = parse(arr[i]); - } - return arr; -} - -/** @summary Method converts JavaScript object into ROOT-like JSON - * @desc When performed properly, JSON can be used in [TBufferJSON::fromJSON()]{@link https://root.cern/doc/master/classTBufferJSON.html} - * method to read data back with C++. Or one can again parse json with {@link parse} function - * @param {object} obj - JavaScript object to convert - * @param {number} [spacing] - optional line spacing in JSON - * @return {string} produced JSON code - * @example - * import { openFile, draw, toJSON } from 'https://root.cern/js/latest/modules/main.mjs'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let obj = await file.readObject('hpxpy;1'); - * obj.fTitle = 'New histogram title'; - * let json = toJSON(obj); */ -function toJSON(obj, spacing) { - if (!isObject(obj)) - return ''; - - const map = [], // map of stored objects - copy_value = value => { - if (isFunc(value)) - return undefined; - - if ((value === undefined) || (value === null) || !isObject(value)) - return value; - - // typed array need to be converted into normal array, otherwise looks strange - if (isArrayProto(Object.prototype.toString.apply(value)) > 0) { - const arr = new Array(value.length); - for (let i = 0; i < value.length; ++i) - arr[i] = copy_value(value[i]); - return arr; - } - - // this is how reference is code - const refid = map.indexOf(value); - if (refid >= 0) - return { $ref: refid }; - - const ks = Object.keys(value), len = ks.length, tgt = {}; - - if ((len === 3) && (ks[0] === '$pair') && (ks[1] === 'first') && (ks[2] === 'second')) { - // special handling of pair objects which does not included into objects map - tgt.$pair = value.$pair; - tgt.first = copy_value(value.first); - tgt.second = copy_value(value.second); - return tgt; - } - - map.push(value); - - for (let k = 0; k < len; ++k) { - const name = ks[k]; - if (name && (name[0] !== '$')) - tgt[name] = copy_value(value[name]); - } - - return tgt; - }, - tgt = copy_value(obj); - - return JSON.stringify(tgt, null, spacing); -} - -/** @summary decodes URL options after '?' mark - * @desc Following options supported ?opt1&opt2=3 - * @param {string} [url] URL string with options, document.URL will be used when not specified - * @return {object} with ```.has(opt)``` and ```.get(opt,dflt)``` methods - * @example - * import { decodeUrl } from 'https://root.cern/js/latest/modules/core.mjs'; - * let d = decodeUrl('any?opt1&op2=3'); - * console.log(`Has opt1 ${d.has('opt1')}`); // true - * console.log(`Get opt1 ${d.get('opt1')}`); // '' - * console.log(`Get opt2 ${d.get('opt2')}`); // '3' - * console.log(`Get opt3 ${d.get('opt3','-')}`); // '-' */ -function decodeUrl(url) { - const res = { - opts: {}, - has(opt) { return this.opts[opt] !== undefined; }, - get(opt, dflt) { return this.opts[opt] ?? dflt; } - }; - - if (!url || !isStr(url)) { - if (settings.IgnoreUrlOptions || (typeof document === 'undefined')) - return res; - url = document.URL; - } - res.url = url; - - const p1 = url.indexOf('?'); - if (p1 < 0) - return res; - url = decodeURI(url.slice(p1 + 1)); - - while (url) { - // try to correctly handle quotes in the URL - let pos = 0, nq = 0, eq = -1, firstq = -1; - while ((pos < url.length) && (nq || ((url[pos] !== '&') && (url[pos] !== '#')))) { - switch (url[pos]) { - case '\'': - if (nq >= 0) - nq = (nq + 1) % 2; - if (firstq < 0) - firstq = pos; - break; - case '"': - if (nq <= 0) - nq = (nq - 1) % 2; - if (firstq < 0) - firstq = pos; - break; - case '=': - if ((firstq < 0) && (eq < 0)) - eq = pos; - break; - } - pos++; - } - - if ((eq < 0) && (firstq < 0)) - res.opts[url.slice(0, pos)] = ''; - else if (eq > 0) { - let val = url.slice(eq + 1, pos); - if (((val[0] === '\'') || (val[0] === '"')) && (val.at(0) === val.at(-1))) - val = val.slice(1, val.length - 1); - res.opts[url.slice(0, eq)] = val; - } - - if ((pos >= url.length) || (url[pos] === '#')) - break; - - url = url.slice(pos + 1); - } - - return res; -} - -/** @summary Find function with given name - * @private */ -function findFunction(name) { - if (isFunc(name)) - return name; - if (!isStr(name)) - return null; - const names = name.split('.'); - let elem = globalThis; - - for (let n = 0; elem && (n < names.length); ++n) - elem = elem[names[n]]; - - return isFunc(elem) ? elem : null; -} - -/** @summary Method to create http request, without promise can be used only in browser environment - * @private */ -function createHttpRequest(url, kind, user_accept_callback, user_reject_callback, use_promise, tmout) { - function handle_error(xhr, message, code, abort_reason) { - if (!xhr.did_abort) { - xhr.did_abort = abort_reason || true; - xhr.abort(); - } - if (!xhr.did_error || abort_reason) - console.warn(message); - if (!xhr.did_error) { - xhr.did_error = true; - xhr.error_callback(Error(message), code); - } - } - function configureXhr(xhr) { - xhr.http_callback = isFunc(user_accept_callback) ? user_accept_callback.bind(xhr) : () => {}; - xhr.error_callback = isFunc(user_reject_callback) ? user_reject_callback.bind(xhr) : function() { this.http_callback(null); }; - - if (!kind) - kind = 'buf'; - - let method = 'GET', is_async = true; - const p = kind.indexOf(';sync'); - if (p > 0) { - kind = kind.slice(0, p); - is_async = false; - } - switch (kind) { - case 'head': - method = 'HEAD'; - break; - case 'posttext': - method = 'POST'; - kind = 'text'; - break; - case 'postbuf': - method = 'POST'; - kind = 'buf'; - break; - case 'post': - case 'multi': - method = 'POST'; - break; - } - - xhr.kind = kind; - - if (settings.WithCredentials) - xhr.withCredentials = true; - - if (settings.HandleWrongHttpResponse && (method === 'GET') && isFunc(xhr.addEventListener)) { - xhr.addEventListener('progress', function(oEvent) { - if (oEvent.lengthComputable && this.expected_size && (oEvent.loaded > this.expected_size)) - handle_error(this, `Server sends more bytes ${oEvent.loaded} than expected ${this.expected_size}. Abort I/O operation`, 598); - }.bind(xhr)); - } - - xhr.onreadystatechange = function() { - if (this.did_abort) - return; - - if ((this.readyState === 2) && this.expected_size) { - const len = parseInt(this.getResponseHeader('Content-Length')); - if (Number.isInteger(len) && (len > this.expected_size) && !settings.HandleWrongHttpResponse) - return handle_error(this, `Server response size ${len} larger than expected ${this.expected_size}. Abort I/O operation`, 599, 'large'); - } - - if (this.readyState !== 4) - return; - - if ((this.status !== 200) && (this.status !== 206) && !browser.qt6 && - // in these special cases browsers not always set status - !((this.status === 0) && ((url.indexOf('file://') === 0) || (url.indexOf('blob:') === 0)))) - return handle_error(this, `Fail to load url ${url}`, this.status); - - if (this.nodejs_checkzip && (this.getResponseHeader('content-encoding') === 'gzip')) { - // special handling of gzip JSON objects in Node.js - return import('zlib').then(handle => { - const res = handle.unzipSync(Buffer.from(this.response)), - obj = JSON.parse(res); // zlib returns Buffer, use JSON to parse it - return this.http_callback(parse(obj)); - }); - } - - switch (this.kind) { - case 'xml': return this.http_callback(this.responseXML); - case 'text': return this.http_callback(this.responseText); - case 'object': return this.http_callback(parse(this.responseText)); - case 'multi': return this.http_callback(parseMulti(this.responseText)); - case 'head': return this.http_callback(this); - } - - // if no response type is supported, return as text (most probably, will fail) - if (this.responseType === undefined) - return this.http_callback(this.responseText); - - if ((this.kind === 'bin') && ('byteLength' in this.response)) { - // if string representation in requested - provide it - const u8Arr = new Uint8Array(this.response); - let filecontent = ''; - for (let i = 0; i < u8Arr.length; ++i) - filecontent += String.fromCharCode(u8Arr[i]); - return this.http_callback(filecontent); - } - - this.http_callback(this.response); - }; - - xhr.open(method, url, is_async); - - if ((kind === 'bin') || (kind === 'buf')) - xhr.responseType = 'arraybuffer'; - - if (nodejs && (method === 'GET') && (kind === 'object') && (url.indexOf('.json.gz') > 0)) { - xhr.nodejs_checkzip = true; - xhr.responseType = 'arraybuffer'; - } - - if (tmout && Number.isFinite(tmout)) { - xhr.timeout = tmout; - xhr.ontimeout = function() { handle_error(this, `Request ${url} timeout set ${tmout} ms`, 600, 'timeout'); }; - } - - return xhr; - } - - if (isNodeJs()) { - if (!use_promise) - throw Error('Not allowed to create http requests in node.js without promise'); - return import('xhr2').then(h => configureXhr(new h.default())); - } - - const xhr = configureXhr(new XMLHttpRequest()); - return use_promise ? Promise.resolve(xhr) : xhr; -} - -/** @summary Submit asynchronous http request - * @desc Following requests kind can be specified: - * - 'bin' - abstract binary data, result as string - * - 'buf' - abstract binary data, result as ArrayBuffer (default) - * - 'text' - returns req.responseText - * - 'object' - returns parse(req.responseText) - * - 'multi' - returns correctly parsed multi.json request - * - 'xml' - returns req.responseXML - * - 'head' - returns request itself, uses 'HEAD' request method - * - 'post' - creates post request, submits req.send(post_data) - * - 'postbuf' - creates post request, expects binary data as response - * @param {string} url - URL for the request - * @param {string} kind - kind of requested data - * @param {string} [post_data] - data submitted with post kind of request - * @return {Promise} Promise for requested data, result type depends from the kind - * @example - * import { httpRequest } from 'https://root.cern/js/latest/modules/core.mjs'; - * httpRequest('https://root.cern/js/files/thstack.json.gz', 'object') - * .then(obj => console.log(`Get object of type ${obj._typename}`)) - * .catch(err => console.error(err.message)); */ -async function httpRequest(url, kind, post_data) { - return new Promise((resolve, reject) => { - createHttpRequest(url, kind, resolve, reject, true).then(xhr => xhr.send(post_data || null)); - }); -} - -/** @summary Inject javascript code - * @desc Replacement for eval - * @return {Promise} when code is injected - * @private */ -async function injectCode(code) { - if (nodejs) { - let name, fs; - return import('tmp').then(tmp => { - name = tmp.tmpNameSync() + '.js'; - return import('fs'); - }).then(_fs => { - fs = _fs; - fs.writeFileSync(name, code); - return import(/* webpackIgnore: true */ /* @vite-ignore */ 'file://' + name); - }).finally(() => fs.unlinkSync(name)); - } - - if (typeof document !== 'undefined') { - // check if code already loaded - to avoid duplication - const scripts = document.getElementsByTagName('script'); - for (let n = 0; n < scripts.length; ++n) { - if (scripts[n].innerText === code) - return true; - } - - // try to detect if code includes import and must be treated as module - const is_v6 = code.indexOf('JSROOT.require') >= 0, - is_mjs = !is_v6 && (code.indexOf('import {') > 0) && (code.indexOf('} from \'') > 0), - is_batch = !is_v6 && !is_mjs && (code.indexOf('JSROOT.ObjectPainter') >= 0), - promise = (is_v6 ? _ensureJSROOT() : Promise.resolve(true)); - - if (is_batch && !globalThis.JSROOT) - globalThis.JSROOT = internals.jsroot; - - return promise.then(() => { - const element = document.createElement('script'); - element.setAttribute('type', is_mjs ? 'module' : 'text/javascript'); - element.innerText = code; - document.head.appendChild(element); - // while onload event not fired, just postpone resolve - return isBatchMode() ? true : postponePromise(true, 10); - }); - } - - return false; -} - -/** @summary Load ES6 modules - * @param {string} arg - single URL or array of URLs - * @return {Promise} */ -async function loadModules(arg) { - if (isStr(arg)) - arg = arg.split(';'); - if (!arg.length) - return true; - return import(/* webpackIgnore: true */ /* @vite-ignore */ arg.shift()).then(() => loadModules(arg)); -} - -/** @summary Load script or CSS file into the browser - * @param {string} url - script or css file URL (or array, in this case they all loaded sequentially) - * @return {Promise} */ -async function loadScript(url) { - if (!url) - return true; - - if (isStr(url) && (url.indexOf(';') >= 0)) - url = url.split(';'); - - if (!isStr(url)) { - const scripts = url, loadNext = () => { - return !scripts.length ? true : loadScript(scripts.shift()).then(loadNext, loadNext); - }; - return loadNext(); - } - - if (url.indexOf('$$$') === 0) { - url = url.slice(3); - if ((url.indexOf('style/') === 0) && (url.indexOf('.css') < 0)) - url += '.css'; - url = source_dir + url; - } - - const isstyle = url.indexOf('.css') > 0; - - if (nodejs) { - if (isstyle) - return null; - if ((url.indexOf('http:') === 0) || (url.indexOf('https:') === 0)) - return httpRequest(url, 'text').then(code => injectCode(code)); - - // local files, read and use it - if (url.indexOf('./') === 0) - return import('fs').then(fs => injectCode(fs.readFileSync(url))); - - return import(/* webpackIgnore: true */ /* @vite-ignore */ url); - } - - const match_url = src => { - if (src === url) - return true; - const indx = src.indexOf(url); - return (indx > 0) && (indx + url.length === src.length) && (src[indx - 1] === '/'); - }; - - if (isstyle) { - const styles = document.getElementsByTagName('link'); - for (let n = 0; n < styles.length; ++n) { - if (!styles[n].href || (styles[n].type !== 'text/css') || (styles[n].rel !== 'stylesheet')) - continue; - if (match_url(styles[n].href)) - return true; - } - } else { - const scripts = document.getElementsByTagName('script'); - for (let n = 0; n < scripts.length; ++n) { - if (match_url(scripts[n].src)) - return true; - } - } - - let element; - if (isstyle) { - element = document.createElement('link'); - element.setAttribute('rel', 'stylesheet'); - element.setAttribute('type', 'text/css'); - element.setAttribute('href', url); - } else { - element = document.createElement('script'); - element.setAttribute('type', 'text/javascript'); - element.setAttribute('src', url); - } - - return new Promise((resolveFunc, rejectFunc) => { - element.onload = () => resolveFunc(true); - element.onerror = () => { - element.remove(); - rejectFunc(Error(`Fail to load ${url}`)); - }; - document.head.appendChild(element); - }); -} - -_ensureJSROOT = async function() { - const pr = globalThis.JSROOT ? Promise.resolve(true) : loadScript(source_dir + 'scripts/JSRoot.core.js'); - - return pr.then(() => { - if (globalThis.JSROOT?._complete_loading) - return globalThis.JSROOT._complete_loading(); - }).then(() => globalThis.JSROOT); -}; - - -const prROOT = 'ROOT.', clTObject = 'TObject', clTNamed = 'TNamed', clTString = 'TString', clTObjString = 'TObjString', - clTKey = 'TKey', clTFile = 'TFile', clTTree = 'TTree', - clTList = 'TList', clTHashList = 'THashList', clTMap = 'TMap', clTObjArray = 'TObjArray', clTClonesArray = 'TClonesArray', - clTAttLine = 'TAttLine', clTAttFill = 'TAttFill', clTAttMarker = 'TAttMarker', clTAttText = 'TAttText', - clTHStack = 'THStack', clTGraph = 'TGraph', clTMultiGraph = 'TMultiGraph', clTCutG = 'TCutG', - clTGraph2DErrors = 'TGraph2DErrors', clTGraph2DAsymmErrors = 'TGraph2DAsymmErrors', - clTGraphPolar = 'TGraphPolar', clTGraphPolargram = 'TGraphPolargram', clTGraphTime = 'TGraphTime', - clTPave = 'TPave', clTPaveText = 'TPaveText', clTPaveStats = 'TPaveStats', clTPavesText = 'TPavesText', - clTPaveLabel = 'TPaveLabel', clTPaveClass = 'TPaveClass', clTDiamond = 'TDiamond', - clTLegend = 'TLegend', clTLegendEntry = 'TLegendEntry', - clTPaletteAxis = 'TPaletteAxis', clTImagePalette = 'TImagePalette', - clTText = 'TText', clTLink = 'TLink', clTLatex = 'TLatex', clTMathText = 'TMathText', clTAnnotation = 'TAnnotation', - clTColor = 'TColor', clTLine = 'TLine', clTMarker = 'TMarker', clTBox = 'TBox', clTPolyLine = 'TPolyLine', - clTPolyLine3D = 'TPolyLine3D', clTPolyMarker3D = 'TPolyMarker3D', - clTAttPad = 'TAttPad', clTPad = 'TPad', clTCanvas = 'TCanvas', clTFrame = 'TFrame', clTAttCanvas = 'TAttCanvas', - clTGaxis = 'TGaxis', clTAttAxis = 'TAttAxis', clTAxis = 'TAxis', clTStyle = 'TStyle', - clTH1 = 'TH1', clTH1I = 'TH1I', clTH1F = 'TH1F', clTH1D = 'TH1D', - clTH2 = 'TH2', clTH2I = 'TH2I', clTH2F = 'TH2F', clTH2D = 'TH2D', - clTH3 = 'TH3', clTH3I = 'TH3I', clTH3F = 'TH3F', clTH3D = 'TH3D', - clTProfile = 'TProfile', clTProfile2D = 'TProfile2D', clTProfile3D = 'TProfile3D', - clTF1 = 'TF1', clTF12 = 'TF12', clTF2 = 'TF2', clTF3 = 'TF3', - clTGeoVolume = 'TGeoVolume', clTGeoNode = 'TGeoNode', clTGeoNodeMatrix = 'TGeoNodeMatrix', - nsROOT = 'ROOT::', nsREX = nsROOT + 'Experimental::', nsSVG = 'http://www.w3.org/2000/svg', - kNoZoom = -1111, kNoStats = BIT(9), kInspect = 'inspect', kTitle = 'title', - urlClassPrefix = 'https://root.cern/doc/master/class'; - - -/** @summary Create some ROOT classes - * @desc Supported classes: `TObject`, `TNamed`, `TList`, `TAxis`, `TLine`, `TText`, `TLatex`, `TPad`, `TCanvas` - * @param {string} typename - ROOT class name - * @example - * import { create } from 'https://root.cern/js/latest/modules/core.mjs'; - * let obj = create('TNamed'); - * obj.fName = 'name'; - * obj.fTitle = 'title'; */ -function create(typename, target) { - const obj = target || {}; - - switch (typename) { - case clTObject: - extend(obj, { fUniqueID: 0, fBits: 0 }); - break; - case clTNamed: - extend(obj, { fUniqueID: 0, fBits: 0, fName: '', fTitle: '' }); - break; - case clTList: - case clTHashList: - extend(obj, { name: typename, arr: [], opt: [] }); - break; - case clTObjArray: - extend(obj, { name: typename, arr: [] }); - break; - case clTAttAxis: - extend(obj, { fNdivisions: 510, fAxisColor: 1, - fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, fTickLength: 0.03, - fTitleOffset: 1, fTitleSize: 0.035, fTitleColor: 1, fTitleFont: 42 }); - break; - case clTAxis: - create(clTNamed, obj); - create(clTAttAxis, obj); - extend(obj, { fNbins: 1, fXmin: 0, fXmax: 1, fXbins: [], fFirst: 0, fLast: 0, - fBits2: 0, fTimeDisplay: false, fTimeFormat: '', fLabels: null, fModLabs: null }); - break; - case clTAttLine: - extend(obj, { fLineColor: 1, fLineStyle: 1, fLineWidth: 1 }); - break; - case clTAttFill: - extend(obj, { fFillColor: 0, fFillStyle: 0 }); - break; - case clTAttMarker: - extend(obj, { fMarkerColor: 1, fMarkerStyle: 1, fMarkerSize: 1 }); - break; - case clTLine: - create(clTObject, obj); - create(clTAttLine, obj); - extend(obj, { fX1: 0, fX2: 1, fY1: 0, fY2: 1 }); - break; - case clTMarker: - create(clTObject, obj); - create(clTAttMarker, obj); - extend(obj, { fX: 0, fY: 0 }); - break; - case clTBox: - create(clTObject, obj); - create(clTAttLine, obj); - create(clTAttFill, obj); - extend(obj, { fX1: 0, fX2: 1, fY1: 0, fY2: 1 }); - break; - case clTPave: - create(clTBox, obj); - extend(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 0, fY2NDC: 0, - fBorderSize: 0, fInit: 1, fShadowColor: 1, - fCornerRadius: 0, fOption: 'brNDC', fName: '' }); - break; - case clTAttText: - extend(obj, { fTextAngle: 0, fTextSize: 0, fTextAlign: 22, fTextColor: 1, fTextFont: 42 }); - break; - case clTPaveText: - create(clTPave, obj); - create(clTAttText, obj); - extend(obj, { fLabel: '', fLongest: 27, fMargin: 0.05, fLines: create(clTList) }); - break; - case clTPaveStats: - create(clTPaveText, obj); - extend(obj, { fFillColor: gStyle.fStatColor, fFillStyle: gStyle.fStatStyle, - fTextFont: gStyle.fStatFont, fTextSize: gStyle.fStatFontSize, fTextColor: gStyle.fStatTextColor, - fBorderSize: gStyle.fStatBorderSize, - fOptFit: gStyle.fOptFit, fOptStat: gStyle.fOptStat, fFitFormat: gStyle.fFitFormat, fStatFormat: gStyle.fStatFormat, fParent: null }); - break; - case clTLegend: - create(clTPave, obj); - create(clTAttText, obj); - extend(obj, { fColumnSeparation: 0, fEntrySeparation: 0.1, fMargin: 0.25, fNColumns: 1, fPrimitives: create(clTList), fName: clTPave, - fBorderSize: gStyle.fLegendBorderSize, fTextFont: gStyle.fLegendFont, fTextSize: gStyle.fLegendTextSize, - fFillColor: gStyle.fLegendFillColor, fFillStyle: gStyle.fLegendFillStyle }); - break; - case clTPaletteAxis: - create(clTPave, obj); - extend(obj, { fAxis: create(clTGaxis), fH: null, fName: clTPave }); - break; - case clTLegendEntry: - create(clTObject, obj); - create(clTAttText, obj); - create(clTAttLine, obj); - create(clTAttFill, obj); - create(clTAttMarker, obj); - extend(obj, { fLabel: '', fObject: null, fOption: '', fTextAlign: 0, fTextColor: 0, fTextFont: 0 }); - break; - case clTText: - create(clTNamed, obj); - create(clTAttText, obj); - extend(obj, { fX: 0, fY: 0 }); - break; - case clTLatex: - create(clTText, obj); - create(clTAttLine, obj); - extend(obj, { fLimitFactorSize: 3, fOriginSize: 0.04 }); - break; - case clTObjString: - create(clTObject, obj); - extend(obj, { fString: '' }); - break; - case clTH1: - create(clTNamed, obj); - create(clTAttLine, obj); - create(clTAttFill, obj); - create(clTAttMarker, obj); - extend(obj, { fBits: 8, fNcells: 0, - fXaxis: create(clTAxis), fYaxis: create(clTAxis), fZaxis: create(clTAxis), - fFillColor: gStyle.fHistFillColor, fFillStyle: gStyle.fHistFillStyle, - fLineColor: gStyle.fHistLineColor, fLineStyle: gStyle.fHistLineStyle, fLineWidth: gStyle.fHistLineWidth, - fBarOffset: 0, fBarWidth: 1000, fEntries: 0, - fTsumw: 0, fTsumw2: 0, fTsumwx: 0, fTsumwx2: 0, - fMaximum: kNoZoom, fMinimum: kNoZoom, fNormFactor: 0, fContour: [], - fSumw2: [], fOption: '', fFunctions: create(clTList), - fBufferSize: 0, fBuffer: [], fBinStatErrOpt: 0, fStatOverflows: 2 }); - break; - case clTH1I: - case clTH1D: - case 'TH1L64': - case clTH1F: - case 'TH1S': - case 'TH1C': - create(clTH1, obj); - obj.fArray = []; - break; - case clTH2: - create(clTH1, obj); - extend(obj, { fScalefactor: 1, fTsumwy: 0, fTsumwy2: 0, fTsumwxy: 0 }); - break; - case clTH2I: - case 'TH2L64': - case clTH2F: - case clTH2D: - case 'TH2S': - case 'TH2C': - create(clTH2, obj); - obj.fArray = []; - break; - case clTH3: - create(clTH1, obj); - extend(obj, { fTsumwy: 0, fTsumwy2: 0, fTsumwz: 0, fTsumwz2: 0, fTsumwxy: 0, fTsumwxz: 0, fTsumwyz: 0 }); - break; - case clTH3I: - case 'TH3L64': - case clTH3F: - case clTH3D: - case 'TH3S': - case 'TH3C': - create(clTH3, obj); - obj.fArray = []; - break; - case clTHStack: - create(clTNamed, obj); - extend(obj, { fHists: create(clTList), fHistogram: null, fMaximum: kNoZoom, fMinimum: kNoZoom }); - break; - case clTGraph: - create(clTNamed, obj); - create(clTAttLine, obj); - create(clTAttFill, obj); - create(clTAttMarker, obj); - extend(obj, { fFunctions: create(clTList), fHistogram: null, - fMaxSize: 0, fMaximum: kNoZoom, fMinimum: kNoZoom, fNpoints: 0, fX: [], fY: [] }); - break; - case 'TGraphAsymmErrors': - create(clTGraph, obj); - extend(obj, { fEXlow: [], fEXhigh: [], fEYlow: [], fEYhigh: [] }); - break; - case clTMultiGraph: - create(clTNamed, obj); - extend(obj, { fFunctions: create(clTList), fGraphs: create(clTList), - fHistogram: null, fMaximum: kNoZoom, fMinimum: kNoZoom }); - break; - case clTGraphPolargram: - create(clTNamed, obj); - create(clTAttText, obj); - create(clTAttLine, obj); - extend(obj, { fRadian: false, fDegree: false, fGrad: false, fPolarLabelColor: 1, fRadialLabelColor: 1, - fAxisAngle: 0, fPolarOffset: 0.04, fPolarTextSize: 0.04, fRadialOffset: 0.025, fRadialTextSize: 0.035, - fRwrmin: 0, fRwrmax: 1, fRwtmin: 0, fRwtmax: 2 * Math.PI, fTickpolarSize: 0.02, - fPolarLabelFont: 62, fRadialLabelFont: 62, fCutRadial: 0, fNdivRad: 508, fNdivPol: 508 }); - break; - case clTPolyLine: - create(clTObject, obj); - create(clTAttLine, obj); - create(clTAttFill, obj); - extend(obj, { fLastPoint: -1, fN: 0, fOption: '', fX: null, fY: null }); - break; - case clTGaxis: - create(clTLine, obj); - create(clTAttText, obj); - extend(obj, { fChopt: '', fFunctionName: '', fGridLength: 0, - fLabelColor: 1, fLabelFont: 42, fLabelOffset: 0.005, fLabelSize: 0.035, - fName: '', fNdiv: 12, fTickSize: 0.02, fTimeFormat: '', - fTitle: '', fTitleOffset: 1, fTitleSize: 0.035, - fWmax: 100, fWmin: 0 }); - break; - case clTAttPad: - extend(obj, { fLeftMargin: gStyle.fPadLeftMargin, - fRightMargin: gStyle.fPadRightMargin, - fBottomMargin: gStyle.fPadBottomMargin, - fTopMargin: gStyle.fPadTopMargin, - fXfile: 2, fYfile: 2, fAfile: 1, fXstat: 0.99, fYstat: 0.99, fAstat: 2, - fFrameFillColor: gStyle.fFrameFillColor, - fFrameFillStyle: gStyle.fFrameFillStyle, - fFrameLineColor: gStyle.fFrameLineColor, - fFrameLineWidth: gStyle.fFrameLineWidth, - fFrameLineStyle: gStyle.fFrameLineStyle, - fFrameBorderSize: gStyle.fFrameBorderSize, - fFrameBorderMode: gStyle.fFrameBorderMode }); - break; - case clTPad: - create(clTObject, obj); - create(clTAttLine, obj); - create(clTAttFill, obj); - create(clTAttPad, obj); - extend(obj, { fFillColor: gStyle.fPadColor, fFillStyle: 1001, - fX1: 0, fY1: 0, fX2: 1, fY2: 1, fXtoAbsPixelk: 1, fXtoPixelk: 1, - fXtoPixel: 1, fYtoAbsPixelk: 1, fYtoPixelk: 1, fYtoPixel: 1, - fUtoAbsPixelk: 1, fUtoPixelk: 1, fUtoPixel: 1, fVtoAbsPixelk: 1, - fVtoPixelk: 1, fVtoPixel: 1, fAbsPixeltoXk: 1, fPixeltoXk: 1, - fPixeltoX: 1, fAbsPixeltoYk: 1, fPixeltoYk: 1, fPixeltoY: 1, - fXlowNDC: 0, fYlowNDC: 0, fXUpNDC: 0, fYUpNDC: 0, fWNDC: 1, fHNDC: 1, - fAbsXlowNDC: 0, fAbsYlowNDC: 0, fAbsWNDC: 1, fAbsHNDC: 1, - fUxmin: 0, fUymin: 0, fUxmax: 0, fUymax: 0, fTheta: 30, fPhi: 30, fAspectRatio: 0, - fNumber: 0, fLogx: gStyle.fOptLogx, fLogy: gStyle.fOptLogy, fLogz: gStyle.fOptLogz, - fTickx: gStyle.fPadTickX, fTicky: gStyle.fPadTickY, - fPadPaint: 0, fCrosshair: 0, fCrosshairPos: 0, fBorderSize: gStyle.fPadBorderSize, - fBorderMode: gStyle.fPadBorderMode, fModified: false, - fGridx: gStyle.fPadGridX, fGridy: gStyle.fPadGridY, - fAbsCoord: false, fEditable: true, fFixedAspectRatio: false, - fPrimitives: create(clTList), fExecs: null, - fName: 'pad', fTitle: 'canvas' }); - break; - case clTAttCanvas: - extend(obj, { fXBetween: 2, fYBetween: 2, fTitleFromTop: 1.2, - fXdate: 0.2, fYdate: 0.3, fAdate: 1 }); - break; - case clTCanvas: - create(clTPad, obj); - extend(obj, { fFillColor: gStyle.fCanvasColor, fFillStyle: 1001, - fNumPaletteColor: 0, fNextPaletteColor: 0, fDISPLAY: '$DISPLAY', - fDoubleBuffer: 0, fRetained: true, fXsizeUser: 0, - fYsizeUser: 0, fXsizeReal: 20, fYsizeReal: 10, - fWindowTopX: 0, fWindowTopY: 0, fWindowWidth: 0, fWindowHeight: 0, - fBorderSize: gStyle.fCanvasBorderSize, fBorderMode: gStyle.fCanvasBorderMode, - fCw: settings.CanvasWidth, fCh: settings.CanvasHeight, fCatt: create(clTAttCanvas), - kMoveOpaque: true, kResizeOpaque: true, fHighLightColor: 5, - fBatch: true, kShowEventStatus: false, kAutoExec: true, kMenuBar: true }); - break; - case clTGeoVolume: - create(clTNamed, obj); - create(clTAttLine, obj); - create(clTAttFill, obj); - extend(obj, { fGeoAtt: 0, fFinder: null, fMedium: null, fNodes: null, fNtotal: 0, fNumber: 0, fRefCount: 0, fShape: null, fVoxels: null }); - break; - case clTGeoNode: - create(clTNamed, obj); - extend(obj, { fGeoAtt: 0, fMother: null, fNovlp: 0, fNumber: 0, fOverlaps: null, fVolume: null }); - break; - case clTGeoNodeMatrix: - create(clTGeoNode, obj); - extend(obj, { fMatrix: null }); - break; - case 'TGeoTrack': - create(clTObject, obj); - create(clTAttLine, obj); - create(clTAttMarker, obj); - extend(obj, { fGeoAtt: 0, fNpoints: 0, fPoints: [] }); - break; - case clTPolyLine3D: - create(clTObject, obj); - create(clTAttLine, obj); - extend(obj, { fLastPoint: -1, fN: 0, fOption: '', fP: [] }); - break; - case clTPolyMarker3D: - create(clTObject, obj); - create(clTAttMarker, obj); - extend(obj, { fLastPoint: -1, fN: 0, fName: '', fOption: '', fP: [] }); - break; - } - - obj._typename = typename; - addMethods(obj, typename); - return obj; -} - -/** @summary Create histogram object of specified type - * @param {string} typename - histogram typename like 'TH1I' or 'TH2F' - * @param {number} nbinsx - number of bins on X-axis - * @param {number} [nbinsy] - number of bins on Y-axis (for 2D/3D histograms) - * @param {number} [nbinsz] - number of bins on Z-axis (for 3D histograms) - * @return {object} created histogram object - * @example - * import { createHistogram } from 'https://root.cern/js/latest/modules/core.mjs'; - * let h1 = createHistogram('TH1I', 20); - * h1.fName = 'Hist1'; - * h1.fTitle = 'Histogram title'; - * h1.fXaxis.fTitle = 'xaxis'; - * h1.fYaxis.fTitle = 'yaxis'; - * h1.fXaxis.fLabelSize = 0.02; */ -function createHistogram(typename, nbinsx, nbinsy, nbinsz) { - const histo = create(typename); - if (!histo.fXaxis || !histo.fYaxis || !histo.fZaxis) - return null; - histo.fName = 'hist'; - histo.fTitle = 'title'; - if (nbinsx) - extend(histo.fXaxis, { fNbins: nbinsx, fXmin: 0, fXmax: nbinsx }); - if (nbinsy) - extend(histo.fYaxis, { fNbins: nbinsy, fXmin: 0, fXmax: nbinsy }); - if (nbinsz) - extend(histo.fZaxis, { fNbins: nbinsz, fXmin: 0, fXmax: nbinsz }); - switch (parseInt(typename[2])) { - case 1: - if (nbinsx) - histo.fNcells = nbinsx + 2; - break; - case 2: - if (nbinsx && nbinsy) - histo.fNcells = (nbinsx + 2) * (nbinsy + 2); - break; - case 3: - if (nbinsx && nbinsy && nbinsz) - histo.fNcells = (nbinsx + 2) * (nbinsy + 2) * (nbinsz + 2); - break; - } - if (histo.fNcells > 0) { - switch (typename[3]) { - case 'C': - histo.fArray = new Int8Array(histo.fNcells); - break; - case 'S': - histo.fArray = new Int16Array(histo.fNcells); - break; - case 'I': - histo.fArray = new Int32Array(histo.fNcells); - break; - case 'F': - histo.fArray = new Float32Array(histo.fNcells); - break; - case 'L': - case 'D': - histo.fArray = new Float64Array(histo.fNcells); - break; - default: - histo.fArray = new Array(histo.fNcells); - } - histo.fArray.fill(0); - } - return histo; -} - -/** @summary Set histogram title - * @desc Title may include axes titles, provided with ';' symbol like "Title;x;y;z" */ - -function setHistogramTitle(histo, title) { - if (!histo || !isStr(title)) - return; - if (title.indexOf(';') < 0) - histo.fTitle = title; - else { - const arr = title.split(';'); - histo.fTitle = arr[0]; - if (arr.length > 1) - histo.fXaxis.fTitle = arr[1]; - if (arr.length > 2) - histo.fYaxis.fTitle = arr[2]; - if (arr.length > 3) - histo.fZaxis.fTitle = arr[3]; - } -} - - -/** @summary Creates TPolyLine object - * @param {number} npoints - number of points - * @param {boolean} [use_int32] - use Int32Array type for points, default is Float32Array */ -function createTPolyLine(npoints, use_int32) { - const poly = create(clTPolyLine); - if (npoints) { - poly.fN = npoints; - if (use_int32) { - poly.fX = new Int32Array(npoints); - poly.fY = new Int32Array(npoints); - } else { - poly.fX = new Float32Array(npoints); - poly.fY = new Float32Array(npoints); - } - } - return poly; -} - -/** @summary Creates TGraph object - * @param {number} npoints - number of points in TGraph - * @param {Array} [xpts] - array with X coordinates - * @param {Array} [ypts] - array with Y coordinates */ -function createTGraph(npoints, xpts, ypts) { - const graph = extend(create(clTGraph), { fBits: 0x408, fName: 'graph', fTitle: 'title' }); - - if (npoints > 0) { - graph.fMaxSize = graph.fNpoints = npoints; - - const usex = isObject(xpts) && (xpts.length === npoints), - usey = isObject(ypts) && (ypts.length === npoints); - - for (let i = 0; i < npoints; ++i) { - graph.fX.push(usex ? xpts[i] : i / npoints); - graph.fY.push(usey ? ypts[i] : i / npoints); - } - } - - return graph; -} - -/** @summary Creates THStack object - * @desc As arguments one could specify any number of histograms objects - * @example - * import { createHistogram, createTHStack } from 'https://root.cern/js/latest/modules/core.mjs'; - * let nbinsx = 20; - * let h1 = createHistogram('TH1F', nbinsx); - * let h2 = createHistogram('TH1F', nbinsx); - * let h3 = createHistogram('TH1F', nbinsx); - * let stack = createTHStack(h1, h2, h3); */ -function createTHStack(...args) { - const stack = create(clTHStack); - for (let i = 0; i < args.length; ++i) - stack.fHists.Add(args[i], ''); - return stack; -} - -/** @summary Creates TMultiGraph object - * @desc As arguments one could specify any number of TGraph objects - * @example - * import { createTGraph, createTMultiGraph } from 'https://root.cern/js/latest/modules/core.mjs'; - * let gr1 = createTGraph(100); - * let gr2 = createTGraph(100); - * let gr3 = createTGraph(100); - * let mgr = createTMultiGraph(gr1, gr2, gr3); */ -function createTMultiGraph(...args) { - const mgraph = create(clTMultiGraph); - for (let i = 0; i < args.length; ++i) - mgraph.fGraphs.Add(args[i], ''); - return mgraph; -} - -/** @summary variable used to keep methods for known classes - * @private */ -const methodsCache = {}; - -/** @summary Returns methods for given typename - * @private */ -function getMethods(typename, obj) { - let m = methodsCache[typename]; - const has_methods = (m !== undefined); - if (!has_methods) - m = {}; - - // Due to binary I/O such TObject methods may not be set for derived classes - // Therefore when methods requested for given object, check also that basic methods are there - if ((typename === clTObject) || (typename === clTNamed) || (obj?.fBits !== undefined)) { - if (typeof m.TestBit === 'undefined') { - m.TestBit = function(f) { return Boolean(this.fBits & f); }; - m.InvertBit = function(f) { this.fBits ^= f & 0xffffff; }; - m.SetBit = function(f, on = true) { this.fBits = on ? this.fBits | f : this.fBits & ~f; }; - } - } - - if (has_methods) - return m; - - if ((typename === clTList) || (typename === clTHashList)) { - m.Clear = function() { - this.arr = []; - this.opt = []; - }; - m.Add = function(elem, opt) { - this.arr.push(elem); - this.opt.push(isStr(opt) ? opt : ''); - }; - m.AddFirst = function(elem, opt) { - this.arr.unshift(elem); - this.opt.unshift(isStr(opt) ? opt : ''); - }; - m.RemoveAt = function(indx) { - this.arr.splice(indx, 1); - this.opt.splice(indx, 1); - }; - } - - if ((typename === clTPaveText) || (typename === clTPaveStats)) { - m.AddText = function(txt) { - const line = create(clTLatex); - line.fTitle = txt; - line.fTextAlign = 0; - line.fTextColor = 0; - line.fTextFont = 0; - line.fTextSize = 0; - this.fLines.Add(line); - }; - m.Clear = function() { - this.fLines.Clear(); - }; - } - - if ((typename.indexOf(clTF1) === 0) || (typename === clTF12) || (typename === clTF2) || (typename === clTF3)) { - m.addFormula = function(formula) { - if (formula) { - if (this.formulas === undefined) - this.formulas = []; - this.formulas.push(formula); - } - }; - m.GetParName = function(n) { - if (this.fParams?.fParNames) - return this.fParams.fParNames[n]; - if (this.fFormula?.fParams) { - for (let k = 0, arr = this.fFormula.fParams; k < arr.length; ++k) { - if (arr[k].second === n) - return arr[k].first; - } - } - return (this.fNames && this.fNames[n]) ? this.fNames[n] : `p${n}`; - }; - m.GetParValue = function(n) { - if (this.fParams?.fParameters) - return this.fParams.fParameters[n]; - if (this.fFormula?.fClingParameters) - return this.fFormula.fClingParameters[n]; - return this.fParams ? this.fParams[n] : undefined; - }; - m.GetParError = function(n) { - return this.fParErrors ? this.fParErrors[n] : undefined; - }; - m.GetNumPars = function() { - return this.fNpar; - }; - } - - if (((typename.indexOf(clTGraph) === 0) || (typename === clTCutG)) && (typename !== clTGraphPolargram) && (typename !== clTGraphTime)) { - // check if point inside figure specified by the TGraph - m.IsInside = function(xp, yp) { - const x = this.fX, y = this.fY; - let i = 0, j = this.fNpoints - 1, oddNodes = false; - - for (; i < this.fNpoints; ++i) { - if ((y[i] < yp && y[j] >= yp) || (y[j] < yp && y[i] >= yp)) { - if (x[i] + (yp - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < xp) - oddNodes = !oddNodes; - } - j = i; - } - - return oddNodes; - }; - } - - if (typename.indexOf(clTH1) === 0 || typename.indexOf(clTH2) === 0 || typename.indexOf(clTH3) === 0) { - m.getBinError = function(bin) { - // -*-*-*-*-*Return value of error associated to bin number bin*-*-*-*-* - // if the sum of squares of weights has been defined (via Sumw2), - // this function returns the sqrt(sum of w2). - // otherwise it returns the sqrt(contents) for this bin. - if (bin >= this.fNcells) - bin = this.fNcells - 1; - if (bin < 0) - bin = 0; - if (bin < this.fSumw2.length) - return Math.sqrt(this.fSumw2[bin]); - return Math.sqrt(Math.abs(this.fArray[bin])); - }; - m.setBinContent = function(bin, content) { - // Set bin content - only trivial case, without expansion - this.fEntries++; - this.fTsumw = 0; - if ((bin >= 0) && (bin < this.fArray.length)) - this.fArray[bin] = content; - }; - } - - if (typename.indexOf(clTH1) === 0) { - m.getBin = function(x) { return x; }; - m.getBinContent = function(bin) { return this.fArray[bin]; }; - m.Fill = function(x, weight) { - const a = this.fXaxis, - bin = Math.max(0, 1 + Math.min(a.fNbins, Math.floor((x - a.fXmin) / (a.fXmax - a.fXmin) * a.fNbins))); - this.fArray[bin] += weight ?? 1; - this.fEntries++; - }; - } - - if (typename.indexOf(clTH2) === 0) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins + 2) * y); }; - m.getBinContent = function(x, y) { return this.fArray[this.getBin(x, y)]; }; - m.Fill = function(x, y, weight) { - const a1 = this.fXaxis, a2 = this.fYaxis, - bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), - bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2) * bin2] += weight ?? 1; - this.fEntries++; - }; - } - - if (typename.indexOf(clTH3) === 0) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); }; - m.getBinContent = function(x, y, z) { return this.fArray[this.getBin(x, y, z)]; }; - m.Fill = function(x, y, z, weight) { - const a1 = this.fXaxis, a2 = this.fYaxis, a3 = this.fZaxis, - bin1 = Math.max(0, 1 + Math.min(a1.fNbins, Math.floor((x - a1.fXmin) / (a1.fXmax - a1.fXmin) * a1.fNbins))), - bin2 = Math.max(0, 1 + Math.min(a2.fNbins, Math.floor((y - a2.fXmin) / (a2.fXmax - a2.fXmin) * a2.fNbins))), - bin3 = Math.max(0, 1 + Math.min(a3.fNbins, Math.floor((z - a3.fXmin) / (a3.fXmax - a3.fXmin) * a3.fNbins))); - this.fArray[bin1 + (a1.fNbins + 2) * (bin2 + (a2.fNbins + 2) * bin3)] += weight ?? 1; - this.fEntries++; - }; - } - - if (typename === clTPad || typename === clTCanvas) { - m.Divide = function(nx, ny, xmargin = 0.01, ymargin = 0.01, color = 0) { - if (!ny) { - const ndiv = nx; - if (ndiv < 2) - return this; - nx = ny = Math.round(Math.sqrt(ndiv)); - if (nx * ny < ndiv) - nx += 1; - } - if (nx * ny < 2) - return 0; - this.fPrimitives.Clear(); - const dy = 1 / ny, dx = 1 / nx; - let n = 0; - for (let iy = 0; iy < ny; iy++) { - const y2 = 1 - iy * dy - ymargin, - y1 = Math.max(0, y2 - dy + 2 * ymargin); - if (y1 > y2) - continue; - for (let ix = 0; ix < nx; ix++) { - const x1 = ix * dx + xmargin, - x2 = x1 + dx - 2 * xmargin; - if (x1 > x2) - continue; - n++; - const pad = create(clTPad); - pad.fName = pad.fTitle = `${this.fName}_${n}`; - pad.fNumber = n; - if (this._typename !== clTCanvas) { - pad.fAbsWNDC = (x2 - x1) * this.fAbsWNDC; - pad.fAbsHNDC = (y2 - y1) * this.fAbsHNDC; - pad.fAbsXlowNDC = this.fAbsXlowNDC + x1 * this.fAbsWNDC; - pad.fAbsYlowNDC = this.fAbsYlowNDC + y1 * this.fAbsHNDC; - } else { - pad.fAbsWNDC = x2 - x1; - pad.fAbsHNDC = y2 - y1; - pad.fAbsXlowNDC = x1; - pad.fAbsYlowNDC = y1; - } - - pad.fFillColor = color || this.fFillColor; - - this.fPrimitives.Add(pad); - } - } - return nx * ny; - }; - m.GetPad = function(number) { - return this.fPrimitives.arr.find(elem => { return elem._typename === clTPad && elem.fNumber === number; }); - }; - } - - if (typename.indexOf(clTProfile) === 0) { - if (typename === clTProfile3D) { - m.getBin = function(x, y, z) { return (x + (this.fXaxis.fNbins + 2) * (y + (this.fYaxis.fNbins + 2) * z)); }; - m.getBinContent = function(x, y, z) { - const bin = this.getBin(x, y, z); - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) - return 0; - return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; - }; - m.getBinEntries = function(x, y, z) { - const bin = this.getBin(x, y, z); - return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; - }; - } else if (typename === clTProfile2D) { - m.getBin = function(x, y) { return (x + (this.fXaxis.fNbins + 2) * y); }; - m.getBinContent = function(x, y) { - const bin = this.getBin(x, y); - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) - return 0; - return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; - }; - m.getBinEntries = function(x, y) { - const bin = this.getBin(x, y); - return (bin < 0 || bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; - }; - } else { - m.getBin = function(x) { return x; }; - m.getBinContent = function(bin) { - if (bin < 0 || bin >= this.fNcells || this.fBinEntries[bin] < 1e-300) - return 0; - return this.fArray ? this.fArray[bin] / this.fBinEntries[bin] : 0; - }; - m.getBinEntries = function(bin) { - return (bin < 0) || (bin >= this.fNcells) ? 0 : this.fBinEntries[bin]; - }; - } - m.getBinEffectiveEntries = function(bin) { - if (bin < 0 || bin >= this.fNcells) - return 0; - const sumOfWeights = this.fBinEntries[bin]; - if (!this.fBinSumw2 || this.fBinSumw2.length !== this.fNcells) - // this can happen when reading an old file - return sumOfWeights; - const sumOfWeightsSquare = this.fBinSumw2[bin]; - return (sumOfWeightsSquare > 0) ? sumOfWeights * sumOfWeights / sumOfWeightsSquare : 0; - }; - m.getBinError = function(bin) { - if (bin < 0 || bin >= this.fNcells) - return 0; - const cont = this.fArray[bin], // sum of bin w *y - sum = this.fBinEntries[bin], // sum of bin weights - err2 = this.fSumw2[bin], // sum of bin w * y^2 - neff = this.getBinEffectiveEntries(bin); // (sum of w)^2 / (sum of w^2) - if (sum < 1e-300) - return 0; // for empty bins - const EErrorType = { kERRORMEAN: 0, kERRORSPREAD: 1, kERRORSPREADI: 2, kERRORSPREADG: 3 }; - // case the values y are gaussian distributed y +/- sigma and w = 1/sigma^2 - if (this.fErrorMode === EErrorType.kERRORSPREADG) - return 1.0 / Math.sqrt(sum); - // compute variance in y (eprim2) and standard deviation in y (eprim) - const contsum = cont / sum, eprim = Math.sqrt(Math.abs(err2 / sum - contsum ** 2)); - if (this.fErrorMode === EErrorType.kERRORSPREADI) { - if (eprim) - return eprim / Math.sqrt(neff); - // in case content y is an integer (so each my has an error +/- 1/sqrt(12) - // when the std(y) is zero - return 1.0 / Math.sqrt(12 * neff); - } - // if approximate compute the sums (of w, wy and wy2) using all the bins - // when the variance in y is zero - // case option 'S' return standard deviation in y - if (this.fErrorMode === EErrorType.kERRORSPREAD) - return eprim; - // default case : fErrorMode = kERRORMEAN - // return standard error on the mean of y - return eprim / Math.sqrt(neff); - }; - } - - if (typename === clTAxis) { - m.GetBinLowEdge = function(bin) { - if (this.fNbins <= 0) - return 0; - if (this.fXbins.length) { - if ((bin > 0) && (bin <= this.fNbins + 1)) - return this.fXbins[bin - 1]; - if (bin === 0) // underflow - return 2 * this.fXbins[0] - this.fXbins[1]; - if (bin === this.fNbins + 2) // right border of overflow bin - return 2 * this.fXbins[bin - 2] - this.fXbins[bin - 3]; - return 0; - } - return this.fXmin + (bin - 1) * (this.fXmax - this.fXmin) / this.fNbins; - }; - m.GetBinCenter = function(bin) { - if (this.fNbins <= 0) - return 0; - if (this.fXbins.length) { - if ((bin > 0) && (bin <= this.fNbins)) - return (this.fXbins[bin - 1] + this.fXbins[bin]) / 2; - if (bin === 0) // underflow - return 1.5 * this.fXbins[0] - 0.5 * this.fXbins[1]; - if (bin === this.fNbins + 1) // overflow - return 1.5 * this.fXbins[bin - 1] - 0.5 * this.fXbins[bin - 2]; - return 0; - } - return this.fXmin + (bin - 0.5) * (this.fXmax - this.fXmin) / this.fNbins; - }; - } - - if (typename.indexOf(nsROOT + 'Math::LorentzVector') === 0) { - m.Px = m.X = function() { return this.fCoordinates.Px(); }; - m.Py = m.Y = function() { return this.fCoordinates.Py(); }; - m.Pz = m.Z = function() { return this.fCoordinates.Pz(); }; - m.E = m.T = function() { return this.fCoordinates.E(); }; - m.M2 = function() { return this.fCoordinates.M2(); }; - m.M = function() { return this.fCoordinates.M(); }; - m.R = m.P = function() { return this.fCoordinates.R(); }; - m.P2 = function() { return this.P() * this.P(); }; - m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; - m.Phi = m.phi = function() { return Math.atan2(this.fCoordinates.Py(), this.fCoordinates.Px()); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz() / this.P()); }; - } - - if (typename.indexOf(nsROOT + 'Math::PxPyPzE4D') === 0) { - m.Px = m.X = function() { return this.fX; }; - m.Py = m.Y = function() { return this.fY; }; - m.Pz = m.Z = function() { return this.fZ; }; - m.E = m.T = function() { return this.fT; }; - m.P2 = function() { return this.fX ** 2 + this.fY ** 2 + this.fZ ** 2; }; - m.R = m.P = function() { return Math.sqrt(this.P2()); }; - m.Mag2 = m.M2 = function() { return this.fT ** 2 - this.fX ** 2 - this.fY ** 2 - this.fZ ** 2; }; - m.Mag = m.M = function() { return (this.M2() >= 0) ? Math.sqrt(this.M2()) : -Math.sqrt(-this.M2()); }; - m.Perp2 = m.Pt2 = function() { return this.fX ** 2 + this.fY ** 2; }; - m.Pt = m.pt = function() { return Math.sqrt(this.P2()); }; - m.Phi = m.phi = function() { return Math.atan2(this.fY, this.fX); }; - m.Eta = m.eta = function() { return Math.atanh(this.Pz / this.P()); }; - } - - methodsCache[typename] = m; - return m; -} - -addMethods = function(obj, typename) { - extend(obj, getMethods(typename || obj._typename, obj)); -}; - -gStyle.fXaxis = create(clTAttAxis); -gStyle.fYaxis = create(clTAttAxis); -gStyle.fZaxis = create(clTAttAxis); - -/** @summary Add methods for specified type. - * @desc Will be automatically applied when decoding JSON string - * @private */ -function registerMethods(typename, m) { - methodsCache[typename] = m; -} - -/** @summary Returns true if object represents basic ROOT collections - * @desc Checks if type is TList or TObjArray or TClonesArray or TMap or THashList - * @param {object} lst - object to check - * @param {string} [typename] - or just typename to check - * @private */ -function isRootCollection(lst, typename) { - if (isObject(lst)) { - if ((lst.$kind === clTList) || (lst.$kind === clTObjArray)) - return true; - if (!typename) - typename = lst._typename; - } - return (typename === clTList) || (typename === clTHashList) || (typename === clTMap) || - (typename === clTObjArray) || (typename === clTClonesArray); -} - -/** @summary Return kind string for type - * @desc Used when internal objects without clear ROOT type are used - * For such objects abstract 'kind' is defined. Used in hpainter and draw handles - * @private */ -function getKindForType(typ) { - return (!isStr(typ) || (typ.indexOf(nsROOT) !== 0)) ? prROOT + typ : typ; -} - -/** @summary Return type name from kind string - * @private */ -function getTypeForKind(kind) { - if (!isStr(kind)) - return null; - if (kind.indexOf(prROOT) === 0) - return kind.slice(prROOT.length); - if (kind.indexOf(nsROOT) === 0) - return kind; - return null; -} - -/** @summary Internal collection of functions potentially used by batch scripts - * @private */ -internals.jsroot = { version, source_dir, settings, gStyle, parse, isBatchMode }; - -export { version_id, version_date, version, source_dir, isNodeJs, isBatchMode, setBatchMode, - browser, internals, constants, settings, gStyle, atob_func, btoa_func, prROOT, - clTObject, clTNamed, clTString, clTObjString, - clTKey, clTFile, clTTree, - clTList, clTHashList, clTMap, clTObjArray, clTClonesArray, - clTAttLine, clTAttFill, clTAttMarker, clTAttText, - clTPave, clTPaveText, clTPavesText, clTPaveStats, clTPaveLabel, clTPaveClass, clTDiamond, - clTLegend, clTLegendEntry, clTPaletteAxis, clTImagePalette, clTText, clTLink, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, - clTColor, clTLine, clTMarker, clTBox, clTPolyLine, clTPad, clTCanvas, clTFrame, clTAttCanvas, clTGaxis, clTAxis, clTStyle, - clTH1, clTH1I, clTH1F, clTH1D, clTH2, clTH2I, clTH2F, clTH2D, clTH3, clTH3I, clTH3F, clTH3D, - clTProfile, clTProfile2D, clTProfile3D, clTF1, clTF12, clTF2, clTF3, clTHStack, - clTGraph, clTGraph2DErrors, clTGraph2DAsymmErrors, - clTGraphPolar, clTGraphPolargram, clTGraphTime, clTCutG, - clTPolyLine3D, clTPolyMarker3D, clTGeoVolume, clTGeoNode, clTGeoNodeMatrix, - nsROOT, nsREX, nsSVG, kNoZoom, kNoStats, kInspect, kTitle, urlClassPrefix, - isArrayProto, getDocument, BIT, clone, addMethods, parse, parseMulti, toJSON, - decodeUrl, findFunction, createHttpRequest, httpRequest, loadModules, loadScript, injectCode, - create, createHistogram, setHistogramTitle, createTPolyLine, createTGraph, createTHStack, createTMultiGraph, - getMethods, registerMethods, isRootCollection, isObject, isFunc, isStr, isPromise, getPromise, postponePromise, - getKindForType, getTypeForKind, _ensureJSROOT }; diff --git a/js/modules/d3.mjs b/js/modules/d3.mjs deleted file mode 100644 index 2a1209f332dfd..0000000000000 --- a/js/modules/d3.mjs +++ /dev/null @@ -1,6879 +0,0 @@ -// https://d3js.org v7.9.0 Copyright 2010-2021 Mike Bostock -var version = "7.9.0"; - -function define(constructor, factory, prototype) { - constructor.prototype = factory.prototype = prototype; - prototype.constructor = constructor; -} - -function extend(parent, definition) { - var prototype = Object.create(parent.prototype); - for (var key in definition) prototype[key] = definition[key]; - return prototype; -} - -function Color() {} - -var darker = 0.7; -var brighter = 1 / darker; - -var reI = "\\s*([+-]?\\d+)\\s*", - reN = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*", - reP = "\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*", - reHex = /^#([0-9a-f]{3,8})$/, - reRgbInteger = new RegExp(`^rgb\\(${reI},${reI},${reI}\\)$`), - reRgbPercent = new RegExp(`^rgb\\(${reP},${reP},${reP}\\)$`), - reRgbaInteger = new RegExp(`^rgba\\(${reI},${reI},${reI},${reN}\\)$`), - reRgbaPercent = new RegExp(`^rgba\\(${reP},${reP},${reP},${reN}\\)$`), - reHslPercent = new RegExp(`^hsl\\(${reN},${reP},${reP}\\)$`), - reHslaPercent = new RegExp(`^hsla\\(${reN},${reP},${reP},${reN}\\)$`); - -var named = { - aliceblue: 0xf0f8ff, - antiquewhite: 0xfaebd7, - aqua: 0x00ffff, - aquamarine: 0x7fffd4, - azure: 0xf0ffff, - beige: 0xf5f5dc, - bisque: 0xffe4c4, - black: 0x000000, - blanchedalmond: 0xffebcd, - blue: 0x0000ff, - blueviolet: 0x8a2be2, - brown: 0xa52a2a, - burlywood: 0xdeb887, - cadetblue: 0x5f9ea0, - chartreuse: 0x7fff00, - chocolate: 0xd2691e, - coral: 0xff7f50, - cornflowerblue: 0x6495ed, - cornsilk: 0xfff8dc, - crimson: 0xdc143c, - cyan: 0x00ffff, - darkblue: 0x00008b, - darkcyan: 0x008b8b, - darkgoldenrod: 0xb8860b, - darkgray: 0xa9a9a9, - darkgreen: 0x006400, - darkgrey: 0xa9a9a9, - darkkhaki: 0xbdb76b, - darkmagenta: 0x8b008b, - darkolivegreen: 0x556b2f, - darkorange: 0xff8c00, - darkorchid: 0x9932cc, - darkred: 0x8b0000, - darksalmon: 0xe9967a, - darkseagreen: 0x8fbc8f, - darkslateblue: 0x483d8b, - darkslategray: 0x2f4f4f, - darkslategrey: 0x2f4f4f, - darkturquoise: 0x00ced1, - darkviolet: 0x9400d3, - deeppink: 0xff1493, - deepskyblue: 0x00bfff, - dimgray: 0x696969, - dimgrey: 0x696969, - dodgerblue: 0x1e90ff, - firebrick: 0xb22222, - floralwhite: 0xfffaf0, - forestgreen: 0x228b22, - fuchsia: 0xff00ff, - gainsboro: 0xdcdcdc, - ghostwhite: 0xf8f8ff, - gold: 0xffd700, - goldenrod: 0xdaa520, - gray: 0x808080, - green: 0x008000, - greenyellow: 0xadff2f, - grey: 0x808080, - honeydew: 0xf0fff0, - hotpink: 0xff69b4, - indianred: 0xcd5c5c, - indigo: 0x4b0082, - ivory: 0xfffff0, - khaki: 0xf0e68c, - lavender: 0xe6e6fa, - lavenderblush: 0xfff0f5, - lawngreen: 0x7cfc00, - lemonchiffon: 0xfffacd, - lightblue: 0xadd8e6, - lightcoral: 0xf08080, - lightcyan: 0xe0ffff, - lightgoldenrodyellow: 0xfafad2, - lightgray: 0xd3d3d3, - lightgreen: 0x90ee90, - lightgrey: 0xd3d3d3, - lightpink: 0xffb6c1, - lightsalmon: 0xffa07a, - lightseagreen: 0x20b2aa, - lightskyblue: 0x87cefa, - lightslategray: 0x778899, - lightslategrey: 0x778899, - lightsteelblue: 0xb0c4de, - lightyellow: 0xffffe0, - lime: 0x00ff00, - limegreen: 0x32cd32, - linen: 0xfaf0e6, - magenta: 0xff00ff, - maroon: 0x800000, - mediumaquamarine: 0x66cdaa, - mediumblue: 0x0000cd, - mediumorchid: 0xba55d3, - mediumpurple: 0x9370db, - mediumseagreen: 0x3cb371, - mediumslateblue: 0x7b68ee, - mediumspringgreen: 0x00fa9a, - mediumturquoise: 0x48d1cc, - mediumvioletred: 0xc71585, - midnightblue: 0x191970, - mintcream: 0xf5fffa, - mistyrose: 0xffe4e1, - moccasin: 0xffe4b5, - navajowhite: 0xffdead, - navy: 0x000080, - oldlace: 0xfdf5e6, - olive: 0x808000, - olivedrab: 0x6b8e23, - orange: 0xffa500, - orangered: 0xff4500, - orchid: 0xda70d6, - palegoldenrod: 0xeee8aa, - palegreen: 0x98fb98, - paleturquoise: 0xafeeee, - palevioletred: 0xdb7093, - papayawhip: 0xffefd5, - peachpuff: 0xffdab9, - peru: 0xcd853f, - pink: 0xffc0cb, - plum: 0xdda0dd, - powderblue: 0xb0e0e6, - purple: 0x800080, - rebeccapurple: 0x663399, - red: 0xff0000, - rosybrown: 0xbc8f8f, - royalblue: 0x4169e1, - saddlebrown: 0x8b4513, - salmon: 0xfa8072, - sandybrown: 0xf4a460, - seagreen: 0x2e8b57, - seashell: 0xfff5ee, - sienna: 0xa0522d, - silver: 0xc0c0c0, - skyblue: 0x87ceeb, - slateblue: 0x6a5acd, - slategray: 0x708090, - slategrey: 0x708090, - snow: 0xfffafa, - springgreen: 0x00ff7f, - steelblue: 0x4682b4, - tan: 0xd2b48c, - teal: 0x008080, - thistle: 0xd8bfd8, - tomato: 0xff6347, - turquoise: 0x40e0d0, - violet: 0xee82ee, - wheat: 0xf5deb3, - white: 0xffffff, - whitesmoke: 0xf5f5f5, - yellow: 0xffff00, - yellowgreen: 0x9acd32 -}; - -define(Color, color, { - copy(channels) { - return Object.assign(new this.constructor, this, channels); - }, - displayable() { - return this.rgb().displayable(); - }, - hex: color_formatHex, // Deprecated! Use color.formatHex. - formatHex: color_formatHex, - formatHex8: color_formatHex8, - formatHsl: color_formatHsl, - formatRgb: color_formatRgb, - toString: color_formatRgb -}); - -function color_formatHex() { - return this.rgb().formatHex(); -} - -function color_formatHex8() { - return this.rgb().formatHex8(); -} - -function color_formatHsl() { - return hslConvert(this).formatHsl(); -} - -function color_formatRgb() { - return this.rgb().formatRgb(); -} - -function color(format) { - var m, l; - format = (format + "").trim().toLowerCase(); - return (m = reHex.exec(format)) ? (l = m[1].length, m = parseInt(m[1], 16), l === 6 ? rgbn(m) // #ff0000 - : l === 3 ? new Rgb((m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), ((m & 0xf) << 4) | (m & 0xf), 1) // #f00 - : l === 8 ? rgba(m >> 24 & 0xff, m >> 16 & 0xff, m >> 8 & 0xff, (m & 0xff) / 0xff) // #ff000000 - : l === 4 ? rgba((m >> 12 & 0xf) | (m >> 8 & 0xf0), (m >> 8 & 0xf) | (m >> 4 & 0xf0), (m >> 4 & 0xf) | (m & 0xf0), (((m & 0xf) << 4) | (m & 0xf)) / 0xff) // #f000 - : null) // invalid hex - : (m = reRgbInteger.exec(format)) ? new Rgb(m[1], m[2], m[3], 1) // rgb(255, 0, 0) - : (m = reRgbPercent.exec(format)) ? new Rgb(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, 1) // rgb(100%, 0%, 0%) - : (m = reRgbaInteger.exec(format)) ? rgba(m[1], m[2], m[3], m[4]) // rgba(255, 0, 0, 1) - : (m = reRgbaPercent.exec(format)) ? rgba(m[1] * 255 / 100, m[2] * 255 / 100, m[3] * 255 / 100, m[4]) // rgb(100%, 0%, 0%, 1) - : (m = reHslPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, 1) // hsl(120, 50%, 50%) - : (m = reHslaPercent.exec(format)) ? hsla(m[1], m[2] / 100, m[3] / 100, m[4]) // hsla(120, 50%, 50%, 1) - : named.hasOwnProperty(format) ? rgbn(named[format]) // eslint-disable-line no-prototype-builtins - : format === "transparent" ? new Rgb(NaN, NaN, NaN, 0) - : null; -} - -function rgbn(n) { - return new Rgb(n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff, 1); -} - -function rgba(r, g, b, a) { - if (a <= 0) r = g = b = NaN; - return new Rgb(r, g, b, a); -} - -function rgbConvert(o) { - if (!(o instanceof Color)) o = color(o); - if (!o) return new Rgb; - o = o.rgb(); - return new Rgb(o.r, o.g, o.b, o.opacity); -} - -function rgb(r, g, b, opacity) { - return arguments.length === 1 ? rgbConvert(r) : new Rgb(r, g, b, opacity == null ? 1 : opacity); -} - -function Rgb(r, g, b, opacity) { - this.r = +r; - this.g = +g; - this.b = +b; - this.opacity = +opacity; -} - -define(Rgb, rgb, extend(Color, { - brighter(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - darker(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity); - }, - rgb() { - return this; - }, - clamp() { - return new Rgb(clampi(this.r), clampi(this.g), clampi(this.b), clampa(this.opacity)); - }, - displayable() { - return (-0.5 <= this.r && this.r < 255.5) - && (-0.5 <= this.g && this.g < 255.5) - && (-0.5 <= this.b && this.b < 255.5) - && (0 <= this.opacity && this.opacity <= 1); - }, - hex: rgb_formatHex, // Deprecated! Use color.formatHex. - formatHex: rgb_formatHex, - formatHex8: rgb_formatHex8, - formatRgb: rgb_formatRgb, - toString: rgb_formatRgb -})); - -function rgb_formatHex() { - return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}`; -} - -function rgb_formatHex8() { - return `#${hex(this.r)}${hex(this.g)}${hex(this.b)}${hex((isNaN(this.opacity) ? 1 : this.opacity) * 255)}`; -} - -function rgb_formatRgb() { - const a = clampa(this.opacity); - return `${a === 1 ? "rgb(" : "rgba("}${clampi(this.r)}, ${clampi(this.g)}, ${clampi(this.b)}${a === 1 ? ")" : `, ${a})`}`; -} - -function clampa(opacity) { - return isNaN(opacity) ? 1 : Math.max(0, Math.min(1, opacity)); -} - -function clampi(value) { - return Math.max(0, Math.min(255, Math.round(value) || 0)); -} - -function hex(value) { - value = clampi(value); - return (value < 16 ? "0" : "") + value.toString(16); -} - -function hsla(h, s, l, a) { - if (a <= 0) h = s = l = NaN; - else if (l <= 0 || l >= 1) h = s = NaN; - else if (s <= 0) h = NaN; - return new Hsl(h, s, l, a); -} - -function hslConvert(o) { - if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity); - if (!(o instanceof Color)) o = color(o); - if (!o) return new Hsl; - if (o instanceof Hsl) return o; - o = o.rgb(); - var r = o.r / 255, - g = o.g / 255, - b = o.b / 255, - min = Math.min(r, g, b), - max = Math.max(r, g, b), - h = NaN, - s = max - min, - l = (max + min) / 2; - if (s) { - if (r === max) h = (g - b) / s + (g < b) * 6; - else if (g === max) h = (b - r) / s + 2; - else h = (r - g) / s + 4; - s /= l < 0.5 ? max + min : 2 - max - min; - h *= 60; - } else { - s = l > 0 && l < 1 ? 0 : h; - } - return new Hsl(h, s, l, o.opacity); -} - -function hsl(h, s, l, opacity) { - return arguments.length === 1 ? hslConvert(h) : new Hsl(h, s, l, opacity == null ? 1 : opacity); -} - -function Hsl(h, s, l, opacity) { - this.h = +h; - this.s = +s; - this.l = +l; - this.opacity = +opacity; -} - -define(Hsl, hsl, extend(Color, { - brighter(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - darker(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Hsl(this.h, this.s, this.l * k, this.opacity); - }, - rgb() { - var h = this.h % 360 + (this.h < 0) * 360, - s = isNaN(h) || isNaN(this.s) ? 0 : this.s, - l = this.l, - m2 = l + (l < 0.5 ? l : 1 - l) * s, - m1 = 2 * l - m2; - return new Rgb( - hsl2rgb(h >= 240 ? h - 240 : h + 120, m1, m2), - hsl2rgb(h, m1, m2), - hsl2rgb(h < 120 ? h + 240 : h - 120, m1, m2), - this.opacity - ); - }, - clamp() { - return new Hsl(clamph(this.h), clampt(this.s), clampt(this.l), clampa(this.opacity)); - }, - displayable() { - return (0 <= this.s && this.s <= 1 || isNaN(this.s)) - && (0 <= this.l && this.l <= 1) - && (0 <= this.opacity && this.opacity <= 1); - }, - formatHsl() { - const a = clampa(this.opacity); - return `${a === 1 ? "hsl(" : "hsla("}${clamph(this.h)}, ${clampt(this.s) * 100}%, ${clampt(this.l) * 100}%${a === 1 ? ")" : `, ${a})`}`; - } -})); - -function clamph(value) { - value = (value || 0) % 360; - return value < 0 ? value + 360 : value; -} - -function clampt(value) { - return Math.max(0, Math.min(1, value || 0)); -} - -/* From FvD 13.37, CSS Color Module Level 3 */ -function hsl2rgb(h, m1, m2) { - return (h < 60 ? m1 + (m2 - m1) * h / 60 - : h < 180 ? m2 - : h < 240 ? m1 + (m2 - m1) * (240 - h) / 60 - : m1) * 255; -} - -const radians = Math.PI / 180; -const degrees$1 = 180 / Math.PI; - -// https://observablehq.com/@mbostock/lab-and-rgb -const K = 18, - Xn = 0.96422, - Yn = 1, - Zn = 0.82521, - t0$1 = 4 / 29, - t1$1 = 6 / 29, - t2 = 3 * t1$1 * t1$1, - t3 = t1$1 * t1$1 * t1$1; - -function labConvert(o) { - if (o instanceof Lab) return new Lab(o.l, o.a, o.b, o.opacity); - if (o instanceof Hcl) return hcl2lab(o); - if (!(o instanceof Rgb)) o = rgbConvert(o); - var r = rgb2lrgb(o.r), - g = rgb2lrgb(o.g), - b = rgb2lrgb(o.b), - y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn), x, z; - if (r === g && g === b) x = z = y; else { - x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn); - z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn); - } - return new Lab(116 * y - 16, 500 * (x - y), 200 * (y - z), o.opacity); -} - -function gray(l, opacity) { - return new Lab(l, 0, 0, opacity == null ? 1 : opacity); -} - -function lab(l, a, b, opacity) { - return arguments.length === 1 ? labConvert(l) : new Lab(l, a, b, opacity == null ? 1 : opacity); -} - -function Lab(l, a, b, opacity) { - this.l = +l; - this.a = +a; - this.b = +b; - this.opacity = +opacity; -} - -define(Lab, lab, extend(Color, { - brighter(k) { - return new Lab(this.l + K * (k == null ? 1 : k), this.a, this.b, this.opacity); - }, - darker(k) { - return new Lab(this.l - K * (k == null ? 1 : k), this.a, this.b, this.opacity); - }, - rgb() { - var y = (this.l + 16) / 116, - x = isNaN(this.a) ? y : y + this.a / 500, - z = isNaN(this.b) ? y : y - this.b / 200; - x = Xn * lab2xyz(x); - y = Yn * lab2xyz(y); - z = Zn * lab2xyz(z); - return new Rgb( - lrgb2rgb( 3.1338561 * x - 1.6168667 * y - 0.4906146 * z), - lrgb2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z), - lrgb2rgb( 0.0719453 * x - 0.2289914 * y + 1.4052427 * z), - this.opacity - ); - } -})); - -function xyz2lab(t) { - return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0$1; -} - -function lab2xyz(t) { - return t > t1$1 ? t * t * t : t2 * (t - t0$1); -} - -function lrgb2rgb(x) { - return 255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055); -} - -function rgb2lrgb(x) { - return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4); -} - -function hclConvert(o) { - if (o instanceof Hcl) return new Hcl(o.h, o.c, o.l, o.opacity); - if (!(o instanceof Lab)) o = labConvert(o); - if (o.a === 0 && o.b === 0) return new Hcl(NaN, 0 < o.l && o.l < 100 ? 0 : NaN, o.l, o.opacity); - var h = Math.atan2(o.b, o.a) * degrees$1; - return new Hcl(h < 0 ? h + 360 : h, Math.sqrt(o.a * o.a + o.b * o.b), o.l, o.opacity); -} - -function lch(l, c, h, opacity) { - return arguments.length === 1 ? hclConvert(l) : new Hcl(h, c, l, opacity == null ? 1 : opacity); -} - -function hcl(h, c, l, opacity) { - return arguments.length === 1 ? hclConvert(h) : new Hcl(h, c, l, opacity == null ? 1 : opacity); -} - -function Hcl(h, c, l, opacity) { - this.h = +h; - this.c = +c; - this.l = +l; - this.opacity = +opacity; -} - -function hcl2lab(o) { - if (isNaN(o.h)) return new Lab(o.l, 0, 0, o.opacity); - var h = o.h * radians; - return new Lab(o.l, Math.cos(h) * o.c, Math.sin(h) * o.c, o.opacity); -} - -define(Hcl, hcl, extend(Color, { - brighter(k) { - return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity); - }, - darker(k) { - return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity); - }, - rgb() { - return hcl2lab(this).rgb(); - } -})); - -var A = -0.14861, - B = 1.78277, - C = -0.29227, - D = -0.90649, - E = 1.97294, - ED = E * D, - EB = E * B, - BC_DA = B * C - D * A; - -function cubehelixConvert(o) { - if (o instanceof Cubehelix) return new Cubehelix(o.h, o.s, o.l, o.opacity); - if (!(o instanceof Rgb)) o = rgbConvert(o); - var r = o.r / 255, - g = o.g / 255, - b = o.b / 255, - l = (BC_DA * b + ED * r - EB * g) / (BC_DA + ED - EB), - bl = b - l, - k = (E * (g - l) - C * bl) / D, - s = Math.sqrt(k * k + bl * bl) / (E * l * (1 - l)), // NaN if l=0 or l=1 - h = s ? Math.atan2(k, bl) * degrees$1 - 120 : NaN; - return new Cubehelix(h < 0 ? h + 360 : h, s, l, o.opacity); -} - -function cubehelix(h, s, l, opacity) { - return arguments.length === 1 ? cubehelixConvert(h) : new Cubehelix(h, s, l, opacity == null ? 1 : opacity); -} - -function Cubehelix(h, s, l, opacity) { - this.h = +h; - this.s = +s; - this.l = +l; - this.opacity = +opacity; -} - -define(Cubehelix, cubehelix, extend(Color, { - brighter(k) { - k = k == null ? brighter : Math.pow(brighter, k); - return new Cubehelix(this.h, this.s, this.l * k, this.opacity); - }, - darker(k) { - k = k == null ? darker : Math.pow(darker, k); - return new Cubehelix(this.h, this.s, this.l * k, this.opacity); - }, - rgb() { - var h = isNaN(this.h) ? 0 : (this.h + 120) * radians, - l = +this.l, - a = isNaN(this.s) ? 0 : this.s * l * (1 - l), - cosh = Math.cos(h), - sinh = Math.sin(h); - return new Rgb( - 255 * (l + a * (A * cosh + B * sinh)), - 255 * (l + a * (C * cosh + D * sinh)), - 255 * (l + a * (E * cosh)), - this.opacity - ); - } -})); - -var abs$1 = Math.abs; -var cos$1 = Math.cos; -var sin$1 = Math.sin; -var pi$2 = Math.PI; -var halfPi$1 = pi$2 / 2; -var tau$2 = pi$2 * 2; -var max$2 = Math.max; -var epsilon$2 = 1e-12; - -function range$1(i, j) { - return Array.from({length: j - i}, (_, k) => i + k); -} - -function compareValue(compare) { - return function(a, b) { - return compare( - a.source.value + a.target.value, - b.source.value + b.target.value - ); - }; -} - -function chord_default() { - return chord(false, false); -} - -function chordTranspose() { - return chord(false, true); -} - -function chordDirected() { - return chord(true, false); -} - -function chord(directed, transpose) { - var padAngle = 0, - sortGroups = null, - sortSubgroups = null, - sortChords = null; - - function chord(matrix) { - var n = matrix.length, - groupSums = new Array(n), - groupIndex = range$1(0, n), - chords = new Array(n * n), - groups = new Array(n), - k = 0, dx; - - matrix = Float64Array.from({length: n * n}, transpose - ? (_, i) => matrix[i % n][i / n | 0] - : (_, i) => matrix[i / n | 0][i % n]); - - // Compute the scaling factor from value to angle in [0, 2pi]. - for (let i = 0; i < n; ++i) { - let x = 0; - for (let j = 0; j < n; ++j) x += matrix[i * n + j] + directed * matrix[j * n + i]; - k += groupSums[i] = x; - } - k = max$2(0, tau$2 - padAngle * n) / k; - dx = k ? padAngle : tau$2 / n; - - // Compute the angles for each group and constituent chord. - { - let x = 0; - if (sortGroups) groupIndex.sort((a, b) => sortGroups(groupSums[a], groupSums[b])); - for (const i of groupIndex) { - const x0 = x; - if (directed) { - const subgroupIndex = range$1(~n + 1, n).filter(j => j < 0 ? matrix[~j * n + i] : matrix[i * n + j]); - if (sortSubgroups) subgroupIndex.sort((a, b) => sortSubgroups(a < 0 ? -matrix[~a * n + i] : matrix[i * n + a], b < 0 ? -matrix[~b * n + i] : matrix[i * n + b])); - for (const j of subgroupIndex) { - if (j < 0) { - const chord = chords[~j * n + i] || (chords[~j * n + i] = {source: null, target: null}); - chord.target = {index: i, startAngle: x, endAngle: x += matrix[~j * n + i] * k, value: matrix[~j * n + i]}; - } else { - const chord = chords[i * n + j] || (chords[i * n + j] = {source: null, target: null}); - chord.source = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; - } - } - groups[i] = {index: i, startAngle: x0, endAngle: x, value: groupSums[i]}; - } else { - const subgroupIndex = range$1(0, n).filter(j => matrix[i * n + j] || matrix[j * n + i]); - if (sortSubgroups) subgroupIndex.sort((a, b) => sortSubgroups(matrix[i * n + a], matrix[i * n + b])); - for (const j of subgroupIndex) { - let chord; - if (i < j) { - chord = chords[i * n + j] || (chords[i * n + j] = {source: null, target: null}); - chord.source = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; - } else { - chord = chords[j * n + i] || (chords[j * n + i] = {source: null, target: null}); - chord.target = {index: i, startAngle: x, endAngle: x += matrix[i * n + j] * k, value: matrix[i * n + j]}; - if (i === j) chord.source = chord.target; - } - if (chord.source && chord.target && chord.source.value < chord.target.value) { - const source = chord.source; - chord.source = chord.target; - chord.target = source; - } - } - groups[i] = {index: i, startAngle: x0, endAngle: x, value: groupSums[i]}; - } - x += dx; - } - } - - // Remove empty chords. - chords = Object.values(chords); - chords.groups = groups; - return sortChords ? chords.sort(sortChords) : chords; - } - - chord.padAngle = function(_) { - return arguments.length ? (padAngle = max$2(0, _), chord) : padAngle; - }; - - chord.sortGroups = function(_) { - return arguments.length ? (sortGroups = _, chord) : sortGroups; - }; - - chord.sortSubgroups = function(_) { - return arguments.length ? (sortSubgroups = _, chord) : sortSubgroups; - }; - - chord.sortChords = function(_) { - return arguments.length ? (_ == null ? sortChords = null : (sortChords = compareValue(_))._ = _, chord) : sortChords && sortChords._; - }; - - return chord; -} - -const pi$1 = Math.PI, - tau$1 = 2 * pi$1, - epsilon$1 = 1e-6, - tauEpsilon = tau$1 - epsilon$1; - -function append(strings) { - this._ += strings[0]; - for (let i = 1, n = strings.length; i < n; ++i) { - this._ += arguments[i] + strings[i]; - } -} - -function appendRound(digits) { - let d = Math.floor(digits); - if (!(d >= 0)) throw new Error(`invalid digits: ${digits}`); - if (d > 15) return append; - const k = 10 ** d; - return function(strings) { - this._ += strings[0]; - for (let i = 1, n = strings.length; i < n; ++i) { - this._ += Math.round(arguments[i] * k) / k + strings[i]; - } - }; -} - -class Path { - constructor(digits) { - this._x0 = this._y0 = // start of current subpath - this._x1 = this._y1 = null; // end of current subpath - this._ = ""; - this._append = digits == null ? append : appendRound(digits); - } - moveTo(x, y) { - this._append`M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}`; - } - closePath() { - if (this._x1 !== null) { - this._x1 = this._x0, this._y1 = this._y0; - this._append`Z`; - } - } - lineTo(x, y) { - this._append`L${this._x1 = +x},${this._y1 = +y}`; - } - quadraticCurveTo(x1, y1, x, y) { - this._append`Q${+x1},${+y1},${this._x1 = +x},${this._y1 = +y}`; - } - bezierCurveTo(x1, y1, x2, y2, x, y) { - this._append`C${+x1},${+y1},${+x2},${+y2},${this._x1 = +x},${this._y1 = +y}`; - } - arcTo(x1, y1, x2, y2, r) { - x1 = +x1, y1 = +y1, x2 = +x2, y2 = +y2, r = +r; - - // Is the radius negative? Error. - if (r < 0) throw new Error(`negative radius: ${r}`); - - let x0 = this._x1, - y0 = this._y1, - x21 = x2 - x1, - y21 = y2 - y1, - x01 = x0 - x1, - y01 = y0 - y1, - l01_2 = x01 * x01 + y01 * y01; - - // Is this path empty? Move to (x1,y1). - if (this._x1 === null) { - this._append`M${this._x1 = x1},${this._y1 = y1}`; - } - - // Or, is (x1,y1) coincident with (x0,y0)? Do nothing. - else if (!(l01_2 > epsilon$1)); - - // Or, are (x0,y0), (x1,y1) and (x2,y2) collinear? - // Equivalently, is (x1,y1) coincident with (x2,y2)? - // Or, is the radius zero? Line to (x1,y1). - else if (!(Math.abs(y01 * x21 - y21 * x01) > epsilon$1) || !r) { - this._append`L${this._x1 = x1},${this._y1 = y1}`; - } - - // Otherwise, draw an arc! - else { - let x20 = x2 - x0, - y20 = y2 - y0, - l21_2 = x21 * x21 + y21 * y21, - l20_2 = x20 * x20 + y20 * y20, - l21 = Math.sqrt(l21_2), - l01 = Math.sqrt(l01_2), - l = r * Math.tan((pi$1 - Math.acos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2), - t01 = l / l01, - t21 = l / l21; - - // If the start tangent is not coincident with (x0,y0), line to. - if (Math.abs(t01 - 1) > epsilon$1) { - this._append`L${x1 + t01 * x01},${y1 + t01 * y01}`; - } - - this._append`A${r},${r},0,0,${+(y01 * x20 > x01 * y20)},${this._x1 = x1 + t21 * x21},${this._y1 = y1 + t21 * y21}`; - } - } - arc(x, y, r, a0, a1, ccw) { - x = +x, y = +y, r = +r, ccw = !!ccw; - - // Is the radius negative? Error. - if (r < 0) throw new Error(`negative radius: ${r}`); - - let dx = r * Math.cos(a0), - dy = r * Math.sin(a0), - x0 = x + dx, - y0 = y + dy, - cw = 1 ^ ccw, - da = ccw ? a0 - a1 : a1 - a0; - - // Is this path empty? Move to (x0,y0). - if (this._x1 === null) { - this._append`M${x0},${y0}`; - } - - // Or, is (x0,y0) not coincident with the previous point? Line to (x0,y0). - else if (Math.abs(this._x1 - x0) > epsilon$1 || Math.abs(this._y1 - y0) > epsilon$1) { - this._append`L${x0},${y0}`; - } - - // Is this arc empty? We’re done. - if (!r) return; - - // Does the angle go the wrong way? Flip the direction. - if (da < 0) da = da % tau$1 + tau$1; - - // Is this a complete circle? Draw two arcs to complete the circle. - if (da > tauEpsilon) { - this._append`A${r},${r},0,1,${cw},${x - dx},${y - dy}A${r},${r},0,1,${cw},${this._x1 = x0},${this._y1 = y0}`; - } - - // Is this arc non-empty? Draw an arc! - else if (da > epsilon$1) { - this._append`A${r},${r},0,${+(da >= pi$1)},${cw},${this._x1 = x + r * Math.cos(a1)},${this._y1 = y + r * Math.sin(a1)}`; - } - } - rect(x, y, w, h) { - this._append`M${this._x0 = this._x1 = +x},${this._y0 = this._y1 = +y}h${w = +w}v${+h}h${-w}Z`; - } - toString() { - return this._; - } -} - -function path() { - return new Path; -} - -// Allow instanceof d3.path -path.prototype = Path.prototype; - -var slice = Array.prototype.slice; - -function constant$4(x) { - return function() { - return x; - }; -} - -function defaultSource(d) { - return d.source; -} - -function defaultTarget(d) { - return d.target; -} - -function defaultRadius(d) { - return d.radius; -} - -function defaultStartAngle(d) { - return d.startAngle; -} - -function defaultEndAngle(d) { - return d.endAngle; -} - -function defaultPadAngle() { - return 0; -} - -function defaultArrowheadRadius() { - return 10; -} - -function ribbon(headRadius) { - var source = defaultSource, - target = defaultTarget, - sourceRadius = defaultRadius, - targetRadius = defaultRadius, - startAngle = defaultStartAngle, - endAngle = defaultEndAngle, - padAngle = defaultPadAngle, - context = null; - - function ribbon() { - var buffer, - s = source.apply(this, arguments), - t = target.apply(this, arguments), - ap = padAngle.apply(this, arguments) / 2, - argv = slice.call(arguments), - sr = +sourceRadius.apply(this, (argv[0] = s, argv)), - sa0 = startAngle.apply(this, argv) - halfPi$1, - sa1 = endAngle.apply(this, argv) - halfPi$1, - tr = +targetRadius.apply(this, (argv[0] = t, argv)), - ta0 = startAngle.apply(this, argv) - halfPi$1, - ta1 = endAngle.apply(this, argv) - halfPi$1; - - if (!context) context = buffer = path(); - - if (ap > epsilon$2) { - if (abs$1(sa1 - sa0) > ap * 2 + epsilon$2) sa1 > sa0 ? (sa0 += ap, sa1 -= ap) : (sa0 -= ap, sa1 += ap); - else sa0 = sa1 = (sa0 + sa1) / 2; - if (abs$1(ta1 - ta0) > ap * 2 + epsilon$2) ta1 > ta0 ? (ta0 += ap, ta1 -= ap) : (ta0 -= ap, ta1 += ap); - else ta0 = ta1 = (ta0 + ta1) / 2; - } - - context.moveTo(sr * cos$1(sa0), sr * sin$1(sa0)); - context.arc(0, 0, sr, sa0, sa1); - if (sa0 !== ta0 || sa1 !== ta1) { - if (headRadius) { - var hr = +headRadius.apply(this, arguments), tr2 = tr - hr, ta2 = (ta0 + ta1) / 2; - context.quadraticCurveTo(0, 0, tr2 * cos$1(ta0), tr2 * sin$1(ta0)); - context.lineTo(tr * cos$1(ta2), tr * sin$1(ta2)); - context.lineTo(tr2 * cos$1(ta1), tr2 * sin$1(ta1)); - } else { - context.quadraticCurveTo(0, 0, tr * cos$1(ta0), tr * sin$1(ta0)); - context.arc(0, 0, tr, ta0, ta1); - } - } - context.quadraticCurveTo(0, 0, sr * cos$1(sa0), sr * sin$1(sa0)); - context.closePath(); - - if (buffer) return context = null, buffer + "" || null; - } - - if (headRadius) ribbon.headRadius = function(_) { - return arguments.length ? (headRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : headRadius; - }; - - ribbon.radius = function(_) { - return arguments.length ? (sourceRadius = targetRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : sourceRadius; - }; - - ribbon.sourceRadius = function(_) { - return arguments.length ? (sourceRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : sourceRadius; - }; - - ribbon.targetRadius = function(_) { - return arguments.length ? (targetRadius = typeof _ === "function" ? _ : constant$4(+_), ribbon) : targetRadius; - }; - - ribbon.startAngle = function(_) { - return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant$4(+_), ribbon) : startAngle; - }; - - ribbon.endAngle = function(_) { - return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant$4(+_), ribbon) : endAngle; - }; - - ribbon.padAngle = function(_) { - return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant$4(+_), ribbon) : padAngle; - }; - - ribbon.source = function(_) { - return arguments.length ? (source = _, ribbon) : source; - }; - - ribbon.target = function(_) { - return arguments.length ? (target = _, ribbon) : target; - }; - - ribbon.context = function(_) { - return arguments.length ? ((context = _ == null ? null : _), ribbon) : context; - }; - - return ribbon; -} - -function ribbon_default() { - return ribbon(); -} - -function ribbonArrow() { - return ribbon(defaultArrowheadRadius); -} - -var noop = {value: () => {}}; - -function dispatch() { - for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) { - if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t); - _[t] = []; - } - return new Dispatch(_); -} - -function Dispatch(_) { - this._ = _; -} - -function parseTypenames$1(typenames, types) { - return typenames.trim().split(/^|\s+/).map(function(t) { - var name = "", i = t.indexOf("."); - if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); - if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t); - return {type: t, name: name}; - }); -} - -Dispatch.prototype = dispatch.prototype = { - constructor: Dispatch, - on: function(typename, callback) { - var _ = this._, - T = parseTypenames$1(typename + "", _), - t, - i = -1, - n = T.length; - - // If no callback was specified, return the callback of the given type and name. - if (arguments.length < 2) { - while (++i < n) if ((t = (typename = T[i]).type) && (t = get$1(_[t], typename.name))) return t; - return; - } - - // If a type was specified, set the callback for the given type and name. - // Otherwise, if a null callback was specified, remove callbacks of the given name. - if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback); - while (++i < n) { - if (t = (typename = T[i]).type) _[t] = set$1(_[t], typename.name, callback); - else if (callback == null) for (t in _) _[t] = set$1(_[t], typename.name, null); - } - - return this; - }, - copy: function() { - var copy = {}, _ = this._; - for (var t in _) copy[t] = _[t].slice(); - return new Dispatch(copy); - }, - call: function(type, that) { - if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2]; - if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); - for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); - }, - apply: function(type, that, args) { - if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type); - for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args); - } -}; - -function get$1(type, name) { - for (var i = 0, n = type.length, c; i < n; ++i) { - if ((c = type[i]).name === name) { - return c.value; - } - } -} - -function set$1(type, name, callback) { - for (var i = 0, n = type.length; i < n; ++i) { - if (type[i].name === name) { - type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1)); - break; - } - } - if (callback != null) type.push({name: name, value: callback}); - return type; -} - -var xhtml = "http://www.w3.org/1999/xhtml"; - -var namespaces = { - svg: "http://www.w3.org/2000/svg", - xhtml: xhtml, - xlink: "http://www.w3.org/1999/xlink", - xml: "http://www.w3.org/XML/1998/namespace", - xmlns: "http://www.w3.org/2000/xmlns/" -}; - -function namespace(name) { - var prefix = name += "", i = prefix.indexOf(":"); - if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); - return namespaces.hasOwnProperty(prefix) ? {space: namespaces[prefix], local: name} : name; // eslint-disable-line no-prototype-builtins -} - -function creatorInherit(name) { - return function() { - var document = this.ownerDocument, - uri = this.namespaceURI; - return uri === xhtml && document.documentElement.namespaceURI === xhtml - ? document.createElement(name) - : document.createElementNS(uri, name); - }; -} - -function creatorFixed(fullname) { - return function() { - return this.ownerDocument.createElementNS(fullname.space, fullname.local); - }; -} - -function creator(name) { - var fullname = namespace(name); - return (fullname.local - ? creatorFixed - : creatorInherit)(fullname); -} - -function none() {} - -function selector(selector) { - return selector == null ? none : function() { - return this.querySelector(selector); - }; -} - -function selection_select(select) { - if (typeof select !== "function") select = selector(select); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { - if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { - if ("__data__" in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - } - } - } - - return new Selection$1(subgroups, this._parents); -} - -// Given something array like (or null), returns something that is strictly an -// array. This is used to ensure that array-like objects passed to d3.selectAll -// or selection.selectAll are converted into proper arrays when creating a -// selection; we don’t ever want to create a selection backed by a live -// HTMLCollection or NodeList. However, note that selection.selectAll will use a -// static NodeList as a group, since it safely derived from querySelectorAll. -function array(x) { - return x == null ? [] : Array.isArray(x) ? x : Array.from(x); -} - -function empty() { - return []; -} - -function selectorAll(selector) { - return selector == null ? empty : function() { - return this.querySelectorAll(selector); - }; -} - -function arrayAll(select) { - return function() { - return array(select.apply(this, arguments)); - }; -} - -function selection_selectAll(select) { - if (typeof select === "function") select = arrayAll(select); - else select = selectorAll(select); - - for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - subgroups.push(select.call(node, node.__data__, i, group)); - parents.push(node); - } - } - } - - return new Selection$1(subgroups, parents); -} - -function matcher(selector) { - return function() { - return this.matches(selector); - }; -} - -function childMatcher(selector) { - return function(node) { - return node.matches(selector); - }; -} - -var find = Array.prototype.find; - -function childFind(match) { - return function() { - return find.call(this.children, match); - }; -} - -function childFirst() { - return this.firstElementChild; -} - -function selection_selectChild(match) { - return this.select(match == null ? childFirst - : childFind(typeof match === "function" ? match : childMatcher(match))); -} - -var filter = Array.prototype.filter; - -function children() { - return Array.from(this.children); -} - -function childrenFilter(match) { - return function() { - return filter.call(this.children, match); - }; -} - -function selection_selectChildren(match) { - return this.selectAll(match == null ? children - : childrenFilter(typeof match === "function" ? match : childMatcher(match))); -} - -function selection_filter(match) { - if (typeof match !== "function") match = matcher(match); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Selection$1(subgroups, this._parents); -} - -function sparse(update) { - return new Array(update.length); -} - -function selection_enter() { - return new Selection$1(this._enter || this._groups.map(sparse), this._parents); -} - -function EnterNode(parent, datum) { - this.ownerDocument = parent.ownerDocument; - this.namespaceURI = parent.namespaceURI; - this._next = null; - this._parent = parent; - this.__data__ = datum; -} - -EnterNode.prototype = { - constructor: EnterNode, - appendChild: function(child) { return this._parent.insertBefore(child, this._next); }, - insertBefore: function(child, next) { return this._parent.insertBefore(child, next); }, - querySelector: function(selector) { return this._parent.querySelector(selector); }, - querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); } -}; - -function constant$3(x) { - return function() { - return x; - }; -} - -function bindIndex(parent, group, enter, update, exit, data) { - var i = 0, - node, - groupLength = group.length, - dataLength = data.length; - - // Put any non-null nodes that fit into update. - // Put any null nodes into enter. - // Put any remaining data into enter. - for (; i < dataLength; ++i) { - if (node = group[i]) { - node.__data__ = data[i]; - update[i] = node; - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Put any non-null nodes that don’t fit into exit. - for (; i < groupLength; ++i) { - if (node = group[i]) { - exit[i] = node; - } - } -} - -function bindKey(parent, group, enter, update, exit, data, key) { - var i, - node, - nodeByKeyValue = new Map, - groupLength = group.length, - dataLength = data.length, - keyValues = new Array(groupLength), - keyValue; - - // Compute the key for each node. - // If multiple nodes have the same key, the duplicates are added to exit. - for (i = 0; i < groupLength; ++i) { - if (node = group[i]) { - keyValues[i] = keyValue = key.call(node, node.__data__, i, group) + ""; - if (nodeByKeyValue.has(keyValue)) { - exit[i] = node; - } else { - nodeByKeyValue.set(keyValue, node); - } - } - } - - // Compute the key for each datum. - // If there a node associated with this key, join and add it to update. - // If there is not (or the key is a duplicate), add it to enter. - for (i = 0; i < dataLength; ++i) { - keyValue = key.call(parent, data[i], i, data) + ""; - if (node = nodeByKeyValue.get(keyValue)) { - update[i] = node; - node.__data__ = data[i]; - nodeByKeyValue.delete(keyValue); - } else { - enter[i] = new EnterNode(parent, data[i]); - } - } - - // Add any remaining nodes that were not bound to data to exit. - for (i = 0; i < groupLength; ++i) { - if ((node = group[i]) && (nodeByKeyValue.get(keyValues[i]) === node)) { - exit[i] = node; - } - } -} - -function datum(node) { - return node.__data__; -} - -function selection_data(value, key) { - if (!arguments.length) return Array.from(this, datum); - - var bind = key ? bindKey : bindIndex, - parents = this._parents, - groups = this._groups; - - if (typeof value !== "function") value = constant$3(value); - - for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) { - var parent = parents[j], - group = groups[j], - groupLength = group.length, - data = arraylike(value.call(parent, parent && parent.__data__, j, parents)), - dataLength = data.length, - enterGroup = enter[j] = new Array(dataLength), - updateGroup = update[j] = new Array(dataLength), - exitGroup = exit[j] = new Array(groupLength); - - bind(parent, group, enterGroup, updateGroup, exitGroup, data, key); - - // Now connect the enter nodes to their following update node, such that - // appendChild can insert the materialized enter node before this node, - // rather than at the end of the parent node. - for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) { - if (previous = enterGroup[i0]) { - if (i0 >= i1) i1 = i0 + 1; - while (!(next = updateGroup[i1]) && ++i1 < dataLength); - previous._next = next || null; - } - } - } - - update = new Selection$1(update, parents); - update._enter = enter; - update._exit = exit; - return update; -} - -// Given some data, this returns an array-like view of it: an object that -// exposes a length property and allows numeric indexing. Note that unlike -// selectAll, this isn’t worried about “live” collections because the resulting -// array will only be used briefly while data is being bound. (It is possible to -// cause the data to change while iterating by using a key function, but please -// don’t; we’d rather avoid a gratuitous copy.) -function arraylike(data) { - return typeof data === "object" && "length" in data - ? data // Array, TypedArray, NodeList, array-like - : Array.from(data); // Map, Set, iterable, string, or anything else -} - -function selection_exit() { - return new Selection$1(this._exit || this._groups.map(sparse), this._parents); -} - -function selection_join(onenter, onupdate, onexit) { - var enter = this.enter(), update = this, exit = this.exit(); - if (typeof onenter === "function") { - enter = onenter(enter); - if (enter) enter = enter.selection(); - } else { - enter = enter.append(onenter + ""); - } - if (onupdate != null) { - update = onupdate(update); - if (update) update = update.selection(); - } - if (onexit == null) exit.remove(); else onexit(exit); - return enter && update ? enter.merge(update).order() : update; -} - -function selection_merge(context) { - var selection = context.selection ? context.selection() : context; - - for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { - for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group0[i] || group1[i]) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Selection$1(merges, this._parents); -} - -function selection_order() { - - for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) { - for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) { - if (node = group[i]) { - if (next && node.compareDocumentPosition(next) ^ 4) next.parentNode.insertBefore(node, next); - next = node; - } - } - } - - return this; -} - -function selection_sort(compare) { - if (!compare) compare = ascending$1; - - function compareNode(a, b) { - return a && b ? compare(a.__data__, b.__data__) : !a - !b; - } - - for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group[i]) { - sortgroup[i] = node; - } - } - sortgroup.sort(compareNode); - } - - return new Selection$1(sortgroups, this._parents).order(); -} - -function ascending$1(a, b) { - return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; -} - -function selection_call() { - var callback = arguments[0]; - arguments[0] = this; - callback.apply(null, arguments); - return this; -} - -function selection_nodes() { - return Array.from(this); -} - -function selection_node() { - - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length; i < n; ++i) { - var node = group[i]; - if (node) return node; - } - } - - return null; -} - -function selection_size() { - let size = 0; - for (const node of this) ++size; // eslint-disable-line no-unused-vars - return size; -} - -function selection_empty() { - return !this.node(); -} - -function selection_each(callback) { - - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if (node = group[i]) callback.call(node, node.__data__, i, group); - } - } - - return this; -} - -function attrRemove$1(name) { - return function() { - this.removeAttribute(name); - }; -} - -function attrRemoveNS$1(fullname) { - return function() { - this.removeAttributeNS(fullname.space, fullname.local); - }; -} - -function attrConstant$1(name, value) { - return function() { - this.setAttribute(name, value); - }; -} - -function attrConstantNS$1(fullname, value) { - return function() { - this.setAttributeNS(fullname.space, fullname.local, value); - }; -} - -function attrFunction$1(name, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.removeAttribute(name); - else this.setAttribute(name, v); - }; -} - -function attrFunctionNS$1(fullname, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.removeAttributeNS(fullname.space, fullname.local); - else this.setAttributeNS(fullname.space, fullname.local, v); - }; -} - -function selection_attr(name, value) { - var fullname = namespace(name); - - if (arguments.length < 2) { - var node = this.node(); - return fullname.local - ? node.getAttributeNS(fullname.space, fullname.local) - : node.getAttribute(fullname); - } - - return this.each((value == null - ? (fullname.local ? attrRemoveNS$1 : attrRemove$1) : (typeof value === "function" - ? (fullname.local ? attrFunctionNS$1 : attrFunction$1) - : (fullname.local ? attrConstantNS$1 : attrConstant$1)))(fullname, value)); -} - -function defaultView(node) { - return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node - || (node.document && node) // node is a Window - || node.defaultView; // node is a Document -} - -function styleRemove$1(name) { - return function() { - this.style.removeProperty(name); - }; -} - -function styleConstant$1(name, value, priority) { - return function() { - this.style.setProperty(name, value, priority); - }; -} - -function styleFunction$1(name, value, priority) { - return function() { - var v = value.apply(this, arguments); - if (v == null) this.style.removeProperty(name); - else this.style.setProperty(name, v, priority); - }; -} - -function selection_style(name, value, priority) { - return arguments.length > 1 - ? this.each((value == null - ? styleRemove$1 : typeof value === "function" - ? styleFunction$1 - : styleConstant$1)(name, value, priority == null ? "" : priority)) - : styleValue(this.node(), name); -} - -function styleValue(node, name) { - return node.style.getPropertyValue(name) - || defaultView(node).getComputedStyle(node, null).getPropertyValue(name); -} - -function propertyRemove(name) { - return function() { - delete this[name]; - }; -} - -function propertyConstant(name, value) { - return function() { - this[name] = value; - }; -} - -function propertyFunction(name, value) { - return function() { - var v = value.apply(this, arguments); - if (v == null) delete this[name]; - else this[name] = v; - }; -} - -function selection_property(name, value) { - return arguments.length > 1 - ? this.each((value == null - ? propertyRemove : typeof value === "function" - ? propertyFunction - : propertyConstant)(name, value)) - : this.node()[name]; -} - -function classArray(string) { - return string.trim().split(/^|\s+/); -} - -function classList(node) { - return node.classList || new ClassList(node); -} - -function ClassList(node) { - this._node = node; - this._names = classArray(node.getAttribute("class") || ""); -} - -ClassList.prototype = { - add: function(name) { - var i = this._names.indexOf(name); - if (i < 0) { - this._names.push(name); - this._node.setAttribute("class", this._names.join(" ")); - } - }, - remove: function(name) { - var i = this._names.indexOf(name); - if (i >= 0) { - this._names.splice(i, 1); - this._node.setAttribute("class", this._names.join(" ")); - } - }, - contains: function(name) { - return this._names.indexOf(name) >= 0; - } -}; - -function classedAdd(node, names) { - var list = classList(node), i = -1, n = names.length; - while (++i < n) list.add(names[i]); -} - -function classedRemove(node, names) { - var list = classList(node), i = -1, n = names.length; - while (++i < n) list.remove(names[i]); -} - -function classedTrue(names) { - return function() { - classedAdd(this, names); - }; -} - -function classedFalse(names) { - return function() { - classedRemove(this, names); - }; -} - -function classedFunction(names, value) { - return function() { - (value.apply(this, arguments) ? classedAdd : classedRemove)(this, names); - }; -} - -function selection_classed(name, value) { - var names = classArray(name + ""); - - if (arguments.length < 2) { - var list = classList(this.node()), i = -1, n = names.length; - while (++i < n) if (!list.contains(names[i])) return false; - return true; - } - - return this.each((typeof value === "function" - ? classedFunction : value - ? classedTrue - : classedFalse)(names, value)); -} - -function textRemove() { - this.textContent = ""; -} - -function textConstant$1(value) { - return function() { - this.textContent = value; - }; -} - -function textFunction$1(value) { - return function() { - var v = value.apply(this, arguments); - this.textContent = v == null ? "" : v; - }; -} - -function selection_text(value) { - return arguments.length - ? this.each(value == null - ? textRemove : (typeof value === "function" - ? textFunction$1 - : textConstant$1)(value)) - : this.node().textContent; -} - -function htmlRemove() { - this.innerHTML = ""; -} - -function htmlConstant(value) { - return function() { - this.innerHTML = value; - }; -} - -function htmlFunction(value) { - return function() { - var v = value.apply(this, arguments); - this.innerHTML = v == null ? "" : v; - }; -} - -function selection_html(value) { - return arguments.length - ? this.each(value == null - ? htmlRemove : (typeof value === "function" - ? htmlFunction - : htmlConstant)(value)) - : this.node().innerHTML; -} - -function raise() { - if (this.nextSibling) this.parentNode.appendChild(this); -} - -function selection_raise() { - return this.each(raise); -} - -function lower() { - if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild); -} - -function selection_lower() { - return this.each(lower); -} - -function selection_append(name) { - var create = typeof name === "function" ? name : creator(name); - return this.select(function() { - return this.appendChild(create.apply(this, arguments)); - }); -} - -function constantNull() { - return null; -} - -function selection_insert(name, before) { - var create = typeof name === "function" ? name : creator(name), - select = before == null ? constantNull : typeof before === "function" ? before : selector(before); - return this.select(function() { - return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null); - }); -} - -function remove() { - var parent = this.parentNode; - if (parent) parent.removeChild(this); -} - -function selection_remove() { - return this.each(remove); -} - -function selection_cloneShallow() { - var clone = this.cloneNode(false), parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; -} - -function selection_cloneDeep() { - var clone = this.cloneNode(true), parent = this.parentNode; - return parent ? parent.insertBefore(clone, this.nextSibling) : clone; -} - -function selection_clone(deep) { - return this.select(deep ? selection_cloneDeep : selection_cloneShallow); -} - -function selection_datum(value) { - return arguments.length - ? this.property("__data__", value) - : this.node().__data__; -} - -function contextListener(listener) { - return function(event) { - listener.call(this, event, this.__data__); - }; -} - -function parseTypenames(typenames) { - return typenames.trim().split(/^|\s+/).map(function(t) { - var name = "", i = t.indexOf("."); - if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i); - return {type: t, name: name}; - }); -} - -function onRemove(typename) { - return function() { - var on = this.__on; - if (!on) return; - for (var j = 0, i = -1, m = on.length, o; j < m; ++j) { - if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) { - this.removeEventListener(o.type, o.listener, o.options); - } else { - on[++i] = o; - } - } - if (++i) on.length = i; - else delete this.__on; - }; -} - -function onAdd(typename, value, options) { - return function() { - var on = this.__on, o, listener = contextListener(value); - if (on) for (var j = 0, m = on.length; j < m; ++j) { - if ((o = on[j]).type === typename.type && o.name === typename.name) { - this.removeEventListener(o.type, o.listener, o.options); - this.addEventListener(o.type, o.listener = listener, o.options = options); - o.value = value; - return; - } - } - this.addEventListener(typename.type, listener, options); - o = {type: typename.type, name: typename.name, value: value, listener: listener, options: options}; - if (!on) this.__on = [o]; - else on.push(o); - }; -} - -function selection_on(typename, value, options) { - var typenames = parseTypenames(typename + ""), i, n = typenames.length, t; - - if (arguments.length < 2) { - var on = this.node().__on; - if (on) for (var j = 0, m = on.length, o; j < m; ++j) { - for (i = 0, o = on[j]; i < n; ++i) { - if ((t = typenames[i]).type === o.type && t.name === o.name) { - return o.value; - } - } - } - return; - } - - on = value ? onAdd : onRemove; - for (i = 0; i < n; ++i) this.each(on(typenames[i], value, options)); - return this; -} - -function dispatchEvent(node, type, params) { - var window = defaultView(node), - event = window.CustomEvent; - - if (typeof event === "function") { - event = new event(type, params); - } else { - event = window.document.createEvent("Event"); - if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail; - else event.initEvent(type, false, false); - } - - node.dispatchEvent(event); -} - -function dispatchConstant(type, params) { - return function() { - return dispatchEvent(this, type, params); - }; -} - -function dispatchFunction(type, params) { - return function() { - return dispatchEvent(this, type, params.apply(this, arguments)); - }; -} - -function selection_dispatch(type, params) { - return this.each((typeof params === "function" - ? dispatchFunction - : dispatchConstant)(type, params)); -} - -function* selection_iterator() { - for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) { - for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) { - if (node = group[i]) yield node; - } - } -} - -var root$1 = [null]; - -function Selection$1(groups, parents) { - this._groups = groups; - this._parents = parents; -} - -function selection() { - return new Selection$1([[document.documentElement]], root$1); -} - -function selection_selection() { - return this; -} - -Selection$1.prototype = selection.prototype = { - constructor: Selection$1, - select: selection_select, - selectAll: selection_selectAll, - selectChild: selection_selectChild, - selectChildren: selection_selectChildren, - filter: selection_filter, - data: selection_data, - enter: selection_enter, - exit: selection_exit, - join: selection_join, - merge: selection_merge, - selection: selection_selection, - order: selection_order, - sort: selection_sort, - call: selection_call, - nodes: selection_nodes, - node: selection_node, - size: selection_size, - empty: selection_empty, - each: selection_each, - attr: selection_attr, - style: selection_style, - property: selection_property, - classed: selection_classed, - text: selection_text, - html: selection_html, - raise: selection_raise, - lower: selection_lower, - append: selection_append, - insert: selection_insert, - remove: selection_remove, - clone: selection_clone, - datum: selection_datum, - on: selection_on, - dispatch: selection_dispatch, - [Symbol.iterator]: selection_iterator -}; - -function select(selector) { - return typeof selector === "string" - ? new Selection$1([[document.querySelector(selector)]], [document.documentElement]) - : new Selection$1([[selector]], root$1); -} - -function create$1(name) { - return select(creator(name).call(document.documentElement)); -} - -var nextId = 0; - -function local() { - return new Local; -} - -function Local() { - this._ = "@" + (++nextId).toString(36); -} - -Local.prototype = local.prototype = { - constructor: Local, - get: function(node) { - var id = this._; - while (!(id in node)) if (!(node = node.parentNode)) return; - return node[id]; - }, - set: function(node, value) { - return node[this._] = value; - }, - remove: function(node) { - return this._ in node && delete node[this._]; - }, - toString: function() { - return this._; - } -}; - -function sourceEvent(event) { - let sourceEvent; - while (sourceEvent = event.sourceEvent) event = sourceEvent; - return event; -} - -function pointer(event, node) { - event = sourceEvent(event); - if (node === undefined) node = event.currentTarget; - if (node) { - var svg = node.ownerSVGElement || node; - if (svg.createSVGPoint) { - var point = svg.createSVGPoint(); - point.x = event.clientX, point.y = event.clientY; - point = point.matrixTransform(node.getScreenCTM().inverse()); - return [point.x, point.y]; - } - if (node.getBoundingClientRect) { - var rect = node.getBoundingClientRect(); - return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop]; - } - } - return [event.pageX, event.pageY]; -} - -function pointers(events, node) { - if (events.target) { // i.e., instanceof Event, not TouchList or iterable - events = sourceEvent(events); - if (node === undefined) node = events.currentTarget; - events = events.touches || [events]; - } - return Array.from(events, event => pointer(event, node)); -} - -function selectAll(selector) { - return typeof selector === "string" - ? new Selection$1([document.querySelectorAll(selector)], [document.documentElement]) - : new Selection$1([array(selector)], root$1); -} - -// These are typically used in conjunction with noevent to ensure that we can -// preventDefault on the event. -const nonpassive = {passive: false}; -const nonpassivecapture = {capture: true, passive: false}; - -function nopropagation(event) { - event.stopImmediatePropagation(); -} - -function noevent(event) { - event.preventDefault(); - event.stopImmediatePropagation(); -} - -function nodrag(view) { - var root = view.document.documentElement, - selection = select(view).on("dragstart.drag", noevent, nonpassivecapture); - if ("onselectstart" in root) { - selection.on("selectstart.drag", noevent, nonpassivecapture); - } else { - root.__noselect = root.style.MozUserSelect; - root.style.MozUserSelect = "none"; - } -} - -function yesdrag(view, noclick) { - var root = view.document.documentElement, - selection = select(view).on("dragstart.drag", null); - if (noclick) { - selection.on("click.drag", noevent, nonpassivecapture); - setTimeout(function() { selection.on("click.drag", null); }, 0); - } - if ("onselectstart" in root) { - selection.on("selectstart.drag", null); - } else { - root.style.MozUserSelect = root.__noselect; - delete root.__noselect; - } -} - -var constant$2 = x => () => x; - -function DragEvent(type, { - sourceEvent, - subject, - target, - identifier, - active, - x, y, dx, dy, - dispatch -}) { - Object.defineProperties(this, { - type: {value: type, enumerable: true, configurable: true}, - sourceEvent: {value: sourceEvent, enumerable: true, configurable: true}, - subject: {value: subject, enumerable: true, configurable: true}, - target: {value: target, enumerable: true, configurable: true}, - identifier: {value: identifier, enumerable: true, configurable: true}, - active: {value: active, enumerable: true, configurable: true}, - x: {value: x, enumerable: true, configurable: true}, - y: {value: y, enumerable: true, configurable: true}, - dx: {value: dx, enumerable: true, configurable: true}, - dy: {value: dy, enumerable: true, configurable: true}, - _: {value: dispatch} - }); -} - -DragEvent.prototype.on = function() { - var value = this._.on.apply(this._, arguments); - return value === this._ ? this : value; -}; - -// Ignore right-click, since that should open the context menu. -function defaultFilter(event) { - return !event.ctrlKey && !event.button; -} - -function defaultContainer() { - return this.parentNode; -} - -function defaultSubject(event, d) { - return d == null ? {x: event.x, y: event.y} : d; -} - -function defaultTouchable() { - return navigator.maxTouchPoints || ("ontouchstart" in this); -} - -function drag() { - var filter = defaultFilter, - container = defaultContainer, - subject = defaultSubject, - touchable = defaultTouchable, - gestures = {}, - listeners = dispatch("start", "drag", "end"), - active = 0, - mousedownx, - mousedowny, - mousemoving, - touchending, - clickDistance2 = 0; - - function drag(selection) { - selection - .on("mousedown.drag", mousedowned) - .filter(touchable) - .on("touchstart.drag", touchstarted) - .on("touchmove.drag", touchmoved, nonpassive) - .on("touchend.drag touchcancel.drag", touchended) - .style("touch-action", "none") - .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)"); - } - - function mousedowned(event, d) { - if (touchending || !filter.call(this, event, d)) return; - var gesture = beforestart(this, container.call(this, event, d), event, d, "mouse"); - if (!gesture) return; - select(event.view) - .on("mousemove.drag", mousemoved, nonpassivecapture) - .on("mouseup.drag", mouseupped, nonpassivecapture); - nodrag(event.view); - nopropagation(event); - mousemoving = false; - mousedownx = event.clientX; - mousedowny = event.clientY; - gesture("start", event); - } - - function mousemoved(event) { - noevent(event); - if (!mousemoving) { - var dx = event.clientX - mousedownx, dy = event.clientY - mousedowny; - mousemoving = dx * dx + dy * dy > clickDistance2; - } - gestures.mouse("drag", event); - } - - function mouseupped(event) { - select(event.view).on("mousemove.drag mouseup.drag", null); - yesdrag(event.view, mousemoving); - noevent(event); - gestures.mouse("end", event); - } - - function touchstarted(event, d) { - if (!filter.call(this, event, d)) return; - var touches = event.changedTouches, - c = container.call(this, event, d), - n = touches.length, i, gesture; - - for (i = 0; i < n; ++i) { - if (gesture = beforestart(this, c, event, d, touches[i].identifier, touches[i])) { - nopropagation(event); - gesture("start", event, touches[i]); - } - } - } - - function touchmoved(event) { - var touches = event.changedTouches, - n = touches.length, i, gesture; - - for (i = 0; i < n; ++i) { - if (gesture = gestures[touches[i].identifier]) { - noevent(event); - gesture("drag", event, touches[i]); - } - } - } - - function touchended(event) { - var touches = event.changedTouches, - n = touches.length, i, gesture; - - if (touchending) clearTimeout(touchending); - touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed! - for (i = 0; i < n; ++i) { - if (gesture = gestures[touches[i].identifier]) { - nopropagation(event); - gesture("end", event, touches[i]); - } - } - } - - function beforestart(that, container, event, d, identifier, touch) { - var dispatch = listeners.copy(), - p = pointer(touch || event, container), dx, dy, - s; - - if ((s = subject.call(that, new DragEvent("beforestart", { - sourceEvent: event, - target: drag, - identifier, - active, - x: p[0], - y: p[1], - dx: 0, - dy: 0, - dispatch - }), d)) == null) return; - - dx = s.x - p[0] || 0; - dy = s.y - p[1] || 0; - - return function gesture(type, event, touch) { - var p0 = p, n; - switch (type) { - case "start": gestures[identifier] = gesture, n = active++; break; - case "end": delete gestures[identifier], --active; // falls through - case "drag": p = pointer(touch || event, container), n = active; break; - } - dispatch.call( - type, - that, - new DragEvent(type, { - sourceEvent: event, - subject: s, - target: drag, - identifier, - active: n, - x: p[0] + dx, - y: p[1] + dy, - dx: p[0] - p0[0], - dy: p[1] - p0[1], - dispatch - }), - d - ); - }; - } - - drag.filter = function(_) { - return arguments.length ? (filter = typeof _ === "function" ? _ : constant$2(!!_), drag) : filter; - }; - - drag.container = function(_) { - return arguments.length ? (container = typeof _ === "function" ? _ : constant$2(_), drag) : container; - }; - - drag.subject = function(_) { - return arguments.length ? (subject = typeof _ === "function" ? _ : constant$2(_), drag) : subject; - }; - - drag.touchable = function(_) { - return arguments.length ? (touchable = typeof _ === "function" ? _ : constant$2(!!_), drag) : touchable; - }; - - drag.on = function() { - var value = listeners.on.apply(listeners, arguments); - return value === listeners ? drag : value; - }; - - drag.clickDistance = function(_) { - return arguments.length ? (clickDistance2 = (_ = +_) * _, drag) : Math.sqrt(clickDistance2); - }; - - return drag; -} - -function ascending(a, b) { - return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; -} - -function descending(a, b) { - return a == null || b == null ? NaN - : b < a ? -1 - : b > a ? 1 - : b >= a ? 0 - : NaN; -} - -function bisector(f) { - let compare1, compare2, delta; - - // If an accessor is specified, promote it to a comparator. In this case we - // can test whether the search value is (self-) comparable. We can’t do this - // for a comparator (except for specific, known comparators) because we can’t - // tell if the comparator is symmetric, and an asymmetric comparator can’t be - // used to test whether a single value is comparable. - if (f.length !== 2) { - compare1 = ascending; - compare2 = (d, x) => ascending(f(d), x); - delta = (d, x) => f(d) - x; - } else { - compare1 = f === ascending || f === descending ? f : zero$1; - compare2 = f; - delta = f; - } - - function left(a, x, lo = 0, hi = a.length) { - if (lo < hi) { - if (compare1(x, x) !== 0) return hi; - do { - const mid = (lo + hi) >>> 1; - if (compare2(a[mid], x) < 0) lo = mid + 1; - else hi = mid; - } while (lo < hi); - } - return lo; - } - - function right(a, x, lo = 0, hi = a.length) { - if (lo < hi) { - if (compare1(x, x) !== 0) return hi; - do { - const mid = (lo + hi) >>> 1; - if (compare2(a[mid], x) <= 0) lo = mid + 1; - else hi = mid; - } while (lo < hi); - } - return lo; - } - - function center(a, x, lo = 0, hi = a.length) { - const i = left(a, x, lo, hi - 1); - return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i; - } - - return {left, center, right}; -} - -function zero$1() { - return 0; -} - -function number$2(x) { - return x === null ? NaN : +x; -} - -function* numbers(values, valueof) { - { - for (let value of values) { - if (value != null && (value = +value) >= value) { - yield value; - } - } - } -} - -const ascendingBisect = bisector(ascending); -const bisectRight = ascendingBisect.right; -bisector(number$2).center; - -class InternMap extends Map { - constructor(entries, key = keyof) { - super(); - Object.defineProperties(this, {_intern: {value: new Map()}, _key: {value: key}}); - if (entries != null) for (const [key, value] of entries) this.set(key, value); - } - get(key) { - return super.get(intern_get(this, key)); - } - has(key) { - return super.has(intern_get(this, key)); - } - set(key, value) { - return super.set(intern_set(this, key), value); - } - delete(key) { - return super.delete(intern_delete(this, key)); - } -} - -function intern_get({_intern, _key}, value) { - const key = _key(value); - return _intern.has(key) ? _intern.get(key) : value; -} - -function intern_set({_intern, _key}, value) { - const key = _key(value); - if (_intern.has(key)) return _intern.get(key); - _intern.set(key, value); - return value; -} - -function intern_delete({_intern, _key}, value) { - const key = _key(value); - if (_intern.has(key)) { - value = _intern.get(key); - _intern.delete(key); - } - return value; -} - -function keyof(value) { - return value !== null && typeof value === "object" ? value.valueOf() : value; -} - -function compareDefined(compare = ascending) { - if (compare === ascending) return ascendingDefined; - if (typeof compare !== "function") throw new TypeError("compare is not a function"); - return (a, b) => { - const x = compare(a, b); - if (x || x === 0) return x; - return (compare(b, b) === 0) - (compare(a, a) === 0); - }; -} - -function ascendingDefined(a, b) { - return (a == null || !(a >= a)) - (b == null || !(b >= b)) || (a < b ? -1 : a > b ? 1 : 0); -} - -const e10 = Math.sqrt(50), - e5 = Math.sqrt(10), - e2 = Math.sqrt(2); - -function tickSpec(start, stop, count) { - const step = (stop - start) / Math.max(0, count), - power = Math.floor(Math.log10(step)), - error = step / Math.pow(10, power), - factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1; - let i1, i2, inc; - if (power < 0) { - inc = Math.pow(10, -power) / factor; - i1 = Math.round(start * inc); - i2 = Math.round(stop * inc); - if (i1 / inc < start) ++i1; - if (i2 / inc > stop) --i2; - inc = -inc; - } else { - inc = Math.pow(10, power) * factor; - i1 = Math.round(start / inc); - i2 = Math.round(stop / inc); - if (i1 * inc < start) ++i1; - if (i2 * inc > stop) --i2; - } - if (i2 < i1 && 0.5 <= count && count < 2) return tickSpec(start, stop, count * 2); - return [i1, i2, inc]; -} - -function ticks(start, stop, count) { - stop = +stop, start = +start, count = +count; - if (!(count > 0)) return []; - if (start === stop) return [start]; - const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count) : tickSpec(start, stop, count); - if (!(i2 >= i1)) return []; - const n = i2 - i1 + 1, ticks = new Array(n); - if (reverse) { - if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) / -inc; - else for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) * inc; - } else { - if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) / -inc; - else for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) * inc; - } - return ticks; -} - -function tickIncrement(start, stop, count) { - stop = +stop, start = +start, count = +count; - return tickSpec(start, stop, count)[2]; -} - -function tickStep(start, stop, count) { - stop = +stop, start = +start, count = +count; - const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count) : tickIncrement(start, stop, count); - return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc); -} - -function max$1(values, valueof) { - let max; - { - for (const value of values) { - if (value != null - && (max < value || (max === undefined && value >= value))) { - max = value; - } - } - } - return max; -} - -function min$1(values, valueof) { - let min; - { - for (const value of values) { - if (value != null - && (min > value || (min === undefined && value >= value))) { - min = value; - } - } - } - return min; -} - -// Based on https://github.com/mourner/quickselect -// ISC license, Copyright 2018 Vladimir Agafonkin. -function quickselect(array, k, left = 0, right = Infinity, compare) { - k = Math.floor(k); - left = Math.floor(Math.max(0, left)); - right = Math.floor(Math.min(array.length - 1, right)); - - if (!(left <= k && k <= right)) return array; - - compare = compare === undefined ? ascendingDefined : compareDefined(compare); - - while (right > left) { - if (right - left > 600) { - const n = right - left + 1; - const m = k - left + 1; - const z = Math.log(n); - const s = 0.5 * Math.exp(2 * z / 3); - const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); - const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); - const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); - quickselect(array, k, newLeft, newRight, compare); - } - - const t = array[k]; - let i = left; - let j = right; - - swap(array, left, k); - if (compare(array[right], t) > 0) swap(array, left, right); - - while (i < j) { - swap(array, i, j), ++i, --j; - while (compare(array[i], t) < 0) ++i; - while (compare(array[j], t) > 0) --j; - } - - if (compare(array[left], t) === 0) swap(array, left, j); - else ++j, swap(array, j, right); - - if (j <= k) left = j + 1; - if (k <= j) right = j - 1; - } - - return array; -} - -function swap(array, i, j) { - const t = array[i]; - array[i] = array[j]; - array[j] = t; -} - -function quantile$1(values, p, valueof) { - values = Float64Array.from(numbers(values)); - if (!(n = values.length) || isNaN(p = +p)) return; - if (p <= 0 || n < 2) return min$1(values); - if (p >= 1) return max$1(values); - var n, - i = (n - 1) * p, - i0 = Math.floor(i), - value0 = max$1(quickselect(values, i0).subarray(0, i0 + 1)), - value1 = min$1(values.subarray(i0 + 1)); - return value0 + (value1 - value0) * (i - i0); -} - -function quantileSorted(values, p, valueof = number$2) { - if (!(n = values.length) || isNaN(p = +p)) return; - if (p <= 0 || n < 2) return +valueof(values[0], 0, values); - if (p >= 1) return +valueof(values[n - 1], n - 1, values); - var n, - i = (n - 1) * p, - i0 = Math.floor(i), - value0 = +valueof(values[i0], i0, values), - value1 = +valueof(values[i0 + 1], i0 + 1, values); - return value0 + (value1 - value0) * (i - i0); -} - -function range(start, stop, step) { - start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step; - - var i = -1, - n = Math.max(0, Math.ceil((stop - start) / step)) | 0, - range = new Array(n); - - while (++i < n) { - range[i] = start + i * step; - } - - return range; -} - -function initRange(domain, range) { - switch (arguments.length) { - case 0: break; - case 1: this.range(domain); break; - default: this.range(range).domain(domain); break; - } - return this; -} - -function initInterpolator(domain, interpolator) { - switch (arguments.length) { - case 0: break; - case 1: { - if (typeof domain === "function") this.interpolator(domain); - else this.range(domain); - break; - } - default: { - this.domain(domain); - if (typeof interpolator === "function") this.interpolator(interpolator); - else this.range(interpolator); - break; - } - } - return this; -} - -const implicit = Symbol("implicit"); - -function ordinal() { - var index = new InternMap(), - domain = [], - range = [], - unknown = implicit; - - function scale(d) { - let i = index.get(d); - if (i === undefined) { - if (unknown !== implicit) return unknown; - index.set(d, i = domain.push(d) - 1); - } - return range[i % range.length]; - } - - scale.domain = function(_) { - if (!arguments.length) return domain.slice(); - domain = [], index = new InternMap(); - for (const value of _) { - if (index.has(value)) continue; - index.set(value, domain.push(value) - 1); - } - return scale; - }; - - scale.range = function(_) { - return arguments.length ? (range = Array.from(_), scale) : range.slice(); - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - scale.copy = function() { - return ordinal(domain, range).unknown(unknown); - }; - - initRange.apply(scale, arguments); - - return scale; -} - -function band() { - var scale = ordinal().unknown(undefined), - domain = scale.domain, - ordinalRange = scale.range, - r0 = 0, - r1 = 1, - step, - bandwidth, - round = false, - paddingInner = 0, - paddingOuter = 0, - align = 0.5; - - delete scale.unknown; - - function rescale() { - var n = domain().length, - reverse = r1 < r0, - start = reverse ? r1 : r0, - stop = reverse ? r0 : r1; - step = (stop - start) / Math.max(1, n - paddingInner + paddingOuter * 2); - if (round) step = Math.floor(step); - start += (stop - start - step * (n - paddingInner)) * align; - bandwidth = step * (1 - paddingInner); - if (round) start = Math.round(start), bandwidth = Math.round(bandwidth); - var values = range(n).map(function(i) { return start + step * i; }); - return ordinalRange(reverse ? values.reverse() : values); - } - - scale.domain = function(_) { - return arguments.length ? (domain(_), rescale()) : domain(); - }; - - scale.range = function(_) { - return arguments.length ? ([r0, r1] = _, r0 = +r0, r1 = +r1, rescale()) : [r0, r1]; - }; - - scale.rangeRound = function(_) { - return [r0, r1] = _, r0 = +r0, r1 = +r1, round = true, rescale(); - }; - - scale.bandwidth = function() { - return bandwidth; - }; - - scale.step = function() { - return step; - }; - - scale.round = function(_) { - return arguments.length ? (round = !!_, rescale()) : round; - }; - - scale.padding = function(_) { - return arguments.length ? (paddingInner = Math.min(1, paddingOuter = +_), rescale()) : paddingInner; - }; - - scale.paddingInner = function(_) { - return arguments.length ? (paddingInner = Math.min(1, _), rescale()) : paddingInner; - }; - - scale.paddingOuter = function(_) { - return arguments.length ? (paddingOuter = +_, rescale()) : paddingOuter; - }; - - scale.align = function(_) { - return arguments.length ? (align = Math.max(0, Math.min(1, _)), rescale()) : align; - }; - - scale.copy = function() { - return band(domain(), [r0, r1]) - .round(round) - .paddingInner(paddingInner) - .paddingOuter(paddingOuter) - .align(align); - }; - - return initRange.apply(rescale(), arguments); -} - -function pointish(scale) { - var copy = scale.copy; - - scale.padding = scale.paddingOuter; - delete scale.paddingInner; - delete scale.paddingOuter; - - scale.copy = function() { - return pointish(copy()); - }; - - return scale; -} - -function point() { - return pointish(band.apply(null, arguments).paddingInner(1)); -} - -var constant$1 = x => () => x; - -function linear$1(a, d) { - return function(t) { - return a + t * d; - }; -} - -function exponential(a, b, y) { - return a = Math.pow(a, y), b = Math.pow(b, y) - a, y = 1 / y, function(t) { - return Math.pow(a + t * b, y); - }; -} - -function gamma(y) { - return (y = +y) === 1 ? nogamma : function(a, b) { - return b - a ? exponential(a, b, y) : constant$1(isNaN(a) ? b : a); - }; -} - -function nogamma(a, b) { - var d = b - a; - return d ? linear$1(a, d) : constant$1(isNaN(a) ? b : a); -} - -var interpolateRgb = (function rgbGamma(y) { - var color = gamma(y); - - function rgb$1(start, end) { - var r = color((start = rgb(start)).r, (end = rgb(end)).r), - g = color(start.g, end.g), - b = color(start.b, end.b), - opacity = nogamma(start.opacity, end.opacity); - return function(t) { - start.r = r(t); - start.g = g(t); - start.b = b(t); - start.opacity = opacity(t); - return start + ""; - }; - } - - rgb$1.gamma = rgbGamma; - - return rgb$1; -})(1); - -function numberArray(a, b) { - if (!b) b = []; - var n = a ? Math.min(b.length, a.length) : 0, - c = b.slice(), - i; - return function(t) { - for (i = 0; i < n; ++i) c[i] = a[i] * (1 - t) + b[i] * t; - return c; - }; -} - -function isNumberArray(x) { - return ArrayBuffer.isView(x) && !(x instanceof DataView); -} - -function genericArray(a, b) { - var nb = b ? b.length : 0, - na = a ? Math.min(nb, a.length) : 0, - x = new Array(na), - c = new Array(nb), - i; - - for (i = 0; i < na; ++i) x[i] = interpolate$1(a[i], b[i]); - for (; i < nb; ++i) c[i] = b[i]; - - return function(t) { - for (i = 0; i < na; ++i) c[i] = x[i](t); - return c; - }; -} - -function date$1(a, b) { - var d = new Date; - return a = +a, b = +b, function(t) { - return d.setTime(a * (1 - t) + b * t), d; - }; -} - -function interpolateNumber(a, b) { - return a = +a, b = +b, function(t) { - return a * (1 - t) + b * t; - }; -} - -function object(a, b) { - var i = {}, - c = {}, - k; - - if (a === null || typeof a !== "object") a = {}; - if (b === null || typeof b !== "object") b = {}; - - for (k in b) { - if (k in a) { - i[k] = interpolate$1(a[k], b[k]); - } else { - c[k] = b[k]; - } - } - - return function(t) { - for (k in i) c[k] = i[k](t); - return c; - }; -} - -var reA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, - reB = new RegExp(reA.source, "g"); - -function zero(b) { - return function() { - return b; - }; -} - -function one(b) { - return function(t) { - return b(t) + ""; - }; -} - -function interpolateString(a, b) { - var bi = reA.lastIndex = reB.lastIndex = 0, // scan index for next number in b - am, // current match in a - bm, // current match in b - bs, // string preceding current number in b, if any - i = -1, // index in s - s = [], // string constants and placeholders - q = []; // number interpolators - - // Coerce inputs to strings. - a = a + "", b = b + ""; - - // Interpolate pairs of numbers in a & b. - while ((am = reA.exec(a)) - && (bm = reB.exec(b))) { - if ((bs = bm.index) > bi) { // a string precedes the next number in b - bs = b.slice(bi, bs); - if (s[i]) s[i] += bs; // coalesce with previous string - else s[++i] = bs; - } - if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match - if (s[i]) s[i] += bm; // coalesce with previous string - else s[++i] = bm; - } else { // interpolate non-matching numbers - s[++i] = null; - q.push({i: i, x: interpolateNumber(am, bm)}); - } - bi = reB.lastIndex; - } - - // Add remains of b. - if (bi < b.length) { - bs = b.slice(bi); - if (s[i]) s[i] += bs; // coalesce with previous string - else s[++i] = bs; - } - - // Special optimization for only a single match. - // Otherwise, interpolate each of the numbers and rejoin the string. - return s.length < 2 ? (q[0] - ? one(q[0].x) - : zero(b)) - : (b = q.length, function(t) { - for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }); -} - -function interpolate$1(a, b) { - var t = typeof b, c; - return b == null || t === "boolean" ? constant$1(b) - : (t === "number" ? interpolateNumber - : t === "string" ? ((c = color(b)) ? (b = c, interpolateRgb) : interpolateString) - : b instanceof color ? interpolateRgb - : b instanceof Date ? date$1 - : isNumberArray(b) ? numberArray - : Array.isArray(b) ? genericArray - : typeof b.valueOf !== "function" && typeof b.toString !== "function" || isNaN(b) ? object - : interpolateNumber)(a, b); -} - -function interpolateRound(a, b) { - return a = +a, b = +b, function(t) { - return Math.round(a * (1 - t) + b * t); - }; -} - -var degrees = 180 / Math.PI; - -var identity$3 = { - translateX: 0, - translateY: 0, - rotate: 0, - skewX: 0, - scaleX: 1, - scaleY: 1 -}; - -function decompose(a, b, c, d, e, f) { - var scaleX, scaleY, skewX; - if (scaleX = Math.sqrt(a * a + b * b)) a /= scaleX, b /= scaleX; - if (skewX = a * c + b * d) c -= a * skewX, d -= b * skewX; - if (scaleY = Math.sqrt(c * c + d * d)) c /= scaleY, d /= scaleY, skewX /= scaleY; - if (a * d < b * c) a = -a, b = -b, skewX = -skewX, scaleX = -scaleX; - return { - translateX: e, - translateY: f, - rotate: Math.atan2(b, a) * degrees, - skewX: Math.atan(skewX) * degrees, - scaleX: scaleX, - scaleY: scaleY - }; -} - -var svgNode; - -/* eslint-disable no-undef */ -function parseCss(value) { - const m = new (typeof DOMMatrix === "function" ? DOMMatrix : WebKitCSSMatrix)(value + ""); - return m.isIdentity ? identity$3 : decompose(m.a, m.b, m.c, m.d, m.e, m.f); -} - -function parseSvg(value) { - if (value == null) return identity$3; - if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g"); - svgNode.setAttribute("transform", value); - if (!(value = svgNode.transform.baseVal.consolidate())) return identity$3; - value = value.matrix; - return decompose(value.a, value.b, value.c, value.d, value.e, value.f); -} - -function interpolateTransform(parse, pxComma, pxParen, degParen) { - - function pop(s) { - return s.length ? s.pop() + " " : ""; - } - - function translate(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push("translate(", null, pxComma, null, pxParen); - q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); - } else if (xb || yb) { - s.push("translate(" + xb + pxComma + yb + pxParen); - } - } - - function rotate(a, b, s, q) { - if (a !== b) { - if (a - b > 180) b += 360; else if (b - a > 180) a += 360; // shortest path - q.push({i: s.push(pop(s) + "rotate(", null, degParen) - 2, x: interpolateNumber(a, b)}); - } else if (b) { - s.push(pop(s) + "rotate(" + b + degParen); - } - } - - function skewX(a, b, s, q) { - if (a !== b) { - q.push({i: s.push(pop(s) + "skewX(", null, degParen) - 2, x: interpolateNumber(a, b)}); - } else if (b) { - s.push(pop(s) + "skewX(" + b + degParen); - } - } - - function scale(xa, ya, xb, yb, s, q) { - if (xa !== xb || ya !== yb) { - var i = s.push(pop(s) + "scale(", null, ",", null, ")"); - q.push({i: i - 4, x: interpolateNumber(xa, xb)}, {i: i - 2, x: interpolateNumber(ya, yb)}); - } else if (xb !== 1 || yb !== 1) { - s.push(pop(s) + "scale(" + xb + "," + yb + ")"); - } - } - - return function(a, b) { - var s = [], // string constants and placeholders - q = []; // number interpolators - a = parse(a), b = parse(b); - translate(a.translateX, a.translateY, b.translateX, b.translateY, s, q); - rotate(a.rotate, b.rotate, s, q); - skewX(a.skewX, b.skewX, s, q); - scale(a.scaleX, a.scaleY, b.scaleX, b.scaleY, s, q); - a = b = null; // gc - return function(t) { - var i = -1, n = q.length, o; - while (++i < n) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; - }; -} - -var interpolateTransformCss = interpolateTransform(parseCss, "px, ", "px)", "deg)"); -var interpolateTransformSvg = interpolateTransform(parseSvg, ", ", ")", ")"); - -function piecewise(interpolate, values) { - if (values === undefined) values = interpolate, interpolate = interpolate$1; - var i = 0, n = values.length - 1, v = values[0], I = new Array(n < 0 ? 0 : n); - while (i < n) I[i] = interpolate(v, v = values[++i]); - return function(t) { - var i = Math.max(0, Math.min(n - 1, Math.floor(t *= n))); - return I[i](t - i); - }; -} - -function constants(x) { - return function() { - return x; - }; -} - -function number$1(x) { - return +x; -} - -var unit = [0, 1]; - -function identity$2(x) { - return x; -} - -function normalize(a, b) { - return (b -= (a = +a)) - ? function(x) { return (x - a) / b; } - : constants(isNaN(b) ? NaN : 0.5); -} - -function clamper(a, b) { - var t; - if (a > b) t = a, a = b, b = t; - return function(x) { return Math.max(a, Math.min(b, x)); }; -} - -// normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. -// interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. -function bimap(domain, range, interpolate) { - var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1]; - if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0); - else d0 = normalize(d0, d1), r0 = interpolate(r0, r1); - return function(x) { return r0(d0(x)); }; -} - -function polymap(domain, range, interpolate) { - var j = Math.min(domain.length, range.length) - 1, - d = new Array(j), - r = new Array(j), - i = -1; - - // Reverse descending domains. - if (domain[j] < domain[0]) { - domain = domain.slice().reverse(); - range = range.slice().reverse(); - } - - while (++i < j) { - d[i] = normalize(domain[i], domain[i + 1]); - r[i] = interpolate(range[i], range[i + 1]); - } - - return function(x) { - var i = bisectRight(domain, x, 1, j) - 1; - return r[i](d[i](x)); - }; -} - -function copy$1(source, target) { - return target - .domain(source.domain()) - .range(source.range()) - .interpolate(source.interpolate()) - .clamp(source.clamp()) - .unknown(source.unknown()); -} - -function transformer$2() { - var domain = unit, - range = unit, - interpolate = interpolate$1, - transform, - untransform, - unknown, - clamp = identity$2, - piecewise, - output, - input; - - function rescale() { - var n = Math.min(domain.length, range.length); - if (clamp !== identity$2) clamp = clamper(domain[0], domain[n - 1]); - piecewise = n > 2 ? polymap : bimap; - output = input = null; - return scale; - } - - function scale(x) { - return x == null || isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x))); - } - - scale.invert = function(y) { - return clamp(untransform((input || (input = piecewise(range, domain.map(transform), interpolateNumber)))(y))); - }; - - scale.domain = function(_) { - return arguments.length ? (domain = Array.from(_, number$1), rescale()) : domain.slice(); - }; - - scale.range = function(_) { - return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); - }; - - scale.rangeRound = function(_) { - return range = Array.from(_), interpolate = interpolateRound, rescale(); - }; - - scale.clamp = function(_) { - return arguments.length ? (clamp = _ ? true : identity$2, rescale()) : clamp !== identity$2; - }; - - scale.interpolate = function(_) { - return arguments.length ? (interpolate = _, rescale()) : interpolate; - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - return function(t, u) { - transform = t, untransform = u; - return rescale(); - }; -} - -function continuous() { - return transformer$2()(identity$2, identity$2); -} - -function formatDecimal(x) { - return Math.abs(x = Math.round(x)) >= 1e21 - ? x.toLocaleString("en").replace(/,/g, "") - : x.toString(10); -} - -// Computes the decimal coefficient and exponent of the specified number x with -// significant digits p, where x is positive and p is in [1, 21] or undefined. -// For example, formatDecimalParts(1.23) returns ["123", 0]. -function formatDecimalParts(x, p) { - if (!isFinite(x) || x === 0) return null; // NaN, ±Infinity, ±0 - var i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e"), coefficient = x.slice(0, i); - - // The string returned by toExponential either has the form \d\.\d+e[-+]\d+ - // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3). - return [ - coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, - +x.slice(i + 1) - ]; -} - -function exponent(x) { - return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN; -} - -function formatGroup(grouping, thousands) { - return function(value, width) { - var i = value.length, - t = [], - j = 0, - g = grouping[0], - length = 0; - - while (i > 0 && g > 0) { - if (length + g + 1 > width) g = Math.max(1, width - length); - t.push(value.substring(i -= g, i + g)); - if ((length += g + 1) > width) break; - g = grouping[j = (j + 1) % grouping.length]; - } - - return t.reverse().join(thousands); - }; -} - -function formatNumerals(numerals) { - return function(value) { - return value.replace(/[0-9]/g, function(i) { - return numerals[+i]; - }); - }; -} - -// [[fill]align][sign][symbol][0][width][,][.precision][~][type] -var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i; - -function formatSpecifier(specifier) { - if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier); - var match; - return new FormatSpecifier({ - fill: match[1], - align: match[2], - sign: match[3], - symbol: match[4], - zero: match[5], - width: match[6], - comma: match[7], - precision: match[8] && match[8].slice(1), - trim: match[9], - type: match[10] - }); -} - -formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof - -function FormatSpecifier(specifier) { - this.fill = specifier.fill === undefined ? " " : specifier.fill + ""; - this.align = specifier.align === undefined ? ">" : specifier.align + ""; - this.sign = specifier.sign === undefined ? "-" : specifier.sign + ""; - this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + ""; - this.zero = !!specifier.zero; - this.width = specifier.width === undefined ? undefined : +specifier.width; - this.comma = !!specifier.comma; - this.precision = specifier.precision === undefined ? undefined : +specifier.precision; - this.trim = !!specifier.trim; - this.type = specifier.type === undefined ? "" : specifier.type + ""; -} - -FormatSpecifier.prototype.toString = function() { - return this.fill - + this.align - + this.sign - + this.symbol - + (this.zero ? "0" : "") - + (this.width === undefined ? "" : Math.max(1, this.width | 0)) - + (this.comma ? "," : "") - + (this.precision === undefined ? "" : "." + Math.max(0, this.precision | 0)) - + (this.trim ? "~" : "") - + this.type; -}; - -// Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k. -function formatTrim(s) { - out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) { - switch (s[i]) { - case ".": i0 = i1 = i; break; - case "0": if (i0 === 0) i0 = i; i1 = i; break; - default: if (!+s[i]) break out; if (i0 > 0) i0 = 0; break; - } - } - return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s; -} - -var prefixExponent; - -function formatPrefixAuto(x, p) { - var d = formatDecimalParts(x, p); - if (!d) return prefixExponent = undefined, x.toPrecision(p); - var coefficient = d[0], - exponent = d[1], - i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1, - n = coefficient.length; - return i === n ? coefficient - : i > n ? coefficient + new Array(i - n + 1).join("0") - : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) - : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y! -} - -function formatRounded(x, p) { - var d = formatDecimalParts(x, p); - if (!d) return x + ""; - var coefficient = d[0], - exponent = d[1]; - return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient - : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) - : coefficient + new Array(exponent - coefficient.length + 2).join("0"); -} - -var formatTypes = { - "%": (x, p) => (x * 100).toFixed(p), - "b": (x) => Math.round(x).toString(2), - "c": (x) => x + "", - "d": formatDecimal, - "e": (x, p) => x.toExponential(p), - "f": (x, p) => x.toFixed(p), - "g": (x, p) => x.toPrecision(p), - "o": (x) => Math.round(x).toString(8), - "p": (x, p) => formatRounded(x * 100, p), - "r": formatRounded, - "s": formatPrefixAuto, - "X": (x) => Math.round(x).toString(16).toUpperCase(), - "x": (x) => Math.round(x).toString(16) -}; - -function identity$1(x) { - return x; -} - -var map = Array.prototype.map, - prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"]; - -function formatLocale$1(locale) { - var group = locale.grouping === undefined || locale.thousands === undefined ? identity$1 : formatGroup(map.call(locale.grouping, Number), locale.thousands + ""), - currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "", - currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "", - decimal = locale.decimal === undefined ? "." : locale.decimal + "", - numerals = locale.numerals === undefined ? identity$1 : formatNumerals(map.call(locale.numerals, String)), - percent = locale.percent === undefined ? "%" : locale.percent + "", - minus = locale.minus === undefined ? "−" : locale.minus + "", - nan = locale.nan === undefined ? "NaN" : locale.nan + ""; - - function newFormat(specifier, options) { - specifier = formatSpecifier(specifier); - - var fill = specifier.fill, - align = specifier.align, - sign = specifier.sign, - symbol = specifier.symbol, - zero = specifier.zero, - width = specifier.width, - comma = specifier.comma, - precision = specifier.precision, - trim = specifier.trim, - type = specifier.type; - - // The "n" type is an alias for ",g". - if (type === "n") comma = true, type = "g"; - - // The "" type, and any invalid type, is an alias for ".12~g". - else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = "g"; - - // If zero fill is specified, padding goes after sign and before digits. - if (zero || (fill === "0" && align === "=")) zero = true, fill = "0", align = "="; - - // Compute the prefix and suffix. - // For SI-prefix, the suffix is lazily computed. - var prefix = (options && options.prefix !== undefined ? options.prefix : "") + (symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : ""), - suffix = (symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : "") + (options && options.suffix !== undefined ? options.suffix : ""); - - // What format function should we use? - // Is this an integer type? - // Can this type generate exponential notation? - var formatType = formatTypes[type], - maybeSuffix = /[defgprs%]/.test(type); - - // Set the default precision if not specified, - // or clamp the specified precision to the supported range. - // For significant precision, it must be in [1, 21]. - // For fixed precision, it must be in [0, 20]. - precision = precision === undefined ? 6 - : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) - : Math.max(0, Math.min(20, precision)); - - function format(value) { - var valuePrefix = prefix, - valueSuffix = suffix, - i, n, c; - - if (type === "c") { - valueSuffix = formatType(value) + valueSuffix; - value = ""; - } else { - value = +value; - - // Determine the sign. -0 is not less than 0, but 1 / -0 is! - var valueNegative = value < 0 || 1 / value < 0; - - // Perform the initial formatting. - value = isNaN(value) ? nan : formatType(Math.abs(value), precision); - - // Trim insignificant zeros. - if (trim) value = formatTrim(value); - - // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign. - if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; - - // Compute the prefix and suffix. - valuePrefix = (valueNegative ? (sign === "(" ? sign : minus) : sign === "-" || sign === "(" ? "" : sign) + valuePrefix; - valueSuffix = (type === "s" && !isNaN(value) && prefixExponent !== undefined ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); - - // Break the formatted value into the integer “value” part that can be - // grouped, and fractional or exponential “suffix” part that is not. - if (maybeSuffix) { - i = -1, n = value.length; - while (++i < n) { - if (c = value.charCodeAt(i), 48 > c || c > 57) { - valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix; - value = value.slice(0, i); - break; - } - } - } - } - - // If the fill character is not "0", grouping is applied before padding. - if (comma && !zero) value = group(value, Infinity); - - // Compute the padding. - var length = valuePrefix.length + value.length + valueSuffix.length, - padding = length < width ? new Array(width - length + 1).join(fill) : ""; - - // If the fill character is "0", grouping is applied after padding. - if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; - - // Reconstruct the final output based on the desired alignment. - switch (align) { - case "<": value = valuePrefix + value + valueSuffix + padding; break; - case "=": value = valuePrefix + padding + value + valueSuffix; break; - case "^": value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length); break; - default: value = padding + valuePrefix + value + valueSuffix; break; - } - - return numerals(value); - } - - format.toString = function() { - return specifier + ""; - }; - - return format; - } - - function formatPrefix(specifier, value) { - var e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3, - k = Math.pow(10, -e), - f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier), {suffix: prefixes[8 + e / 3]}); - return function(value) { - return f(k * value); - }; - } - - return { - format: newFormat, - formatPrefix: formatPrefix - }; -} - -var locale$1; -var format; -var formatPrefix; - -defaultLocale$1({ - thousands: ",", - grouping: [3], - currency: ["$", ""] -}); - -function defaultLocale$1(definition) { - locale$1 = formatLocale$1(definition); - format = locale$1.format; - formatPrefix = locale$1.formatPrefix; - return locale$1; -} - -function precisionFixed(step) { - return Math.max(0, -exponent(Math.abs(step))); -} - -function precisionPrefix(step, value) { - return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step))); -} - -function precisionRound(step, max) { - step = Math.abs(step), max = Math.abs(max) - step; - return Math.max(0, exponent(max) - exponent(step)) + 1; -} - -function tickFormat(start, stop, count, specifier) { - var step = tickStep(start, stop, count), - precision; - specifier = formatSpecifier(specifier == null ? ",f" : specifier); - switch (specifier.type) { - case "s": { - var value = Math.max(Math.abs(start), Math.abs(stop)); - if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision; - return formatPrefix(specifier, value); - } - case "": - case "e": - case "g": - case "p": - case "r": { - if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e"); - break; - } - case "f": - case "%": { - if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2; - break; - } - } - return format(specifier); -} - -function linearish(scale) { - var domain = scale.domain; - - scale.ticks = function(count) { - var d = domain(); - return ticks(d[0], d[d.length - 1], count == null ? 10 : count); - }; - - scale.tickFormat = function(count, specifier) { - var d = domain(); - return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier); - }; - - scale.nice = function(count) { - if (count == null) count = 10; - - var d = domain(); - var i0 = 0; - var i1 = d.length - 1; - var start = d[i0]; - var stop = d[i1]; - var prestep; - var step; - var maxIter = 10; - - if (stop < start) { - step = start, start = stop, stop = step; - step = i0, i0 = i1, i1 = step; - } - - while (maxIter-- > 0) { - step = tickIncrement(start, stop, count); - if (step === prestep) { - d[i0] = start; - d[i1] = stop; - return domain(d); - } else if (step > 0) { - start = Math.floor(start / step) * step; - stop = Math.ceil(stop / step) * step; - } else if (step < 0) { - start = Math.ceil(start * step) / step; - stop = Math.floor(stop * step) / step; - } else { - break; - } - prestep = step; - } - - return scale; - }; - - return scale; -} - -function linear() { - var scale = continuous(); - - scale.copy = function() { - return copy$1(scale, linear()); - }; - - initRange.apply(scale, arguments); - - return linearish(scale); -} - -function identity(domain) { - var unknown; - - function scale(x) { - return x == null || isNaN(x = +x) ? unknown : x; - } - - scale.invert = scale; - - scale.domain = scale.range = function(_) { - return arguments.length ? (domain = Array.from(_, number$1), scale) : domain.slice(); - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - scale.copy = function() { - return identity(domain).unknown(unknown); - }; - - domain = arguments.length ? Array.from(domain, number$1) : [0, 1]; - - return linearish(scale); -} - -function nice(domain, interval) { - domain = domain.slice(); - - var i0 = 0, - i1 = domain.length - 1, - x0 = domain[i0], - x1 = domain[i1], - t; - - if (x1 < x0) { - t = i0, i0 = i1, i1 = t; - t = x0, x0 = x1, x1 = t; - } - - domain[i0] = interval.floor(x0); - domain[i1] = interval.ceil(x1); - return domain; -} - -function transformLog(x) { - return Math.log(x); -} - -function transformExp(x) { - return Math.exp(x); -} - -function transformLogn(x) { - return -Math.log(-x); -} - -function transformExpn(x) { - return -Math.exp(-x); -} - -function pow10(x) { - return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; -} - -function powp(base) { - return base === 10 ? pow10 - : base === Math.E ? Math.exp - : x => Math.pow(base, x); -} - -function logp(base) { - return base === Math.E ? Math.log - : base === 10 && Math.log10 - || base === 2 && Math.log2 - || (base = Math.log(base), x => Math.log(x) / base); -} - -function reflect(f) { - return (x, k) => -f(-x, k); -} - -function loggish(transform) { - const scale = transform(transformLog, transformExp); - const domain = scale.domain; - let base = 10; - let logs; - let pows; - - function rescale() { - logs = logp(base), pows = powp(base); - if (domain()[0] < 0) { - logs = reflect(logs), pows = reflect(pows); - transform(transformLogn, transformExpn); - } else { - transform(transformLog, transformExp); - } - return scale; - } - - scale.base = function(_) { - return arguments.length ? (base = +_, rescale()) : base; - }; - - scale.domain = function(_) { - return arguments.length ? (domain(_), rescale()) : domain(); - }; - - scale.ticks = count => { - const d = domain(); - let u = d[0]; - let v = d[d.length - 1]; - const r = v < u; - - if (r) ([u, v] = [v, u]); - - let i = logs(u); - let j = logs(v); - let k; - let t; - const n = count == null ? 10 : +count; - let z = []; - - if (!(base % 1) && j - i < n) { - i = Math.floor(i), j = Math.ceil(j); - if (u > 0) for (; i <= j; ++i) { - for (k = 1; k < base; ++k) { - t = i < 0 ? k / pows(-i) : k * pows(i); - if (t < u) continue; - if (t > v) break; - z.push(t); - } - } else for (; i <= j; ++i) { - for (k = base - 1; k >= 1; --k) { - t = i > 0 ? k / pows(-i) : k * pows(i); - if (t < u) continue; - if (t > v) break; - z.push(t); - } - } - if (z.length * 2 < n) z = ticks(u, v, n); - } else { - z = ticks(i, j, Math.min(j - i, n)).map(pows); - } - return r ? z.reverse() : z; - }; - - scale.tickFormat = (count, specifier) => { - if (count == null) count = 10; - if (specifier == null) specifier = base === 10 ? "s" : ","; - if (typeof specifier !== "function") { - if (!(base % 1) && (specifier = formatSpecifier(specifier)).precision == null) specifier.trim = true; - specifier = format(specifier); - } - if (count === Infinity) return specifier; - const k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate? - return d => { - let i = d / pows(Math.round(logs(d))); - if (i * base < base - 0.5) i *= base; - return i <= k ? specifier(d) : ""; - }; - }; - - scale.nice = () => { - return domain(nice(domain(), { - floor: x => pows(Math.floor(logs(x))), - ceil: x => pows(Math.ceil(logs(x))) - })); - }; - - return scale; -} - -function log() { - const scale = loggish(transformer$2()).domain([1, 10]); - scale.copy = () => copy$1(scale, log()).base(scale.base()); - initRange.apply(scale, arguments); - return scale; -} - -function transformSymlog(c) { - return function(x) { - return Math.sign(x) * Math.log1p(Math.abs(x / c)); - }; -} - -function transformSymexp(c) { - return function(x) { - return Math.sign(x) * Math.expm1(Math.abs(x)) * c; - }; -} - -function symlogish(transform) { - var c = 1, scale = transform(transformSymlog(c), transformSymexp(c)); - - scale.constant = function(_) { - return arguments.length ? transform(transformSymlog(c = +_), transformSymexp(c)) : c; - }; - - return linearish(scale); -} - -function symlog() { - var scale = symlogish(transformer$2()); - - scale.copy = function() { - return copy$1(scale, symlog()).constant(scale.constant()); - }; - - return initRange.apply(scale, arguments); -} - -function transformPow(exponent) { - return function(x) { - return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); - }; -} - -function transformSqrt(x) { - return x < 0 ? -Math.sqrt(-x) : Math.sqrt(x); -} - -function transformSquare(x) { - return x < 0 ? -x * x : x * x; -} - -function powish(transform) { - var scale = transform(identity$2, identity$2), - exponent = 1; - - function rescale() { - return exponent === 1 ? transform(identity$2, identity$2) - : exponent === 0.5 ? transform(transformSqrt, transformSquare) - : transform(transformPow(exponent), transformPow(1 / exponent)); - } - - scale.exponent = function(_) { - return arguments.length ? (exponent = +_, rescale()) : exponent; - }; - - return linearish(scale); -} - -function pow() { - var scale = powish(transformer$2()); - - scale.copy = function() { - return copy$1(scale, pow()).exponent(scale.exponent()); - }; - - initRange.apply(scale, arguments); - - return scale; -} - -function sqrt$1() { - return pow.apply(null, arguments).exponent(0.5); -} - -function square(x) { - return Math.sign(x) * x * x; -} - -function unsquare(x) { - return Math.sign(x) * Math.sqrt(Math.abs(x)); -} - -function radial() { - var squared = continuous(), - range = [0, 1], - round = false, - unknown; - - function scale(x) { - var y = unsquare(squared(x)); - return isNaN(y) ? unknown : round ? Math.round(y) : y; - } - - scale.invert = function(y) { - return squared.invert(square(y)); - }; - - scale.domain = function(_) { - return arguments.length ? (squared.domain(_), scale) : squared.domain(); - }; - - scale.range = function(_) { - return arguments.length ? (squared.range((range = Array.from(_, number$1)).map(square)), scale) : range.slice(); - }; - - scale.rangeRound = function(_) { - return scale.range(_).round(true); - }; - - scale.round = function(_) { - return arguments.length ? (round = !!_, scale) : round; - }; - - scale.clamp = function(_) { - return arguments.length ? (squared.clamp(_), scale) : squared.clamp(); - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - scale.copy = function() { - return radial(squared.domain(), range) - .round(round) - .clamp(squared.clamp()) - .unknown(unknown); - }; - - initRange.apply(scale, arguments); - - return linearish(scale); -} - -function quantile() { - var domain = [], - range = [], - thresholds = [], - unknown; - - function rescale() { - var i = 0, n = Math.max(1, range.length); - thresholds = new Array(n - 1); - while (++i < n) thresholds[i - 1] = quantileSorted(domain, i / n); - return scale; - } - - function scale(x) { - return x == null || isNaN(x = +x) ? unknown : range[bisectRight(thresholds, x)]; - } - - scale.invertExtent = function(y) { - var i = range.indexOf(y); - return i < 0 ? [NaN, NaN] : [ - i > 0 ? thresholds[i - 1] : domain[0], - i < thresholds.length ? thresholds[i] : domain[domain.length - 1] - ]; - }; - - scale.domain = function(_) { - if (!arguments.length) return domain.slice(); - domain = []; - for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d); - domain.sort(ascending); - return rescale(); - }; - - scale.range = function(_) { - return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - scale.quantiles = function() { - return thresholds.slice(); - }; - - scale.copy = function() { - return quantile() - .domain(domain) - .range(range) - .unknown(unknown); - }; - - return initRange.apply(scale, arguments); -} - -function quantize() { - var x0 = 0, - x1 = 1, - n = 1, - domain = [0.5], - range = [0, 1], - unknown; - - function scale(x) { - return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown; - } - - function rescale() { - var i = -1; - domain = new Array(n); - while (++i < n) domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1); - return scale; - } - - scale.domain = function(_) { - return arguments.length ? ([x0, x1] = _, x0 = +x0, x1 = +x1, rescale()) : [x0, x1]; - }; - - scale.range = function(_) { - return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice(); - }; - - scale.invertExtent = function(y) { - var i = range.indexOf(y); - return i < 0 ? [NaN, NaN] - : i < 1 ? [x0, domain[0]] - : i >= n ? [domain[n - 1], x1] - : [domain[i - 1], domain[i]]; - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : scale; - }; - - scale.thresholds = function() { - return domain.slice(); - }; - - scale.copy = function() { - return quantize() - .domain([x0, x1]) - .range(range) - .unknown(unknown); - }; - - return initRange.apply(linearish(scale), arguments); -} - -function threshold() { - var domain = [0.5], - range = [0, 1], - unknown, - n = 1; - - function scale(x) { - return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown; - } - - scale.domain = function(_) { - return arguments.length ? (domain = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : domain.slice(); - }; - - scale.range = function(_) { - return arguments.length ? (range = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : range.slice(); - }; - - scale.invertExtent = function(y) { - var i = range.indexOf(y); - return [domain[i - 1], domain[i]]; - }; - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - scale.copy = function() { - return threshold() - .domain(domain) - .range(range) - .unknown(unknown); - }; - - return initRange.apply(scale, arguments); -} - -const t0 = new Date, t1 = new Date; - -function timeInterval(floori, offseti, count, field) { - - function interval(date) { - return floori(date = arguments.length === 0 ? new Date : new Date(+date)), date; - } - - interval.floor = (date) => { - return floori(date = new Date(+date)), date; - }; - - interval.ceil = (date) => { - return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date; - }; - - interval.round = (date) => { - const d0 = interval(date), d1 = interval.ceil(date); - return date - d0 < d1 - date ? d0 : d1; - }; - - interval.offset = (date, step) => { - return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date; - }; - - interval.range = (start, stop, step) => { - const range = []; - start = interval.ceil(start); - step = step == null ? 1 : Math.floor(step); - if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date - let previous; - do range.push(previous = new Date(+start)), offseti(start, step), floori(start); - while (previous < start && start < stop); - return range; - }; - - interval.filter = (test) => { - return timeInterval((date) => { - if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1); - }, (date, step) => { - if (date >= date) { - if (step < 0) while (++step <= 0) { - while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty - } else while (--step >= 0) { - while (offseti(date, 1), !test(date)) {} // eslint-disable-line no-empty - } - } - }); - }; - - if (count) { - interval.count = (start, end) => { - t0.setTime(+start), t1.setTime(+end); - floori(t0), floori(t1); - return Math.floor(count(t0, t1)); - }; - - interval.every = (step) => { - step = Math.floor(step); - return !isFinite(step) || !(step > 0) ? null - : !(step > 1) ? interval - : interval.filter(field - ? (d) => field(d) % step === 0 - : (d) => interval.count(0, d) % step === 0); - }; - } - - return interval; -} - -const millisecond = timeInterval(() => { - // noop -}, (date, step) => { - date.setTime(+date + step); -}, (start, end) => { - return end - start; -}); - -// An optimized implementation for this simple case. -millisecond.every = (k) => { - k = Math.floor(k); - if (!isFinite(k) || !(k > 0)) return null; - if (!(k > 1)) return millisecond; - return timeInterval((date) => { - date.setTime(Math.floor(date / k) * k); - }, (date, step) => { - date.setTime(+date + step * k); - }, (start, end) => { - return (end - start) / k; - }); -}; - -millisecond.range; - -const durationSecond = 1000; -const durationMinute = durationSecond * 60; -const durationHour = durationMinute * 60; -const durationDay = durationHour * 24; -const durationWeek = durationDay * 7; -const durationMonth = durationDay * 30; -const durationYear = durationDay * 365; - -const second = timeInterval((date) => { - date.setTime(date - date.getMilliseconds()); -}, (date, step) => { - date.setTime(+date + step * durationSecond); -}, (start, end) => { - return (end - start) / durationSecond; -}, (date) => { - return date.getUTCSeconds(); -}); - -second.range; - -const timeMinute = timeInterval((date) => { - date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond); -}, (date, step) => { - date.setTime(+date + step * durationMinute); -}, (start, end) => { - return (end - start) / durationMinute; -}, (date) => { - return date.getMinutes(); -}); - -timeMinute.range; - -const utcMinute = timeInterval((date) => { - date.setUTCSeconds(0, 0); -}, (date, step) => { - date.setTime(+date + step * durationMinute); -}, (start, end) => { - return (end - start) / durationMinute; -}, (date) => { - return date.getUTCMinutes(); -}); - -utcMinute.range; - -const timeHour = timeInterval((date) => { - date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute); -}, (date, step) => { - date.setTime(+date + step * durationHour); -}, (start, end) => { - return (end - start) / durationHour; -}, (date) => { - return date.getHours(); -}); - -timeHour.range; - -const utcHour = timeInterval((date) => { - date.setUTCMinutes(0, 0, 0); -}, (date, step) => { - date.setTime(+date + step * durationHour); -}, (start, end) => { - return (end - start) / durationHour; -}, (date) => { - return date.getUTCHours(); -}); - -utcHour.range; - -const timeDay = timeInterval( - date => date.setHours(0, 0, 0, 0), - (date, step) => date.setDate(date.getDate() + step), - (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay, - date => date.getDate() - 1 -); - -timeDay.range; - -const utcDay = timeInterval((date) => { - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCDate(date.getUTCDate() + step); -}, (start, end) => { - return (end - start) / durationDay; -}, (date) => { - return date.getUTCDate() - 1; -}); - -utcDay.range; - -const unixDay = timeInterval((date) => { - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCDate(date.getUTCDate() + step); -}, (start, end) => { - return (end - start) / durationDay; -}, (date) => { - return Math.floor(date / durationDay); -}); - -unixDay.range; - -function timeWeekday(i) { - return timeInterval((date) => { - date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); - date.setHours(0, 0, 0, 0); - }, (date, step) => { - date.setDate(date.getDate() + step * 7); - }, (start, end) => { - return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek; - }); -} - -const timeSunday = timeWeekday(0); -const timeMonday = timeWeekday(1); -const timeTuesday = timeWeekday(2); -const timeWednesday = timeWeekday(3); -const timeThursday = timeWeekday(4); -const timeFriday = timeWeekday(5); -const timeSaturday = timeWeekday(6); - -timeSunday.range; -timeMonday.range; -timeTuesday.range; -timeWednesday.range; -timeThursday.range; -timeFriday.range; -timeSaturday.range; - -function utcWeekday(i) { - return timeInterval((date) => { - date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); - date.setUTCHours(0, 0, 0, 0); - }, (date, step) => { - date.setUTCDate(date.getUTCDate() + step * 7); - }, (start, end) => { - return (end - start) / durationWeek; - }); -} - -const utcSunday = utcWeekday(0); -const utcMonday = utcWeekday(1); -const utcTuesday = utcWeekday(2); -const utcWednesday = utcWeekday(3); -const utcThursday = utcWeekday(4); -const utcFriday = utcWeekday(5); -const utcSaturday = utcWeekday(6); - -utcSunday.range; -utcMonday.range; -utcTuesday.range; -utcWednesday.range; -utcThursday.range; -utcFriday.range; -utcSaturday.range; - -const timeMonth = timeInterval((date) => { - date.setDate(1); - date.setHours(0, 0, 0, 0); -}, (date, step) => { - date.setMonth(date.getMonth() + step); -}, (start, end) => { - return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; -}, (date) => { - return date.getMonth(); -}); - -timeMonth.range; - -const utcMonth = timeInterval((date) => { - date.setUTCDate(1); - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCMonth(date.getUTCMonth() + step); -}, (start, end) => { - return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; -}, (date) => { - return date.getUTCMonth(); -}); - -utcMonth.range; - -const timeYear = timeInterval((date) => { - date.setMonth(0, 1); - date.setHours(0, 0, 0, 0); -}, (date, step) => { - date.setFullYear(date.getFullYear() + step); -}, (start, end) => { - return end.getFullYear() - start.getFullYear(); -}, (date) => { - return date.getFullYear(); -}); - -// An optimized implementation for this simple case. -timeYear.every = (k) => { - return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { - date.setFullYear(Math.floor(date.getFullYear() / k) * k); - date.setMonth(0, 1); - date.setHours(0, 0, 0, 0); - }, (date, step) => { - date.setFullYear(date.getFullYear() + step * k); - }); -}; - -timeYear.range; - -const utcYear = timeInterval((date) => { - date.setUTCMonth(0, 1); - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCFullYear(date.getUTCFullYear() + step); -}, (start, end) => { - return end.getUTCFullYear() - start.getUTCFullYear(); -}, (date) => { - return date.getUTCFullYear(); -}); - -// An optimized implementation for this simple case. -utcYear.every = (k) => { - return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { - date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); - date.setUTCMonth(0, 1); - date.setUTCHours(0, 0, 0, 0); - }, (date, step) => { - date.setUTCFullYear(date.getUTCFullYear() + step * k); - }); -}; - -utcYear.range; - -function ticker(year, month, week, day, hour, minute) { - - const tickIntervals = [ - [second, 1, durationSecond], - [second, 5, 5 * durationSecond], - [second, 15, 15 * durationSecond], - [second, 30, 30 * durationSecond], - [minute, 1, durationMinute], - [minute, 5, 5 * durationMinute], - [minute, 15, 15 * durationMinute], - [minute, 30, 30 * durationMinute], - [ hour, 1, durationHour ], - [ hour, 3, 3 * durationHour ], - [ hour, 6, 6 * durationHour ], - [ hour, 12, 12 * durationHour ], - [ day, 1, durationDay ], - [ day, 2, 2 * durationDay ], - [ week, 1, durationWeek ], - [ month, 1, durationMonth ], - [ month, 3, 3 * durationMonth ], - [ year, 1, durationYear ] - ]; - - function ticks(start, stop, count) { - const reverse = stop < start; - if (reverse) [start, stop] = [stop, start]; - const interval = count && typeof count.range === "function" ? count : tickInterval(start, stop, count); - const ticks = interval ? interval.range(start, +stop + 1) : []; // inclusive stop - return reverse ? ticks.reverse() : ticks; - } - - function tickInterval(start, stop, count) { - const target = Math.abs(stop - start) / count; - const i = bisector(([,, step]) => step).right(tickIntervals, target); - if (i === tickIntervals.length) return year.every(tickStep(start / durationYear, stop / durationYear, count)); - if (i === 0) return millisecond.every(Math.max(tickStep(start, stop, count), 1)); - const [t, step] = tickIntervals[target / tickIntervals[i - 1][2] < tickIntervals[i][2] / target ? i - 1 : i]; - return t.every(step); - } - - return [ticks, tickInterval]; -} - -const [utcTicks, utcTickInterval] = ticker(utcYear, utcMonth, utcSunday, unixDay, utcHour, utcMinute); -const [timeTicks, timeTickInterval] = ticker(timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute); - -function localDate(d) { - if (0 <= d.y && d.y < 100) { - var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); - date.setFullYear(d.y); - return date; - } - return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); -} - -function utcDate(d) { - if (0 <= d.y && d.y < 100) { - var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); - date.setUTCFullYear(d.y); - return date; - } - return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); -} - -function newDate(y, m, d) { - return {y: y, m: m, d: d, H: 0, M: 0, S: 0, L: 0}; -} - -function formatLocale(locale) { - var locale_dateTime = locale.dateTime, - locale_date = locale.date, - locale_time = locale.time, - locale_periods = locale.periods, - locale_weekdays = locale.days, - locale_shortWeekdays = locale.shortDays, - locale_months = locale.months, - locale_shortMonths = locale.shortMonths; - - var periodRe = formatRe(locale_periods), - periodLookup = formatLookup(locale_periods), - weekdayRe = formatRe(locale_weekdays), - weekdayLookup = formatLookup(locale_weekdays), - shortWeekdayRe = formatRe(locale_shortWeekdays), - shortWeekdayLookup = formatLookup(locale_shortWeekdays), - monthRe = formatRe(locale_months), - monthLookup = formatLookup(locale_months), - shortMonthRe = formatRe(locale_shortMonths), - shortMonthLookup = formatLookup(locale_shortMonths); - - var formats = { - "a": formatShortWeekday, - "A": formatWeekday, - "b": formatShortMonth, - "B": formatMonth, - "c": null, - "d": formatDayOfMonth, - "e": formatDayOfMonth, - "f": formatMicroseconds, - "g": formatYearISO, - "G": formatFullYearISO, - "H": formatHour24, - "I": formatHour12, - "j": formatDayOfYear, - "L": formatMilliseconds, - "m": formatMonthNumber, - "M": formatMinutes, - "p": formatPeriod, - "q": formatQuarter, - "Q": formatUnixTimestamp, - "s": formatUnixTimestampSeconds, - "S": formatSeconds, - "u": formatWeekdayNumberMonday, - "U": formatWeekNumberSunday, - "V": formatWeekNumberISO, - "w": formatWeekdayNumberSunday, - "W": formatWeekNumberMonday, - "x": null, - "X": null, - "y": formatYear, - "Y": formatFullYear, - "Z": formatZone, - "%": formatLiteralPercent - }; - - var utcFormats = { - "a": formatUTCShortWeekday, - "A": formatUTCWeekday, - "b": formatUTCShortMonth, - "B": formatUTCMonth, - "c": null, - "d": formatUTCDayOfMonth, - "e": formatUTCDayOfMonth, - "f": formatUTCMicroseconds, - "g": formatUTCYearISO, - "G": formatUTCFullYearISO, - "H": formatUTCHour24, - "I": formatUTCHour12, - "j": formatUTCDayOfYear, - "L": formatUTCMilliseconds, - "m": formatUTCMonthNumber, - "M": formatUTCMinutes, - "p": formatUTCPeriod, - "q": formatUTCQuarter, - "Q": formatUnixTimestamp, - "s": formatUnixTimestampSeconds, - "S": formatUTCSeconds, - "u": formatUTCWeekdayNumberMonday, - "U": formatUTCWeekNumberSunday, - "V": formatUTCWeekNumberISO, - "w": formatUTCWeekdayNumberSunday, - "W": formatUTCWeekNumberMonday, - "x": null, - "X": null, - "y": formatUTCYear, - "Y": formatUTCFullYear, - "Z": formatUTCZone, - "%": formatLiteralPercent - }; - - var parses = { - "a": parseShortWeekday, - "A": parseWeekday, - "b": parseShortMonth, - "B": parseMonth, - "c": parseLocaleDateTime, - "d": parseDayOfMonth, - "e": parseDayOfMonth, - "f": parseMicroseconds, - "g": parseYear, - "G": parseFullYear, - "H": parseHour24, - "I": parseHour24, - "j": parseDayOfYear, - "L": parseMilliseconds, - "m": parseMonthNumber, - "M": parseMinutes, - "p": parsePeriod, - "q": parseQuarter, - "Q": parseUnixTimestamp, - "s": parseUnixTimestampSeconds, - "S": parseSeconds, - "u": parseWeekdayNumberMonday, - "U": parseWeekNumberSunday, - "V": parseWeekNumberISO, - "w": parseWeekdayNumberSunday, - "W": parseWeekNumberMonday, - "x": parseLocaleDate, - "X": parseLocaleTime, - "y": parseYear, - "Y": parseFullYear, - "Z": parseZone, - "%": parseLiteralPercent - }; - - // These recursive directive definitions must be deferred. - formats.x = newFormat(locale_date, formats); - formats.X = newFormat(locale_time, formats); - formats.c = newFormat(locale_dateTime, formats); - utcFormats.x = newFormat(locale_date, utcFormats); - utcFormats.X = newFormat(locale_time, utcFormats); - utcFormats.c = newFormat(locale_dateTime, utcFormats); - - function newFormat(specifier, formats) { - return function(date) { - var string = [], - i = -1, - j = 0, - n = specifier.length, - c, - pad, - format; - - if (!(date instanceof Date)) date = new Date(+date); - - while (++i < n) { - if (specifier.charCodeAt(i) === 37) { - string.push(specifier.slice(j, i)); - if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i); - else pad = c === "e" ? " " : "0"; - if (format = formats[c]) c = format(date, pad); - string.push(c); - j = i + 1; - } - } - - string.push(specifier.slice(j, i)); - return string.join(""); - }; - } - - function newParse(specifier, Z) { - return function(string) { - var d = newDate(1900, undefined, 1), - i = parseSpecifier(d, specifier, string += "", 0), - week, day; - if (i != string.length) return null; - - // If a UNIX timestamp is specified, return it. - if ("Q" in d) return new Date(d.Q); - if ("s" in d) return new Date(d.s * 1000 + ("L" in d ? d.L : 0)); - - // If this is utcParse, never use the local timezone. - if (Z && !("Z" in d)) d.Z = 0; - - // The am-pm flag is 0 for AM, and 1 for PM. - if ("p" in d) d.H = d.H % 12 + d.p * 12; - - // If the month was not specified, inherit from the quarter. - if (d.m === undefined) d.m = "q" in d ? d.q : 0; - - // Convert day-of-week and week-of-year to day-of-year. - if ("V" in d) { - if (d.V < 1 || d.V > 53) return null; - if (!("w" in d)) d.w = 1; - if ("Z" in d) { - week = utcDate(newDate(d.y, 0, 1)), day = week.getUTCDay(); - week = day > 4 || day === 0 ? utcMonday.ceil(week) : utcMonday(week); - week = utcDay.offset(week, (d.V - 1) * 7); - d.y = week.getUTCFullYear(); - d.m = week.getUTCMonth(); - d.d = week.getUTCDate() + (d.w + 6) % 7; - } else { - week = localDate(newDate(d.y, 0, 1)), day = week.getDay(); - week = day > 4 || day === 0 ? timeMonday.ceil(week) : timeMonday(week); - week = timeDay.offset(week, (d.V - 1) * 7); - d.y = week.getFullYear(); - d.m = week.getMonth(); - d.d = week.getDate() + (d.w + 6) % 7; - } - } else if ("W" in d || "U" in d) { - if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0; - day = "Z" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay(); - d.m = 0; - d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day + 5) % 7 : d.w + d.U * 7 - (day + 6) % 7; - } - - // If a time zone is specified, all fields are interpreted as UTC and then - // offset according to the specified time zone. - if ("Z" in d) { - d.H += d.Z / 100 | 0; - d.M += d.Z % 100; - return utcDate(d); - } - - // Otherwise, all fields are in local time. - return localDate(d); - }; - } - - function parseSpecifier(d, specifier, string, j) { - var i = 0, - n = specifier.length, - m = string.length, - c, - parse; - - while (i < n) { - if (j >= m) return -1; - c = specifier.charCodeAt(i++); - if (c === 37) { - c = specifier.charAt(i++); - parse = parses[c in pads ? specifier.charAt(i++) : c]; - if (!parse || ((j = parse(d, string, j)) < 0)) return -1; - } else if (c != string.charCodeAt(j++)) { - return -1; - } - } - - return j; - } - - function parsePeriod(d, string, i) { - var n = periodRe.exec(string.slice(i)); - return n ? (d.p = periodLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseShortWeekday(d, string, i) { - var n = shortWeekdayRe.exec(string.slice(i)); - return n ? (d.w = shortWeekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseWeekday(d, string, i) { - var n = weekdayRe.exec(string.slice(i)); - return n ? (d.w = weekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseShortMonth(d, string, i) { - var n = shortMonthRe.exec(string.slice(i)); - return n ? (d.m = shortMonthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseMonth(d, string, i) { - var n = monthRe.exec(string.slice(i)); - return n ? (d.m = monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; - } - - function parseLocaleDateTime(d, string, i) { - return parseSpecifier(d, locale_dateTime, string, i); - } - - function parseLocaleDate(d, string, i) { - return parseSpecifier(d, locale_date, string, i); - } - - function parseLocaleTime(d, string, i) { - return parseSpecifier(d, locale_time, string, i); - } - - function formatShortWeekday(d) { - return locale_shortWeekdays[d.getDay()]; - } - - function formatWeekday(d) { - return locale_weekdays[d.getDay()]; - } - - function formatShortMonth(d) { - return locale_shortMonths[d.getMonth()]; - } - - function formatMonth(d) { - return locale_months[d.getMonth()]; - } - - function formatPeriod(d) { - return locale_periods[+(d.getHours() >= 12)]; - } - - function formatQuarter(d) { - return 1 + ~~(d.getMonth() / 3); - } - - function formatUTCShortWeekday(d) { - return locale_shortWeekdays[d.getUTCDay()]; - } - - function formatUTCWeekday(d) { - return locale_weekdays[d.getUTCDay()]; - } - - function formatUTCShortMonth(d) { - return locale_shortMonths[d.getUTCMonth()]; - } - - function formatUTCMonth(d) { - return locale_months[d.getUTCMonth()]; - } - - function formatUTCPeriod(d) { - return locale_periods[+(d.getUTCHours() >= 12)]; - } - - function formatUTCQuarter(d) { - return 1 + ~~(d.getUTCMonth() / 3); - } - - return { - format: function(specifier) { - var f = newFormat(specifier += "", formats); - f.toString = function() { return specifier; }; - return f; - }, - parse: function(specifier) { - var p = newParse(specifier += "", false); - p.toString = function() { return specifier; }; - return p; - }, - utcFormat: function(specifier) { - var f = newFormat(specifier += "", utcFormats); - f.toString = function() { return specifier; }; - return f; - }, - utcParse: function(specifier) { - var p = newParse(specifier += "", true); - p.toString = function() { return specifier; }; - return p; - } - }; -} - -var pads = {"-": "", "_": " ", "0": "0"}, - numberRe = /^\s*\d+/, // note: ignores next directive - percentRe = /^%/, - requoteRe = /[\\^$*+?|[\]().{}]/g; - -function pad(value, fill, width) { - var sign = value < 0 ? "-" : "", - string = (sign ? -value : value) + "", - length = string.length; - return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); -} - -function requote(s) { - return s.replace(requoteRe, "\\$&"); -} - -function formatRe(names) { - return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); -} - -function formatLookup(names) { - return new Map(names.map((name, i) => [name.toLowerCase(), i])); -} - -function parseWeekdayNumberSunday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 1)); - return n ? (d.w = +n[0], i + n[0].length) : -1; -} - -function parseWeekdayNumberMonday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 1)); - return n ? (d.u = +n[0], i + n[0].length) : -1; -} - -function parseWeekNumberSunday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.U = +n[0], i + n[0].length) : -1; -} - -function parseWeekNumberISO(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.V = +n[0], i + n[0].length) : -1; -} - -function parseWeekNumberMonday(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.W = +n[0], i + n[0].length) : -1; -} - -function parseFullYear(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 4)); - return n ? (d.y = +n[0], i + n[0].length) : -1; -} - -function parseYear(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1; -} - -function parseZone(d, string, i) { - var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6)); - return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1; -} - -function parseQuarter(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 1)); - return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1; -} - -function parseMonthNumber(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.m = n[0] - 1, i + n[0].length) : -1; -} - -function parseDayOfMonth(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.d = +n[0], i + n[0].length) : -1; -} - -function parseDayOfYear(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 3)); - return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; -} - -function parseHour24(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.H = +n[0], i + n[0].length) : -1; -} - -function parseMinutes(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.M = +n[0], i + n[0].length) : -1; -} - -function parseSeconds(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 2)); - return n ? (d.S = +n[0], i + n[0].length) : -1; -} - -function parseMilliseconds(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 3)); - return n ? (d.L = +n[0], i + n[0].length) : -1; -} - -function parseMicroseconds(d, string, i) { - var n = numberRe.exec(string.slice(i, i + 6)); - return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1; -} - -function parseLiteralPercent(d, string, i) { - var n = percentRe.exec(string.slice(i, i + 1)); - return n ? i + n[0].length : -1; -} - -function parseUnixTimestamp(d, string, i) { - var n = numberRe.exec(string.slice(i)); - return n ? (d.Q = +n[0], i + n[0].length) : -1; -} - -function parseUnixTimestampSeconds(d, string, i) { - var n = numberRe.exec(string.slice(i)); - return n ? (d.s = +n[0], i + n[0].length) : -1; -} - -function formatDayOfMonth(d, p) { - return pad(d.getDate(), p, 2); -} - -function formatHour24(d, p) { - return pad(d.getHours(), p, 2); -} - -function formatHour12(d, p) { - return pad(d.getHours() % 12 || 12, p, 2); -} - -function formatDayOfYear(d, p) { - return pad(1 + timeDay.count(timeYear(d), d), p, 3); -} - -function formatMilliseconds(d, p) { - return pad(d.getMilliseconds(), p, 3); -} - -function formatMicroseconds(d, p) { - return formatMilliseconds(d, p) + "000"; -} - -function formatMonthNumber(d, p) { - return pad(d.getMonth() + 1, p, 2); -} - -function formatMinutes(d, p) { - return pad(d.getMinutes(), p, 2); -} - -function formatSeconds(d, p) { - return pad(d.getSeconds(), p, 2); -} - -function formatWeekdayNumberMonday(d) { - var day = d.getDay(); - return day === 0 ? 7 : day; -} - -function formatWeekNumberSunday(d, p) { - return pad(timeSunday.count(timeYear(d) - 1, d), p, 2); -} - -function dISO(d) { - var day = d.getDay(); - return (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d); -} - -function formatWeekNumberISO(d, p) { - d = dISO(d); - return pad(timeThursday.count(timeYear(d), d) + (timeYear(d).getDay() === 4), p, 2); -} - -function formatWeekdayNumberSunday(d) { - return d.getDay(); -} - -function formatWeekNumberMonday(d, p) { - return pad(timeMonday.count(timeYear(d) - 1, d), p, 2); -} - -function formatYear(d, p) { - return pad(d.getFullYear() % 100, p, 2); -} - -function formatYearISO(d, p) { - d = dISO(d); - return pad(d.getFullYear() % 100, p, 2); -} - -function formatFullYear(d, p) { - return pad(d.getFullYear() % 10000, p, 4); -} - -function formatFullYearISO(d, p) { - var day = d.getDay(); - d = (day >= 4 || day === 0) ? timeThursday(d) : timeThursday.ceil(d); - return pad(d.getFullYear() % 10000, p, 4); -} - -function formatZone(d) { - var z = d.getTimezoneOffset(); - return (z > 0 ? "-" : (z *= -1, "+")) - + pad(z / 60 | 0, "0", 2) - + pad(z % 60, "0", 2); -} - -function formatUTCDayOfMonth(d, p) { - return pad(d.getUTCDate(), p, 2); -} - -function formatUTCHour24(d, p) { - return pad(d.getUTCHours(), p, 2); -} - -function formatUTCHour12(d, p) { - return pad(d.getUTCHours() % 12 || 12, p, 2); -} - -function formatUTCDayOfYear(d, p) { - return pad(1 + utcDay.count(utcYear(d), d), p, 3); -} - -function formatUTCMilliseconds(d, p) { - return pad(d.getUTCMilliseconds(), p, 3); -} - -function formatUTCMicroseconds(d, p) { - return formatUTCMilliseconds(d, p) + "000"; -} - -function formatUTCMonthNumber(d, p) { - return pad(d.getUTCMonth() + 1, p, 2); -} - -function formatUTCMinutes(d, p) { - return pad(d.getUTCMinutes(), p, 2); -} - -function formatUTCSeconds(d, p) { - return pad(d.getUTCSeconds(), p, 2); -} - -function formatUTCWeekdayNumberMonday(d) { - var dow = d.getUTCDay(); - return dow === 0 ? 7 : dow; -} - -function formatUTCWeekNumberSunday(d, p) { - return pad(utcSunday.count(utcYear(d) - 1, d), p, 2); -} - -function UTCdISO(d) { - var day = d.getUTCDay(); - return (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); -} - -function formatUTCWeekNumberISO(d, p) { - d = UTCdISO(d); - return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2); -} - -function formatUTCWeekdayNumberSunday(d) { - return d.getUTCDay(); -} - -function formatUTCWeekNumberMonday(d, p) { - return pad(utcMonday.count(utcYear(d) - 1, d), p, 2); -} - -function formatUTCYear(d, p) { - return pad(d.getUTCFullYear() % 100, p, 2); -} - -function formatUTCYearISO(d, p) { - d = UTCdISO(d); - return pad(d.getUTCFullYear() % 100, p, 2); -} - -function formatUTCFullYear(d, p) { - return pad(d.getUTCFullYear() % 10000, p, 4); -} - -function formatUTCFullYearISO(d, p) { - var day = d.getUTCDay(); - d = (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); - return pad(d.getUTCFullYear() % 10000, p, 4); -} - -function formatUTCZone() { - return "+0000"; -} - -function formatLiteralPercent() { - return "%"; -} - -function formatUnixTimestamp(d) { - return +d; -} - -function formatUnixTimestampSeconds(d) { - return Math.floor(+d / 1000); -} - -var locale; -var timeFormat; -var timeParse; -var utcFormat; -var utcParse; - -defaultLocale({ - dateTime: "%x, %X", - date: "%-m/%-d/%Y", - time: "%-I:%M:%S %p", - periods: ["AM", "PM"], - days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], - shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] -}); - -function defaultLocale(definition) { - locale = formatLocale(definition); - timeFormat = locale.format; - timeParse = locale.parse; - utcFormat = locale.utcFormat; - utcParse = locale.utcParse; - return locale; -} - -var isoSpecifier = "%Y-%m-%dT%H:%M:%S.%LZ"; - -function formatIsoNative(date) { - return date.toISOString(); -} - -var formatIso = Date.prototype.toISOString - ? formatIsoNative - : utcFormat(isoSpecifier); - -function parseIsoNative(string) { - var date = new Date(string); - return isNaN(date) ? null : date; -} - -var parseIso = +new Date("2000-01-01T00:00:00.000Z") - ? parseIsoNative - : utcParse(isoSpecifier); - -function date(t) { - return new Date(t); -} - -function number(t) { - return t instanceof Date ? +t : +new Date(+t); -} - -function calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format) { - var scale = continuous(), - invert = scale.invert, - domain = scale.domain; - - var formatMillisecond = format(".%L"), - formatSecond = format(":%S"), - formatMinute = format("%I:%M"), - formatHour = format("%I %p"), - formatDay = format("%a %d"), - formatWeek = format("%b %d"), - formatMonth = format("%B"), - formatYear = format("%Y"); - - function tickFormat(date) { - return (second(date) < date ? formatMillisecond - : minute(date) < date ? formatSecond - : hour(date) < date ? formatMinute - : day(date) < date ? formatHour - : month(date) < date ? (week(date) < date ? formatDay : formatWeek) - : year(date) < date ? formatMonth - : formatYear)(date); - } - - scale.invert = function(y) { - return new Date(invert(y)); - }; - - scale.domain = function(_) { - return arguments.length ? domain(Array.from(_, number)) : domain().map(date); - }; - - scale.ticks = function(interval) { - var d = domain(); - return ticks(d[0], d[d.length - 1], interval == null ? 10 : interval); - }; - - scale.tickFormat = function(count, specifier) { - return specifier == null ? tickFormat : format(specifier); - }; - - scale.nice = function(interval) { - var d = domain(); - if (!interval || typeof interval.range !== "function") interval = tickInterval(d[0], d[d.length - 1], interval == null ? 10 : interval); - return interval ? domain(nice(d, interval)) : scale; - }; - - scale.copy = function() { - return copy$1(scale, calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format)); - }; - - return scale; -} - -function time() { - return initRange.apply(calendar(timeTicks, timeTickInterval, timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute, second, timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]), arguments); -} - -function utcTime() { - return initRange.apply(calendar(utcTicks, utcTickInterval, utcYear, utcMonth, utcSunday, utcDay, utcHour, utcMinute, second, utcFormat).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]), arguments); -} - -function transformer$1() { - var x0 = 0, - x1 = 1, - t0, - t1, - k10, - transform, - interpolator = identity$2, - clamp = false, - unknown; - - function scale(x) { - return x == null || isNaN(x = +x) ? unknown : interpolator(k10 === 0 ? 0.5 : (x = (transform(x) - t0) * k10, clamp ? Math.max(0, Math.min(1, x)) : x)); - } - - scale.domain = function(_) { - return arguments.length ? ([x0, x1] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1]; - }; - - scale.clamp = function(_) { - return arguments.length ? (clamp = !!_, scale) : clamp; - }; - - scale.interpolator = function(_) { - return arguments.length ? (interpolator = _, scale) : interpolator; - }; - - function range(interpolate) { - return function(_) { - var r0, r1; - return arguments.length ? ([r0, r1] = _, interpolator = interpolate(r0, r1), scale) : [interpolator(0), interpolator(1)]; - }; - } - - scale.range = range(interpolate$1); - - scale.rangeRound = range(interpolateRound); - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - return function(t) { - transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0); - return scale; - }; -} - -function copy(source, target) { - return target - .domain(source.domain()) - .interpolator(source.interpolator()) - .clamp(source.clamp()) - .unknown(source.unknown()); -} - -function sequential() { - var scale = linearish(transformer$1()(identity$2)); - - scale.copy = function() { - return copy(scale, sequential()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function sequentialLog() { - var scale = loggish(transformer$1()).domain([1, 10]); - - scale.copy = function() { - return copy(scale, sequentialLog()).base(scale.base()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function sequentialSymlog() { - var scale = symlogish(transformer$1()); - - scale.copy = function() { - return copy(scale, sequentialSymlog()).constant(scale.constant()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function sequentialPow() { - var scale = powish(transformer$1()); - - scale.copy = function() { - return copy(scale, sequentialPow()).exponent(scale.exponent()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function sequentialSqrt() { - return sequentialPow.apply(null, arguments).exponent(0.5); -} - -function sequentialQuantile() { - var domain = [], - interpolator = identity$2; - - function scale(x) { - if (x != null && !isNaN(x = +x)) return interpolator((bisectRight(domain, x, 1) - 1) / (domain.length - 1)); - } - - scale.domain = function(_) { - if (!arguments.length) return domain.slice(); - domain = []; - for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d); - domain.sort(ascending); - return scale; - }; - - scale.interpolator = function(_) { - return arguments.length ? (interpolator = _, scale) : interpolator; - }; - - scale.range = function() { - return domain.map((d, i) => interpolator(i / (domain.length - 1))); - }; - - scale.quantiles = function(n) { - return Array.from({length: n + 1}, (_, i) => quantile$1(domain, i / n)); - }; - - scale.copy = function() { - return sequentialQuantile(interpolator).domain(domain); - }; - - return initInterpolator.apply(scale, arguments); -} - -function transformer() { - var x0 = 0, - x1 = 0.5, - x2 = 1, - s = 1, - t0, - t1, - t2, - k10, - k21, - interpolator = identity$2, - transform, - clamp = false, - unknown; - - function scale(x) { - return isNaN(x = +x) ? unknown : (x = 0.5 + ((x = +transform(x)) - t1) * (s * x < s * t1 ? k10 : k21), interpolator(clamp ? Math.max(0, Math.min(1, x)) : x)); - } - - scale.domain = function(_) { - return arguments.length ? ([x0, x1, x2] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), t2 = transform(x2 = +x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1, scale) : [x0, x1, x2]; - }; - - scale.clamp = function(_) { - return arguments.length ? (clamp = !!_, scale) : clamp; - }; - - scale.interpolator = function(_) { - return arguments.length ? (interpolator = _, scale) : interpolator; - }; - - function range(interpolate) { - return function(_) { - var r0, r1, r2; - return arguments.length ? ([r0, r1, r2] = _, interpolator = piecewise(interpolate, [r0, r1, r2]), scale) : [interpolator(0), interpolator(0.5), interpolator(1)]; - }; - } - - scale.range = range(interpolate$1); - - scale.rangeRound = range(interpolateRound); - - scale.unknown = function(_) { - return arguments.length ? (unknown = _, scale) : unknown; - }; - - return function(t) { - transform = t, t0 = t(x0), t1 = t(x1), t2 = t(x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1; - return scale; - }; -} - -function diverging() { - var scale = linearish(transformer()(identity$2)); - - scale.copy = function() { - return copy(scale, diverging()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function divergingLog() { - var scale = loggish(transformer()).domain([0.1, 1, 10]); - - scale.copy = function() { - return copy(scale, divergingLog()).base(scale.base()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function divergingSymlog() { - var scale = symlogish(transformer()); - - scale.copy = function() { - return copy(scale, divergingSymlog()).constant(scale.constant()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function divergingPow() { - var scale = powish(transformer()); - - scale.copy = function() { - return copy(scale, divergingPow()).exponent(scale.exponent()); - }; - - return initInterpolator.apply(scale, arguments); -} - -function divergingSqrt() { - return divergingPow.apply(null, arguments).exponent(0.5); -} - -function constant(x) { - return function constant() { - return x; - }; -} - -const abs = Math.abs; -const atan2 = Math.atan2; -const cos = Math.cos; -const max = Math.max; -const min = Math.min; -const sin = Math.sin; -const sqrt = Math.sqrt; - -const epsilon = 1e-12; -const pi = Math.PI; -const halfPi = pi / 2; -const tau = 2 * pi; - -function acos(x) { - return x > 1 ? 0 : x < -1 ? pi : Math.acos(x); -} - -function asin(x) { - return x >= 1 ? halfPi : x <= -1 ? -halfPi : Math.asin(x); -} - -function withPath(shape) { - let digits = 3; - - shape.digits = function(_) { - if (!arguments.length) return digits; - if (_ == null) { - digits = null; - } else { - const d = Math.floor(_); - if (!(d >= 0)) throw new RangeError(`invalid digits: ${_}`); - digits = d; - } - return shape; - }; - - return () => new Path(digits); -} - -function arcInnerRadius(d) { - return d.innerRadius; -} - -function arcOuterRadius(d) { - return d.outerRadius; -} - -function arcStartAngle(d) { - return d.startAngle; -} - -function arcEndAngle(d) { - return d.endAngle; -} - -function arcPadAngle(d) { - return d && d.padAngle; // Note: optional! -} - -function intersect(x0, y0, x1, y1, x2, y2, x3, y3) { - var x10 = x1 - x0, y10 = y1 - y0, - x32 = x3 - x2, y32 = y3 - y2, - t = y32 * x10 - x32 * y10; - if (t * t < epsilon) return; - t = (x32 * (y0 - y2) - y32 * (x0 - x2)) / t; - return [x0 + t * x10, y0 + t * y10]; -} - -// Compute perpendicular offset line of length rc. -// http://mathworld.wolfram.com/Circle-LineIntersection.html -function cornerTangents(x0, y0, x1, y1, r1, rc, cw) { - var x01 = x0 - x1, - y01 = y0 - y1, - lo = (cw ? rc : -rc) / sqrt(x01 * x01 + y01 * y01), - ox = lo * y01, - oy = -lo * x01, - x11 = x0 + ox, - y11 = y0 + oy, - x10 = x1 + ox, - y10 = y1 + oy, - x00 = (x11 + x10) / 2, - y00 = (y11 + y10) / 2, - dx = x10 - x11, - dy = y10 - y11, - d2 = dx * dx + dy * dy, - r = r1 - rc, - D = x11 * y10 - x10 * y11, - d = (dy < 0 ? -1 : 1) * sqrt(max(0, r * r * d2 - D * D)), - cx0 = (D * dy - dx * d) / d2, - cy0 = (-D * dx - dy * d) / d2, - cx1 = (D * dy + dx * d) / d2, - cy1 = (-D * dx + dy * d) / d2, - dx0 = cx0 - x00, - dy0 = cy0 - y00, - dx1 = cx1 - x00, - dy1 = cy1 - y00; - - // Pick the closer of the two intersection points. - // TODO Is there a faster way to determine which intersection to use? - if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1; - - return { - cx: cx0, - cy: cy0, - x01: -ox, - y01: -oy, - x11: cx0 * (r1 / r - 1), - y11: cy0 * (r1 / r - 1) - }; -} - -function arc() { - var innerRadius = arcInnerRadius, - outerRadius = arcOuterRadius, - cornerRadius = constant(0), - padRadius = null, - startAngle = arcStartAngle, - endAngle = arcEndAngle, - padAngle = arcPadAngle, - context = null, - path = withPath(arc); - - function arc() { - var buffer, - r, - r0 = +innerRadius.apply(this, arguments), - r1 = +outerRadius.apply(this, arguments), - a0 = startAngle.apply(this, arguments) - halfPi, - a1 = endAngle.apply(this, arguments) - halfPi, - da = abs(a1 - a0), - cw = a1 > a0; - - if (!context) context = buffer = path(); - - // Ensure that the outer radius is always larger than the inner radius. - if (r1 < r0) r = r1, r1 = r0, r0 = r; - - // Is it a point? - if (!(r1 > epsilon)) context.moveTo(0, 0); - - // Or is it a circle or annulus? - else if (da > tau - epsilon) { - context.moveTo(r1 * cos(a0), r1 * sin(a0)); - context.arc(0, 0, r1, a0, a1, !cw); - if (r0 > epsilon) { - context.moveTo(r0 * cos(a1), r0 * sin(a1)); - context.arc(0, 0, r0, a1, a0, cw); - } - } - - // Or is it a circular or annular sector? - else { - var a01 = a0, - a11 = a1, - a00 = a0, - a10 = a1, - da0 = da, - da1 = da, - ap = padAngle.apply(this, arguments) / 2, - rp = (ap > epsilon) && (padRadius ? +padRadius.apply(this, arguments) : sqrt(r0 * r0 + r1 * r1)), - rc = min(abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments)), - rc0 = rc, - rc1 = rc, - t0, - t1; - - // Apply padding? Note that since r1 ≥ r0, da1 ≥ da0. - if (rp > epsilon) { - var p0 = asin(rp / r0 * sin(ap)), - p1 = asin(rp / r1 * sin(ap)); - if ((da0 -= p0 * 2) > epsilon) p0 *= (cw ? 1 : -1), a00 += p0, a10 -= p0; - else da0 = 0, a00 = a10 = (a0 + a1) / 2; - if ((da1 -= p1 * 2) > epsilon) p1 *= (cw ? 1 : -1), a01 += p1, a11 -= p1; - else da1 = 0, a01 = a11 = (a0 + a1) / 2; - } - - var x01 = r1 * cos(a01), - y01 = r1 * sin(a01), - x10 = r0 * cos(a10), - y10 = r0 * sin(a10); - - // Apply rounded corners? - if (rc > epsilon) { - var x11 = r1 * cos(a11), - y11 = r1 * sin(a11), - x00 = r0 * cos(a00), - y00 = r0 * sin(a00), - oc; - - // Restrict the corner radius according to the sector angle. If this - // intersection fails, it’s probably because the arc is too small, so - // disable the corner radius entirely. - if (da < pi) { - if (oc = intersect(x01, y01, x00, y00, x11, y11, x10, y10)) { - var ax = x01 - oc[0], - ay = y01 - oc[1], - bx = x11 - oc[0], - by = y11 - oc[1], - kc = 1 / sin(acos((ax * bx + ay * by) / (sqrt(ax * ax + ay * ay) * sqrt(bx * bx + by * by))) / 2), - lc = sqrt(oc[0] * oc[0] + oc[1] * oc[1]); - rc0 = min(rc, (r0 - lc) / (kc - 1)); - rc1 = min(rc, (r1 - lc) / (kc + 1)); - } else { - rc0 = rc1 = 0; - } - } - } - - // Is the sector collapsed to a line? - if (!(da1 > epsilon)) context.moveTo(x01, y01); - - // Does the sector’s outer ring have rounded corners? - else if (rc1 > epsilon) { - t0 = cornerTangents(x00, y00, x01, y01, r1, rc1, cw); - t1 = cornerTangents(x11, y11, x10, y10, r1, rc1, cw); - - context.moveTo(t0.cx + t0.x01, t0.cy + t0.y01); - - // Have the corners merged? - if (rc1 < rc) context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); - - // Otherwise, draw the two corners and the ring. - else { - context.arc(t0.cx, t0.cy, rc1, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); - context.arc(0, 0, r1, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), !cw); - context.arc(t1.cx, t1.cy, rc1, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); - } - } - - // Or is the outer ring just a circular arc? - else context.moveTo(x01, y01), context.arc(0, 0, r1, a01, a11, !cw); - - // Is there no inner ring, and it’s a circular sector? - // Or perhaps it’s an annular sector collapsed due to padding? - if (!(r0 > epsilon) || !(da0 > epsilon)) context.lineTo(x10, y10); - - // Does the sector’s inner ring (or point) have rounded corners? - else if (rc0 > epsilon) { - t0 = cornerTangents(x10, y10, x11, y11, r0, -rc0, cw); - t1 = cornerTangents(x01, y01, x00, y00, r0, -rc0, cw); - - context.lineTo(t0.cx + t0.x01, t0.cy + t0.y01); - - // Have the corners merged? - if (rc0 < rc) context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t1.y01, t1.x01), !cw); - - // Otherwise, draw the two corners and the ring. - else { - context.arc(t0.cx, t0.cy, rc0, atan2(t0.y01, t0.x01), atan2(t0.y11, t0.x11), !cw); - context.arc(0, 0, r0, atan2(t0.cy + t0.y11, t0.cx + t0.x11), atan2(t1.cy + t1.y11, t1.cx + t1.x11), cw); - context.arc(t1.cx, t1.cy, rc0, atan2(t1.y11, t1.x11), atan2(t1.y01, t1.x01), !cw); - } - } - - // Or is the inner ring just a circular arc? - else context.arc(0, 0, r0, a10, a00, cw); - } - - context.closePath(); - - if (buffer) return context = null, buffer + "" || null; - } - - arc.centroid = function() { - var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, - a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - pi / 2; - return [cos(a) * r, sin(a) * r]; - }; - - arc.innerRadius = function(_) { - return arguments.length ? (innerRadius = typeof _ === "function" ? _ : constant(+_), arc) : innerRadius; - }; - - arc.outerRadius = function(_) { - return arguments.length ? (outerRadius = typeof _ === "function" ? _ : constant(+_), arc) : outerRadius; - }; - - arc.cornerRadius = function(_) { - return arguments.length ? (cornerRadius = typeof _ === "function" ? _ : constant(+_), arc) : cornerRadius; - }; - - arc.padRadius = function(_) { - return arguments.length ? (padRadius = _ == null ? null : typeof _ === "function" ? _ : constant(+_), arc) : padRadius; - }; - - arc.startAngle = function(_) { - return arguments.length ? (startAngle = typeof _ === "function" ? _ : constant(+_), arc) : startAngle; - }; - - arc.endAngle = function(_) { - return arguments.length ? (endAngle = typeof _ === "function" ? _ : constant(+_), arc) : endAngle; - }; - - arc.padAngle = function(_) { - return arguments.length ? (padAngle = typeof _ === "function" ? _ : constant(+_), arc) : padAngle; - }; - - arc.context = function(_) { - return arguments.length ? ((context = _ == null ? null : _), arc) : context; - }; - - return arc; -} - -var frame = 0, // is an animation frame pending? - timeout$1 = 0, // is a timeout pending? - interval = 0, // are any timers active? - pokeDelay = 1000, // how frequently we check for clock skew - taskHead, - taskTail, - clockLast = 0, - clockNow = 0, - clockSkew = 0, - clock = typeof performance === "object" && performance.now ? performance : Date, - setFrame = typeof window === "object" && window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : function(f) { setTimeout(f, 17); }; - -function now() { - return clockNow || (setFrame(clearNow), clockNow = clock.now() + clockSkew); -} - -function clearNow() { - clockNow = 0; -} - -function Timer() { - this._call = - this._time = - this._next = null; -} - -Timer.prototype = timer.prototype = { - constructor: Timer, - restart: function(callback, delay, time) { - if (typeof callback !== "function") throw new TypeError("callback is not a function"); - time = (time == null ? now() : +time) + (delay == null ? 0 : +delay); - if (!this._next && taskTail !== this) { - if (taskTail) taskTail._next = this; - else taskHead = this; - taskTail = this; - } - this._call = callback; - this._time = time; - sleep(); - }, - stop: function() { - if (this._call) { - this._call = null; - this._time = Infinity; - sleep(); - } - } -}; - -function timer(callback, delay, time) { - var t = new Timer; - t.restart(callback, delay, time); - return t; -} - -function timerFlush() { - now(); // Get the current time, if not already set. - ++frame; // Pretend we’ve set an alarm, if we haven’t already. - var t = taskHead, e; - while (t) { - if ((e = clockNow - t._time) >= 0) t._call.call(undefined, e); - t = t._next; - } - --frame; -} - -function wake() { - clockNow = (clockLast = clock.now()) + clockSkew; - frame = timeout$1 = 0; - try { - timerFlush(); - } finally { - frame = 0; - nap(); - clockNow = 0; - } -} - -function poke() { - var now = clock.now(), delay = now - clockLast; - if (delay > pokeDelay) clockSkew -= delay, clockLast = now; -} - -function nap() { - var t0, t1 = taskHead, t2, time = Infinity; - while (t1) { - if (t1._call) { - if (time > t1._time) time = t1._time; - t0 = t1, t1 = t1._next; - } else { - t2 = t1._next, t1._next = null; - t1 = t0 ? t0._next = t2 : taskHead = t2; - } - } - taskTail = t0; - sleep(time); -} - -function sleep(time) { - if (frame) return; // Soonest alarm already set, or will be. - if (timeout$1) timeout$1 = clearTimeout(timeout$1); - var delay = time - clockNow; // Strictly less than if we recomputed clockNow. - if (delay > 24) { - if (time < Infinity) timeout$1 = setTimeout(wake, time - clock.now() - clockSkew); - if (interval) interval = clearInterval(interval); - } else { - if (!interval) clockLast = clock.now(), interval = setInterval(poke, pokeDelay); - frame = 1, setFrame(wake); - } -} - -function timeout(callback, delay, time) { - var t = new Timer; - delay = delay == null ? 0 : +delay; - t.restart(elapsed => { - t.stop(); - callback(elapsed + delay); - }, delay, time); - return t; -} - -var emptyOn = dispatch("start", "end", "cancel", "interrupt"); -var emptyTween = []; - -var CREATED = 0; -var SCHEDULED = 1; -var STARTING = 2; -var STARTED = 3; -var RUNNING = 4; -var ENDING = 5; -var ENDED = 6; - -function schedule(node, name, id, index, group, timing) { - var schedules = node.__transition; - if (!schedules) node.__transition = {}; - else if (id in schedules) return; - create(node, id, { - name: name, - index: index, // For context during callback. - group: group, // For context during callback. - on: emptyOn, - tween: emptyTween, - time: timing.time, - delay: timing.delay, - duration: timing.duration, - ease: timing.ease, - timer: null, - state: CREATED - }); -} - -function init(node, id) { - var schedule = get(node, id); - if (schedule.state > CREATED) throw new Error("too late; already scheduled"); - return schedule; -} - -function set(node, id) { - var schedule = get(node, id); - if (schedule.state > STARTED) throw new Error("too late; already running"); - return schedule; -} - -function get(node, id) { - var schedule = node.__transition; - if (!schedule || !(schedule = schedule[id])) throw new Error("transition not found"); - return schedule; -} - -function create(node, id, self) { - var schedules = node.__transition, - tween; - - // Initialize the self timer when the transition is created. - // Note the actual delay is not known until the first callback! - schedules[id] = self; - self.timer = timer(schedule, 0, self.time); - - function schedule(elapsed) { - self.state = SCHEDULED; - self.timer.restart(start, self.delay, self.time); - - // If the elapsed delay is less than our first sleep, start immediately. - if (self.delay <= elapsed) start(elapsed - self.delay); - } - - function start(elapsed) { - var i, j, n, o; - - // If the state is not SCHEDULED, then we previously errored on start. - if (self.state !== SCHEDULED) return stop(); - - for (i in schedules) { - o = schedules[i]; - if (o.name !== self.name) continue; - - // While this element already has a starting transition during this frame, - // defer starting an interrupting transition until that transition has a - // chance to tick (and possibly end); see d3/d3-transition#54! - if (o.state === STARTED) return timeout(start); - - // Interrupt the active transition, if any. - if (o.state === RUNNING) { - o.state = ENDED; - o.timer.stop(); - o.on.call("interrupt", node, node.__data__, o.index, o.group); - delete schedules[i]; - } - - // Cancel any pre-empted transitions. - else if (+i < id) { - o.state = ENDED; - o.timer.stop(); - o.on.call("cancel", node, node.__data__, o.index, o.group); - delete schedules[i]; - } - } - - // Defer the first tick to end of the current frame; see d3/d3#1576. - // Note the transition may be canceled after start and before the first tick! - // Note this must be scheduled before the start event; see d3/d3-transition#16! - // Assuming this is successful, subsequent callbacks go straight to tick. - timeout(function() { - if (self.state === STARTED) { - self.state = RUNNING; - self.timer.restart(tick, self.delay, self.time); - tick(elapsed); - } - }); - - // Dispatch the start event. - // Note this must be done before the tween are initialized. - self.state = STARTING; - self.on.call("start", node, node.__data__, self.index, self.group); - if (self.state !== STARTING) return; // interrupted - self.state = STARTED; - - // Initialize the tween, deleting null tween. - tween = new Array(n = self.tween.length); - for (i = 0, j = -1; i < n; ++i) { - if (o = self.tween[i].value.call(node, node.__data__, self.index, self.group)) { - tween[++j] = o; - } - } - tween.length = j + 1; - } - - function tick(elapsed) { - var t = elapsed < self.duration ? self.ease.call(null, elapsed / self.duration) : (self.timer.restart(stop), self.state = ENDING, 1), - i = -1, - n = tween.length; - - while (++i < n) { - tween[i].call(node, t); - } - - // Dispatch the end event. - if (self.state === ENDING) { - self.on.call("end", node, node.__data__, self.index, self.group); - stop(); - } - } - - function stop() { - self.state = ENDED; - self.timer.stop(); - delete schedules[id]; - for (var i in schedules) return; // eslint-disable-line no-unused-vars - delete node.__transition; - } -} - -function interrupt(node, name) { - var schedules = node.__transition, - schedule, - active, - empty = true, - i; - - if (!schedules) return; - - name = name == null ? null : name + ""; - - for (i in schedules) { - if ((schedule = schedules[i]).name !== name) { empty = false; continue; } - active = schedule.state > STARTING && schedule.state < ENDING; - schedule.state = ENDED; - schedule.timer.stop(); - schedule.on.call(active ? "interrupt" : "cancel", node, node.__data__, schedule.index, schedule.group); - delete schedules[i]; - } - - if (empty) delete node.__transition; -} - -function selection_interrupt(name) { - return this.each(function() { - interrupt(this, name); - }); -} - -function tweenRemove(id, name) { - var tween0, tween1; - return function() { - var schedule = set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = tween0 = tween; - for (var i = 0, n = tween1.length; i < n; ++i) { - if (tween1[i].name === name) { - tween1 = tween1.slice(); - tween1.splice(i, 1); - break; - } - } - } - - schedule.tween = tween1; - }; -} - -function tweenFunction(id, name, value) { - var tween0, tween1; - if (typeof value !== "function") throw new Error; - return function() { - var schedule = set(this, id), - tween = schedule.tween; - - // If this node shared tween with the previous node, - // just assign the updated shared tween and we’re done! - // Otherwise, copy-on-write. - if (tween !== tween0) { - tween1 = (tween0 = tween).slice(); - for (var t = {name: name, value: value}, i = 0, n = tween1.length; i < n; ++i) { - if (tween1[i].name === name) { - tween1[i] = t; - break; - } - } - if (i === n) tween1.push(t); - } - - schedule.tween = tween1; - }; -} - -function transition_tween(name, value) { - var id = this._id; - - name += ""; - - if (arguments.length < 2) { - var tween = get(this.node(), id).tween; - for (var i = 0, n = tween.length, t; i < n; ++i) { - if ((t = tween[i]).name === name) { - return t.value; - } - } - return null; - } - - return this.each((value == null ? tweenRemove : tweenFunction)(id, name, value)); -} - -function tweenValue(transition, name, value) { - var id = transition._id; - - transition.each(function() { - var schedule = set(this, id); - (schedule.value || (schedule.value = {}))[name] = value.apply(this, arguments); - }); - - return function(node) { - return get(node, id).value[name]; - }; -} - -function interpolate(a, b) { - var c; - return (typeof b === "number" ? interpolateNumber - : b instanceof color ? interpolateRgb - : (c = color(b)) ? (b = c, interpolateRgb) - : interpolateString)(a, b); -} - -function attrRemove(name) { - return function() { - this.removeAttribute(name); - }; -} - -function attrRemoveNS(fullname) { - return function() { - this.removeAttributeNS(fullname.space, fullname.local); - }; -} - -function attrConstant(name, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = this.getAttribute(name); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function attrConstantNS(fullname, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = this.getAttributeNS(fullname.space, fullname.local); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function attrFunction(name, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0, value1 = value(this), string1; - if (value1 == null) return void this.removeAttribute(name); - string0 = this.getAttribute(name); - string1 = value1 + ""; - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function attrFunctionNS(fullname, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0, value1 = value(this), string1; - if (value1 == null) return void this.removeAttributeNS(fullname.space, fullname.local); - string0 = this.getAttributeNS(fullname.space, fullname.local); - string1 = value1 + ""; - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function transition_attr(name, value) { - var fullname = namespace(name), i = fullname === "transform" ? interpolateTransformSvg : interpolate; - return this.attrTween(name, typeof value === "function" - ? (fullname.local ? attrFunctionNS : attrFunction)(fullname, i, tweenValue(this, "attr." + name, value)) - : value == null ? (fullname.local ? attrRemoveNS : attrRemove)(fullname) - : (fullname.local ? attrConstantNS : attrConstant)(fullname, i, value)); -} - -function attrInterpolate(name, i) { - return function(t) { - this.setAttribute(name, i.call(this, t)); - }; -} - -function attrInterpolateNS(fullname, i) { - return function(t) { - this.setAttributeNS(fullname.space, fullname.local, i.call(this, t)); - }; -} - -function attrTweenNS(fullname, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolateNS(fullname, i); - return t0; - } - tween._value = value; - return tween; -} - -function attrTween(name, value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && attrInterpolate(name, i); - return t0; - } - tween._value = value; - return tween; -} - -function transition_attrTween(name, value) { - var key = "attr." + name; - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - var fullname = namespace(name); - return this.tween(key, (fullname.local ? attrTweenNS : attrTween)(fullname, value)); -} - -function delayFunction(id, value) { - return function() { - init(this, id).delay = +value.apply(this, arguments); - }; -} - -function delayConstant(id, value) { - return value = +value, function() { - init(this, id).delay = value; - }; -} - -function transition_delay(value) { - var id = this._id; - - return arguments.length - ? this.each((typeof value === "function" - ? delayFunction - : delayConstant)(id, value)) - : get(this.node(), id).delay; -} - -function durationFunction(id, value) { - return function() { - set(this, id).duration = +value.apply(this, arguments); - }; -} - -function durationConstant(id, value) { - return value = +value, function() { - set(this, id).duration = value; - }; -} - -function transition_duration(value) { - var id = this._id; - - return arguments.length - ? this.each((typeof value === "function" - ? durationFunction - : durationConstant)(id, value)) - : get(this.node(), id).duration; -} - -function easeConstant(id, value) { - if (typeof value !== "function") throw new Error; - return function() { - set(this, id).ease = value; - }; -} - -function transition_ease(value) { - var id = this._id; - - return arguments.length - ? this.each(easeConstant(id, value)) - : get(this.node(), id).ease; -} - -function easeVarying(id, value) { - return function() { - var v = value.apply(this, arguments); - if (typeof v !== "function") throw new Error; - set(this, id).ease = v; - }; -} - -function transition_easeVarying(value) { - if (typeof value !== "function") throw new Error; - return this.each(easeVarying(this._id, value)); -} - -function transition_filter(match) { - if (typeof match !== "function") match = matcher(match); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) { - if ((node = group[i]) && match.call(node, node.__data__, i, group)) { - subgroup.push(node); - } - } - } - - return new Transition(subgroups, this._parents, this._name, this._id); -} - -function transition_merge(transition) { - if (transition._id !== this._id) throw new Error; - - for (var groups0 = this._groups, groups1 = transition._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) { - for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) { - if (node = group0[i] || group1[i]) { - merge[i] = node; - } - } - } - - for (; j < m0; ++j) { - merges[j] = groups0[j]; - } - - return new Transition(merges, this._parents, this._name, this._id); -} - -function start(name) { - return (name + "").trim().split(/^|\s+/).every(function(t) { - var i = t.indexOf("."); - if (i >= 0) t = t.slice(0, i); - return !t || t === "start"; - }); -} - -function onFunction(id, name, listener) { - var on0, on1, sit = start(name) ? init : set; - return function() { - var schedule = sit(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) (on1 = (on0 = on).copy()).on(name, listener); - - schedule.on = on1; - }; -} - -function transition_on(name, listener) { - var id = this._id; - - return arguments.length < 2 - ? get(this.node(), id).on.on(name) - : this.each(onFunction(id, name, listener)); -} - -function removeFunction(id) { - return function() { - var parent = this.parentNode; - for (var i in this.__transition) if (+i !== id) return; - if (parent) parent.removeChild(this); - }; -} - -function transition_remove() { - return this.on("end.remove", removeFunction(this._id)); -} - -function transition_select(select) { - var name = this._name, - id = this._id; - - if (typeof select !== "function") select = selector(select); - - for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) { - if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) { - if ("__data__" in node) subnode.__data__ = node.__data__; - subgroup[i] = subnode; - schedule(subgroup[i], name, id, i, subgroup, get(node, id)); - } - } - } - - return new Transition(subgroups, this._parents, name, id); -} - -function transition_selectAll(select) { - var name = this._name, - id = this._id; - - if (typeof select !== "function") select = selectorAll(select); - - for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - for (var children = select.call(node, node.__data__, i, group), child, inherit = get(node, id), k = 0, l = children.length; k < l; ++k) { - if (child = children[k]) { - schedule(child, name, id, k, children, inherit); - } - } - subgroups.push(children); - parents.push(node); - } - } - } - - return new Transition(subgroups, parents, name, id); -} - -var Selection = selection.prototype.constructor; - -function transition_selection() { - return new Selection(this._groups, this._parents); -} - -function styleNull(name, interpolate) { - var string00, - string10, - interpolate0; - return function() { - var string0 = styleValue(this, name), - string1 = (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, string10 = string1); - }; -} - -function styleRemove(name) { - return function() { - this.style.removeProperty(name); - }; -} - -function styleConstant(name, interpolate, value1) { - var string00, - string1 = value1 + "", - interpolate0; - return function() { - var string0 = styleValue(this, name); - return string0 === string1 ? null - : string0 === string00 ? interpolate0 - : interpolate0 = interpolate(string00 = string0, value1); - }; -} - -function styleFunction(name, interpolate, value) { - var string00, - string10, - interpolate0; - return function() { - var string0 = styleValue(this, name), - value1 = value(this), - string1 = value1 + ""; - if (value1 == null) string1 = value1 = (this.style.removeProperty(name), styleValue(this, name)); - return string0 === string1 ? null - : string0 === string00 && string1 === string10 ? interpolate0 - : (string10 = string1, interpolate0 = interpolate(string00 = string0, value1)); - }; -} - -function styleMaybeRemove(id, name) { - var on0, on1, listener0, key = "style." + name, event = "end." + key, remove; - return function() { - var schedule = set(this, id), - on = schedule.on, - listener = schedule.value[key] == null ? remove || (remove = styleRemove(name)) : undefined; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0 || listener0 !== listener) (on1 = (on0 = on).copy()).on(event, listener0 = listener); - - schedule.on = on1; - }; -} - -function transition_style(name, value, priority) { - var i = (name += "") === "transform" ? interpolateTransformCss : interpolate; - return value == null ? this - .styleTween(name, styleNull(name, i)) - .on("end.style." + name, styleRemove(name)) - : typeof value === "function" ? this - .styleTween(name, styleFunction(name, i, tweenValue(this, "style." + name, value))) - .each(styleMaybeRemove(this._id, name)) - : this - .styleTween(name, styleConstant(name, i, value), priority) - .on("end.style." + name, null); -} - -function styleInterpolate(name, i, priority) { - return function(t) { - this.style.setProperty(name, i.call(this, t), priority); - }; -} - -function styleTween(name, value, priority) { - var t, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t = (i0 = i) && styleInterpolate(name, i, priority); - return t; - } - tween._value = value; - return tween; -} - -function transition_styleTween(name, value, priority) { - var key = "style." + (name += ""); - if (arguments.length < 2) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - return this.tween(key, styleTween(name, value, priority == null ? "" : priority)); -} - -function textConstant(value) { - return function() { - this.textContent = value; - }; -} - -function textFunction(value) { - return function() { - var value1 = value(this); - this.textContent = value1 == null ? "" : value1; - }; -} - -function transition_text(value) { - return this.tween("text", typeof value === "function" - ? textFunction(tweenValue(this, "text", value)) - : textConstant(value == null ? "" : value + "")); -} - -function textInterpolate(i) { - return function(t) { - this.textContent = i.call(this, t); - }; -} - -function textTween(value) { - var t0, i0; - function tween() { - var i = value.apply(this, arguments); - if (i !== i0) t0 = (i0 = i) && textInterpolate(i); - return t0; - } - tween._value = value; - return tween; -} - -function transition_textTween(value) { - var key = "text"; - if (arguments.length < 1) return (key = this.tween(key)) && key._value; - if (value == null) return this.tween(key, null); - if (typeof value !== "function") throw new Error; - return this.tween(key, textTween(value)); -} - -function transition_transition() { - var name = this._name, - id0 = this._id, - id1 = newId(); - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - var inherit = get(node, id0); - schedule(node, name, id1, i, group, { - time: inherit.time + inherit.delay + inherit.duration, - delay: 0, - duration: inherit.duration, - ease: inherit.ease - }); - } - } - } - - return new Transition(groups, this._parents, name, id1); -} - -function transition_end() { - var on0, on1, that = this, id = that._id, size = that.size(); - return new Promise(function(resolve, reject) { - var cancel = {value: reject}, - end = {value: function() { if (--size === 0) resolve(); }}; - - that.each(function() { - var schedule = set(this, id), - on = schedule.on; - - // If this node shared a dispatch with the previous node, - // just assign the updated shared dispatch and we’re done! - // Otherwise, copy-on-write. - if (on !== on0) { - on1 = (on0 = on).copy(); - on1._.cancel.push(cancel); - on1._.interrupt.push(cancel); - on1._.end.push(end); - } - - schedule.on = on1; - }); - - // The selection was empty, resolve end immediately - if (size === 0) resolve(); - }); -} - -var id = 0; - -function Transition(groups, parents, name, id) { - this._groups = groups; - this._parents = parents; - this._name = name; - this._id = id; -} - -function transition(name) { - return selection().transition(name); -} - -function newId() { - return ++id; -} - -var selection_prototype = selection.prototype; - -Transition.prototype = transition.prototype = { - constructor: Transition, - select: transition_select, - selectAll: transition_selectAll, - selectChild: selection_prototype.selectChild, - selectChildren: selection_prototype.selectChildren, - filter: transition_filter, - merge: transition_merge, - selection: transition_selection, - transition: transition_transition, - call: selection_prototype.call, - nodes: selection_prototype.nodes, - node: selection_prototype.node, - size: selection_prototype.size, - empty: selection_prototype.empty, - each: selection_prototype.each, - on: transition_on, - attr: transition_attr, - attrTween: transition_attrTween, - style: transition_style, - styleTween: transition_styleTween, - text: transition_text, - textTween: transition_textTween, - remove: transition_remove, - tween: transition_tween, - delay: transition_delay, - duration: transition_duration, - ease: transition_ease, - easeVarying: transition_easeVarying, - end: transition_end, - [Symbol.iterator]: selection_prototype[Symbol.iterator] -}; - -function cubicInOut(t) { - return ((t *= 2) <= 1 ? t * t * t : (t -= 2) * t * t + 2) / 2; -} - -var defaultTiming = { - time: null, // Set on use. - delay: 0, - duration: 250, - ease: cubicInOut -}; - -function inherit(node, id) { - var timing; - while (!(timing = node.__transition) || !(timing = timing[id])) { - if (!(node = node.parentNode)) { - throw new Error(`transition ${id} not found`); - } - } - return timing; -} - -function selection_transition(name) { - var id, - timing; - - if (name instanceof Transition) { - id = name._id, name = name._name; - } else { - id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + ""; - } - - for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) { - for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) { - if (node = group[i]) { - schedule(node, name, id, i, group, timing || inherit(node, id)); - } - } - } - - return new Transition(groups, this._parents, name, id); -} - -selection.prototype.interrupt = selection_interrupt; -selection.prototype.transition = selection_transition; - -var root = [null]; - -function active(node, name) { - var schedules = node.__transition, - schedule, - i; - - if (schedules) { - name = name == null ? null : name + ""; - for (i in schedules) { - if ((schedule = schedules[i]).state > SCHEDULED && schedule.name === name) { - return new Transition([[node]], root, name, +i); - } - } - } - - return null; -} - -export { active, arc, chord_default as chord, chordDirected, chordTranspose, color, create$1 as create, creator, cubehelix, drag, nodrag as dragDisable, yesdrag as dragEnable, gray, hcl, hsl, interrupt, formatIso as isoFormat, parseIso as isoParse, lab, lch, local, matcher, namespace, namespaces, pointer, pointers, rgb, ribbon_default as ribbon, ribbonArrow, band as scaleBand, diverging as scaleDiverging, divergingLog as scaleDivergingLog, divergingPow as scaleDivergingPow, divergingSqrt as scaleDivergingSqrt, divergingSymlog as scaleDivergingSymlog, identity as scaleIdentity, implicit as scaleImplicit, linear as scaleLinear, log as scaleLog, ordinal as scaleOrdinal, point as scalePoint, pow as scalePow, quantile as scaleQuantile, quantize as scaleQuantize, radial as scaleRadial, sequential as scaleSequential, sequentialLog as scaleSequentialLog, sequentialPow as scaleSequentialPow, sequentialQuantile as scaleSequentialQuantile, sequentialSqrt as scaleSequentialSqrt, sequentialSymlog as scaleSequentialSymlog, sqrt$1 as scaleSqrt, symlog as scaleSymlog, threshold as scaleThreshold, time as scaleTime, utcTime as scaleUtc, select, selectAll, selection, selector, selectorAll, styleValue as style, tickFormat, timeFormat, defaultLocale as timeFormatDefaultLocale, formatLocale as timeFormatLocale, timeParse, transition, utcFormat, utcParse, version, defaultView as window }; diff --git a/js/modules/draw.mjs b/js/modules/draw.mjs deleted file mode 100644 index fce705599dc9a..0000000000000 --- a/js/modules/draw.mjs +++ /dev/null @@ -1,804 +0,0 @@ -import { select as d3_select } from './d3.mjs'; -import { loadScript, loadModules, findFunction, internals, settings, getPromise, isNodeJs, isObject, isFunc, isStr, - _ensureJSROOT, getKindForType, getTypeForKind, - clTObject, clTNamed, clTString, clTAttLine, clTAttFill, clTAttMarker, clTAttText, - clTObjString, clTFile, clTList, clTHashList, clTMap, clTObjArray, clTClonesArray, - clTPave, clTPaveText, clTPavesText, clTPaveStats, clTPaveLabel, clTPaveClass, clTDiamond, clTLegend, clTPaletteAxis, - clTText, clTLink, clTLine, clTMarker, clTBox, clTLatex, clTMathText, clTAnnotation, clTMultiGraph, clTH2, clTF1, clTF12, clTF2, clTF3, clTH3, - clTProfile, clTProfile2D, clTProfile3D, clTFrame, clTTree, - clTColor, clTHStack, clTGraph, clTGraph2DErrors, clTGraph2DAsymmErrors, - clTGraphPolar, clTGraphPolargram, clTGraphTime, clTCutG, clTPolyLine, clTPolyLine3D, clTPolyMarker3D, - clTPad, clTStyle, clTCanvas, clTGaxis, clTGeoVolume, kInspect, nsREX, nsSVG, atob_func } from './core.mjs'; -import { clTStreamerInfoList, kBaseClass } from './io.mjs'; -import { clTBranchFunc } from './tree.mjs'; -import { BasePainter, compressSVG, svgToImage, _loadJSDOM } from './base/BasePainter.mjs'; -import { ObjectPainter, cleanup, drawRawText, getElementCanvPainter, getElementMainPainter } from './base/ObjectPainter.mjs'; -import { TPadPainter, clTButton } from './gpad/TPadPainter.mjs'; -import { makePDF } from './base/makepdf.mjs'; - - -async function import_more() { return import('./draw/more.mjs'); } - -async function import_canvas() { return import('./gpad/TCanvasPainter.mjs'); } - -async function import_tree() { return import('./draw/TTree.mjs'); } - -async function import_h() { return import('./gui/HierarchyPainter.mjs'); } - -let import_v7 = null, import_geo = null; - -const clTGraph2D = 'TGraph2D', clTH2Poly = 'TH2Poly', clTEllipse = 'TEllipse', - clTSpline3 = 'TSpline3', clTCanvasWebSnapshot = 'TCanvasWebSnapshot', - fPrimitives = 'fPrimitives', fFunctions = 'fFunctions'; - -/** @summary list of registered draw functions - * @private */ -/* eslint-disable-next-line one-var */ -const drawFuncs = { lst: [ - { name: clTCanvas, icon: 'img_canvas', class: () => import_canvas().then(h => h.TCanvasPainter), opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, - { name: clTPad, icon: 'img_canvas', func: TPadPainter.draw, opt: ';grid;gridx;gridy;tick;tickx;ticky;log;logx;logy;logz', expand_item: fPrimitives, noappend: true }, - { name: 'TSlider', icon: 'img_canvas', func: TPadPainter.draw }, - { name: clTButton, icon: 'img_canvas', func: TPadPainter.draw }, - { name: 'TInspectCanvas', icon: 'img_canvas', sameas: clTCanvas }, - { name: clTFrame, icon: 'img_frame', draw: () => import_canvas().then(h => h.drawTFrame) }, - { name: clTPave, icon: 'img_pavetext', class: () => import('./hist/TPavePainter.mjs').then(h => h.TPavePainter) }, - { name: clTPaveText, sameas: clTPave }, - { name: clTPavesText, sameas: clTPave }, - { name: clTPaveStats, sameas: clTPave }, - { name: clTPaveLabel, sameas: clTPave }, - { name: clTPaveClass, sameas: clTPave }, - { name: clTDiamond, sameas: clTPave }, - { name: clTLegend, icon: 'img_pavelabel', sameas: clTPave }, - { name: clTPaletteAxis, icon: 'img_colz', sameas: clTPave }, - { name: clTText, icon: 'img_text', class: () => import('./draw/TTextPainter.mjs').then(h => h.TTextPainter), build3d: () => import('./base/latex3d.mjs').then(h => h.build3dlatex) }, - { name: clTMathText, sameas: clTText }, - { name: clTLatex, sameas: clTText }, - { name: clTLink, sameas: clTText }, - { name: clTAnnotation, icon: 'img_text', class: () => import('./draw/TAnnotation3DPainter.mjs').then(h => h.TAnnotation3DPainter) }, - { name: /^TH1/, icon: 'img_histo1d', class: () => import('./hist/TH1Painter.mjs').then(h => h.TH1Painter), opt: ';hist;P;P0;E;E1;E2;E3;E4;E1X0;L;LF2;C;B;B1;A;TEXT;LEGO;same', ctrl: 'l', expand_item: fFunctions, for_derived: true }, - { name: clTProfile, icon: 'img_profile', class: () => import('./hist/TH1Painter.mjs').then(h => h.TH1Painter), opt: ';E0;E1;E2;p;AH;hist;projx;projxb;projxc=e;projxw', expand_item: fFunctions }, - { name: clTH2Poly, icon: 'img_histo2d', class: () => import('./hist/TH2Painter.mjs').then(h => h.TH2Painter), opt: ';COL;COL0;COLZ;LCOL;LCOL0;LCOLZ;LEGO;TEXT;same', expand_item: 'fBins', theonly: true }, - { name: 'TProfile2Poly', sameas: clTH2Poly }, - { name: 'TH2PolyBin', icon: 'img_histo2d', draw_field: 'fPoly', draw_field_opt: 'L' }, - { name: /^TH2/, icon: 'img_histo2d', class: () => import('./hist/TH2Painter.mjs').then(h => h.TH2Painter), opt: ';COL;COLZ;COL0;COL1;COL0Z;COL1Z;COLA;COL_POL;COL_ARR;BOX;BOX1;PROJ;PROJX1;PROJX2;PROJX3;PROJY1;PROJY2;PROJY3;PROJXY1;PROJXY2;PROJXY3;SCAT;TEXT;TEXTE;TEXTE0;CANDLE;CANDLE1;CANDLE2;CANDLE3;CANDLE4;CANDLE5;CANDLE6;CANDLEY1;CANDLEY2;CANDLEY3;CANDLEY4;CANDLEY5;CANDLEY6;VIOLIN;VIOLIN1;VIOLIN2;VIOLINY1;VIOLINY2;CONT;CONT1;CONT2;CONT3;CONT4;ARR;CHORD;SURF;SURF1;SURF2;SURF3;SURF4;SURF6;E;A;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same', ctrl: 'lego', expand_item: fFunctions, for_derived: true }, - { name: clTProfile2D, sameas: clTH2, opt2: ';projxyb;projxyc=e;projxyw' }, - { name: /^TH3/, icon: 'img_histo3d', class: () => import('./hist/TH3Painter.mjs').then(h => h.TH3Painter), opt: ';SCAT;BOX;BOX2;BOX3;GLBOX1;GLBOX2;GLCOL', expand_item: fFunctions, for_derived: true }, - { name: clTProfile3D, sameas: clTH3 }, - { name: clTHStack, icon: 'img_histo1d', class: () => import('./hist/THStackPainter.mjs').then(h => h.THStackPainter), expand_item: 'fHists', opt: 'NOSTACK;HIST;COL;LEGO;E;PFC;PLC;PADS' }, - { name: clTPolyMarker3D, icon: 'img_histo3d', draw: () => import('./draw/draw3d.mjs').then(h => h.drawPolyMarker3D), direct: true, frame: '3d' }, - { name: clTPolyLine3D, icon: 'img_graph', draw: () => import('./draw/draw3d.mjs').then(h => h.drawPolyLine3D), direct: true, frame: '3d' }, - { name: 'TGraphStruct' }, - { name: 'TGraphNode' }, - { name: 'TGraphEdge' }, - { name: clTGraphTime, icon: 'img_graph', class: () => import('./hist/TGraphTimePainter.mjs').then(h => h.TGraphTimePainter), opt: 'once;repeat;first', theonly: true }, - { name: clTGraph2D, icon: 'img_graph', class: () => import('./hist/TGraph2DPainter.mjs').then(h => h.TGraph2DPainter), opt: ';P;PCOL', theonly: true }, - { name: clTGraph2DErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true }, - { name: clTGraph2DAsymmErrors, sameas: clTGraph2D, opt: ';P;PCOL;ERR', theonly: true }, - { name: clTGraphPolargram, icon: 'img_graph', class: () => import('./draw/TGraphPolarPainter.mjs').then(h => h.TGraphPolargramPainter), theonly: true }, - { name: clTGraphPolar, icon: 'img_graph', class: () => import('./draw/TGraphPolarPainter.mjs').then(h => h.TGraphPolarPainter), opt: ';F;L;P;PE', theonly: true }, - { name: /^TGraph/, icon: 'img_graph', class: () => import('./hist2d/TGraphPainter.mjs').then(h => h.TGraphPainter), opt: ';L;P' }, - { name: 'TEfficiency', icon: 'img_graph', class: () => import('./hist/TEfficiencyPainter.mjs').then(h => h.TEfficiencyPainter), opt: ';AP;A4P;B' }, - { name: clTCutG, sameas: clTGraph }, - { name: /^RooHist/, sameas: clTGraph }, - { name: /^RooCurve/, sameas: clTGraph }, - { name: /^RooEllipse/, sameas: clTGraph }, - { name: 'TScatter', icon: 'img_graph', class: () => import('./hist2d/TScatterPainter.mjs').then(h => h.TScatterPainter), opt: ';A' }, - { name: 'RooPlot', icon: 'img_canvas', draw: () => import('./hist/TGraphTimePainter.mjs').then(h => h.drawRooPlot) }, - { name: 'TRatioPlot', icon: 'img_mgraph', class: () => import('./draw/TRatioPlotPainter.mjs').then(h => h.TRatioPlotPainter), opt: '' }, - { name: clTMultiGraph, icon: 'img_mgraph', class: () => import('./hist/TMultiGraphPainter.mjs').then(h => h.TMultiGraphPainter), opt: ';ac;l;p;3d;pads', expand_item: 'fGraphs' }, - { name: clTStreamerInfoList, icon: 'img_question', draw: () => import_h().then(h => h.drawStreamerInfo) }, - { name: 'TWebPainting', icon: 'img_graph', class: () => import('./draw/TWebPaintingPainter.mjs').then(h => h.TWebPaintingPainter) }, - { name: clTCanvasWebSnapshot, icon: 'img_canvas', draw: () => import_canvas().then(h => h.drawTPadSnapshot) }, - { name: 'TPadWebSnapshot', sameas: clTCanvasWebSnapshot }, - { name: 'kind:Text', icon: 'img_text', func: drawRawText }, - { name: clTObjString, icon: 'img_text', func: drawRawText }, - { name: clTF1, icon: 'img_tf1', class: () => import('./hist/TF1Painter.mjs').then(h => h.TF1Painter), opt: ';L;C;FC;FL' }, - { name: clTF12, sameas: clTF1 }, - { name: clTF2, icon: 'img_tf2', class: () => import('./hist/TF2Painter.mjs').then(h => h.TF2Painter), opt: ';BOX;ARR;SURF;SURF1;SURF2;SURF4;SURF6;LEGO;LEGO0;LEGO1;LEGO2;LEGO3;LEGO4;same' }, - { name: clTF3, icon: 'img_histo3d', class: () => import('./hist/TF3Painter.mjs').then(h => h.TF3Painter), opt: ';SURF' }, - { name: clTSpline3, icon: 'img_tf1', class: () => import('./draw/TSplinePainter.mjs').then(h => h.TSplinePainter) }, - { name: 'TSpline5', sameas: clTSpline3 }, - { name: clTEllipse, icon: 'img_graph', draw: () => import_more().then(h => h.drawEllipse), direct: true }, - { name: 'TArc', sameas: clTEllipse }, - { name: 'TCrown', sameas: clTEllipse }, - { name: 'TPie', icon: 'img_graph', class: () => import('./draw/TPiePainter.mjs').then(h => h.TPiePainter), opt: ';3D' }, - { name: 'TPieSlice', icon: 'img_graph', dummy: true }, - { name: 'TExec', icon: 'img_graph', dummy: true }, - { name: clTLine, icon: 'img_graph', class: () => import('./draw/TLinePainter.mjs').then(h => h.TLinePainter) }, - { name: 'TArrow', icon: 'img_graph', class: () => import('./draw/TArrowPainter.mjs').then(h => h.TArrowPainter) }, - { name: clTPolyLine, icon: 'img_graph', class: () => import('./draw/TPolyLinePainter.mjs').then(h => h.TPolyLinePainter), opt: ';F' }, - { name: 'TCurlyLine', sameas: clTPolyLine }, - { name: 'TCurlyArc', sameas: clTPolyLine }, - { name: 'TParallelCoord', icon: 'img_graph', dummy: true }, - { name: clTGaxis, icon: 'img_graph', class: () => import('./draw/TGaxisPainter.mjs').then(h => h.TGaxisPainter) }, - { name: clTBox, icon: 'img_graph', class: () => import('./draw/TBoxPainter.mjs').then(h => h.TBoxPainter), opt: ';L' }, - { name: 'TWbox', sameas: clTBox }, - { name: 'TSliderBox', sameas: clTBox }, - { name: clTMarker, icon: 'img_graph', draw: () => import_more().then(h => h.drawMarker), direct: true }, - { name: 'TPolyMarker', icon: 'img_graph', draw: () => import_more().then(h => h.drawPolyMarker), direct: true }, - { name: 'TASImage', icon: 'img_mgraph', class: () => import('./draw/TASImagePainter.mjs').then(h => h.TASImagePainter), opt: ';z' }, - { name: 'TJSImage', icon: 'img_mgraph', draw: () => import_more().then(h => h.drawJSImage), opt: ';scale;center' }, - { name: clTGeoVolume, icon: 'img_histo3d', class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;count;projx;projz;wire;no_screen;dflt', ctrl: 'dflt' }, - { name: 'TEveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, - { name: nsREX + 'REveGeoShapeExtract', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt' }, - { name: 'TGeoOverlap', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;dflt', dflt: 'dflt', ctrl: 'expand' }, - { name: 'TGeoManager', sameas: clTGeoVolume, opt: ';more;all;count;projx;projz;wire;tracks;no_screen;dflt', dflt: 'expand', pm: true, ctrl: 'dflt', noappend: true, expand_after_draw: true }, - { name: 'TGeoVolumeAssembly', sameas: clTGeoVolume, /* icon: 'img_geoassembly', */ opt: ';more;all;count' }, - { name: /^TGeo/, class: () => import_geo().then(h => h.TGeoPainter), get_expand: () => import_geo().then(h => h.expandGeoObject), opt: ';more;all;axis;compa;count;projx;projz;wire;no_screen;dflt', dflt: 'dflt', ctrl: 'expand' }, - { name: 'TAxis3D', icon: 'img_graph', draw: () => import_geo().then(h => h.drawAxis3D), direct: true }, - // these are not draw functions, but provide extra info about correspondent classes - { name: 'kind:Command', icon: 'img_execute', execute: true }, - { name: 'TFolder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true, get_expand: () => import_h().then(h => h.folderHierarchy) }, - { name: 'TTask', icon: 'img_task', get_expand: () => import_h().then(h => h.taskHierarchy), for_derived: true }, - { name: clTTree, icon: 'img_tree', get_expand: () => import('./tree.mjs').then(h => h.treeHierarchy), draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: 'player;testio', shift: kInspect, pm: true, transform: true }, - { name: 'TNtuple', sameas: clTTree }, - { name: 'TNtupleD', sameas: clTTree }, - { name: clTBranchFunc, icon: 'img_leaf_method', draw: () => import_tree().then(h => h.drawTree), opt: ';dump;dumpall', noinspect: true, transform: true }, - { name: /^TBranch/, icon: 'img_branch', draw: () => import_tree().then(h => h.drawTree), dflt: 'expand', opt: ';dump;dumpall', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true, transform: true }, - { name: /^TLeaf/, icon: 'img_leaf', noexpand: true, draw: () => import_tree().then(h => h.drawTree), opt: ';dump;dumpall', ctrl: 'dump', ignore_online: true, always_draw: true, transform: true }, - { name: 'ROOT::RNTuple', icon: 'img_tree', get_expand: () => import('./rntuple.mjs').then(h => h.tupleHierarchy), draw: () => import('./draw/RNTuple.mjs').then(h => h.drawRNTuple), dflt: 'expand', opt: 'player', pm: true, transform: true }, - { name: 'ROOT::RNTupleField', icon: 'img_leaf', draw: () => import('./draw/RNTuple.mjs').then(h => h.drawRNTuple), opt: ';dump;dumpall', ctrl: 'dump', shift: kInspect, ignore_online: true, always_draw: true, transform: true }, - { name: clTList, icon: 'img_list', draw: () => import_h().then(h => h.drawList), get_expand: () => import_h().then(h => h.listHierarchy), dflt: 'expand' }, - { name: clTHashList, sameas: clTList }, - { name: clTObjArray, sameas: clTList }, - { name: clTClonesArray, sameas: clTList }, - { name: clTMap, sameas: clTList }, - { name: clTColor, icon: 'img_color' }, - { name: clTFile, icon: 'img_file', noinspect: true, pm: true }, - { name: 'TMemFile', icon: 'img_file', noinspect: true, pm: true }, - { name: clTStyle, icon: 'img_question', noexpand: true }, - { name: 'Session', icon: 'img_globe' }, - { name: 'kind:TopFolder', icon: 'img_base' }, - { name: 'kind:Folder', icon: 'img_folder', icon2: 'img_folderopen', noinspect: true }, - { name: nsREX + 'RCanvas', icon: 'img_canvas', class: () => import_v7().then(h => h.RCanvasPainter), opt: '', expand_item: fPrimitives }, - { name: nsREX + 'RCanvasDisplayItem', icon: 'img_canvas', draw: () => import_v7().then(h => h.drawRPadSnapshot), opt: '', expand_item: fPrimitives }, - { name: nsREX + 'RText', icon: 'img_text', draw: () => import_v7('more').then(h => h.drawText), opt: '', direct: 'v7', csstype: 'text' }, - { name: nsREX + 'RFrameTitle', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFrameTitle), opt: '', direct: 'v7', csstype: 'title' }, - { name: nsREX + 'RPaletteDrawable', icon: 'img_text', class: () => import_v7('more').then(h => h.RPalettePainter), opt: '' }, - { name: nsREX + 'RLine', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawLine), opt: '', direct: 'v7', csstype: 'line' }, - { name: nsREX + 'RBox', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawBox), opt: '', direct: 'v7', csstype: 'box' }, - { name: nsREX + 'RMarker', icon: 'img_graph', draw: () => import_v7('more').then(h => h.drawMarker), opt: '', direct: 'v7', csstype: 'marker' }, - { name: nsREX + 'RPave', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPavePainter), opt: '' }, - { name: nsREX + 'RLegend', icon: 'img_graph', class: () => import_v7('pave').then(h => h.RLegendPainter), opt: '' }, - { name: nsREX + 'RPaveText', icon: 'img_pavetext', class: () => import_v7('pave').then(h => h.RPaveTextPainter), opt: '' }, - { name: nsREX + 'RFrame', icon: 'img_frame', draw: () => import_v7().then(h => h.drawRFrame), opt: '' }, - { name: nsREX + 'RFont', icon: 'img_text', draw: () => import_v7().then(h => h.drawRFont), opt: '', direct: 'v7', csstype: 'font' }, - { name: nsREX + 'RAxisDrawable', icon: 'img_frame', draw: () => import_v7().then(h => h.drawRAxis), opt: '' }, - { name: nsREX + 'RTreeMapPainter', class: () => import('./draw/RTreeMapPainter.mjs').then(h => h.RTreeMapPainter), opt: '' } -], cache: {} }; - - -/** @summary Register draw function for the class - * @param {object} args - arguments - * @param {string|RegExp} args.name - class name or regexp pattern or '*' - * @param {Function} [args.func] - draw function - * @param {string} [args.sameas] - let behave same as specified class - * @param {Function} [args.draw] - async function to load draw function - * @param {Function} [args.class] - async function to load painter class with static draw function - * @param {boolean} [args.direct] - if true, function is just Redraw() method of ObjectPainter - * @param {string} [args.opt] - list of supported draw options (separated with semicolon) like 'col;scat;' - * @param {string} [args.icon] - icon name shown for the class in hierarchy browser - * @param {string} [args.draw_field] - draw only data member from object, like fHistogram - * @param {string} [args.noinspect] - disable inspect - * @param {string} [args.noexpand] - disable expand - * @param {string} [args.pm] - always show plus or minus sign even when no child items exists - * @desc List of supported draw options could be provided, separated with ';' - * If args.name parameter is '*', function will be invoked before object drawing. - * If such function does not return value - normal drawing will be continued. - * @protected */ -function addDrawFunc(args) { - if (args?.name === '*') - internals._alt_draw = isFunc(args.func) ? args.func : null; - else - drawFuncs.lst.push(args); - return args; -} - -/** @summary return draw handle for specified item kind - * @desc kind could be ROOT.TH1I for ROOT classes or just - * kind string like 'Command' or 'Text' - * selector can be used to search for draw handle with specified option (string) - * or just sequence id - * @private */ -function getDrawHandle(kind, selector) { - if (!isStr(kind)) - return null; - if (selector === '') - selector = null; - - let first = null; - - if ((selector === null) && (kind in drawFuncs.cache)) - return drawFuncs.cache[kind]; - - const search = getTypeForKind(kind) || `kind:${kind}`; - let counter = 0; - for (let i = 0; i < drawFuncs.lst.length; ++i) { - const h = drawFuncs.lst[i]; - if (isStr(h.name)) { - if (h.name !== search) - continue; - } else if (!search.match(h.name)) - continue; - - if (h.sameas) { - const hs = getDrawHandle(getKindForType(h.sameas), selector); - if (hs) { - for (const key in hs) { - if (h[key] === undefined) - h[key] = hs[key]; - } - delete h.sameas; - } - return h; - } - - if ((selector === null) || (selector === undefined)) { - // store found handle in cache, can reuse later - if (!(kind in drawFuncs.cache)) - drawFuncs.cache[kind] = h; - return h; - } else if (isStr(selector)) { - if (!first) - first = h; - // if draw option specified, check it present in the list - - if (selector === '::expand') { - if (('expand' in h) || ('expand_item' in h)) - return h; - } else if ('opt' in h) { - const opts = h.opt.split(';'); - for (let j = 0; j < opts.length; ++j) { - if (opts[j].toLowerCase() === selector.toLowerCase()) - return h; - } - } - } else if (selector === counter) - return h; - ++counter; - } - - return first; -} - -/** @summary Returns true if handle can be potentially drawn - * @private */ -function canDrawHandle(h) { - if (isStr(h)) - h = getDrawHandle(h); - if (!isObject(h)) - return false; - return (h.func || h.class || h.draw || h.draw_field || h.opt === 'inspect'); -} - -/** @summary Provide draw settings for specified class or kind - * @private */ -function getDrawSettings(kind, selector) { - const res = { opts: null, inspect: false, expand: false, draw: false, handle: null }; - if (!isStr(kind)) - return res; - let isany = false, noinspect = false, canexpand = false; - if (!isStr(selector)) - selector = ''; - - for (let cnt = 0; cnt < 1000; ++cnt) { - const h = getDrawHandle(kind, cnt); - if (!h) - break; - if (!res.handle) - res.handle = h; - if (h.noinspect) - noinspect = true; - if (h.noappend) - res.noappend = true; - if (h.expand || h.get_expand || h.expand_item || h.can_expand) - canexpand = true; - if (!h.func && !h.class && !h.draw) - break; - isany = true; - if (h.opt === undefined) - continue; - let opt = h.opt; - if (isStr(h.opt2)) - opt += h.opt2; - const opts = opt.split(';'); - for (let i = 0; i < opts.length; ++i) { - opts[i] = opts[i].toLowerCase(); - if (opts[i].indexOf('same') === 0) { - res.has_same = true; - if (selector.indexOf('nosame') >= 0) - continue; - } - - if (res.opts === null) - res.opts = []; - if (res.opts.indexOf(opts[i]) < 0) - res.opts.push(opts[i]); - } - if (h.theonly) - break; - } - - if (selector.indexOf('noinspect') >= 0) - noinspect = true; - - if (isany && (res.opts === null)) - res.opts = ['']; - - // if no any handle found, let inspect ROOT-based objects - if (!isany && getTypeForKind(kind) && !noinspect) - res.opts = []; - - if (!noinspect && res.opts) - res.opts.push(kInspect); - - res.inspect = !noinspect; - res.expand = canexpand; - res.draw = Boolean(res.opts); - - return res; -} - -/** @summary Set default draw option for provided class - * @example - import { setDefaultDrawOpt } from 'https://root.cern/js/latest/modules/draw.mjs'; - setDefaultDrawOpt('TH1', 'text'); - setDefaultDrawOpt('TH2', 'col'); */ -function setDefaultDrawOpt(classname, opt) { - if (!classname) - return; - if ((opt === undefined) && isStr(classname) && (classname.indexOf(':') > 0)) { - // special usage to set list of options like TH2:lego2;TH3:glbox2 - opt.split(';').forEach(part => { - const arr = part.split(':'); - if (arr.length >= 1) - setDefaultDrawOpt(arr[0], arr[1] || ''); - }); - } else { - const handle = getDrawHandle(getKindForType(classname), 0); - if (handle) - handle.dflt = opt; - } -} - -/** @summary Draw object in specified HTML element with given draw options. - * @param {string|object} dom - id of div element to draw or directly DOMElement - * @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc} - * @param {string} opt - draw options separated by space, comma or semicolon - * @return {Promise} with painter object - * @public - * @desc An extensive list of support draw options can be found on [examples page]{@link https://root.cern/js/latest/examples.htm} - * @example - * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; - * import { draw } from 'https://root.cern/js/latest/modules/draw.mjs'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let obj = await file.readObject('hpxpy;1'); - * await draw('drawing', obj, 'colz;logx;gridx;gridy'); */ -async function draw(dom, obj, opt) { - if (!isObject(obj)) - return Promise.reject(Error('not an object in draw call')); - - if (isStr(opt) && (opt.indexOf(kInspect) === 0)) - return import_h().then(h => h.drawInspector(dom, obj, opt)); - - let handle, type_info; - if ('_typename' in obj) { - type_info = 'type ' + obj._typename; - handle = getDrawHandle(getKindForType(obj._typename), opt); - } else if ('_kind' in obj) { - type_info = 'kind ' + obj._kind; - handle = getDrawHandle(obj._kind, opt); - } else - return import_h().then(h => h.drawInspector(dom, obj, opt)); - - // this is case of unsupported class, close it normally - if (!handle) - return Promise.reject(Error(`Object of ${type_info} cannot be shown with draw`)); - - if (handle.dummy) - return null; - - if (handle.draw_field && obj[handle.draw_field]) - return draw(dom, obj[handle.draw_field], opt || handle.draw_field_opt); - - if (internals._alt_draw && !handle.transform) { - const v = internals._alt_draw(dom, obj, opt); - if (v) - return v; - } - - if (!canDrawHandle(handle)) { - if (opt && (opt.indexOf('same') >= 0)) { - const main_painter = getElementMainPainter(dom); - - if (isFunc(main_painter?.performDrop)) - return main_painter.performDrop(obj, '', null, opt); - } - - return Promise.reject(Error(`Function not specified to draw object ${type_info}`)); - } - - function performDraw() { - let promise, painter; - if (handle.direct === 'v7') { - promise = import('./gpad/RCanvasPainter.mjs').then(v7h => { - painter = new v7h.RObjectPainter(dom, obj, opt, handle.csstype); - painter.redraw = handle.func; - return v7h.ensureRCanvas(painter); - }).then(() => painter.redraw()); - } else if (handle.direct) { - painter = new ObjectPainter(dom, obj, opt); - painter.redraw = handle.func; - promise = import_canvas().then(v6h => v6h.ensureTCanvas(painter, handle.frame || false)) - .then(() => painter.redraw()); - } else - promise = getPromise(handle.func(dom, obj, opt)); - - return promise.then(p => { - if (!painter) - painter = p; - if (painter === false) - return null; - if (!painter) - throw Error(`Fail to draw object ${type_info}`); - if (isObject(painter) && !painter.options) - painter.options = { original: opt || '' }; // keep original draw options - return painter; - }); - } - - if (isFunc(handle.func)) - return performDraw(); - - let promise; - - if (isFunc(handle.class)) { - // class coded as async function which returns class handle - // simple extract class and access class.draw method - promise = handle.class().then(cl => { handle.func = cl.draw; }); - } else if (isFunc(handle.draw)) { - // draw function without special class - promise = handle.draw().then(h => { handle.func = h; }); - } else if (!handle.func || !isStr(handle.func)) - return Promise.reject(Error(`Draw function or class not specified to draw ${type_info}`)); - else { - let func = findFunction(handle.func); - if (isFunc(func)) { - handle.func = func; - return performDraw(); - } - let modules = null; - if (isStr(handle.script)) { - if (handle.script.indexOf('modules:') === 0) - modules = handle.script.slice(8); - else if (handle.script.indexOf('.mjs') > 0) - modules = handle.script; - } - - if (!modules && !handle.prereq && !handle.script) - return Promise.reject(Error(`Prerequicities to load ${handle.func} are not specified`)); - - let init_promise = Promise.resolve(true); - if (modules) - init_promise = loadModules(modules); - else if (!internals.ignore_v6) { - init_promise = _ensureJSROOT().then(v6 => { - const pr = handle.prereq ? v6.require(handle.prereq) : Promise.resolve(true); - return pr.then(() => { - if (handle.script) - return loadScript(handle.script); - }).then(() => v6._complete_loading()); - }); - } - - promise = init_promise.then(() => { - func = findFunction(handle.func); - if (!isFunc(func)) - return Promise.reject(Error(`Fail to find function ${handle.func} after loading ${handle.prereq || handle.script}`)); - - handle.func = func; - }); - } - - return promise.then(() => performDraw()); -} - -/** @summary Redraw object in specified HTML element with given draw options. - * @param {string|object} dom - id of div element to draw or directly DOMElement - * @param {object} obj - object to draw, object type should be registered before with {@link addDrawFunc} - * @param {string} opt - draw options - * @return {Promise} with painter object - * @desc If drawing was not done before, it will be performed with {@link draw}. - * Otherwise drawing content will be updated - * @public - * @example - * import { openFile } from 'https://root.cern/js/latest/modules/io.mjs'; - * import { draw, redraw } from 'https://root.cern/js/latest/modules/draw.mjs'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let obj = await file.readObject('hpxpy;1'); - * await draw('drawing', obj, 'colz'); - * let cnt = 0; - * setInterval(() => { - * obj.fTitle = `Next iteration ${cnt++}`; - * redraw('drawing', obj, 'colz'); - * }, 1000); */ -async function redraw(dom, obj, opt) { - if (!isObject(obj)) - return Promise.reject(Error('not an object in redraw')); - - const can_painter = getElementCanvPainter(dom); - let handle, res_painter = null, redraw_res; - if (obj._typename) - handle = getDrawHandle(getKindForType(obj._typename)); - if (handle?.draw_field && obj[handle.draw_field]) - obj = obj[handle.draw_field]; - - if (can_painter) { - if (can_painter.matchObjectType(obj._typename)) { - redraw_res = can_painter.redrawObject(obj, opt); - if (redraw_res) - res_painter = can_painter; - } else { - can_painter.forEachPainterInPad(painter => { - if (!res_painter && painter.matchObjectType(obj._typename)) { - redraw_res = painter.redrawObject(obj, opt); - if (redraw_res) - res_painter = painter; - } - }, 'objects'); - } - } else { - const top = new BasePainter(dom).getTopPainter(); - // base painter do not have this method, if it there use it - // it can be object painter here or can be specially introduce method to handling redraw! - if (isFunc(top?.redrawObject)) { - redraw_res = top.redrawObject(obj, opt); - if (redraw_res) - res_painter = top; - } - } - - if (res_painter) - return getPromise(redraw_res).then(() => res_painter); - - cleanup(dom); - - return draw(dom, obj, opt); -} - -/** @summary Create three.js model for object - * @param {object} obj - object - * @param {string} opt - draw options - * @return {Promise} with three.js model */ - -async function build3d(obj, opt) { - if (!isObject(obj) || !obj?._typename) - return Promise.reject(Error('not an object in build3d')); - - const handle = getDrawHandle(getKindForType(obj._typename)); - if (!handle?.class && !handle.build3d) - return Promise.reject(Error(`not able to create three.js for ${obj._typename}`)); - - if (handle.build3d) - return handle.build3d().then(func => func(obj, opt)); - - return handle.class().then(cl => { - if (!isFunc(cl?.build3d)) - return Promise.reject(Error(`painter class for ${obj._typename} does not implement build3d method`)); - - return cl.build3d(obj, opt); - }); -} - -/** @summary Scan streamer infos for derived classes - * @desc Assign draw functions for such derived classes - * @private */ -function addStreamerInfosForPainter(lst) { - if (!lst) - return; - - const basics = [clTObject, clTNamed, clTString, 'TCollection', clTAttLine, clTAttFill, clTAttMarker, clTAttText]; - - function checkBaseClasses(si, lvl) { - const element = si.fElements?.arr[0]; - if ((element?.fTypeName !== kBaseClass) || (lvl > 4)) - return null; - // exclude very basic classes - if (basics.indexOf(element.fName) >= 0) - return null; - - let handle = getDrawHandle(getKindForType(element.fName)); - if (handle && !handle.for_derived) - handle = null; - - // now try find that base class of base in the list - if (handle === null) { - for (let k = 0; k < lst.arr.length; ++k) { - if (lst.arr[k].fName === element.fName) { - handle = checkBaseClasses(lst.arr[k], lvl + 1); - break; - } - } - } - - return handle?.for_derived ? handle : null; - } - - lst.arr.forEach(si => { - if (getDrawHandle(getKindForType(si.fName)) !== null) - return; - - const handle = checkBaseClasses(si, 0); - if (handle) { - const newhandle = Object.assign({}, handle); - delete newhandle.for_derived; // should we disable? - newhandle.name = si.fName; - addDrawFunc(newhandle); - } - }); -} - -/** @summary Create SVG/PNG/JPEG image for provided object. - * @desc Function especially useful in Node.js environment to generate images for - * supported ROOT classes, but also can be used from web browser - * @param {object} args - function settings - * @param {object} args.object - object for the drawing - * @param {string} [args.format = 'svg'] - image format like 'svg' (default), 'png' or 'jpeg' - * @param {string} [args.option = ''] - draw options - * @param {number} [args.width = 1200] - image width - * @param {number} [args.height = 800] - image height - * @param {boolean} [args.as_buffer = false] - returns image as Buffer instance, can store directly to file - * @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object - * @return {Promise} with image code - svg as is, png/jpeg as base64 string or buffer (if as_buffer) specified - * @example - * // how makeImage can be used in node.js - * import { openFile, makeImage } from 'jsroot'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let object = await file.readObject('hpxpy;1'); - * let png64 = await makeImage({ format: 'png', object, option: 'colz', width: 1200, height: 800 }); - * let pngbuf = await makeImage({ format: 'png', as_buffer: true, object, option: 'colz', width: 1200, height: 800 }); */ -async function makeImage(args) { - if (!args) - args = {}; - - if (!isObject(args.object)) - return Promise.reject(Error('No object specified to generate SVG')); - if (!args.format) - args.format = 'svg'; - if (!args.width) - args.width = settings.CanvasWidth; - if (!args.height) - args.height = settings.CanvasHeight; - - async function build(main) { - main.attr('width', args.width).attr('height', args.height) - .style('width', args.width + 'px').style('height', args.height + 'px') - .property('_batch_use_canvsize', args.use_canvas_size ?? false) - .property('_batch_mode', true) - .property('_batch_format', args.format !== 'svg' ? args.format : null); - - function complete(res) { - cleanup(main.node()); - main.remove(); - return res; - } - - return draw(main.node(), args.object, args.option || '').then(() => { - if (args.format !== 'svg') { - const only_img = main.select('svg').selectChild('image'); - if (!only_img.empty()) { - const href = only_img.attr('href'); - - if (args.as_buffer) { - const p = href.indexOf('base64,'), - str = atob_func(href.slice(p + 7)), - buf = new ArrayBuffer(str.length), - bufView = new Uint8Array(buf); - for (let i = 0; i < str.length; i++) - bufView[i] = str.charCodeAt(i); - return isNodeJs() ? Buffer.from(buf) : buf; - } - return href; - } - } - - const mainsvg = main.select('svg'), - style_filter = mainsvg.style('filter'); - - mainsvg.attr('xmlns', nsSVG) - .attr('style', null).attr('class', null).attr('x', null).attr('y', null); - - if (!mainsvg.attr('width') && !mainsvg.attr('height')) - mainsvg.attr('width', args.width).attr('height', args.height); - - if (style_filter && (style_filter !== 'none')) - mainsvg.style('filter', style_filter); - - function clear_element() { - const elem = d3_select(this); - if (elem.style('display') === 'none') - elem.remove(); - } - - main.selectAll('g.root_frame').each(clear_element); - main.selectAll('svg').each(clear_element); - - let svg; - if (args.format === 'pdf') - svg = { node: mainsvg.node(), width: args.width, height: args.height, can_modify: true }; - else { - svg = compressSVG(main.html()); - if (args.format === 'svg') - return complete(svg); - } - - return svgToImage(svg, args.format, args).then(complete); - }); - } - - return isNodeJs() - ? _loadJSDOM().then(handle => build(handle.body.append('div'))) - : build(d3_select('body').append('div').style('display', 'none')); -} - - -/** @summary Create SVG image for provided object. - * @desc Function especially useful in Node.js environment to generate images for - * supported ROOT classes - * @param {object} args - function settings - * @param {object} args.object - object for the drawing - * @param {string} [args.option] - draw options - * @param {number} [args.width = 1200] - image width - * @param {number} [args.height = 800] - image height - * @param {boolean} [args.use_canvas_size = false] - if configured used size stored in TCanvas object - * @return {Promise} with svg code - * @example - * // how makeSVG can be used in node.js - * import { openFile, makeSVG } from 'jsroot'; - * let file = await openFile('https://root.cern/js/files/hsimple.root'); - * let object = await file.readObject('hpxpy;1'); - * let svg = await makeSVG({ object, option: 'lego2,pal50', width: 1200, height: 800 }); */ -async function makeSVG(args) { - if (!args) - args = {}; - args.format = 'svg'; - return makeImage(args); -} - -function assignPadPainterDraw(PadPainterClass) { - PadPainterClass.prototype.drawObject = draw; - PadPainterClass.prototype.getObjectDrawSettings = getDrawSettings; -} - -// only now one can draw primitives in the canvas -assignPadPainterDraw(TPadPainter); - -import_geo = async function() { - return import('./geom/TGeoPainter.mjs').then(geo => { - const handle = getDrawHandle(getKindForType('TGeoVolumeAssembly')); - if (handle) - handle.icon = 'img_geoassembly'; - return geo; - }); -}; - -// load v7 only on demand -import_v7 = async function(arg) { - return import('./gpad/RCanvasPainter.mjs').then(h => { - // only now one can draw primitives in the canvas - assignPadPainterDraw(h.RPadPainter); - switch (arg) { - case 'more': return import('./draw/v7more.mjs'); - case 'pave': return import('./hist/RPavePainter.mjs'); - } - return h; - }); -}; - -// to avoid cross-dependency between modules -Object.assign(internals, { addStreamerInfosForPainter, addDrawFunc, setDefaultDrawOpt, makePDF }); - -Object.assign(internals.jsroot, { draw, redraw, makeSVG, makeImage, addDrawFunc }); - -export { addDrawFunc, getDrawHandle, canDrawHandle, getDrawSettings, setDefaultDrawOpt, - draw, redraw, cleanup, build3d, makeSVG, makeImage, assignPadPainterDraw }; diff --git a/js/modules/draw/RNTuple.mjs b/js/modules/draw/RNTuple.mjs deleted file mode 100644 index 99634af0feeb4..0000000000000 --- a/js/modules/draw/RNTuple.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import { isStr } from '../core.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { createTreePlayer, treeDrawProgress } from './TTree.mjs'; -import { rntupleDraw } from '../rntuple.mjs'; - -/** @summary function called from draw() - * @desc just envelope for real TTree::Draw method which do the main job - * Can be also used for the branch and leaf object - * @private */ -async function drawRNTuple(dom, obj, opt) { - const args = {}; - let tuple; - - if (obj?.$tuple && obj.$field) { - // case of fictional ROOT::RNTupleField - tuple = obj.$tuple; - args.expr = obj._name; - if (isStr(opt) && opt.indexOf('dump') === 0) { - args.expr += '>>' + opt; - args.branch = obj.$field; - args.copy_fields = false; // no need to copy fields, reading is simple - } else if (opt) - args.expr += opt; - } else { - tuple = obj; - args.expr = opt; - if (args.expr && isStr(args.expr)) { - const p = args.expr.indexOf('player'); - if (p === 0) { - args.player = true; - args.expr = args.expr.slice(6); - if (args.expr[0] === ':') - args.expr = args.expr.slice(1); - } else if ((p >= 0) && (p === args.expr.length - 6)) { - args.player = true; - args.expr = args.expr.slice(0, p); - if ((p > 0) && (args.expr[p - 1] === ';')) - args.expr = args.expr.slice(0, p - 1); - } - } - } - - if (!tuple) - throw Error('No RNTuple object available for drawing'); - - let painter; - - if (args.player) { - painter = new ObjectPainter(dom, obj, opt); - createTreePlayer(painter, null, tuple); - painter.rntupleDraw = rntupleDraw; - painter.showPlayer(args); - args.drawid = painter.drawid; - } else - args.drawid = dom; - - args.progress = treeDrawProgress.bind(args); - - if (args.expr || args.branch) - return rntupleDraw(tuple, args).then(res => args.progress(res, true)); - - return painter; -} - -export { drawRNTuple }; diff --git a/js/modules/draw/RTreeMapPainter.mjs b/js/modules/draw/RTreeMapPainter.mjs deleted file mode 100644 index b82ce1a77715c..0000000000000 --- a/js/modules/draw/RTreeMapPainter.mjs +++ /dev/null @@ -1,387 +0,0 @@ -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { RTreeMapTooltip } from './RTreeMapTooltip.mjs'; - - -function computeFnv(str) { - const FNV_offset = 14695981039346656037n, FNV_prime = 1099511628211n; - let h = FNV_offset; - for (let i = 0; i < str.length; ++i) { - const octet = BigInt(str.charCodeAt(i) & 0xFF); - h ^= octet; - h *= FNV_prime; - } - return h; -} - -class RTreeMapPainter extends ObjectPainter { - - static CONSTANTS = { - STROKE_WIDTH: 0.15, - STROKE_COLOR: 'black', - - COLOR_HOVER_BOOST: 10, - - DATA_UNITS: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'], - - MAIN_TREEMAP: { LEFT: 0.025, BOTTOM: 0.05, RIGHT: 0.825, TOP: 0.9 }, - - LEGEND: { - START_Y: 0.835, - ITEM_HEIGHT: 0.05, - BOX_WIDTH: 0.05, - TEXT_OFFSET_X: 0.01, - TEXT_OFFSET_Y: 0.01, - TEXT_LINE_SPACING: 0.015, - MAX_ITEMS: 10 - }, - - TEXT: { SIZE_VW: 0.6, MIN_RECT_WIDTH: 0.025, MIN_RECT_HEIGHT: 0.05, PADDING: 10, LEAF_OFFSET_Y: 0.015 }, - - INDENT: 0.005, - LEGEND_INDENT_MULTIPLIER: 4 - }; - - constructor(dom, obj, opt) { - super(dom, obj, opt); - this.tooltip = new RTreeMapTooltip(this); - this.rootIndex = 0; - this.parentIndices = []; - } - - cleanup() { - if (this._frame_hidden) { - delete this._frame_hidden; - this.getPadPainter()?.getFrameSvg().style('display', null); - } - - this.tooltip.cleanup(); - super.cleanup(); - } - - appendRect(begin, end, color, strokeColor = RTreeMapPainter.CONSTANTS.STROKE_COLOR, strokeWidth = RTreeMapPainter.CONSTANTS.STROKE_WIDTH, node = null) { - const rect = this.getG() - .append('rect') - .attr('x', this.axisToSvg('x', begin.x, this.isndc)) - .attr('y', this.axisToSvg('y', begin.y, this.isndc)) - .attr('width', `${Math.abs(end.x - begin.x) * 100}%`) - .attr('height', `${Math.abs(end.y - begin.y) * 100}%`) - .attr('fill', color) - .attr('stroke', strokeColor) - .attr('stroke-width', strokeWidth) - .attr('pointer-events', 'fill'); - - if (node) { - rect.datum(node); - this.attachPointerEventsTreeMap(rect, node); - } - return rect; - } - - appendText(content, pos, size, color, anchor = 'start') { - return this.getG() - .append('text') - .attr('x', this.axisToSvg('x', pos.x, this.isndc)) - .attr('y', this.axisToSvg('y', pos.y, this.isndc)) - .attr('font-size', `${size}vw`) - .attr('fill', color) - .attr('text-anchor', anchor) - .attr('pointer-events', 'none') - .text(content); - } - - getRgbList(rgbStr) { return rgbStr.slice(4, -1).split(',').map((x) => parseInt(x)); } - - toRgbStr(rgbList) { return `rgb(${rgbList.join()})`; } - - attachPointerEventsTreeMap(element, node) { - const original_color = element.attr('fill'), hovered_color = this.toRgbStr(this.getRgbList(original_color) - .map((color) => Math.min(color + RTreeMapPainter.CONSTANTS.COLOR_HOVER_BOOST, 255))), - mouseenter = () => { - element.attr('fill', hovered_color); - this.tooltip.content = this.tooltip.generateTooltipContent(node); - this.tooltip.x = 0; - this.tooltip.y = 0; - }, - mouseleave = () => { - element.attr('fill', original_color); - this.tooltip.hideTooltip(); - }, - mousemove = (event) => { - this.tooltip.x = event.pageX; - this.tooltip.y = event.pageY; - this.tooltip.showTooltip(); - }, - click = () => { - const obj = this.getObject(), nodeIndex = obj.fNodes.findIndex((elem) => elem === node); - if (nodeIndex === this.rootIndex) - this.rootIndex = this.parentIndices[nodeIndex]; - else { - let parentIndex = nodeIndex; - while (this.parentIndices[parentIndex] !== this.rootIndex) - parentIndex = this.parentIndices[parentIndex]; - this.rootIndex = parentIndex; - if (obj.fNodes[parentIndex].fNChildren === 0) - this.rootIndex = this.parentIndices[nodeIndex]; - } - this.redraw(); - }; - this.attachPointerEvents(element, { mouseenter, mouseleave, mousemove, click }); - } - - attachPointerEventsLegend(element, type) { - const rects = this.getG().selectAll('rect'), - mouseenter = () => { rects.filter((node) => node !== undefined && node.fType !== type).attr('opacity', '0.5'); }, - mouseleave = () => { rects.attr('opacity', '1'); }; - this.attachPointerEvents(element, { mouseenter, mouseleave, mousemove: () => {}, click: () => {} }); - } - - attachPointerEvents(element, events) { - for (const [key, value] of Object.entries(events)) - element.on(key, value); - } - - computeColor(n) { - const hash = Number(computeFnv(String(n)) & 0xFFFFFFFFn), - r = (hash >> 16) & 0xFF, - g = (hash >> 8) & 0xFF, - b = hash & 0xFF; - return this.toRgbStr([r, g, b]); - } - - getDataStr(bytes) { - const units = RTreeMapPainter.CONSTANTS.DATA_UNITS, - order = Math.floor(Math.log10(bytes) / 3), - finalSize = bytes / Math.pow(1000, order); - return `${finalSize.toFixed(2)}${units[order]}`; - } - - computeWorstRatio(row, width, height, totalSize, horizontalRows) { - if (row.length === 0) - return 0; - - const sumRow = row.reduce((sum, child) => sum + child.fSize, 0); - if (sumRow === 0) - return 0; - - let worstRatio = 0; - for (const child of row) { - const ratio = horizontalRows ? (child.fSize * width * totalSize) / (sumRow * sumRow * height) - : (child.fSize * height * totalSize) / (sumRow * sumRow * width), - aspectRatio = Math.max(ratio, 1 / ratio); - if (aspectRatio > worstRatio) - worstRatio = aspectRatio; - } - return worstRatio; - } - - squarifyChildren(children, rect, horizontalRows, totalSize) { - const width = rect.topRight.x - rect.bottomLeft.x, - height = rect.topRight.y - rect.bottomLeft.y, - remaining = [...children].sort((a, b) => b.fSize - a.fSize), - result = [], - remainingBegin = { ...rect.bottomLeft }; - - while (remaining.length > 0) { - const row = []; - let currentWorstRatio = Infinity; - const remainingWidth = rect.topRight.x - remainingBegin.x, - remainingHeight = rect.topRight.y - remainingBegin.y; - - if (remainingWidth <= 0 || remainingHeight <= 0) - break; - - while (remaining.length > 0) { - row.push(remaining.shift()); - const newWorstRatio = - this.computeWorstRatio(row, remainingWidth, remainingHeight, totalSize, horizontalRows); - if (newWorstRatio > currentWorstRatio) { - remaining.unshift(row.pop()); - break; - } - currentWorstRatio = newWorstRatio; - } - - const sumRow = row.reduce((sum, child) => sum + child.fSize, 0); - if (sumRow === 0) - continue; - - const dimension = horizontalRows ? (sumRow / totalSize * height) : (sumRow / totalSize * width); - let position = 0; - - for (const child of row) { - const childDimension = child.fSize / sumRow * (horizontalRows ? width : height), - childBegin = horizontalRows ? { x: remainingBegin.x + position, y: remainingBegin.y } - : { x: remainingBegin.x, y: remainingBegin.y + position }, - childEnd = horizontalRows - ? { x: remainingBegin.x + position + childDimension, y: remainingBegin.y + dimension } - : { x: remainingBegin.x + dimension, y: remainingBegin.y + position + childDimension }; - - result.push({ node: child, rect: { bottomLeft: childBegin, topRight: childEnd } }); - position += childDimension; - } - - if (horizontalRows) - remainingBegin.y += dimension; - else - remainingBegin.x += dimension; - } - return result; - } - - drawLegend() { - const obj = this.getObject(), - diskMap = {}; - - let stack = [this.rootIndex]; - while (stack.length > 0) { - const node = obj.fNodes[stack.pop()]; - if (node.fNChildren === 0) - diskMap[node.fType] = (diskMap[node.fType] || 0) + node.fSize; - stack = stack.concat(Array.from({ length: node.fNChildren }, (_, a) => a + node.fChildrenIdx)); - } - - const diskEntries = Object.entries(diskMap) - .sort((a, b) => b[1] - a[1]) - .slice(0, RTreeMapPainter.CONSTANTS.LEGEND.MAX_ITEMS) - .filter(([, size]) => size > 0), - - legend = RTreeMapPainter.CONSTANTS.LEGEND; - - diskEntries.forEach(([typeName, size], index) => { - const posY = legend.START_Y - index * legend.ITEM_HEIGHT, - posX = legend.START_Y + legend.ITEM_HEIGHT + legend.TEXT_OFFSET_X, - textSize = RTreeMapPainter.CONSTANTS.TEXT.SIZE_VW, - - rect = this.appendRect({ x: legend.START_Y, y: posY }, - { x: legend.START_Y + legend.ITEM_HEIGHT, y: posY - legend.ITEM_HEIGHT }, - this.computeColor(typeName)); - this.attachPointerEventsLegend(rect, typeName); - - const diskOccupPercent = `${(size / obj.fNodes[this.rootIndex].fSize * 100).toFixed(2)}%`, - diskOccup = `(${this.getDataStr(size)} / ${this.getDataStr(obj.fNodes[this.rootIndex].fSize)})`; - - [typeName, diskOccup, diskOccupPercent].forEach( - (content, i) => - this.appendText(content, { x: posX, y: posY - legend.TEXT_OFFSET_Y - legend.TEXT_LINE_SPACING * (i) }, - textSize, 'black')); - }); - } - - trimText(textElement, rect) { - const nodeElem = textElement.node(); - let textContent = nodeElem.textContent; - const availablePx = Math.abs(this.axisToSvg('x', rect.topRight.x, this.isndc) - - this.axisToSvg('x', rect.bottomLeft.x, this.isndc)) - - RTreeMapPainter.CONSTANTS.TEXT.PADDING; - - while (nodeElem.getComputedTextLength && nodeElem.getComputedTextLength() > availablePx && textContent.length > 0) { - textContent = textContent.slice(0, -1); - nodeElem.textContent = textContent + '…'; - } - return textContent; - } - - drawTreeMap(node, rect, depth = 0) { - const isLeaf = node.fNChildren === 0, - color = isLeaf ? this.computeColor(node.fType) : 'rgb(100,100,100)'; - this.appendRect({ x: rect.bottomLeft.x, y: rect.topRight.y }, { x: rect.topRight.x, y: rect.bottomLeft.y }, color, - RTreeMapPainter.CONSTANTS.STROKE_COLOR, RTreeMapPainter.CONSTANTS.STROKE_WIDTH, node); - - const rectWidth = rect.topRight.x - rect.bottomLeft.x, - rectHeight = rect.topRight.y - rect.bottomLeft.y, - labelBase = `${node.fName} (${this.getDataStr(node.fSize)})`, - - textConstants = RTreeMapPainter.CONSTANTS.TEXT, - textSize = (rectWidth <= textConstants.MIN_RECT_WIDTH || rectHeight <= textConstants.MIN_RECT_HEIGHT) - ? 0 - : textConstants.SIZE_VW; - - if (textSize > 0) { - const textElement = this.appendText(labelBase, { - x: rect.bottomLeft.x + (isLeaf ? rectWidth / 2 : RTreeMapPainter.CONSTANTS.INDENT), - y: isLeaf ? (rect.bottomLeft.y + rect.topRight.y) / 2 : (rect.topRight.y - textConstants.LEAF_OFFSET_Y) - }, - textSize, 'white', isLeaf ? 'middle' : 'start'); - textElement.textContent = this.trimText(textElement, rect); - } - - if (!isLeaf && node.fNChildren > 0) { - const obj = this.getObject(), - children = obj.fNodes.slice(node.fChildrenIdx, node.fChildrenIdx + node.fNChildren), - totalSize = children.reduce((sum, child) => sum + child.fSize, 0); - - if (totalSize > 0) { - const indent = RTreeMapPainter.CONSTANTS.INDENT, - innerRect = { - bottomLeft: { x: rect.bottomLeft.x + indent, y: rect.bottomLeft.y + indent }, - topRight: { - x: rect.topRight.x - indent, - y: rect.topRight.y - indent * RTreeMapPainter.CONSTANTS.LEGEND_INDENT_MULTIPLIER - } - }, - - width = innerRect.topRight.x - innerRect.bottomLeft.x, - height = innerRect.topRight.y - innerRect.bottomLeft.y, - horizontalRows = width > height, - - rects = this.squarifyChildren(children, innerRect, horizontalRows, totalSize); - rects.forEach( - ({ node: childNode, rect: childRect }) => { this.drawTreeMap(childNode, childRect, depth + 1); }); - } - } - } - - createParentIndices() { - const obj = this.getObject(); - this.parentIndices = new Array(obj.fNodes.length).fill(0); - obj.fNodes.forEach((node, index) => { - for (let i = node.fChildrenIdx; i < node.fChildrenIdx + node.fNChildren; i++) - this.parentIndices[i] = index; - }); - } - - getDirectory() { - const obj = this.getObject(); - let result = '', - currentIndex = this.rootIndex; - while (currentIndex !== 0) { - result = obj.fNodes[currentIndex].fName + '/' + result; - currentIndex = this.parentIndices[currentIndex]; - } - return result; - } - - redraw() { - const svg = this.getPadPainter().getFrameSvg(); - if (!svg.empty()) { - svg.style('display', 'none'); - this._frame_hidden = true; - } - - const obj = this.getObject(); - this.createG(); - this.isndc = true; - - if (obj.fNodes && obj.fNodes.length > 0) { - this.createParentIndices(); - const mainArea = RTreeMapPainter.CONSTANTS.MAIN_TREEMAP; - this.drawTreeMap( - obj.fNodes[this.rootIndex], - { bottomLeft: { x: mainArea.LEFT, y: mainArea.BOTTOM }, topRight: { x: mainArea.RIGHT, y: mainArea.TOP } }); - this.drawLegend(); - this.appendText(this.getDirectory(), { x: RTreeMapPainter.CONSTANTS.MAIN_TREEMAP.LEFT, y: RTreeMapPainter.CONSTANTS.MAIN_TREEMAP.TOP + 0.01 }, - RTreeMapPainter.CONSTANTS.TEXT.SIZE_VW, 'black'); - } - return this; - } - - static async draw(dom, obj, opt) { - const painter = new RTreeMapPainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } - -} // class RTreeMapPainter - - -export { RTreeMapPainter }; diff --git a/js/modules/draw/RTreeMapTooltip.mjs b/js/modules/draw/RTreeMapTooltip.mjs deleted file mode 100644 index 9e3b669e8d2f2..0000000000000 --- a/js/modules/draw/RTreeMapTooltip.mjs +++ /dev/null @@ -1,87 +0,0 @@ -class RTreeMapTooltip { - - static CONSTANTS = { DELAY: 0, OFFSET_X: 10, OFFSET_Y: -10, PADDING: 8, BORDER_RADIUS: 4 }; - - constructor(painter) - { - this.painter = painter; - this.tooltip = null; - this.content = ''; - this.x = 0; - this.y = 0; - } - - cleanup() { - if (this.tooltip !== null) { - document.body.removeChild(this.tooltip); - this.tooltip = null; - } - } - - createTooltip() - { - if (this.tooltip) - return; - - this.tooltip = document.createElement('div'); - this.tooltip.style.cssText = ` - position: absolute; - background: rgba(0, 0, 0, 0.9); - color: white; - padding: ${RTreeMapTooltip.CONSTANTS.PADDING}px; - border-radius: ${RTreeMapTooltip.CONSTANTS.BORDER_RADIUS}px; - font-size: 12px; - pointer-events: none; - z-index: 10000; - opacity: 0; - transition: opacity 0.2s; - max-width: 200px; - word-wrap: break-word; - `; - document.body.appendChild(this.tooltip); - } - - showTooltip() - { - if (!this.tooltip) - this.createTooltip(); - - this.tooltip.innerHTML = this.content; - this.tooltip.style.left = (this.x + RTreeMapTooltip.CONSTANTS.OFFSET_X) + 'px'; - this.tooltip.style.top = (this.y + RTreeMapTooltip.CONSTANTS.OFFSET_Y) + 'px'; - this.tooltip.style.opacity = '1'; - } - - hideTooltip() - { - if (this.tooltip) - this.tooltip.style.opacity = '0'; - } - - generateTooltipContent(node) - { - const isLeaf = node.fNChildren === 0; - let content = (node.fName.length > 0) ? `<strong>${node.fName}</strong><br>` : ''; - - content += `<i>${(isLeaf ? 'Column' : 'Field')}</i><br>`; - content += `Size: ${this.painter.getDataStr(node.fSize)}<br>`; - - if (isLeaf && node.fType !== undefined) - content += `Type: ${node.fType}<br>`; - - if (!isLeaf) - content += `Children: ${node.fNChildren}<br>`; - - const obj = this.painter.getObject(); - if (obj.fNodes && obj.fNodes.length > 0) { - const totalSize = obj.fNodes[0].fSize, - percentage = ((node.fSize / totalSize) * 100).toFixed(2); - content += `Disk Usage: ${percentage}%`; - } - - return content; - } - -} - -export { RTreeMapTooltip }; diff --git a/js/modules/draw/TASImagePainter.mjs b/js/modules/draw/TASImagePainter.mjs deleted file mode 100644 index 03923b50e7c5e..0000000000000 --- a/js/modules/draw/TASImagePainter.mjs +++ /dev/null @@ -1,456 +0,0 @@ -import { create, settings, isNodeJs, isStr, btoa_func, clTAxis, clTPaletteAxis, clTImagePalette, getDocument } from '../core.mjs'; -import { toColor } from '../base/colors.mjs'; -import { assignContextMenu, kNoReorder } from '../gui/menu.mjs'; -import { DrawOptions } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { TPavePainter } from '../hist/TPavePainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; - -/** - * @summary Painter for TASImage object. - * - * @private - */ - -class TASImagePainter extends ObjectPainter { - - #contour; - - /** @summary Decode options string */ - decodeOptions(opt) { - const d = new DrawOptions(opt), - obj = this.getObject(); - - if (d.check('CONST') && obj) - obj.fConstRatio = true; - - this.setOptions({ Zscale: d.check('Z') }); - } - - /** @summary Create RGBA buffers */ - createRGBA(nlevels) { - const obj = this.getObject(), - pal = obj?.fPalette; - if (!pal) - return null; - - const rgba = new Array((nlevels + 1) * 4).fill(0); // precalculated colors - - for (let lvl = 0, indx = 1; lvl <= nlevels; ++lvl) { - const l = lvl / nlevels; - while ((pal.fPoints[indx] < l) && (indx < pal.fPoints.length - 1)) - indx++; - - const r1 = (pal.fPoints[indx] - l) / (pal.fPoints[indx] - pal.fPoints[indx - 1]), - r2 = (l - pal.fPoints[indx - 1]) / (pal.fPoints[indx] - pal.fPoints[indx - 1]); - - rgba[lvl * 4] = Math.min(255, Math.round((pal.fColorRed[indx - 1] * r1 + pal.fColorRed[indx] * r2) / 256)); - rgba[lvl * 4 + 1] = Math.min(255, Math.round((pal.fColorGreen[indx - 1] * r1 + pal.fColorGreen[indx] * r2) / 256)); - rgba[lvl * 4 + 2] = Math.min(255, Math.round((pal.fColorBlue[indx - 1] * r1 + pal.fColorBlue[indx] * r2) / 256)); - rgba[lvl * 4 + 3] = Math.min(255, Math.round((pal.fColorAlpha[indx - 1] * r1 + pal.fColorAlpha[indx] * r2) / 256)); - } - - return rgba; - } - - /** @summary Cleanup painter - * @private */ - cleanup() { - this.#contour = undefined; - super.cleanup(); - } - - /** @summary Return colors contour - * @private */ - getContour() { return this.#contour; } - - /** @summary Create url using image buffer - * @private */ - async makeUrlFromImageBuf(obj, fp) { - const nlevels = 1000; - this.rgba = this.createRGBA(nlevels); // precalculated colors - - let min = obj.fImgBuf[0], max = obj.fImgBuf[0]; - for (let k = 1; k < obj.fImgBuf.length; ++k) { - const v = obj.fImgBuf[k]; - min = Math.min(v, min); - max = Math.max(v, max); - } - - // does not work properly in Node.js, causes 'Maximum call stack size exceeded' error - // min = Math.min.apply(null, obj.fImgBuf), - // max = Math.max.apply(null, obj.fImgBuf); - - // create contour like in hist painter to allow palette drawing - this.#contour = { - arr: new Array(200), - rgba: this.rgba, - getLevels() { return this.arr; }, - getPaletteColor(pal, zval) { - if (!this.arr || !this.rgba) - return 'white'; - const indx = Math.round((zval - this.arr[0]) / (this.arr.at(-1) - this.arr.at(0)) * (this.rgba.length - 4) / 4) * 4; - return toColor(this.rgba[indx] / 255, this.rgba[indx + 1] / 255, this.rgba[indx + 2] / 255, this.rgba[indx + 3] / 255); - } - }; - for (let k = 0; k < 200; k++) - this.#contour.arr[k] = min + (max - min) / (200 - 1) * k; - - if (min >= max) - max = min + 1; - - const z = this.getImageZoomRange(fp, obj.fConstRatio, obj.fWidth, obj.fHeight), - pr = isNodeJs() - ? import('canvas').then(h => h.default.createCanvas(z.xmax - z.xmin, z.ymax - z.ymin)) - : new Promise(resolveFunc => { - const c = document.createElement('canvas'); - c.width = z.xmax - z.xmin; - c.height = z.ymax - z.ymin; - resolveFunc(c); - }); - - return pr.then(canvas => { - const context = canvas.getContext('2d'), - imageData = context.getImageData(0, 0, canvas.width, canvas.height), - arr = imageData.data; - - for (let i = z.ymin; i < z.ymax; ++i) { - let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4; - const row = i * obj.fWidth; - for (let j = z.xmin; j < z.xmax; ++j) { - let iii = Math.round((obj.fImgBuf[row + j] - min) / (max - min) * nlevels) * 4; - // copy rgba value for specified point - arr[dst++] = this.rgba[iii++]; - arr[dst++] = this.rgba[iii++]; - arr[dst++] = this.rgba[iii++]; - arr[dst++] = this.rgba[iii]; - } - } - - context.putImageData(imageData, 0, 0); - - return { url: canvas.toDataURL(), constRatio: obj.fConstRatio, can_zoom: true }; - }); - } - - getImageZoomRange(fp, constRatio, width, height) { - const res = { xmin: 0, xmax: width, ymin: 0, ymax: height }; - if (!fp) - return res; - - let offx = 0, offy = 0, sizex = width, sizey = height; - - if (constRatio) { - const image_ratio = height / width, - frame_ratio = fp.getFrameHeight() / fp.getFrameWidth(); - - if (image_ratio > frame_ratio) { - const w2 = height / frame_ratio; - offx = Math.round((w2 - width) / 2); - sizex = Math.round(w2); - } else { - const h2 = frame_ratio * width; - offy = Math.round((h2 - height) / 2); - sizey = Math.round(h2); - } - } - - if (fp.zoom_xmin !== fp.zoom_xmax) { - res.xmin = Math.min(width, Math.max(0, Math.round(fp.zoom_xmin * sizex) - offx)); - res.xmax = Math.min(width, Math.max(0, Math.round(fp.zoom_xmax * sizex) - offx)); - } - if (fp.zoom_ymin !== fp.zoom_ymax) { - res.ymin = Math.min(height, Math.max(0, Math.round(fp.zoom_ymin * sizey) - offy)); - res.ymax = Math.min(height, Math.max(0, Math.round(fp.zoom_ymax * sizey) - offy)); - } - return res; - } - - /** @summary Produce data url from png buffer */ - async makeUrlFromPngBuf(obj, fp) { - const buf = obj.fPngBuf; - let pngbuf = ''; - - if (isStr(buf)) - pngbuf = buf; - else { - for (let k = 0; k < buf.length; ++k) - pngbuf += String.fromCharCode(buf[k] < 0 ? 256 + buf[k] : buf[k]); - } - - const res = { url: 'data:image/png;base64,' + btoa_func(pngbuf), constRatio: obj.fConstRatio, can_zoom: fp && !isNodeJs() }, - doc = getDocument(); - - if (!res.can_zoom || ((fp?.zoom_xmin === fp?.zoom_xmax) && (fp?.zoom_ymin === fp?.zoom_ymax))) - return res; - - return new Promise(resolveFunc => { - const image = doc.createElement('img'); - - image.onload = () => { - const canvas = doc.createElement('canvas'); - canvas.width = image.width; - canvas.height = image.height; - - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0); - - const arr = context.getImageData(0, 0, image.width, image.height).data, - z = this.getImageZoomRange(fp, res.constRatio, image.width, image.height), - canvas2 = doc.createElement('canvas'); - canvas2.width = z.xmax - z.xmin; - canvas2.height = z.ymax - z.ymin; - - const context2 = canvas2.getContext('2d'), - imageData2 = context2.getImageData(0, 0, canvas2.width, canvas2.height), - arr2 = imageData2.data; - - for (let i = z.ymin; i < z.ymax; ++i) { - let dst = (z.ymax - i - 1) * (z.xmax - z.xmin) * 4, - src = ((image.height - i - 1) * image.width + z.xmin) * 4; - for (let j = z.xmin; j < z.xmax; ++j) { - // copy rgba value for specified point - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - arr2[dst++] = arr[src++]; - } - } - - context2.putImageData(imageData2, 0, 0); - - res.url = canvas2.toDataURL(); - - resolveFunc(res); - }; - - image.onerror = () => resolveFunc(res); - - image.src = res.url; - }); - } - - /** @summary Use in frame painter to check zoom Y is allowed - * @protected */ - get _wheel_zoomy() { return true; } - - /** @summary Draw image */ - async drawImage() { - const obj = this.getObject(), - fp = this.getFramePainter(), - rect = fp?.getFrameRect() ?? this.getPadPainter().getPadRect(); - - if (obj._blob) { - // try to process blob data due to custom streamer - if ((obj._blob.length === 15) && !obj._blob[0]) { - obj.fImageQuality = obj._blob[1]; - obj.fImageCompression = obj._blob[2]; - obj.fConstRatio = obj._blob[3]; - obj.fPalette = { - _typename: clTImagePalette, - fUniqueID: obj._blob[4], - fBits: obj._blob[5], - fNumPoints: obj._blob[6], - fPoints: obj._blob[7], - fColorRed: obj._blob[8], - fColorGreen: obj._blob[9], - fColorBlue: obj._blob[10], - fColorAlpha: obj._blob[11] - }; - - obj.fWidth = obj._blob[12]; - obj.fHeight = obj._blob[13]; - obj.fImgBuf = obj._blob[14]; - - if ((obj.fWidth * obj.fHeight !== obj.fImgBuf.length) || - (obj.fPalette.fNumPoints !== obj.fPalette.fPoints.length)) { - console.error(`TASImage _blob decoding error ${obj.fWidth * obj.fHeight} != ${obj.fImgBuf.length} ${obj.fPalette.fNumPoints} != ${obj.fPalette.fPoints.length}`); - delete obj.fImgBuf; - delete obj.fPalette; - } - } else if ((obj._blob.length === 3) && obj._blob[0]) { - obj.fPngBuf = obj._blob[2]; - if (obj.fPngBuf?.length !== obj._blob[1]) { - console.error(`TASImage with png buffer _blob error ${obj._blob[1]} != ${obj.fPngBuf?.length}`); - delete obj.fPngBuf; - } - } else - console.error(`TASImage _blob len ${obj._blob.length} not recognized`); - - delete obj._blob; - } - - let promise; - - if (obj.fImgBuf && obj.fPalette) - promise = this.makeUrlFromImageBuf(obj, fp); - else if (obj.fPngBuf) - promise = this.makeUrlFromPngBuf(obj, fp); - else - promise = Promise.resolve(null); - - return promise.then(res => { - if (!res?.url) - return this; - - const img = this.createG(Boolean(fp)) - .append('image') - .attr('href', res.url) - .attr('width', rect.width) - .attr('height', rect.height) - .attr('preserveAspectRatio', res.constRatio ? null : 'none'); - - if (!this.isBatchMode()) { - if (settings.MoveResize || settings.ContextMenu) - img.style('pointer-events', 'visibleFill'); - - if (res.can_zoom) - img.style('cursor', 'pointer'); - } - - assignContextMenu(this, kNoReorder); - - if (!fp || !res.can_zoom) - return this; - - return this.drawColorPalette(this.getOptions().Zscale, true).then(() => { - fp.setAxesRanges(create(clTAxis), 0, 1, create(clTAxis), 0, 1, null, 0, 0); - fp.createXY({ ndim: 2, check_pad_range: false }); - return fp.addInteractivity(); - }); - }); - } - - /** @summary Fill TASImage context menu */ - fillContextMenuItems(menu) { - const obj = this.getObject(), o = this.getOptions(); - if (obj) { - menu.addchk(obj.fConstRatio, 'Const ratio', flag => { - obj.fConstRatio = flag; - this.interactiveRedraw('pad', `exec:SetConstRatio(${flag})`); - }, 'Change const ratio flag of image'); - } - if (obj?.fPalette) { - menu.addchk(o.Zscale, 'Color palette', flag => { - o.Zscale = flag; - this.drawColorPalette(flag, true); - }, 'Toggle color palette'); - } - } - - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis, min, max) { - const obj = this.getObject(); - - if (!obj) - return false; - - if (((axis === 'x') || (axis === 'y')) && (max - min > 0.01)) - return true; - - return false; - } - - /** @summary Return palette - dummy here - * @private */ - getHistPalette() { return true; } - - /** @summary Draw color palette - * @private */ - async drawColorPalette(enabled, can_move) { - if (!this.isMainPainter()) - return null; - - if (!this.draw_palette) { - const pal = create(clTPaletteAxis); - Object.assign(pal, { fX1NDC: 0.91, fX2NDC: 0.95, fY1NDC: 0.1, fY2NDC: 0.9, fInit: 1 }); - pal.fAxis.fChopt = '+'; - this.draw_palette = pal; - } - - let pal_painter = this.getPadPainter().findPainterFor(this.draw_palette); - - if (!enabled) { - if (pal_painter) { - pal_painter.Enabled = false; - pal_painter.removeG(); // completely remove drawing without need to redraw complete pad - } - return null; - } - - const fp = this.getFramePainter(); - - // keep palette width - if (can_move && fp) { - const pal = this.draw_palette; - pal.fX2NDC = fp.fX2NDC + 0.01 + (pal.fX2NDC - pal.fX1NDC); - pal.fX1NDC = fp.fX2NDC + 0.01; - pal.fY1NDC = fp.fY1NDC; - pal.fY2NDC = fp.fY2NDC; - } - - if (pal_painter) { - pal_painter.Enabled = true; - return pal_painter.drawPave(''); - } - - return TPavePainter.draw(this.getPadPainter(), this.draw_palette).then(p => { - pal_painter = p; - - // mark painter as secondary - not in list of TCanvas primitives - pal_painter.setSecondaryId(this); - - // make dummy redraw, palette will be updated only from histogram painter - pal_painter.redraw = function() {}; - }); - } - - /** @summary Toggle colz draw option - * @private */ - toggleColz() { - if (this.getObject()?.fPalette) { - const o = this.getOptions(); - o.Zscale = !o.Zscale; - return this.drawColorPalette(o.Zscale, true); - } - } - - /** @summary Redraw image */ - redraw() { - return this.drawImage(); - } - - /** @summary Process click on TASImage-defined buttons - * @desc may return promise or simply false */ - clickButton(funcname) { - if (this.isMainPainter() && funcname === 'ToggleColorZ') - return this.toggleColz(); - - return false; - } - - /** @summary Fill pad toolbar for TASImage */ - fillToolbar() { - const pp = this.getPadPainter(); - if (pp && this.getObject()?.fPalette) { - pp.addPadButton('th2colorz', 'Toggle color palette', 'ToggleColorZ'); - pp.showPadButtons(); - } - } - - /** @summary Draw TASImage object */ - static async draw(dom, obj, opt) { - const painter = new TASImagePainter(dom, obj, opt); - painter.setAsMainPainter(); - painter.decodeOptions(opt); - return ensureTCanvas(painter, false) - .then(() => painter.drawImage()) - .then(() => { - painter.fillToolbar(); - return painter; - }); - } - -} // class TASImagePainter - -export { TASImagePainter }; diff --git a/js/modules/draw/TAnnotation3DPainter.mjs b/js/modules/draw/TAnnotation3DPainter.mjs deleted file mode 100644 index e0169fbcb3a3f..0000000000000 --- a/js/modules/draw/TAnnotation3DPainter.mjs +++ /dev/null @@ -1,72 +0,0 @@ -import { isFunc } from '../core.mjs'; -import { makeTranslate } from '../base/BasePainter.mjs'; -import { TTextPainter } from './TTextPainter.mjs'; -import { build3dlatex } from '../base/latex3d.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; - -function getRotation(camera, mesh) { - const dx = camera.position.x - mesh.position.x, - dy = camera.position.y - mesh.position.y; - return Math.atan2(dy, dx) + Math.PI / 2; -} - -class TAnnotation3DPainter extends TTextPainter { - - /** @summary Redraw annotation - * @desc handle 3d and 2d mode */ - - async redraw() { - const fp = this.getFramePainter(), - text = this.getObject(); - - if (fp?.mode3d && !this.use_2d) { - const mesh = build3dlatex(text, '', this, fp); - mesh.traverse(o => o.geometry?.rotateX(Math.PI / 2)); - mesh.position.set(fp.grx(text.fX), fp.gry(text.fY), fp.grz(text.fZ)); - mesh.rotation.set(0, 0, getRotation(fp.camera, mesh)); - fp.processRender3D = true; - fp.add3DMesh(mesh, this, true); - fp.render3D(100); - return this; - } - - const mode = fp?.mode3d && isFunc(fp?.convert3DtoPadNDC) ? '3d' : '2d'; - let x = text.fX, y = text.fY; - - if (mode === '3d') { - const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ); - x = pos.x; - y = pos.y; - } - - return this._redrawText(x, y, mode).then(() => { - fp.processRender3D = mode === '3d'; - return this; - }); - } - - /** @summary Extra handling during 3d rendering - * @desc Allows to reposition annotation when rotate/zoom drawing */ - handleRender3D() { - const text = this.getObject(), - fp = this.getFramePainter(); - if (this.use_2d) { - const pos = fp.convert3DtoPadNDC(text.fX, text.fY, text.fZ), - new_x = this.axisToSvg('x', pos.x, true), - new_y = this.axisToSvg('y', pos.y, true); - makeTranslate(this.getG(), new_x - this.pos_x, new_y - this.pos_y); - } else - fp.get3DMeshes(this).forEach(mesh => mesh.rotation.set(0, 0, getRotation(fp.camera, mesh))); - } - - /** @summary draw TAnnotation3D object */ - static async draw(dom, obj, opt) { - const painter = new TAnnotation3DPainter(dom, obj, opt); - painter.use_2d = (opt === '2d') || (opt === '2D'); - return ensureTCanvas(painter, painter.use_2d ? true : '3d').then(() => painter.redraw()); - } - -} // class TAnnotation3DPainter - - -export { TAnnotation3DPainter }; diff --git a/js/modules/draw/TArrowPainter.mjs b/js/modules/draw/TArrowPainter.mjs deleted file mode 100644 index bd94e2d3ae618..0000000000000 --- a/js/modules/draw/TArrowPainter.mjs +++ /dev/null @@ -1,108 +0,0 @@ -import { TLinePainter } from './TLinePainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; - - -/** - * @summary Painter for TArrow class - * @private - */ - -class TArrowPainter extends TLinePainter { - - #beg; - #mid; - #end; - #angle2; // half of angle in rad - #wsize; // arrow size - - /** @summary Create line segment with rotation */ - rotate(angle, x0, y0) { - let dx = this.#wsize * Math.cos(angle), dy = this.#wsize * Math.sin(angle), res = ''; - if ((x0 !== undefined) && (y0 !== undefined)) - res = `M${Math.round(x0 - dx)},${Math.round(y0 - dy)}`; - else { - dx = -dx; - dy = -dy; - } - res += `l${Math.round(dx)},${Math.round(dy)}`; - if (x0 && (y0 === undefined)) - res += 'z'; - return res; - } - - /** @summary Create SVG path for the arrow */ - createPath() { - const angle = Math.atan2(this.y2 - this.y1, this.x2 - this.x1), - dlen = this.#wsize * Math.cos(this.#angle2), - dx = dlen * Math.cos(angle), dy = dlen * Math.sin(angle); - - let path = ''; - if (this.#beg) { - path += this.rotate(angle - Math.PI - this.#angle2, this.x1, this.y1) + - this.rotate(angle - Math.PI + this.#angle2, this.#beg > 10); - } - - if (this.#mid % 10 === 2) { - path += this.rotate(angle - Math.PI - this.#angle2, (this.x1 + this.x2 - dx) / 2, (this.y1 + this.y2 - dy) / 2) + - this.rotate(angle - Math.PI + this.#angle2, this.#mid > 10); - } - - if (this.#mid % 10 === 1) { - path += this.rotate(angle - this.#angle2, (this.x1 + this.x2 + dx) / 2, (this.y1 + this.y2 + dy) / 2) + - this.rotate(angle + this.#angle2, this.#mid > 10); - } - - if (this.#end) { - path += this.rotate(angle - this.#angle2, this.x2, this.y2) + - this.rotate(angle + this.#angle2, this.#end > 10); - } - - return `M${Math.round(this.x1 + (this.#beg > 10 ? dx : 0))},${Math.round(this.y1 + (this.#beg > 10 ? dy : 0))}` + - `L${Math.round(this.x2 - (this.#end > 10 ? dx : 0))},${Math.round(this.y2 - (this.#end > 10 ? dy : 0))}` + - path; - } - - /** @summary calculate all TArrow coordinates */ - prepareDraw() { - super.prepareDraw(); - - const arrow = this.getObject(), - oo = arrow.fOption, - rect = this.getPadPainter().getPadRect(); - - this.#wsize = Math.max(3, Math.round(Math.max(rect.width, rect.height) * arrow.fArrowSize * 0.8)); - this.#angle2 = arrow.fAngle / 2 / 180 * Math.PI; - this.#beg = this.#mid = this.#end = 0; - - if (oo.indexOf('<') === 0) - this.#beg = (oo.indexOf('<|') === 0) ? 12 : 2; - if (oo.indexOf('->-') >= 0) - this.#mid = 1; - else if (oo.indexOf('-|>-') >= 0) - this.#mid = 11; - else if (oo.indexOf('-<-') >= 0) - this.#mid = 2; - else if (oo.indexOf('-<|-') >= 0) - this.#mid = 12; - - const p1 = oo.lastIndexOf('>'), p2 = oo.lastIndexOf('|>'), len = oo.length; - if ((p1 >= 0) && (p1 === len - 1)) - this.#end = ((p2 >= 0) && (p2 === len - 2)) ? 11 : 1; - - this.createAttFill({ attr: arrow, enable: (this.#beg > 10) || (this.#end > 10) }); - } - - /** @summary Add extras to path for TArrow */ - addExtras(elem) { - elem.call(this.fillatt.func); - } - - /** @summary Draw TArrow object */ - static async draw(dom, obj, opt) { - const painter = new TArrowPainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } - -} // class TArrowPainter - -export { TArrowPainter }; diff --git a/js/modules/draw/TBoxPainter.mjs b/js/modules/draw/TBoxPainter.mjs deleted file mode 100644 index 0143b6ad496a7..0000000000000 --- a/js/modules/draw/TBoxPainter.mjs +++ /dev/null @@ -1,164 +0,0 @@ -import { rgb as d3_rgb, select as d3_select } from '../d3.mjs'; -import { DrawOptions, getBoxDecorations } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; - -/** - * @summary Painter for TBox class - * @private - */ - -class TBoxPainter extends ObjectPainter { - - #border_mode; - #border_size; - - /** @summary start of drag handler - * @private */ - moveStart(x, y) { - const ww = Math.abs(this.x2 - this.x1), hh = Math.abs(this.y1 - this.y2); - - this.c_x1 = Math.abs(x - this.x2) > ww * 0.1; - this.c_x2 = Math.abs(x - this.x1) > ww * 0.1; - this.c_y1 = Math.abs(y - this.y2) > hh * 0.1; - this.c_y2 = Math.abs(y - this.y1) > hh * 0.1; - if (this.c_x1 !== this.c_x2 && this.c_y1 && this.c_y2) - this.c_y1 = this.c_y2 = false; - if (this.c_y1 !== this.c_y2 && this.c_x1 && this.c_x2) - this.c_x1 = this.c_x2 = false; - } - - /** @summary drag handler - * @private */ - moveDrag(dx, dy) { - this.x1 += this.c_x1 ? dx : 0; - this.x2 += this.c_x2 ? dx : 0; - this.y1 += this.c_y1 ? dy : 0; - this.y2 += this.c_y2 ? dy : 0; - - const nodes = this.getG().selectAll('path').nodes(), - pathes = this.getPathes(); - - pathes.forEach((path, i) => d3_select(nodes[i]).attr('d', path)); - } - - /** @summary end of drag handler - * @private */ - moveEnd(not_changed) { - if (not_changed) - return; - const box = this.getObject(), X = this.swap_xy ? 'Y' : 'X', Y = this.swap_xy ? 'X' : 'Y'; - let exec = ''; - if (this.c_x1) { - const v = this.svgToAxis('x', this.x1); - box[`f${X}1`] = v; - exec += `Set${X}1(${v});;`; - } - if (this.c_x2) { - const v = this.svgToAxis('x', this.x2); - box[`f${X}2`] = v; - exec += `Set${X}2(${v});;`; - } - if (this.c_y1) { - const v = this.svgToAxis('y', this.y1); - box[`f${Y}1`] = v; - exec += `Set${Y}1(${v});;`; - } - if (this.c_y2) { - const v = this.svgToAxis('y', this.y2); - box[`f${Y}2`] = v; - exec += `Set${Y}2(${v});;`; - } - this.submitCanvExec(exec + 'Notify();;'); - } - - /** @summary Returns object ranges - * @desc Can be used for newly created canvas */ - getUserRanges() { - const box = this.getObject(), - minx = Math.min(box.fX1, box.fX2), - maxx = Math.max(box.fX1, box.fX2), - miny = Math.min(box.fY1, box.fY2), - maxy = Math.max(box.fY1, box.fY2); - return { minx, miny, maxx, maxy }; - } - - /** @summary Create path */ - getPathes() { - const xx = Math.round(Math.min(this.x1, this.x2)), - yy = Math.round(Math.min(this.y1, this.y2)), - ww = Math.round(Math.abs(this.x2 - this.x1)), - hh = Math.round(Math.abs(this.y1 - this.y2)), - path = `M${xx},${yy}h${ww}v${hh}h${-ww}z`; - if (!this.#border_mode) - return [path]; - return [path].concat(getBoxDecorations(xx, yy, ww, hh, this.#border_mode, this.#border_size, this.#border_size)); - } - - /** @summary Redraw box */ - redraw() { - const box = this.getObject(), - d = new DrawOptions(this.getDrawOpt()), - fp = d.check('FRAME') ? this.getFramePainter() : null, - draw_line = d.check('L'); - - this.createAttLine({ attr: box }); - this.createAttFill({ attr: box }); - - this.swap_xy = fp?.swap_xy(); - - // if box filled, contour line drawn only with 'L' draw option: - if (!this.fillatt.empty() && !draw_line) - this.lineatt.color = 'none'; - - const g = this.createG(fp); - - this.x1 = this.axisToSvg('x', box.fX1); - this.x2 = this.axisToSvg('x', box.fX2); - this.y1 = this.axisToSvg('y', box.fY1); - this.y2 = this.axisToSvg('y', box.fY2); - - if (this.swap_xy) - [this.x1, this.x2, this.y1, this.y2] = [this.y1, this.y2, this.x1, this.x2]; - - this.#border_mode = (box.fBorderMode && this.fillatt.hasColor()) ? box.fBorderMode : 0; - this.#border_size = box.fBorderSize || 2; - - const paths = this.getPathes(); - - g.append('svg:path') - .attr('d', paths[0]) - .call(this.lineatt.func) - .call(this.fillatt.func); - - if (this.#border_mode) { - g.append('svg:path') - .attr('d', paths[1]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); - - g.append('svg:path') - .attr('d', paths[2]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); - } - - assignContextMenu(this); - - addMoveHandler(this); - - return this; - } - - /** @summary Draw TBox object */ - static async draw(dom, obj, opt) { - const painter = new TBoxPainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } - -} // class TBoxPainter - - -export { TBoxPainter }; diff --git a/js/modules/draw/TGaxisPainter.mjs b/js/modules/draw/TGaxisPainter.mjs deleted file mode 100644 index 08ec6c899c071..0000000000000 --- a/js/modules/draw/TGaxisPainter.mjs +++ /dev/null @@ -1,234 +0,0 @@ -import { clTF1 } from '../core.mjs'; -import { scaleLinear as d3_scaleLinear, scaleLog as d3_scaleLog } from '../d3.mjs'; -import { makeTranslate } from '../base/BasePainter.mjs'; -import { EAxisBits, TAxisPainter } from '../gpad/TAxisPainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu, kNoReorder } from '../gui/menu.mjs'; -import { getHPainter } from '../gui/display.mjs'; -import { provideEvalPar } from '../base/func.mjs'; - - -/** @summary Drawing TGaxis - * @private */ -class TGaxisPainter extends TAxisPainter { - - /** @summary Convert TGaxis position into NDC to fix it when frame zoomed */ - convertTo(opt) { - const gaxis = this.getObject(), - x1 = this.axisToSvg('x', gaxis.fX1), - y1 = this.axisToSvg('y', gaxis.fY1), - x2 = this.axisToSvg('x', gaxis.fX2), - y2 = this.axisToSvg('y', gaxis.fY2); - - if (opt === 'ndc') { - const pw = this.getPadPainter().getPadWidth(), - ph = this.getPadPainter().getPadHeight(); - - gaxis.fX1 = x1 / pw; - gaxis.fX2 = x2 / pw; - gaxis.fY1 = (ph - y1) / ph; - gaxis.fY2 = (ph - y2) / ph; - this.use_ndc = true; - } else if (opt === 'frame') { - const rect = this.getFramePainter().getFrameRect(); - gaxis.fX1 = (x1 - rect.x) / rect.width; - gaxis.fX2 = (x2 - rect.x) / rect.width; - gaxis.fY1 = (y1 - rect.y) / rect.height; - gaxis.fY2 = (y2 - rect.y) / rect.height; - this.bind_frame = true; - } - } - - /** @summary Drag moving handle */ - moveDrag(dx, dy) { - this.gaxis_x += dx; - this.gaxis_y += dy; - makeTranslate(this.getG(), this.gaxis_x, this.gaxis_y); - } - - /** @summary Drag end handle */ - moveEnd(not_changed) { - if (not_changed) - return; - - const gaxis = this.getObject(); - - let fx, fy; - if (this.bind_frame) { - const rect = this.getFramePainter().getFrameRect(); - fx = (this.gaxis_x - rect.x) / rect.width; - fy = (this.gaxis_y - rect.y) / rect.height; - } else { - fx = this.svgToAxis('x', this.gaxis_x, this.use_ndc); - fy = this.svgToAxis('y', this.gaxis_y, this.use_ndc); - } - - if (this.vertical) { - gaxis.fX1 = gaxis.fX2 = fx; - if (this.reverse) { - gaxis.fY2 = fy + (gaxis.fY2 - gaxis.fY1); - gaxis.fY1 = fy; - } else { - gaxis.fY1 = fy + (gaxis.fY1 - gaxis.fY2); - gaxis.fY2 = fy; - } - } else { - if (this.reverse) { - gaxis.fX1 = fx + (gaxis.fX1 - gaxis.fX2); - gaxis.fX2 = fx; - } else { - gaxis.fX2 = fx + (gaxis.fX2 - gaxis.fX1); - gaxis.fX1 = fx; - } - gaxis.fY1 = gaxis.fY2 = fy; - } - - this.submitAxisExec(`SetX1(${gaxis.fX1});;SetX2(${gaxis.fX2});;SetY1(${gaxis.fY1});;SetY2(${gaxis.fY2})`, true); - } - - /** @summary Redraw axis, used in standalone mode for TGaxis */ - redraw() { - const gaxis = this.getObject(), - min = gaxis.fWmin, - max = gaxis.fWmax; - let x1, y1, x2, y2; - - if (this.bind_frame) { - const rect = this.getFramePainter().getFrameRect(); - x1 = Math.round(rect.x + gaxis.fX1 * rect.width); - x2 = Math.round(rect.x + gaxis.fX2 * rect.width); - y1 = Math.round(rect.y + gaxis.fY1 * rect.height); - y2 = Math.round(rect.y + gaxis.fY2 * rect.height); - } else { - x1 = this.axisToSvg('x', gaxis.fX1, this.use_ndc); - y1 = this.axisToSvg('y', gaxis.fY1, this.use_ndc); - x2 = this.axisToSvg('x', gaxis.fX2, this.use_ndc); - y2 = this.axisToSvg('y', gaxis.fY2, this.use_ndc); - } - - const w = x2 - x1, h = y1 - y2, - vertical = Math.abs(w) < Math.abs(h); - let sz = vertical ? h : w, reverse = false; - - if (sz < 0) { - reverse = true; - sz = -sz; - if (vertical) - y2 = y1; - else - x1 = x2; - } - - this.configureAxis(vertical ? 'yaxis' : 'xaxis', min, max, min, max, vertical, [0, sz], { - time_scale: gaxis.fChopt.indexOf('t') >= 0, - log: (gaxis.fChopt.indexOf('G') >= 0) ? 1 : 0, - reverse, - swap_side: reverse, - axis_func: this.axis_func - }); - - this.gaxis_x = x1; - this.gaxis_y = y2; - - return this.drawAxis(this.createG(), Math.abs(w), Math.abs(h), makeTranslate(this.gaxis_x, this.gaxis_y) || '').then(() => { - addMoveHandler(this); - assignContextMenu(this, kNoReorder); - return this; - }); - } - - /** @summary Fill TGaxis context menu items */ - fillContextMenuItems(menu) { - menu.addTAxisMenu(EAxisBits, this, this.getObject(), ''); - } - - /** @summary Check if there is function for TGaxis can be found */ - async checkFunction() { - const gaxis = this.getObject(); - if (!gaxis.fFunctionName) { - this.axis_func = null; - return; - } - const func = this.getPadPainter()?.findInPrimitives(gaxis.fFunctionName, clTF1); - - let promise = Promise.resolve(func); - if (!func) { - const h = getHPainter(), - item = h?.findItem({ name: gaxis.fFunctionName, check_keys: true }); - if (item) { - promise = h.getObject({ item }).then(res => { - return res?.obj?._typename === clTF1 ? res.obj : null; - }); - } - } - - return promise.then(f => { - this.axis_func = f; - if (f) - provideEvalPar(f); - }); - } - - /** @summary Create handle for custom function in the axis */ - createFuncHandle(func, logbase, smin, smax) { - const res = function(v) { return res.toGraph(v); }; - res._func = func; - res._domain = [smin, smax]; - res._scale = logbase ? d3_scaleLog().base(logbase) : d3_scaleLinear(); - res._scale.domain(res._domain).range([0, 100]); - res.eval = function(v) { - try { - v = res._func.evalPar(v); - } catch { - v = 0; - } - return Number.isFinite(v) ? v : 0; - }; - - const vmin = res.eval(smin), vmax = res.eval(smax); - if ((vmin < vmax) === (smin < smax)) { - res._vmin = vmin; - res._vk = 1 / (vmax - vmin); - } else if (vmin === vmax) { - res._vmin = 0; - res._vk = 1; - } else { - res._vmin = vmax; - res._vk = 1 / (vmin - vmax); - } - res._range = [0, 100]; - res.range = function(arr) { - if (arr) { - res._range = arr; - return res; - } - return res._range; - }; - - res.domain = function() { return res._domain; }; - - res.toGraph = function(v) { - const rel = (res.eval(v) - res._vmin) * res._vk; - return res._range[0] * (1 - rel) + res._range[1] * rel; - }; - - res.ticks = function(arg) { return res._scale.ticks(arg); }; - - return res; - } - - /** @summary Draw TGaxis object */ - static async draw(dom, obj, opt) { - const painter = new TGaxisPainter(dom, obj, false); - - return ensureTCanvas(painter, false).then(() => { - if (opt) - painter.convertTo(opt); - return painter.checkFunction(); - }).then(() => painter.redraw()); - } - -} // class TGaxisPainter - -export { TGaxisPainter }; diff --git a/js/modules/draw/TGraphPolarPainter.mjs b/js/modules/draw/TGraphPolarPainter.mjs deleted file mode 100644 index 1fae72bd9666f..0000000000000 --- a/js/modules/draw/TGraphPolarPainter.mjs +++ /dev/null @@ -1,756 +0,0 @@ -import { settings, create, BIT, clTGraphPolargram } from '../core.mjs'; -import { scaleLinear, pointer as d3_pointer } from '../d3.mjs'; -import { DrawOptions, buildSvgCurve, makeTranslate } from '../base/BasePainter.mjs'; -import { ObjectPainter, getElementMainPainter } from '../base/ObjectPainter.mjs'; -import { drawObjectTitle } from '../hist/TPavePainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { TooltipHandler } from '../gpad/TFramePainter.mjs'; -import { assignContextMenu, kNoReorder } from '../gui/menu.mjs'; - -const kNoTitle = BIT(17); - -/** - * @summary Painter for TGraphPolargram objects. - * - * @private */ - -class TGraphPolargramPainter extends TooltipHandler { - - /** @summary Create painter - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} polargram - object to draw */ - constructor(dom, polargram, opt) { - super(dom, polargram, opt); - this.$polargram = true; // indicate that this is polargram - this.zoom_rmin = this.zoom_rmax = 0; - this.t0 = 0; - this.mult = 1; - this.decodeOptions(opt); - this.setTooltipEnabled(true); - } - - /** @summary Returns true if fixed coordinates are configured */ - isNormalAngles() { - const polar = this.getObject(); - return polar?.fRadian || polar?.fGrad || polar?.fDegree; - } - - /** @summary Decode draw options */ - decodeOptions(opt) { - const d = new DrawOptions(opt); - - this.setOptions({ - rdot: d.check('RDOT'), - rangle: d.check('RANGLE', true) ? d.partAsInt() : 0, - NoLabels: d.check('N'), - OrthoLabels: d.check('O') - }); - - this.storeDrawOpt(opt); - } - - /** @summary Set angles range displayed by the polargram */ - setAnglesRange(tmin, tmax, set_obj) { - if (tmin >= tmax) - tmax = tmin + 1; - if (set_obj) { - const polar = this.getObject(); - polar.fRwtmin = tmin; - polar.fRwtmax = tmax; - } - this.t0 = tmin; - this.mult = 2 * Math.PI / (tmax - tmin); - } - - /** @summary Translate coordinates */ - translate(input_angle, radius, keep_float) { - // recalculate angle - const angle = (input_angle - this.t0) * this.mult; - let rx = this.r(radius), - ry = rx / this.szx * this.szy, - grx = rx * Math.cos(-angle), - gry = ry * Math.sin(-angle); - - if (!keep_float) { - grx = Math.round(grx); - gry = Math.round(gry); - rx = Math.round(rx); - ry = Math.round(ry); - } - return { grx, gry, rx, ry }; - } - - /** @summary format label for radius ticks */ - format(radius) { - if (radius === Math.round(radius)) - return radius.toString(); - if (this.ndig > 10) - return radius.toExponential(4); - return radius.toFixed((this.ndig > 0) ? this.ndig : 0); - } - - /** @summary Convert axis values to text */ - axisAsText(axis, value) { - if (axis === 'r') { - if (value === Math.round(value)) - return value.toString(); - if (this.ndig > 10) - return value.toExponential(4); - return value.toFixed(this.ndig + 2); - } - - value *= 180 / Math.PI; - return (value === Math.round(value)) ? value.toString() : value.toFixed(1); - } - - /** @summary Returns coordinate of frame - without using frame itself */ - getFrameRect() { - const pp = this.getPadPainter(), - pad = pp.getRootPad(true), - w = pp.getPadWidth(), - h = pp.getPadHeight(), - rect = {}; - - if (pad) { - rect.szx = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fLeftMargin, pad.fRightMargin)) * w); - rect.szy = Math.round(Math.max(0.1, 0.5 - Math.max(pad.fBottomMargin, pad.fTopMargin)) * h); - } else { - rect.szx = Math.round(0.5 * w); - rect.szy = Math.round(0.5 * h); - } - - rect.width = 2 * rect.szx; - rect.height = 2 * rect.szy; - rect.x = Math.round(w / 2 - rect.szx); - rect.y = Math.round(h / 2 - rect.szy); - - rect.hint_delta_x = rect.szx; - rect.hint_delta_y = rect.szy; - - rect.transform = makeTranslate(rect.x, rect.y) || ''; - - return rect; - } - - /** @summary Process mouse event */ - mouseEvent(kind, evnt) { - let pnt = null; - if (kind !== 'leave') { - const pos = d3_pointer(evnt, this.getG()?.node()); - pnt = { x: pos[0], y: pos[1], touch: false }; - } - this.processFrameTooltipEvent(pnt); - } - - /** @summary Process mouse wheel event */ - mouseWheel(evnt) { - evnt.stopPropagation(); - evnt.preventDefault(); - - this.processFrameTooltipEvent(null); // remove all tooltips - - const polar = this.getObject(); - if (!polar) - return; - - let delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); - if (!delta) - return; - - delta = (delta < 0) ? -0.2 : 0.2; - - let rmin = this.scale_rmin, rmax = this.scale_rmax; - const range = rmax - rmin; - - // rmin -= delta*range; - rmax += delta * range; - - if ((rmin < polar.fRwrmin) || (rmax > polar.fRwrmax)) - rmin = rmax = 0; - - if ((this.zoom_rmin !== rmin) || (this.zoom_rmax !== rmax)) { - this.zoom_rmin = rmin; - this.zoom_rmax = rmax; - this.redrawPad(); - } - } - - /** @summary Process mouse double click event */ - mouseDoubleClick() { - if (this.zoom_rmin || this.zoom_rmax) { - this.zoom_rmin = this.zoom_rmax = 0; - this.redrawPad(); - } - } - - /** @summary Draw polargram polar labels */ - async drawPolarLabels(polar, nmajor) { - const fontsize = Math.round(polar.fPolarTextSize * this.szy * 2), - o = this.getOptions(); - - return this.startTextDrawingAsync(polar.fPolarLabelFont, fontsize).then(() => { - const lbls = (nmajor === 8) ? ['0', '#frac{#pi}{4}', '#frac{#pi}{2}', '#frac{3#pi}{4}', '#pi', '#frac{5#pi}{4}', '#frac{3#pi}{2}', '#frac{7#pi}{4}'] : ['0', '#frac{2#pi}{3}', '#frac{4#pi}{3}'], - aligns = [12, 11, 21, 31, 32, 33, 23, 13]; - - for (let n = 0; n < nmajor; ++n) { - const angle = -n * 2 * Math.PI / nmajor; - this.getG().append('svg:path') - .attr('d', `M0,0L${Math.round(this.szx * Math.cos(angle))},${Math.round(this.szy * Math.sin(angle))}`) - .call(this.lineatt.func); - - let align = 12, rotate = 0; - - if (o.OrthoLabels) { - rotate = -n / nmajor * 360; - if ((rotate > -271) && (rotate < -91)) { - align = 32; - rotate += 180; - } - } else { - const aindx = Math.round(16 - angle / Math.PI * 4) % 8; // index in align table, here absolute angle is important - align = aligns[aindx]; - } - - this.drawText({ - align, rotate, - x: Math.round((this.szx + fontsize) * Math.cos(angle)), - y: Math.round((this.szy + fontsize / this.szx * this.szy) * (Math.sin(angle))), - text: lbls[n], - color: this.getColor(polar.fPolarLabelColor), latex: 1 - }); - } - - return this.finishTextDrawing(); - }); - } - - /** @summary Redraw polargram */ - async redraw() { - if (!this.isMainPainter()) - return; - - const polar = this.getObject(), - o = this.getOptions(), - rect = this.getPadPainter().getFrameRect(), - g = this.createG(); - - makeTranslate(g, Math.round(rect.x + rect.width / 2), Math.round(rect.y + rect.height / 2)); - this.szx = rect.szx; - this.szy = rect.szy; - - this.scale_rmin = polar.fRwrmin; - this.scale_rmax = polar.fRwrmax; - if (this.zoom_rmin !== this.zoom_rmax) { - this.scale_rmin = this.zoom_rmin; - this.scale_rmax = this.zoom_rmax; - } - - this.r = scaleLinear().domain([this.scale_rmin, this.scale_rmax]).range([0, this.szx]); - - if (polar.fRadian) { - polar.fRwtmin = 0; - polar.fRwtmax = 2 * Math.PI; - } else if (polar.fDegree) { - polar.fRwtmin = 0; - polar.fRwtmax = 360; - } else if (polar.fGrad) { - polar.fRwtmin = 0; - polar.fRwtmax = 200; - } - - this.setAnglesRange(polar.fRwtmin, polar.fRwtmax); - - const ticks = this.r.ticks(5); - let nminor = Math.floor((polar.fNdivRad % 10000) / 100), - nmajor = polar.fNdivPol % 100; - if (nmajor !== 3) - nmajor = 8; - - this.createAttLine({ attr: polar }); - if (!this.gridatt) - this.gridatt = this.createAttLine({ color: polar.fLineColor, style: 2, width: 1, std: false }); - - const range = Math.abs(polar.fRwrmax - polar.fRwrmin); - this.ndig = (range <= 0) ? -3 : Math.round(Math.log10(ticks.length / range)); - - // verify that all radius labels are unique - let lbls = [], indx = 0; - while (indx < ticks.length) { - const lbl = this.format(ticks[indx]); - if (lbls.indexOf(lbl) >= 0) { - if (++this.ndig > 10) - break; - lbls = []; - indx = 0; - continue; - } - lbls.push(lbl); - indx++; - } - - let exclude_last = false; - const pointer_events = this.isBatchMode() ? null : 'visibleFill'; - - if ((ticks.at(-1) < polar.fRwrmax) && (this.zoom_rmin === this.zoom_rmax)) { - ticks.push(polar.fRwrmax); - exclude_last = true; - } - - return this.startTextDrawingAsync(polar.fRadialLabelFont, Math.round(polar.fRadialTextSize * this.szy * 2)).then(() => { - const axis_angle = - (o.rangle || polar.fAxisAngle) / 180 * Math.PI, - ca = Math.cos(axis_angle), - sa = Math.sin(axis_angle); - for (let n = 0; n < ticks.length; ++n) { - let rx = this.r(ticks[n]), - ry = rx / this.szx * this.szy; - g.append('ellipse') - .attr('cx', 0) - .attr('cy', 0) - .attr('rx', Math.round(rx)) - .attr('ry', Math.round(ry)) - .style('fill', 'none') - .style('pointer-events', pointer_events) - .call(this.lineatt.func); - - if ((n < ticks.length - 1) || !exclude_last) { - const halign = ca > 0.7 ? 1 : (ca > 0 ? 3 : (ca > -0.7 ? 1 : 3)), - valign = Math.abs(ca) < 0.7 ? 1 : 3; - this.drawText({ - align: 10 * halign + valign, - x: Math.round(rx * ca), - y: Math.round(ry * sa), - text: this.format(ticks[n]), - color: this.getColor(polar.fRadialLabelColor), latex: 0 - }); - if (o.rdot) { - g.append('ellipse') - .attr('cx', Math.round(rx * ca)) - .attr('cy', Math.round(ry * sa)) - .attr('rx', 3) - .attr('ry', 3) - .style('fill', 'red'); - } - } - - if ((nminor > 1) && ((n < ticks.length - 1) || !exclude_last)) { - const dr = (ticks[1] - ticks[0]) / nminor; - for (let nn = 1; nn < nminor; ++nn) { - const gridr = ticks[n] + dr * nn; - if (gridr > this.scale_rmax) - break; - rx = this.r(gridr); - ry = rx / this.szx * this.szy; - g.append('ellipse') - .attr('cx', 0) - .attr('cy', 0) - .attr('rx', Math.round(rx)) - .attr('ry', Math.round(ry)) - .style('fill', 'none') - .style('pointer-events', pointer_events) - .call(this.gridatt.func); - } - } - } - - if (ca < 0.999) { - g.append('path') - .attr('d', `M0,0L${Math.round(this.szx * ca)},${Math.round(this.szy * sa)}`) - .style('pointer-events', pointer_events) - .call(this.lineatt.func); - } - - return this.finishTextDrawing(); - }).then(() => { - return o.NoLabels ? true : this.drawPolarLabels(polar, nmajor); - }).then(() => { - nminor = Math.floor((polar.fNdivPol % 10000) / 100); - - if (nminor > 1) { - for (let n = 0; n < nmajor * nminor; ++n) { - if (n % nminor === 0) - continue; - const angle = -n * 2 * Math.PI / nmajor / nminor; - g.append('svg:path') - .attr('d', `M0,0L${Math.round(this.szx * Math.cos(angle))},${Math.round(this.szy * Math.sin(angle))}`) - .call(this.gridatt.func); - } - } - - if (this.isBatchMode()) - return; - - assignContextMenu(this, kNoReorder); - - this.assignZoomHandler(g); - }); - } - - /** @summary Fill TGraphPolargram context menu */ - fillContextMenuItems(menu) { - const pp = this.getObject(), o = this.getOptions(); - menu.sub('Axis range'); - menu.addchk(pp.fRadian, 'Radian', flag => { - pp.fRadian = flag; - pp.fDegree = pp.fGrad = false; - this.interactiveRedraw('pad', flag ? 'exec:SetToRadian()' : 'exec:SetTwoPi()'); - }, 'Handle data angles as radian range 0..2*Pi'); - menu.addchk(pp.fDegree, 'Degree', flag => { - pp.fDegree = flag; - pp.fRadian = pp.fGrad = false; - this.interactiveRedraw('pad', flag ? 'exec:SetToDegree()' : 'exec:SetTwoPi()'); - }, 'Handle data angles as degree range 0..360'); - menu.addchk(pp.fGrad, 'Grad', flag => { - pp.fGrad = flag; - pp.fRadian = pp.fDegree = false; - this.interactiveRedraw('pad', flag ? 'exec:SetToGrad()' : 'exec:SetTwoPi()'); - }, 'Handle data angles as grad range 0..200'); - menu.endsub(); - menu.addSizeMenu('Axis angle', 0, 315, 45, o.rangle || pp.fAxisAngle, v => { - o.rangle = pp.fAxisAngle = v; - this.interactiveRedraw('pad', `exec:SetAxisAngle(${v})`); - }); - } - - /** @summary Assign zoom handler to element - * @private */ - assignZoomHandler(elem) { - elem.on('mouseenter', evnt => this.mouseEvent('enter', evnt)) - .on('mousemove', evnt => this.mouseEvent('move', evnt)) - .on('mouseleave', evnt => this.mouseEvent('leave', evnt)); - - if (settings.Zooming) - elem.on('dblclick', evnt => this.mouseDoubleClick(evnt)); - - if (settings.Zooming && settings.ZoomWheel) - elem.on('wheel', evnt => this.mouseWheel(evnt)); - } - - /** @summary Draw TGraphPolargram */ - static async draw(dom, polargram, opt) { - const main = getElementMainPainter(dom); - if (main) { - if (main.getObject() === polargram) - return main; - throw Error('Cannot superimpose TGraphPolargram with any other drawings'); - } - - const painter = new TGraphPolargramPainter(dom, polargram, opt); - return ensureTCanvas(painter, false).then(() => { - painter.setAsMainPainter(); - return painter.redraw(); - }).then(() => painter); - } - -} // class TGraphPolargramPainter - - -/** - * @summary Painter for TGraphPolar objects. - * - * @private - */ - -class TGraphPolarPainter extends ObjectPainter { - - /** @summary Decode options for drawing TGraphPolar */ - decodeOptions(opt) { - const d = new DrawOptions(opt || 'L'), - rdot = d.check('RDOT'), - rangle = d.check('RANGLE', true) ? d.partAsInt() : 0, - o = this.setOptions({ - mark: d.check('P'), - err: d.check('E'), - fill: d.check('F'), - line: d.check('L'), - curve: d.check('C'), - radian: d.check('R'), - degree: d.check('D'), - grad: d.check('G'), - Axis: d.check('N') ? 'N' : '' - }, opt); - - if (d.check('O')) - o.Axis += 'O'; - if (rdot) - o.Axis += '_rdot'; - if (rangle) - o.Axis += `_rangle${rangle}`; - - this.storeDrawOpt(opt); - } - - /** @summary Update TGraphPolar with polargram */ - updateObject(obj, opt) { - if (!this.matchObjectType(obj)) - return false; - - if (opt && (opt !== this.getOptions().original)) - this.decodeOptions(opt); - - if (this._draw_axis && obj.fPolargram) - this.getMainPainter().updateObject(obj.fPolargram); - - delete obj.fPolargram; - // copy all properties but not polargram - Object.assign(this.getObject(), obj); - return true; - } - - /** @summary Draw TGraphPolar title */ - async drawTitle(first_time) { - return drawObjectTitle(this, first_time, this._draw_axis, !this.getObject()?.TestBit(kNoTitle)); - } - - /** @summary Redraw TGraphPolar */ - async redraw() { - return this.drawGraphPolar() - .then(() => this.drawTitle()); - } - - /** @summary Drawing TGraphPolar */ - async drawGraphPolar() { - const graph = this.getObject(), - o = this.getOptions(), - main = this.getMainPainter(); - - if (!graph || !main?.$polargram) - return; - - if (o.mark) - this.createAttMarker({ attr: graph }); - if (o.err || o.line || o.curve) - this.createAttLine({ attr: graph }); - if (o.fill) - this.createAttFill({ attr: graph }); - - const g = this.createG(); - - if (this._draw_axis && !main.isNormalAngles()) { - const has_err = graph.fEX?.length; - let rwtmin = graph.fX[0], - rwtmax = graph.fX[0]; - for (let n = 0; n < graph.fNpoints; ++n) { - rwtmin = Math.min(rwtmin, graph.fX[n] - (has_err ? graph.fEX[n] : 0)); - rwtmax = Math.max(rwtmax, graph.fX[n] + (has_err ? graph.fEX[n] : 0)); - } - rwtmax += (rwtmax - rwtmin) / graph.fNpoints; - main.setAnglesRange(rwtmin, rwtmax, true); - } - - g.attr('transform', main.getG().attr('transform')); - - let mpath = '', epath = ''; - const bins = [], pointer_events = this.isBatchMode() ? null : 'visibleFill'; - - for (let n = 0; n < graph.fNpoints; ++n) { - if (graph.fY[n] > main.scale_rmax) - continue; - - if (o.err) { - const p1 = main.translate(graph.fX[n], graph.fY[n] - graph.fEY[n]), - p2 = main.translate(graph.fX[n], graph.fY[n] + graph.fEY[n]), - p3 = main.translate(graph.fX[n] + graph.fEX[n], graph.fY[n]), - p4 = main.translate(graph.fX[n] - graph.fEX[n], graph.fY[n]); - - epath += `M${p1.grx},${p1.gry}L${p2.grx},${p2.gry}` + - `M${p3.grx},${p3.gry}A${p4.rx},${p4.ry},0,0,1,${p4.grx},${p4.gry}`; - } - - const pos = main.translate(graph.fX[n], graph.fY[n]); - - if (o.mark) - mpath += this.markeratt.create(pos.grx, pos.gry); - - if (o.curve || o.line || o.fill) - bins.push(pos); - } - - if ((o.fill || o.line) && bins.length) { - const lpath = buildSvgCurve(bins, { line: true }); - if (o.fill) { - g.append('svg:path') - .attr('d', lpath + 'Z') - .style('pointer-events', pointer_events) - .call(this.fillatt.func); - } - - if (o.line) { - g.append('svg:path') - .attr('d', lpath) - .style('fill', 'none') - .style('pointer-events', pointer_events) - .call(this.lineatt.func); - } - } - - if (o.curve && bins.length) { - g.append('svg:path') - .attr('d', buildSvgCurve(bins)) - .style('fill', 'none') - .style('pointer-events', pointer_events) - .call(this.lineatt.func); - } - - if (epath) { - g.append('svg:path') - .attr('d', epath) - .style('fill', 'none') - .style('pointer-events', pointer_events) - .call(this.lineatt.func); - } - - if (mpath) { - g.append('svg:path') - .attr('d', mpath) - .style('pointer-events', pointer_events) - .call(this.markeratt.func); - } - - if (!this.isBatchMode()) { - assignContextMenu(this, kNoReorder); - main.assignZoomHandler(g); - } - } - - /** @summary Create polargram object */ - createPolargram(gr) { - const o = this.getOptions(); - if (!gr.fPolargram) { - gr.fPolargram = create(clTGraphPolargram); - if (o.radian) - gr.fPolargram.fRadian = true; - else if (o.degree) - gr.fPolargram.fDegree = true; - else if (o.grad) - gr.fPolargram.fGrad = true; - } - - let rmin = gr.fY[0] || 0, rmax = rmin; - const has_err = gr.fEY?.length; - for (let n = 0; n < gr.fNpoints; ++n) { - const err = has_err ? gr.fEY[n] : 0; - rmin = Math.min(rmin, gr.fY[n] - err); - rmax = Math.max(rmax, gr.fY[n] + err); - } - if (rmin === rmax) - rmax += 1; - - gr.fPolargram.fRwrmin = rmin - (rmax - rmin) * 0.1; - gr.fPolargram.fRwrmax = rmax + (rmax - rmin) * 0.1; - - return gr.fPolargram; - } - - /** @summary Provide tooltip at specified point */ - extractTooltip(pnt) { - if (!pnt) - return null; - - const graph = this.getObject(), - main = this.getMainPainter(); - let best_dist2 = 1e10, bestindx = -1, bestpos = null; - - for (let n = 0; n < graph.fNpoints; ++n) { - const pos = main.translate(graph.fX[n], graph.fY[n]), - dist2 = (pos.grx - pnt.x) ** 2 + (pos.gry - pnt.y) ** 2; - if (dist2 < best_dist2) { - best_dist2 = dist2; - bestindx = n; - bestpos = pos; - } - } - - let match_distance = 5; - if (this.markeratt?.used) - match_distance = this.markeratt.getFullSize(); - - if (Math.sqrt(best_dist2) > match_distance) - return null; - - const res = { - name: this.getObject().fName, title: this.getObject().fTitle, - x: bestpos.grx, y: bestpos.gry, - color1: (this.markeratt?.used ? this.markeratt.color : undefined) ?? (this.fillatt?.used ? this.fillatt.color : undefined) ?? this.lineatt?.color, - exact: Math.sqrt(best_dist2) < 4, - lines: [this.getObjectHint()], - binindx: bestindx, - menu_dist: match_distance, - radius: match_distance - }; - - res.lines.push(`r = ${main.axisAsText('r', graph.fY[bestindx])}`, - `phi = ${main.axisAsText('phi', graph.fX[bestindx])}`); - - if (graph.fEY && graph.fEY[bestindx]) - res.lines.push(`error r = ${main.axisAsText('r', graph.fEY[bestindx])}`); - - if (graph.fEX && graph.fEX[bestindx]) - res.lines.push(`error phi = ${main.axisAsText('phi', graph.fEX[bestindx])}`); - - return res; - } - - /** @summary Show tooltip */ - showTooltip(hint) { - let ttcircle = this.getG()?.selectChild('.tooltip_bin'); - - if (!hint || !this.getG()) { - ttcircle?.remove(); - return; - } - - if (ttcircle.empty()) { - ttcircle = this.getG().append('svg:ellipse') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none'); - } - - hint.changed = ttcircle.property('current_bin') !== hint.binindx; - - if (hint.changed) { - ttcircle.attr('cx', hint.x) - .attr('cy', hint.y) - .attr('rx', Math.round(hint.radius)) - .attr('ry', Math.round(hint.radius)) - .style('fill', 'none') - .style('stroke', hint.color1) - .property('current_bin', hint.binindx); - } - } - - /** @summary Process tooltip event */ - processTooltipEvent(pnt) { - const hint = this.extractTooltip(pnt); - if (!pnt || !pnt.disabled) - this.showTooltip(hint); - return hint; - } - - /** @summary Draw TGraphPolar */ - static async draw(dom, graph, opt) { - const painter = new TGraphPolarPainter(dom, graph, opt); - painter.decodeOptions(opt); - - const main = painter.getMainPainter(); - if (main && !main.$polargram) { - console.error('Cannot superimpose TGraphPolar with plain histograms'); - return null; - } - - let pr = Promise.resolve(null); - if (!main) { - // indicate that axis defined by this graph - painter._draw_axis = true; - pr = TGraphPolargramPainter.draw(dom, painter.createPolargram(graph), painter.options.Axis); - } - - return pr.then(gram_painter => { - gram_painter?.setSecondaryId(painter, 'polargram'); - painter.addToPadPrimitives(); - return painter.drawGraphPolar(); - }).then(() => painter.drawTitle(true)); - } - -} // class TGraphPolarPainter - -export { TGraphPolargramPainter, TGraphPolarPainter }; diff --git a/js/modules/draw/TLinePainter.mjs b/js/modules/draw/TLinePainter.mjs deleted file mode 100644 index 884bf55d8b9a2..0000000000000 --- a/js/modules/draw/TLinePainter.mjs +++ /dev/null @@ -1,144 +0,0 @@ -import { BIT } from '../core.mjs'; -import { DrawOptions } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; - - -const kLineNDC = BIT(14); - -/** - * @summary Painter for TLine class - * @private - */ - -class TLinePainter extends ObjectPainter { - - #side; // side which is interactively moved - - /** @summary Start interactive moving */ - moveStart(x, y) { - const fullsize = Math.max(1, Math.sqrt((this.x1 - this.x2) ** 2 + (this.y1 - this.y2) ** 2)), - sz1 = Math.sqrt((x - this.x1) ** 2 + (y - this.y1) ** 2) / fullsize, - sz2 = Math.sqrt((x - this.x2) ** 2 + (y - this.y2) ** 2) / fullsize; - if (sz1 > 0.9) - this.#side = 1; - else if (sz2 > 0.9) - this.#side = -1; - else - this.#side = 0; - } - - /** @summary Continue interactive moving */ - moveDrag(dx, dy) { - if (this.#side !== 1) { - this.x1 += dx; - this.y1 += dy; - } - if (this.#side !== -1) { - this.x2 += dx; - this.y2 += dy; - } - this.getG().select('path').attr('d', this.createPath()); - } - - /** @summary Finish interactive moving */ - moveEnd(not_changed) { - if (not_changed) - return; - const line = this.getObject(); - let exec = '', - fx1 = this.svgToAxis('x', this.x1, this.isndc), - fx2 = this.svgToAxis('x', this.x2, this.isndc), - fy1 = this.svgToAxis('y', this.y1, this.isndc), - fy2 = this.svgToAxis('y', this.y2, this.isndc); - if (this.swap_xy) - [fx1, fy1, fx2, fy2] = [fy1, fx1, fy2, fx2]; - line.fX1 = fx1; - line.fX2 = fx2; - line.fY1 = fy1; - line.fY2 = fy2; - if (this.#side !== 1) - exec += `SetX1(${fx1});;SetY1(${fy1});;`; - if (this.#side !== -1) - exec += `SetX2(${fx2});;SetY2(${fy2});;`; - this.submitCanvExec(exec + 'Notify();;'); - } - - /** @summary Returns object ranges - * @desc Can be used for newly created canvas */ - getUserRanges() { - const line = this.getObject(), - isndc = line.TestBit(kLineNDC); - if (isndc) - return null; - const minx = Math.min(line.fX1, line.fX2), - maxx = Math.max(line.fX1, line.fX2), - miny = Math.min(line.fY1, line.fY2), - maxy = Math.max(line.fY1, line.fY2); - return { minx, miny, maxx, maxy }; - } - - /** @summary Calculate line coordinates */ - prepareDraw() { - const line = this.getObject(); - - this.isndc = line.TestBit(kLineNDC); - - const use_frame = this.isndc ? false : new DrawOptions(this.getDrawOpt()).check('FRAME'); - - this.createG(use_frame ? 'frame2d' : undefined); - - this.swap_xy = use_frame && this.getFramePainter()?.swap_xy(); - - const func = this.getAxisToSvgFunc(this.isndc, true); - - this.x1 = func.x(line.fX1); - this.y1 = func.y(line.fY1); - this.x2 = func.x(line.fX2); - this.y2 = func.y(line.fY2); - - if (this.swap_xy) - [this.x1, this.y1, this.x2, this.y2] = [this.y1, this.x1, this.y2, this.x2]; - - this.createAttLine({ attr: line }); - } - - /** @summary Create path */ - createPath() { - const x1 = Math.round(this.x1), x2 = Math.round(this.x2), y1 = Math.round(this.y1), y2 = Math.round(this.y2); - return `M${x1},${y1}` + (x1 === x2 ? `V${y2}` : (y1 === y2 ? `H${x2}` : `L${x2},${y2}`)); - } - - /** @summary Add extras - used for TArrow */ - addExtras() {} - - /** @summary Redraw line */ - redraw() { - this.prepareDraw(); - - const elem = this.appendPath(this.createPath()) - .call(this.lineatt.func); - - if (this.getObject()?.$do_not_draw) - elem.remove(); - else { - this.addExtras(elem); - addMoveHandler(this); - assignContextMenu(this); - } - - return this; - } - - /** @summary Draw TLine object */ - static async draw(dom, obj, opt) { - const painter = new TLinePainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } - -} // class TLinePainter - - -export { TLinePainter }; diff --git a/js/modules/draw/TPiePainter.mjs b/js/modules/draw/TPiePainter.mjs deleted file mode 100644 index 217005617d75a..0000000000000 --- a/js/modules/draw/TPiePainter.mjs +++ /dev/null @@ -1,525 +0,0 @@ -import { makeTranslate, DrawOptions, floatToString } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { drawObjectTitle } from '../hist/TPavePainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { addMoveHandler, getColorId, getColorExec } from '../gui/utils.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; - - -/** - * @summary Painter for TPiePainter class - * @private - */ - -class TPiePainter extends ObjectPainter { - - #cx; // recent cx - #cy; // recent cy - #rx; // recent rx - #ry; // recent ry - #slices; // recent slices - #movex; // moving X coordinate - #movey; // moving Y coordinate - #angle0; // initial angle - #offset0; // initial offset - #slice; // moving slice - #mode; // moving mode - #padh; // pad height - - /** @summary Decode options */ - decodeOptions(opt) { - const d = new DrawOptions(opt), - o = this.getOptions(); - o.is3d = d.check('3D'); - o.lblor = 0; - o.sort = 0; - o.samecolor = false; - o.same = d.check('SAME'); - if (d.check('SC')) - o.samecolor = true; // around - if (d.check('T')) - o.lblor = 2; // around - if (d.check('R')) - o.lblor = 1; // along the radius - if (d.check('>')) - o.sort = 1; - if (d.check('<')) - o.sort = -1; - } - - #findDrawnSlice(x, y) { - if ((!x && !y) || !this.#slices || !this.#rx || !this.#ry) - return null; - let angle = Math.atan2(y / this.#ry, x / this.#rx); - - while (angle < 0.5 * Math.PI) - angle += 2 * Math.PI; - - return this.#slices.find(elem => { - return ((elem.a1 < angle) && (angle < elem.a2)) || - ((elem.a1 < angle + 2 * Math.PI) && (angle + 2 * Math.PI < elem.a2)); - }); - } - - /** @summary start of drag handler - * @private */ - moveStart(x, y) { - if ((!x && !y) || !this.#slices || !this.#rx || !this.#ry) - return; - let angle = Math.atan2(y / this.#ry, x / this.#rx); - - while (angle < 0.5 * Math.PI) - angle += 2 * Math.PI; - - const pie = this.getObject(), - len = Math.sqrt((x / this.#rx) ** 2 + (y / this.#ry) ** 2), - slice = this.#findDrawnSlice(x, y); - - // kind of cursor shown - this.#mode = ((len > 0.95) && (x > this.#rx * 0.95) && this.options.is3d) ? 'n-resize' : ((slice && len - slice.offset < 0.7) ? 'grab' : 'w-resize'); - - this.#movex = x; - this.#movey = y; - - this.getG().style('cursor', this.#mode); - - if (this.#mode === 'grab') { - this.#slice = slice.n; - this.#angle0 = len; - this.#offset0 = pie.fPieSlices[this.#slice].fRadiusOffset; - } else if (this.#mode === 'n-resize') { - this.#padh = this.getPadPainter().getPadHeight(); - this.#angle0 = pie.fAngle3D; - this.#offset0 = y; - } else { - this.#angle0 = angle; - this.#offset0 = pie.fAngularOffset; - } - } - - /** @summary drag handler - * @private */ - moveDrag(dx, dy) { - this.#movex += dx; - this.#movey += dy; - - const pie = this.getObject(); - - if (this.#mode === 'grab') { - const len = Math.sqrt((this.#movex / this.#rx) ** 2 + (this.#movey / this.#ry) ** 2); - pie.fPieSlices[this.#slice].fRadiusOffset = Math.max(0, this.#offset0 + 0.25 * (len - this.#angle0)); - } else if (this.#mode === 'n-resize') - pie.fAngle3D = Math.max(5, Math.min(85, this.#angle0 + (this.#movey - this.#offset0) / this.#padh * 180)); - else { - const angle = Math.atan2(this.#movey / this.#ry, this.#movex / this.#rx); - pie.fAngularOffset = this.#offset0 - (angle - this.#angle0) / Math.PI * 180; - } - - this.drawPie(); - } - - /** @summary end of drag handler - * @private */ - moveEnd(not_changed) { - if (not_changed) - return; - - const pie = this.getObject(); - - let exec; - - if (this.#mode === 'grab') - exec = `SetEntryRadiusOffset(${this.#slice},${pie.fPieSlices[this.#slice].fRadiusOffset})`; - else if (this.#mode === 'n-resize') - exec = `SetAngle3D(${pie.fAngle3D})`; - else - exec = `SetAngularOffset(${pie.fAngularOffset})`; - - if (exec) - this.submitCanvExec(exec + ';;Notify();;'); - - this.#mode = null; - - this.getG().style('cursor', null); - } - - /** @summary Update TPie object */ - updateObject(obj, opt) { - if (!this.matchObjectType(obj)) - return false; - - this.decodeOptions(opt); - - Object.assign(this.getObject(), obj); - - return true; - } - - /** @summary Redraw pie */ - async drawPie() { - const maing = this.createG(), - pie = this.getObject(), - o = this.getOptions(), - pp = this.getPadPainter(), - radX = pie.fRadius; - - this.#cx = this.axisToSvg('x', pie.fX); - this.#cy = this.axisToSvg('y', pie.fY); - - let radY = radX, pixelHeight = 1; - - if (o.is3d) { - radY *= Math.sin(pie.fAngle3D / 180 * Math.PI); - pixelHeight = this.axisToSvg('y', pie.fY - pie.fHeight) - this.#cy; - } - - maing.style('cursor', this.#mode || null); - - this.createAttText({ attr: pie }); - - const rx = this.axisToSvg('x', pie.fX + radX) - this.#cx, - ry = this.axisToSvg('y', pie.fY - radY) - this.#cy, - dist_to_15pi = a => { - while (a < 0.5 * Math.PI) - a += 2 * Math.PI; - while (a >= 2.5 * Math.PI) - a -= 2 * Math.PI; - return Math.abs(a - 1.5 * Math.PI); - }; - - makeTranslate(maing, this.#cx, this.#cy); - - // pie.fPieSlices[4].fValue = 100; - - const arr = []; - let total = 0, af = -pie.fAngularOffset / 180 * Math.PI; - // ensure all angles are positive - while (af <= 2 * Math.PI) - af += 2 * Math.PI; - - for (let n = 0; n < pie.fPieSlices.length; n++) { - const slice = pie.fPieSlices[n], - value = slice.fValue; - total += value; - arr.push({ - n, value, slice, - offset: slice.fRadiusOffset, - attline: this.createAttLine(slice), - attfill: this.createAttFill(slice) - }); - } - - // sort in increase/decrease order - if (o.sort !== 0) - arr.sort((v1, v2) => { return o.sort * (v1.value - v2.value); }); - - // now assign angles for each slice - - for (let n = 0; n < arr.length; n++) { - const entry = arr[n]; - entry.seq = n; - entry.a2 = af; - af -= entry.value / total * 2 * Math.PI; - entry.a1 = af; - - entry.large_arc = entry.value > total * 0.5 ? 1 : 0; - entry.x1 = Math.round(rx * Math.cos(entry.a1)); - entry.y1 = Math.round(ry * Math.sin(entry.a1)); - entry.x2 = Math.round(rx * Math.cos(entry.a2)); - entry.y2 = Math.round(ry * Math.sin(entry.a2)); - - if (entry.offset) { - const coef = radX > 0 ? entry.offset / radX : 0.1, - mid_angle = (entry.a1 + entry.a2) / 2; - entry.dx = Math.round(rx * coef * Math.cos(mid_angle)); - entry.dy = Math.round(ry * coef * Math.sin(mid_angle)); - } else - entry.dx = entry.dy = 0; - } - - const add_path = (entry, path) => { - const elem = maing.append('svg:path') - .attr('d', path) - .call(entry.attline.func) - .call(entry.attfill.func); - if (entry.offset) - makeTranslate(elem, entry.dx, entry.dy); - }, build_pie = (entry, func) => { - // use same segments for side and top/bottom curves - let a = entry.a1, border = 0; - while (border <= entry.a1) - border += Math.PI; - while (a < entry.a2) { - if (border >= entry.a2) { - func(a, entry.a2, entry); - a = entry.a2; - } else { - func(a, border, entry); - a = border; - border += Math.PI; - } - } - }, add_curved_side = (aa1, aa2, entry) => { - if (dist_to_15pi((aa1 + aa2) / 2) < 0.5 * Math.PI) - return; - const xx1 = Math.round(rx * Math.cos(aa1)), - yy1 = Math.round(ry * Math.sin(aa1)), - xx2 = Math.round(rx * Math.cos(aa2)), - yy2 = Math.round(ry * Math.sin(aa2)); - add_path(entry, `M${xx1},${yy1}a${rx},${ry},0,0,1,${xx2 - xx1},${yy2 - yy1}v${pixelHeight}a${rx},${ry},0,0,0,${xx1 - xx2},${yy1 - yy2}z`); - }, add_planar_side = (x, y, entry) => { - add_path(entry, `M0,0v${pixelHeight}l${x},${y}v${-pixelHeight}z`); - }; - - // build main paths for each slice - - for (let indx = 0; indx < arr.length; indx++) { - const entry = arr[indx]; - if (o.is3d) { - entry.pie_path = ''; - build_pie(entry, (aa1, aa2) => { - const xx1 = Math.round(rx * Math.cos(aa1)), - yy1 = Math.round(ry * Math.sin(aa1)), - xx2 = Math.round(rx * Math.cos(aa2)), - yy2 = Math.round(ry * Math.sin(aa2)); - entry.pie_path += `a${rx},${ry},0,0,1,${xx2 - xx1},${yy2 - yy1}`; - }); - } else - entry.pie_path = `a${rx},${ry},0,${entry.large_arc},1,${entry.x2 - entry.x1},${entry.y2 - entry.y1}`; - } - - // code to create 3d effect - - if (o.is3d) { - let start_indx = -1, border = Math.PI / 2; - for (let indx = 0; indx < arr.length; indx++) { - const entry = arr[indx]; - - // first add bottom - add_path(entry, `M0,${pixelHeight}l${entry.x1},${entry.y1}${entry.pie_path}z`); - - if ((entry.a1 <= 1.5 * Math.PI) && (entry.a2 >= 1.5 * Math.PI)) - start_indx = indx; - else if ((entry.a1 <= 3.5 * Math.PI) && (entry.a2 >= 3.5 * Math.PI)) { - start_indx = indx; - border = 2.5 * Math.PI; - } - } - - if (start_indx < 0) { - console.error('fail to find start index, use default'); - start_indx = 0; - } - - let indx = start_indx, cnt = arr.length; - - while ((arr[indx].a1 > border) && (cnt-- > 0)) { - const entry1 = arr[indx]; - indx++; - if (indx === arr.length) { - indx = 0; - border += 2 * Math.PI; - } - const entry2 = arr[indx]; - - if (entry1.offset || entry2.offset) { - add_planar_side(entry1.x1, entry1.y1, entry1); - add_planar_side(entry2.x2, entry2.y2, entry2); - } - // curved - build_pie(entry1, add_curved_side); - } - - indx = start_indx; - - while (cnt-- > 0) { - const entry1 = arr[indx]; - indx = (indx === 0) ? arr.length - 1 : indx - 1; - const entry2 = arr[indx]; - - if (entry1.offset || entry2.offset) { - add_planar_side(entry1.x2, entry1.y2, entry1); - add_planar_side(entry2.x1, entry2.y1, entry2); - } - - build_pie(entry2, add_curved_side); - } - } - - // add main path - for (let indx = 0; indx < arr.length; indx++) { - const entry = arr[indx]; - add_path(entry, `M0,0l${entry.x1},${entry.y1}${entry.pie_path}z`); - } - - // at the end draw text - - return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(pp), maing).then(() => { - for (let indx = 0; indx < arr.length; indx++) { - const entry = arr[indx], - slice = entry.slice, - mid_angle = (entry.a1 + entry.a2) / 2, - frac = total ? slice.fValue / total : 0; - - let tmptxt = pie.fLabelFormat; - tmptxt = tmptxt.replaceAll('%txt', slice.fTitle); - tmptxt = tmptxt.replaceAll('%val', floatToString(slice.fValue, pie.fValueFormat)); - tmptxt = tmptxt.replaceAll('%frac', floatToString(frac, pie.fFractionFormat)); - tmptxt = tmptxt.replaceAll('%perc', floatToString(frac * 100, pie.fPercentFormat) + '%'); - - const arg = { - draw_g: maing, - x: entry.dx + rx * (1 + pie.fLabelsOffset) * Math.cos(mid_angle), - y: entry.dy + ry * (1 + pie.fLabelsOffset) * Math.sin(mid_angle), - latex: 1, - align: 22, - text: tmptxt - }; - - if (o.samecolor) - arg.color = this.getColor(slice.fFillColor); - - if (o.lblor === 1) { - // radial positioning of the labels - arg.rotate = Math.atan2(arg.y, arg.x) / Math.PI * 180; - if (arg.x > 0) - arg.align = 12; - else { - arg.align = 32; - arg.rotate += 180; - } - } else if (o.lblor === 2) { - // in the slice - arg.rotate = Math.atan2(entry.y2 - entry.y1, entry.x2 - entry.x1) / Math.PI * 180; - if ((arg.rotate > 90) || (arg.rotate < -90)) { - arg.rotate += 180; - arg.align = 21; - } else - arg.align = 23; - } else if ((arg.x >= 0) && (arg.y >= 0)) { - arg.align = 13; - if (o.is3d) - arg.y += pixelHeight; - } else if ((arg.x > 0) && (arg.y < 0)) - arg.align = 11; - else if ((arg.x < 0) && (arg.y >= 0)) { - arg.align = 33; - if (o.is3d) - arg.y += pixelHeight; - } else if ((arg.x < 0) && (arg.y < 0)) - arg.align = 31; - - this.drawText(arg); - } - return this.finishTextDrawing(maing); - }).then(() => { - this.#rx = rx; - this.#ry = ry; - this.#slices = arr; - return this; - }); - } - - /** @summary Draw TPie title */ - async drawTitle(first_time) { - return drawObjectTitle(this, first_time, !this.options.same, true); - } - - /** @summary Redraw TPie object */ - async redraw() { - return this.drawPie().then(() => this.drawTitle()).then(() => { - assignContextMenu(this); - addMoveHandler(this); - return this; - }); - } - - /** @summary Fill specific items */ - fillContextMenuItems(menu) { - const pie = this.getObject(); - menu.add('Change title', () => menu.input('Enter new title', pie.fTitle).then(t => { - pie.fTitle = t; - this.interactiveRedraw('pad', `exec:SetTitle("${t}")`); - })); - menu.add('Angular offset', () => menu.input('Enter new angular offset', pie.fAngularOffset, 'float').then(v => { - pie.fAngularOffset = v; - this.interactiveRedraw('pad', `exec:SetAngularOffset(${v})`); - })); - if (this.options.is3d) { - menu.add('Angle 3D', () => menu.input('Enter new angle 3D', pie.fAngle3D, 'float', 0, 90).then(v => { - pie.fAngle3D = v; - this.interactiveRedraw('pad', `exec:SetAngle3D(${v})`); - })); - } - - if (!menu.getEventPosition()) - return; - - const svg = this.getPadPainter()?.getPadSvg(), - rect = svg.node().getBoundingClientRect(), - x = menu.getEventPosition().clientX - rect.left - svg.node().clientLeft, - y = menu.getEventPosition().clientY - rect.top - svg.node().clientTop, - elem = this.#findDrawnSlice(x - this.#cx, y - this.#cy); - if (!elem) - return; - - menu.sub(`Slice${elem.n}`); - - menu.add('Title', () => menu.input('Enter new title', elem.slice.fTitle).then(t => { - elem.slice.fTitle = t; - this.interactiveRedraw('pad', `exec:SetEntryLabel(${elem.n},"${t}")`); - })); - menu.add('Offset', () => menu.input('Enter new slice offset', elem.slice.fRadiusOffset, 'float', 0, 1).then(v => { - elem.slice.fRadiusOffset = v; - this.interactiveRedraw('pad', `exec:SetEntryRadiusOffset(${elem.n},${v})`); - })); - - menu.sub('Line att'); - menu.addSizeMenu('width', 1, 10, 1, elem.attline.width, arg => { - elem.slice.fLineWidth = arg; - this.interactiveRedraw('pad', `exec:SetEntryLineWidth(${elem.n},${arg})`); - }); - if (!elem.attline.nocolor) { - menu.addColorMenu('color', elem.attline.color, arg => { - elem.slice.fLineColor = getColorId(arg).id; - this.interactiveRedraw('pad', getColorExec(arg, 'SetEntryLineColor', elem.n)); - }); - } - menu.addLineStyleMenu('style', elem.attline.style, id => { - elem.slice.fLineStyle = id; - this.interactiveRedraw('pad', `exec:SetEntryLineStyle(${elem.n},${id})`); - }); - menu.endsub(); - - menu.sub('Fill att'); - menu.addColorMenu('color', elem.attfill.colorindx, arg => { - elem.slice.fFillColor = getColorId(arg).id; - this.interactiveRedraw('pad', getColorExec(arg, 'SetEntryFillColor', elem.n)); - }, elem.attfill.kind); - menu.addFillStyleMenu('style', elem.attfill.pattern, elem.attfill.colorindx, id => { - elem.slice.fFillStyle = id; - this.interactiveRedraw('pad', `exec:SetEntryFillStyle(${elem.n},${id})`); - }); - menu.endsub(); - - menu.endsub(); - } - - /** @summary Draw TPie object */ - static async draw(dom, obj, opt) { - const painter = new TPiePainter(dom, obj, opt); - painter.decodeOptions(opt); - return ensureTCanvas(painter, false) - .then(() => painter.drawPie()) - .then(() => painter.drawTitle(true)) - .then(() => { - assignContextMenu(painter); - addMoveHandler(painter); - return painter; - }); - } - -} // class TPiePainter - - -export { TPiePainter }; diff --git a/js/modules/draw/TPolyLinePainter.mjs b/js/modules/draw/TPolyLinePainter.mjs deleted file mode 100644 index 5c05009deb2fe..0000000000000 --- a/js/modules/draw/TPolyLinePainter.mjs +++ /dev/null @@ -1,108 +0,0 @@ -import { BIT, isStr, clTPolyLine } from '../core.mjs'; -import { makeTranslate } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; - - -const kPolyLineNDC = BIT(14); - -/** - * @summary Painter for TPolyLine class - * @private - */ - -class TPolyLinePainter extends ObjectPainter { - - #dx; // interactive change - #dy; // interactive change - #isndc; // if NDC coordinates used - - /** @summary Dragging object - * @private */ - moveDrag(dx, dy) { - this.#dx += dx; - this.#dy += dy; - makeTranslate(this.getG().select('path'), this.#dx, this.#dy); - } - - /** @summary End dragging object - * @private */ - moveEnd(not_changed) { - if (not_changed) - return; - const polyline = this.getObject(), - func = this.getAxisToSvgFunc(this.#isndc); - let exec = ''; - - for (let n = 0; n <= polyline.fLastPoint; ++n) { - const x = this.svgToAxis('x', func.x(polyline.fX[n]) + this.#dx, this.#isndc), - y = this.svgToAxis('y', func.y(polyline.fY[n]) + this.#dy, this.#isndc); - polyline.fX[n] = x; - polyline.fY[n] = y; - exec += `SetPoint(${n},${x},${y});;`; - } - this.submitCanvExec(exec + 'Notify();;'); - this.redraw(); - } - - /** @summary Returns object ranges - * @desc Can be used for newly created canvas */ - getUserRanges() { - const polyline = this.getObject(), - isndc = polyline.TestBit(kPolyLineNDC); - if (isndc || !polyline.fLastPoint) - return null; - let minx = polyline.fX[0], maxx = minx, - miny = polyline.fY[0], maxy = miny; - for (let n = 1; n <= polyline.fLastPoint; ++n) { - minx = Math.min(minx, polyline.fX[n]); - maxx = Math.max(maxx, polyline.fX[n]); - miny = Math.min(miny, polyline.fY[n]); - maxy = Math.max(maxy, polyline.fY[n]); - } - return { minx, miny, maxx, maxy }; - } - - /** @summary Redraw poly line */ - redraw() { - const g = this.createG(), - polyline = this.getObject(), - isndc = polyline.TestBit(kPolyLineNDC), - opt = this.getDrawOpt() || polyline.fOption, - dofill = (polyline._typename === clTPolyLine) && (isStr(opt) && opt.toLowerCase().indexOf('f') >= 0), - func = this.getAxisToSvgFunc(isndc); - - this.createAttLine({ attr: polyline }); - this.createAttFill({ attr: polyline, enable: dofill }); - - let cmd = ''; - for (let n = 0; n <= polyline.fLastPoint; ++n) - cmd += `${n > 0 ? 'L' : 'M'}${func.x(polyline.fX[n])},${func.y(polyline.fY[n])}`; - - g.append('svg:path') - .attr('d', cmd + (dofill ? 'Z' : '')) - .call(dofill ? () => {} : this.lineatt.func) - .call(this.fillatt.func); - - assignContextMenu(this); - - addMoveHandler(this); - - this.#dx = this.#dy = 0; - this.#isndc = isndc; - - return this; - } - - /** @summary Draw TPolyLine object */ - static async draw(dom, obj, opt) { - const painter = new TPolyLinePainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } - -} // class TPolyLinePainter - - -export { TPolyLinePainter }; diff --git a/js/modules/draw/TPolyMarker3D.mjs b/js/modules/draw/TPolyMarker3D.mjs deleted file mode 100644 index 0090fc29e6515..0000000000000 --- a/js/modules/draw/TPolyMarker3D.mjs +++ /dev/null @@ -1,96 +0,0 @@ -import { settings, isObject } from '../core.mjs'; -import { PointsCreator } from '../base/base3d.mjs'; - -/** @summary direct draw function for TPolyMarker3D object - * @private */ -async function drawPolyMarker3D() { - const fp = this.$fp || this.getFramePainter(); - - delete this.$fp; - - if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) - return this; - - const poly = this.getObject(), sizelimit = 50000, fP = poly.fP; - let step = 1, numselect = 0; - - for (let i = 0; i < fP.length; i += 3) { - if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || - (fP[i + 1] < fp.scale_ymin) || (fP[i + 1] > fp.scale_ymax) || - (fP[i + 2] < fp.scale_zmin) || (fP[i + 2] > fp.scale_zmax)) - continue; - ++numselect; - } - - if ((settings.OptimizeDraw > 0) && (numselect > sizelimit)) - step = Math.max(2, Math.floor(numselect / sizelimit)); - - const size = Math.floor(numselect / step), - pnts = new PointsCreator(size, fp.webgl, fp.size_x3d / 100), - index = new Int32Array(size); - let select = 0, icnt = 0; - - for (let i = 0; i < fP.length; i += 3) { - if ((fP[i] < fp.scale_xmin) || (fP[i] > fp.scale_xmax) || - (fP[i + 1] < fp.scale_ymin) || (fP[i + 1] > fp.scale_ymax) || - (fP[i + 2] < fp.scale_zmin) || (fP[i + 2] > fp.scale_zmax)) - continue; - - if (step > 1) { - select = (select + 1) % step; - if (select) - continue; - } - - index[icnt++] = i; - - pnts.addPoint(fp.grx(fP[i]), fp.gry(fP[i + 1]), fp.grz(fP[i + 2])); - } - - return pnts.createPoints({ color: this.getColor(poly.fMarkerColor), style: poly.fMarkerStyle }).then(mesh => { - mesh.tip_color = (poly.fMarkerColor === 3) ? 0xFF0000 : 0x00FF00; - mesh.tip_name = poly.fName || 'Poly3D'; - mesh.poly = poly; - mesh.fp = fp; - mesh.scale0 = 0.7 * pnts.scale; - mesh.index = index; - - fp.add3DMesh(mesh, this, true); - - mesh.tooltip = function(intersect) { - let indx = Math.floor(intersect.index / this.nvertex); - if ((indx < 0) || (indx >= this.index.length)) - return null; - - indx = this.index[indx]; - - const fp2 = this.fp, - grx = fp2.grx(this.poly.fP[indx]), - gry = fp2.gry(this.poly.fP[indx + 1]), - grz = fp2.grz(this.poly.fP[indx + 2]); - - return { - x1: grx - this.scale0, - x2: grx + this.scale0, - y1: gry - this.scale0, - y2: gry + this.scale0, - z1: grz - this.scale0, - z2: grz + this.scale0, - color: this.tip_color, - lines: [ - this.tip_name, - 'pnt: ' + indx / 3, - 'x: ' + fp2.axisAsText('x', this.poly.fP[indx]), - 'y: ' + fp2.axisAsText('y', this.poly.fP[indx + 1]), - 'z: ' + fp2.axisAsText('z', this.poly.fP[indx + 2]) - ] - }; - }; - - fp.render3D(100); // set timeout to be able draw other points - - return this; - }); -} - -export { drawPolyMarker3D }; diff --git a/js/modules/draw/TRatioPlotPainter.mjs b/js/modules/draw/TRatioPlotPainter.mjs deleted file mode 100644 index 27535871f35b7..0000000000000 --- a/js/modules/draw/TRatioPlotPainter.mjs +++ /dev/null @@ -1,244 +0,0 @@ -import { create, clTPad, clTLine, isFunc } from '../core.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { TLinePainter } from './TLinePainter.mjs'; - - -const k_upper_pad = 'upper_pad', k_lower_pad = 'lower_pad', k_top_pad = 'top_pad'; - - -/** - * @summary Painter class for TRatioPlot - * - * @private - */ - -class TRatioPlotPainter extends ObjectPainter { - - #oldratio; // old ratio plot - - /** @summary Set grids range */ - setGridsRange(xmin, xmax, ymin, ymax, low_p) { - const ratio = this.getObject(); - if (xmin === xmax) { - const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad)?.getFramePainter()?.x_handle; - if (!x_handle) - return; - if (xmin === 0) { - // in case of unzoom full range should be used - xmin = x_handle.full_min; - xmax = x_handle.full_max; - } else { - // in case of y-scale zooming actual range has to be used - xmin = x_handle.scale_min; - xmax = x_handle.scale_max; - } - } - ratio.fGridlines.forEach(line => { - line.fX1 = xmin; - line.fX2 = xmax; - }); - const nlines = Math.min(ratio.fGridlines.length, ratio.fGridlinePositions.length); - for (let i = 0; i < nlines; ++i) { - const y = ratio.fGridlinePositions[i], - line = ratio.fGridlines[i]; - if (ymin !== 'ignorey') { - line.$do_not_draw = (ymin !== ymax) && ((y < ymin) || (y > ymax)); - line.fY1 = line.fY2 = y; - } - low_p?.findPainterFor(line)?.redraw(); - } - } - - /** @summary Configure custom interactive handlers for ratio plot - * @desc Should work for both new and old code */ - configureInteractive() { - const ratio = this.getObject(), - pp = this.getPadPainter(), - up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad), - up_fp = up_p?.getFramePainter(), - low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad), - low_fp = low_p?.getFramePainter(); - - if (!up_p || !low_p) - return; - - low_p.forEachPainterInPad(objp => { - if (isFunc(objp?.testEditable)) - objp.testEditable(false); - }); - - this.setGridsRange(low_fp.scale_xmin, low_fp.scale_xmax, low_fp.scale_ymin, low_fp.scale_ymax, low_p); - - if (up_p._ratio_interactive && low_p._ratio_interactive) - return; - - up_p._ratio_interactive = true; - low_p._ratio_interactive = true; - - up_fp.o_zoom = up_fp.zoom; - up_fp._ratio_low_fp = low_fp; - up_fp._ratio_painter = this; - - up_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { - return this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive).then(res => { - this._ratio_painter.setGridsRange(up_fp.scale_xmin, up_fp.scale_xmax, 'ignorey'); - return this._ratio_low_fp.o_zoom(up_fp.scale_xmin, up_fp.scale_xmax).then(() => res); - }); - }; - - up_fp.o_sizeChanged = up_fp.sizeChanged; - up_fp.sizeChanged = function() { - this.o_sizeChanged(); - this._ratio_low_fp.fX1NDC = this.fX1NDC; - this._ratio_low_fp.fX2NDC = this.fX2NDC; - this._ratio_low_fp.o_sizeChanged(); - }; - - low_fp.o_zoom = low_fp.zoom; - low_fp._ratio_up_fp = up_fp; - low_fp._ratio_painter = this; - - low_fp.zoom = function(xmin, xmax, ymin, ymax, zmin, zmax) { - if (xmin === xmax) { - xmin = up_fp.xmin; - xmax = up_fp.xmax; - } else { - xmin = Math.min(xmin, up_fp.xmin); - xmax = Math.max(xmax, up_fp.xmax); - } - this._ratio_painter.setGridsRange(xmin, xmax, ymin, ymax); - return this._ratio_up_fp.o_zoom(xmin, xmax).then(() => this.o_zoom(xmin, xmax, ymin, ymax, zmin, zmax)); - }; - - low_fp.o_sizeChanged = low_fp.sizeChanged; - low_fp.sizeChanged = function() { - this.o_sizeChanged(); - this._ratio_up_fp.fX1NDC = this.fX1NDC; - this._ratio_up_fp.fX2NDC = this.fX2NDC; - this._ratio_up_fp.o_sizeChanged(); - }; - } - - /** @summary Redraw old TRatioPlot where object was in very end of list of primitives */ - async redrawOld() { - const ratio = this.getObject(), - pp = this.getPadPainter(), - top_p = pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad), - pad = pp.getRootPad(), - mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0, - tick_x = pad.fTickx || mirrow_axis, - tick_y = pad.fTicky || mirrow_axis; - - top_p?.disablePadDrawing(); - - const up_p = pp.findPainterFor(ratio.fUpperPad, k_upper_pad, clTPad), - up_main = up_p?.getMainPainter(), - up_fp = up_p?.getFramePainter(), - low_p = pp.findPainterFor(ratio.fLowerPad, k_lower_pad, clTPad), - low_main = low_p?.getMainPainter(), - low_fp = low_p?.getFramePainter(); - let promise_up = Promise.resolve(true); - - if (up_p && up_main && up_fp && low_fp && !up_p._ratio_configured) { - up_p._ratio_configured = true; - - up_main.options.Axis = 0; // draw both axes - - const h = up_main.getHisto(); - - h.fYaxis.$use_top_pad = true; // workaround to use same scaling - h.fXaxis.fLabelSize = 0; // do not draw X axis labels - h.fXaxis.fTitle = ''; // do not draw X axis title - - up_p.getRootPad().fTickx = tick_x; - up_p.getRootPad().fTicky = tick_y; - - promise_up = up_p.redrawPad(); - } - - return promise_up.then(() => { - if (!low_p || !low_main || !low_fp || !up_fp || low_p._ratio_configured) - return this; - - low_p._ratio_configured = true; - low_main.options.Axis = 0; // draw both axes - const h = low_main.getHisto(); - h.fXaxis.fTitle = 'x'; - - h.fXaxis.$use_top_pad = true; - h.fYaxis.$use_top_pad = true; - low_p.getRootPad().fTickx = tick_x; - low_p.getRootPad().fTicky = tick_y; - - const arr = []; - - // add missing lines in old ratio painter - if (ratio.fGridlinePositions.length && (ratio.fGridlines.length < ratio.fGridlinePositions.length)) { - ratio.fGridlinePositions.forEach(gridy => { - let found = false; - ratio.fGridlines.forEach(line => { - if ((line.fY1 === line.fY2) && (Math.abs(line.fY1 - gridy) < 1e-6)) - found = true; - }); - if (!found) { - const line = create(clTLine); - line.fX1 = up_fp.scale_xmin; - line.fX2 = up_fp.scale_xmax; - line.fY1 = line.fY2 = gridy; - line.fLineStyle = 2; - ratio.fGridlines.push(line); - arr.push(TLinePainter.draw(low_p, line)); - } - }); - } - - return Promise.all(arr) - .then(() => low_fp.zoomSingle('x', up_fp.scale_xmin, up_fp.scale_xmax)) - .then(changed => { return changed ? true : low_p.redrawPad(); }) - .then(() => this); - }); - } - - /** @summary Redraw TRatioPlot */ - async redraw() { - const ratio = this.getObject(), - pp = this.getPadPainter(); - - if (this.#oldratio === undefined) - this.#oldratio = Boolean(pp.findPainterFor(ratio.fTopPad, k_top_pad, clTPad)); - - // configure ratio interactive at the end - pp.$userInteractive = () => this.configureInteractive(); - - if (this.#oldratio) - return this.redrawOld(); - - const pad = pp.getRootPad(), - mirrow_axis = (pad.fFrameFillStyle === 0) ? 1 : 0, - tick_x = pad.fTickx || mirrow_axis, - tick_y = pad.fTicky || mirrow_axis; - - // do not draw primitives and pad itself - ratio.fTopPad.$disable_drawing = true; - - ratio.fUpperPad.$ratio_pad = 'up'; // indicate drawing of the axes for main painter - ratio.fUpperPad.fTickx = tick_x; - ratio.fUpperPad.fTicky = tick_y; - - ratio.fLowerPad.$ratio_pad = 'low'; // indicate drawing of the axes for main painter - ratio.fLowerPad.fTickx = tick_x; - ratio.fLowerPad.fTicky = tick_y; - - return this; - } - - /** @summary Draw TRatioPlot */ - static async draw(dom, ratio, opt) { - const painter = new TRatioPlotPainter(dom, ratio, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } - -} // class TRatioPlotPainter - -export { TRatioPlotPainter }; diff --git a/js/modules/draw/TSplinePainter.mjs b/js/modules/draw/TSplinePainter.mjs deleted file mode 100644 index a1c4b8ee59f12..0000000000000 --- a/js/modules/draw/TSplinePainter.mjs +++ /dev/null @@ -1,337 +0,0 @@ -import { gStyle, clTH1I, kNoStats, createHistogram } from '../core.mjs'; -import { DrawOptions, floatToString, buildSvgCurve } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { TH1Painter } from '../hist/TH1Painter.mjs'; - - -/** - * @summary Painter for TSpline classes. - * - * @private - */ - -class TSplinePainter extends ObjectPainter { - - #knot_size; // graphical size of each knot - - /** @summary Update TSpline object - * @private */ - updateObject(obj, opt) { - const spline = this.getObject(); - - if (spline._typename !== obj._typename) - return false; - - if (spline !== obj) - Object.assign(spline, obj); - - if (opt !== undefined) - this.decodeOptions(opt); - - return true; - } - - /** @summary Evaluate spline at given position - * @private */ - eval(knot, x) { - const dx = x - knot.fX; - - if (knot._typename === 'TSplinePoly3') - return knot.fY + dx * (knot.fB + dx * (knot.fC + dx * knot.fD)); - - if (knot._typename === 'TSplinePoly5') - return knot.fY + dx * (knot.fB + dx * (knot.fC + dx * (knot.fD + dx * (knot.fE + dx * knot.fF)))); - - return knot.fY + dx; - } - - /** @summary Find idex for x value - * @private */ - findX(x) { - const spline = this.getObject(); - let klow = 0, khig = spline.fNp - 1; - - if (x <= spline.fXmin) - return klow; - if (x >= spline.fXmax) - return khig; - - if (spline.fKstep) { - // Equidistant knots, use histogram - klow = Math.round((x - spline.fXmin) / spline.fDelta); - // Correction for rounding errors - if (x < spline.fPoly[klow].fX) - klow = Math.max(klow - 1, 0); - else if ((klow < khig) && (x > spline.fPoly[klow + 1].fX)) - ++klow; - } else { - // Non equidistant knots, binary search - while (khig - klow > 1) { - const khalf = Math.round((klow + khig) / 2); - if (x > spline.fPoly[khalf].fX) - klow = khalf; - else - khig = khalf; - } - } - return klow; - } - - /** @summary Create histogram for axes drawing - * @private */ - createDummyHisto() { - const spline = this.getObject(); - let xmin = 0, xmax = 1, ymin = 0, ymax = 1; - - if (spline.fPoly) { - xmin = xmax = spline.fPoly[0].fX; - ymin = ymax = spline.fPoly[0].fY; - - spline.fPoly.forEach(knot => { - xmin = Math.min(knot.fX, xmin); - xmax = Math.max(knot.fX, xmax); - ymin = Math.min(knot.fY, ymin); - ymax = Math.max(knot.fY, ymax); - }); - - if (ymax > 0) - ymax *= (1 + gStyle.fHistTopMargin); - if (ymin < 0) - ymin *= (1 + gStyle.fHistTopMargin); - } - - const histo = createHistogram(clTH1I, 10); - - histo.fName = spline.fName + '_hist'; - histo.fTitle = spline.fTitle; - histo.fBits |= kNoStats; - - histo.fXaxis.fXmin = xmin; - histo.fXaxis.fXmax = xmax; - histo.fYaxis.fXmin = ymin; - histo.fYaxis.fXmax = ymax; - histo.fMinimum = ymin; - histo.fMaximum = ymax; - - return histo; - } - - /** @summary Process tooltip event - * @private */ - processTooltipEvent(pnt) { - const spline = this.getObject(), - o = this.getOptions(), - funcs = this.getFramePainter()?.getGrFuncs(o.second_x, o.second_y); - let cleanup = false, xx, yy, knot = null, indx = 0; - - if ((pnt === null) || !spline || !funcs) - cleanup = true; - else { - xx = funcs.revertAxis('x', pnt.x); - indx = this.findX(xx); - knot = spline.fPoly[indx]; - yy = this.eval(knot, xx); - - if ((indx < spline.fNp - 1) && (Math.abs(spline.fPoly[indx + 1].fX - xx) < Math.abs(xx - knot.fX))) - knot = spline.fPoly[++indx]; - - if (Math.abs(funcs.grx(knot.fX) - pnt.x) < 0.5 * this.#knot_size) { - xx = knot.fX; - yy = knot.fY; - } else { - knot = null; - if ((xx < spline.fXmin) || (xx > spline.fXmax)) - cleanup = true; - } - } - - let gbin = this.getG()?.selectChild('.tooltip_bin'); - const radius = this.lineatt.width + 3; - - if (cleanup || !this.getG()) { - gbin?.remove(); - return null; - } - - if (gbin.empty()) { - gbin = this.getG().append('svg:circle') - .attr('class', 'tooltip_bin') - .style('pointer-events', 'none') - .attr('r', radius) - .style('fill', 'none') - .call(this.lineatt.func); - } - - const res = { - name: this.getObject().fName, - title: this.getObject().fTitle, - x: funcs.grx(xx), - y: funcs.gry(yy), - color1: this.lineatt.color, - lines: [], - exact: (knot !== null) || (Math.abs(funcs.gry(yy) - pnt.y) < radius) - }; - - res.changed = gbin.property('current_xx') !== xx; - res.menu = res.exact; - res.menu_dist = Math.sqrt((res.x - pnt.x) ** 2 + (res.y - pnt.y) ** 2); - - if (res.changed) { - gbin.attr('cx', Math.round(res.x)) - .attr('cy', Math.round(res.y)) - .property('current_xx', xx); - } - - const name = this.getObjectHint(); - if (name) - res.lines.push(name); - res.lines.push(`x = ${funcs.axisAsText('x', xx)}`, - `y = ${funcs.axisAsText('y', yy)}`); - if (knot !== null) { - res.lines.push(`knot = ${indx}`, - `B = ${floatToString(knot.fB, gStyle.fStatFormat)}`, - `C = ${floatToString(knot.fC, gStyle.fStatFormat)}`, - `D = ${floatToString(knot.fD, gStyle.fStatFormat)}`); - if ((knot.fE !== undefined) && (knot.fF !== undefined)) { - res.lines.push(`E = ${floatToString(knot.fE, gStyle.fStatFormat)}`, - `F = ${floatToString(knot.fF, gStyle.fStatFormat)}`); - } - } - - return res; - } - - /** @summary Redraw object - * @private */ - redraw() { - const spline = this.getObject(), - o = this.getOptions(), - funcs = this.getFramePainter().getGrFuncs(o.second_x, o.second_y), - w = funcs.getFrameWidth(), - h = funcs.getFrameHeight(), - g = this.createG(true); - - this.#knot_size = 5; // used in tooltip handling - - this.createAttLine({ attr: spline }); - - if (o.Line || o.Curve) { - const npx = Math.max(10, spline.fNpx), bins = []; // index of current knot - let xmin = Math.max(funcs.scale_xmin, spline.fXmin), - xmax = Math.min(funcs.scale_xmax, spline.fXmax), - indx = this.findX(xmin); - - if (funcs.logx) { - xmin = Math.log(xmin); - xmax = Math.log(xmax); - } - - for (let n = 0; n < npx; ++n) { - let x = xmin + (xmax - xmin) / (npx - 1) * n; - if (funcs.logx) - x = Math.exp(x); - - while ((indx < spline.fNp - 1) && (x > spline.fPoly[indx + 1].fX)) - ++indx; - - const y = this.eval(spline.fPoly[indx], x); - - bins.push({ x, y, grx: funcs.grx(x), gry: funcs.gry(y) }); - } - - g.append('svg:path') - .attr('class', 'line') - .attr('d', buildSvgCurve(bins)) - .style('fill', 'none') - .call(this.lineatt.func); - } - - if (o.Mark) { - // for tooltips use markers only if nodes where not created - - this.createAttMarker({ attr: spline }); - - this.markeratt.resetPos(); - - this.#knot_size = this.markeratt.getFullSize(); - - let path = ''; - - for (let n = 0; n < spline.fPoly.length; n++) { - const knot = spline.fPoly[n], - grx = funcs.grx(knot.fX); - if ((grx > -this.#knot_size) && (grx < w + this.#knot_size)) { - const gry = funcs.gry(knot.fY); - if ((gry > -this.#knot_size) && (gry < h + this.#knot_size)) - path += this.markeratt.create(grx, gry); - } - } - - if (path) { - g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } - } - } - - /** @summary Checks if it makes sense to zoom inside specified axis range */ - canZoomInside(axis /* , min, max */) { - // spline can always be calculated and therefore one can zoom inside - return (axis !== 'x') ? false : Boolean(this.getObject()); - } - - /** @summary Decode options for TSpline drawing */ - decodeOptions(opt) { - const d = new DrawOptions(opt), - o = this.setOptions({ - Same: d.check('SAME'), - Line: d.check('L'), - Curve: d.check('C'), - Mark: d.check('P'), - Hopt: '', - second_x: false, - second_y: false - }); - - if (!o.Line && !o.Curve && !o.Mark) - o.Curve = true; - - if (d.check('X+')) { - o.Hopt += 'X+'; - o.second_x = Boolean(this.getMainPainter()); - } - if (d.check('Y+')) { - o.Hopt += 'Y+'; - o.second_y = Boolean(this.getMainPainter()); - } - - this.storeDrawOpt(opt); - } - - /** @summary Draw TSpline */ - static async draw(dom, spline, opt) { - const painter = new TSplinePainter(dom, spline); - painter.decodeOptions(opt); - - const no_main = !painter.getMainPainter(); - let promise = Promise.resolve(); - if (no_main || painter.options.second_x || painter.options.second_y) { - if (painter.options.Same && no_main) { - console.warn('TSpline painter requires histogram to be drawn'); - return null; - } - const histo = painter.createDummyHisto(); - promise = TH1Painter.draw(dom, histo, painter.options.Hopt); - } - - return promise.then(() => { - painter.addToPadPrimitives(); - painter.redraw(); - return painter; - }); - } - -} // class TSplinePainter - -export { TSplinePainter }; diff --git a/js/modules/draw/TTextPainter.mjs b/js/modules/draw/TTextPainter.mjs deleted file mode 100644 index ff4e5f046a73d..0000000000000 --- a/js/modules/draw/TTextPainter.mjs +++ /dev/null @@ -1,138 +0,0 @@ -import { clTLink, clTLatex, clTMathText, BIT } from '../core.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { makeTranslate, DrawOptions } from '../base/BasePainter.mjs'; -import { addMoveHandler } from '../gui/utils.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; - - -const kTextNDC = BIT(14); - -class TTextPainter extends ObjectPainter { - - async _redrawText(x, y, annot) { - const text = this.getObject(), - pp = this.getPadPainter(), - fp = this.getFramePainter(), - is_url = text.fName.startsWith('http://') || text.fName.startsWith('https://'); - - // special handling of dummy frame painter - if (fp?.getDrawDom() === null) - return this; - - let fact = 1, use_frame = false; - - this.createAttText({ attr: text }); - - if ((annot === '3d') || text.TestBit(kTextNDC)) - this.isndc = true; - else if (!annot && pp?.getRootPad(true)) { - // force pad coordinates - const d = new DrawOptions(this.getDrawOpt()); - use_frame = d.check('FRAME'); - } else if (!annot) { - // place in the middle - this.isndc = true; - x = y = 0.5; - text.fTextAlign = 22; - } - - const g = this.createG(use_frame ? 'frame2d' : undefined, is_url); - - g.attr('transform', null); // remove transform from interactive changes - - x = this.axisToSvg('x', x, this.isndc); - y = this.axisToSvg('y', y, this.isndc); - this.swap_xy = use_frame && fp?.swap_xy(); - - if (this.swap_xy) - [x, y] = [y, x]; - - const arg = this.textatt.createArg({ x, y, text: text.fTitle, latex: 0 }); - - if (this.matchObjectType(clTLatex) || annot) - arg.latex = 1; - else if (this.matchObjectType(clTMathText)) { - arg.latex = 2; - fact = 0.8; - } - - if (is_url) { - g.attr('href', text.fName); - if (!this.isBatchMode()) - g.append('svg:title').text(`link on ${text.fName}`); - } - - return this.startTextDrawingAsync(this.textatt.font, this.textatt.getSize(pp, fact)) - .then(() => this.drawText(arg)) - .then(() => this.finishTextDrawing()) - .then(() => { - if (this.isBatchMode()) - return this; - - if (pp.isButton() && !pp.isEditable()) { - g.on('click', () => this.getCanvPainter().selectActivePad(pp)); - return this; - } - - Object.assign(this, { pos_x: x, pos_y: y, pos_dx: 0, pos_dy: 0 }); - - if (annot !== '3d') - addMoveHandler(this, true, is_url); - - assignContextMenu(this); - - if (this.matchObjectType(clTLink)) - g.style('cursor', 'pointer').on('click', () => this.submitCanvExec('ExecuteEvent(kButton1Up, 0, 0);;')); - - return this; - }); - } - - async redraw() { - return this._redrawText(this.getObject().fX, this.getObject().fY); - } - - moveDrag(dx, dy) { - this.pos_dx += dx; - this.pos_dy += dy; - makeTranslate(this.getG(), this.pos_dx, this.pos_dy); - } - - moveEnd(not_changed) { - if (not_changed) - return; - const txt = this.getObject(); - let fx = this.svgToAxis('x', this.pos_x + this.pos_dx, this.isndc), - fy = this.svgToAxis('y', this.pos_y + this.pos_dy, this.isndc); - if (this.swap_xy) - [fx, fy] = [fy, fx]; - - txt.fX = fx; - txt.fY = fy; - this.submitCanvExec(`SetX(${fx});;SetY(${fy});;`); - } - - fillContextMenuItems(menu) { - const text = this.getObject(); - menu.add('Edit', () => menu.input('Enter new text', text.fTitle).then(t => { - text.fTitle = t; - this.interactiveRedraw('pad', `exec:SetTitle("${t}")`); - }), `Edit ${text._typename} text`); - menu.add('Copy', () => navigator.clipboard.writeText(text.fTitle), 'Copy text into clipboard'); - menu.add('Paste', () => navigator.clipboard.readText().then(t => { - text.fTitle = t; - this.interactiveRedraw('pad', `exec:SetTitle("${t}")`); - }), 'Set text from clipboard'); - } - - /** @summary draw TText-derived object */ - static async draw(dom, obj, opt) { - const painter = new TTextPainter(dom, obj, opt); - return ensureTCanvas(painter, false).then(() => painter.redraw()); - } - -} // class TTextPainter - - -export { TTextPainter }; diff --git a/js/modules/draw/TTree.mjs b/js/modules/draw/TTree.mjs deleted file mode 100644 index a3fbf320abda9..0000000000000 --- a/js/modules/draw/TTree.mjs +++ /dev/null @@ -1,515 +0,0 @@ -import { internals, httpRequest, isBatchMode, isFunc, isStr, create, toJSON, getPromise, - getKindForType, clTObjString, clTGraph, clTPolyMarker3D, clTH1, clTH2, clTH3 } from '../core.mjs'; -import { select as d3_select } from '../d3.mjs'; -import { kTString, kObject, kAnyP } from '../io.mjs'; -import { kClonesNode, kSTLNode, clTBranchFunc, treeDraw, treeIOTest, TDrawSelector } from '../tree.mjs'; -import { BasePainter } from '../base/BasePainter.mjs'; -import { cleanup, resize, drawRawText, ObjectPainter } from '../base/ObjectPainter.mjs'; -import { TH1Painter } from '../hist/TH1Painter.mjs'; -import { TH2Painter } from '../hist/TH2Painter.mjs'; -import { TH3Painter } from '../hist/TH3Painter.mjs'; -import { TGraphPainter } from '../hist/TGraphPainter.mjs'; -import { drawPolyMarker3D } from '../draw/TPolyMarker3D.mjs'; -import { showProgress, registerForResize } from '../gui/utils.mjs'; - - -/** @summary Show TTree::Draw progress during processing - * @private */ -TDrawSelector.prototype.ShowProgress = function(value) { - let msg, ret; - if ((value === undefined) || !Number.isFinite(value)) - msg = ret = ''; - else if (this._break) { - msg = 'Breaking ... '; - ret = 'break'; - } else { - if (this.last_progress !== value) { - const diff = value - this.last_progress; - if (!this.aver_diff) - this.aver_diff = diff; - this.aver_diff = diff * 0.3 + this.aver_diff * 0.7; - } - - this.last_progress = value; - - let ndig = 0; - if (this.aver_diff <= 0) - ndig = 0; - else if (this.aver_diff < 0.0001) - ndig = 3; - else if (this.aver_diff < 0.001) - ndig = 2; - else if (this.aver_diff < 0.01) - ndig = 1; - msg = `TTree draw ${(value * 100).toFixed(ndig)} % `; - } - - showProgress(msg, 0, () => { this._break = 1; }); - return ret; -}; - -/** @summary Draw result of tree drawing - * @private */ -async function drawTreeDrawResult(dom, obj, opt) { - const typ = obj?._typename; - - if (!typ || !isStr(typ)) - return Promise.reject(Error('Object without type cannot be draw with TTree')); - - if (internals._alt_draw) { - const v = internals._alt_draw(dom, obj, opt); - if (v) - return v; - } - - if (typ.indexOf(clTH1) === 0) - return TH1Painter.draw(dom, obj, opt); - if (typ.indexOf(clTH2) === 0) - return TH2Painter.draw(dom, obj, opt); - if (typ.indexOf(clTH3) === 0) - return TH3Painter.draw(dom, obj, opt); - if (typ.indexOf(clTGraph) === 0) - return TGraphPainter.draw(dom, obj, opt); - if ((typ === clTPolyMarker3D) && obj.$hist) { - return TH3Painter.draw(dom, obj.$hist, opt).then(() => { - const p2 = new ObjectPainter(dom, obj, opt); - p2.addToPadPrimitives(); - p2.redraw = drawPolyMarker3D; - return p2.redraw(); - }); - } - - return Promise.reject(Error(`Object of type ${typ} cannot be draw with TTree`)); -} - - -/** @summary Handle callback function with progress of tree draw - * @private */ -async function treeDrawProgress(obj, final) { - // no need to update drawing if previous is not yet completed - if (!final && !this.last_pr) - return; - - if (this.dump || this.dump_entries || this.testio) { - if (!final) - return; - if (isBatchMode()) { - const painter = new BasePainter(this.drawid); - painter.selectDom().property('_json_object_', obj); - return painter; - } - if (isFunc(internals.drawInspector)) - return internals.drawInspector(this.drawid, obj); - const str = create(clTObjString); - str.fString = toJSON(obj, 2); - return drawRawText(this.drawid, str); - } - - // complex logic with intermediate update - // while TTree reading not synchronized with drawing, - // next portion can appear before previous is drawn - // critical is last drawing which should wait for previous one - // therefore last_pr is kept as indication that promise is not yet processed - - if (!this.last_pr) - this.last_pr = Promise.resolve(true); - - return this.last_pr.then(() => { - if (this.obj_painter) - this.last_pr = getPromise(this.obj_painter.redrawObject(obj)).then(() => this.obj_painter); - else if (!obj) { - if (final) - console.log('no result after tree drawing'); - this.last_pr = false; // return false indicating no drawing is done - } else { - this.last_pr = drawTreeDrawResult(this.drawid, obj, this.drawopt).then(p => { - this.obj_painter = p; - if (!final) - this.last_pr = null; - return p; // return painter for histogram - }); - } - - return final ? this.last_pr : null; - }); -} - - -/** @summary Create painter to perform tree drawing on server side - * @private */ -function createTreePlayer(player, tree, rntuple) { - player.draw_first = true; - - if (tree) - player.local_tree = tree; - else if (rntuple) - player.local_rntuple = rntuple; - - player.configureOnline = function(itemname, url, askey, root_version, expr) { - this.setItemName(itemname, '', this); - this.url = url; - this.root_version = root_version; - this.askey = askey; - this.draw_expr = expr; - }; - - player.showExtraButtons = function(args) { - const main = this.selectDom(), - numentries = this.local_tree?.fEntries || (this.local_rntuple ? 10000000 : 0); - - main.select('.treedraw_more').remove(); // remove more button first - - main.select('.treedraw_buttons').node().innerHTML += - 'Cut: <input class="treedraw_cut ui-corner-all ui-widget" style="width:8em;margin-left:5px" title="cut expression"></input>' + - 'Opt: <input class="treedraw_opt ui-corner-all ui-widget" style="width:5em;margin-left:5px" title="histogram draw options"></input>' + - `Num: <input class="treedraw_number" type='number' min="0" max="${numentries}" step="1000" style="width:7em;margin-left:5px" title="number of entries to process (default all)"></input>` + - `First: <input class="treedraw_first" type='number' min="0" max="${numentries}" step="1000" style="width:7em;margin-left:5px" title="first entry to process (default first)"></input>` + - '<button class="treedraw_clear" title="Clear drawing">Clear</button>'; - - main.select('.treedraw_exe').on('click', () => this.performDraw()); - main.select('.treedraw_cut').property('value', args?.parse_cut || '').on('change', () => this.performDraw()); - main.select('.treedraw_opt').property('value', args?.drawopt || '').on('change', () => this.performDraw()); - main.select('.treedraw_number').attr('value', args?.numentries || ''); // .on('change', () => this.performDraw()); - main.select('.treedraw_first').attr('value', args?.firstentry || ''); // .on('change', () => this.performDraw()); - main.select('.treedraw_clear').on('click', () => cleanup(this.drawid)); - - const expr = this.getValue('.treedraw_varexp'); - if (isStr(expr)) { - const p = expr.indexOf('::'); - if (p >= 0) { - main.select('.treedraw_varexp').attr('value', expr.slice(0, p)); - main.select('.treedraw_cut').attr('value', expr.slice(p + 2)); - } - } - }; - - player.showPlayer = function(args) { - const main = this.selectDom(); - - this.drawid = 'jsroot_tree_player_' + internals.id_counter++ + '_draw'; - - const show_extra = args?.parse_cut || args?.numentries || args?.firstentry; - - main.html('<div style="display:flex; flex-flow:column; height:100%; width:100%;">' + - '<div class="treedraw_buttons" style="flex: 0 1 auto;margin-top:0.2em;">' + - '<button class="treedraw_exe" title="Execute draw expression" style="margin-inline:0.5em">Draw</button>' + - 'Expr:<input class="treedraw_varexp treedraw_varexp_info" style="width:12em;margin-left:5px" title="draw expression"></input>' + - '<label class="treedraw_varexp_info" style="margin-inline:0.5em">\u24D8</label>' + - '<button class="treedraw_more">More</button>' + - '</div>' + - '<div style="flex: 0 1 auto"><hr/></div>' + - `<div id="${this.drawid}" style="flex: 1 1 auto; overflow:hidden;"></div>` + - '</div>'); - - // only when main html element created, one can set painter - // ObjectPainter allow such usage of methods from BasePainter - this.setTopPainter(); - - if (this.local_tree) - main.select('.treedraw_buttons').attr('title', 'Tree draw player for: ' + this.local_tree.fName); - else if (this.local_rntuple) - main.select('.treedraw_buttons').attr('title', 'RNTuple draw player for local rntuple'); - main.select('.treedraw_exe').on('click', () => this.performDraw()); - main.select('.treedraw_varexp') - .attr('value', args?.parse_expr || args?.expr || this.draw_expr || 'px:py') - .on('change', () => this.performDraw()); - main.select('.treedraw_varexp_info') - .attr('title', 'Example of valid draw expressions:\n' + - ' px - 1-dim draw\n' + - ' px:py - 2-dim draw\n' + - ' px:py:pz - 3-dim draw\n' + - ' px+py:px-py - use any expressions\n' + - ' px:py>>Graph - create and draw TGraph\n' + - ' px:py>>dump - dump extracted variables\n' + - ' px:py>>h(50,-5,5,50,-5,5) - custom histogram\n' + - ' px:py;hbins:100 - custom number of bins'); - - if (show_extra) - this.showExtraButtons(args); - else - main.select('.treedraw_more').on('click', () => this.showExtraButtons(args)); - - this.checkResize(); - - registerForResize(this); - }; - - player.getValue = function(sel) { - const elem = this.selectDom().select(sel); - if (elem.empty()) - return; - return elem.property('value') ?? elem.attr('value'); - }; - - player.performLocalDraw = function() { - const frame = this.selectDom(), - args = { expr: this.getValue('.treedraw_varexp') }; - - if (frame.select('.treedraw_more').empty()) { - args.cut = this.getValue('.treedraw_cut'); - if (!args.cut) - delete args.cut; - - args.drawopt = this.getValue('.treedraw_opt'); - if (args.drawopt === 'dump') { - args.dump = true; - args.drawopt = ''; - } - if (!args.drawopt) - delete args.drawopt; - - args.numentries = parseInt(this.getValue('.treedraw_number')); - if (!Number.isInteger(args.numentries)) - delete args.numentries; - - args.firstentry = parseInt(this.getValue('.treedraw_first')); - if (!Number.isInteger(args.firstentry)) - delete args.firstentry; - } - - const json = JSON.stringify(args); - - // prevent execute same expression twice because of some extra browser events - if (this._last_args_json && this._last_args_json === json) - return; - this._last_args_json = json; - - cleanup(this.drawid); - - args.drawid = this.drawid; - - args.progress = treeDrawProgress.bind(args); - - if (this.local_tree) - treeDraw(this.local_tree, args).then(obj => args.progress(obj, true)); - else if (this.local_rntuple && isFunc(this.rntupleDraw)) - this.rntupleDraw(this.local_rntuple, args).then(obj => args.progress(obj, true)); - }; - - player.getDrawOpt = function() { - let res = 'player'; - const expr = this.getValue('.treedraw_varexp'); - if (expr) - res += ':' + expr; - return res; - }; - - player.performDraw = function() { - if (this.local_tree || this.local_rntuple) - return this.performLocalDraw(); - - const frame = this.selectDom(); - let url = this.url + '/exe.json.gz?compact=3&method=Draw', - expr = this.getValue('.treedraw_varexp'), - hname = 'h_tree_draw', option = ''; - const pos = expr.indexOf('>>'); - - if (pos < 0) - expr += `>>${hname}`; - else { - hname = expr.slice(pos + 2); - if (hname[0] === '+') - hname = hname.slice(1); - const pos2 = hname.indexOf('('); - if (pos2 > 0) - hname = hname.slice(0, pos2); - } - - if (frame.select('.treedraw_more').empty()) { - const cut = this.getValue('.treedraw_cut'); - let nentries = this.getValue('.treedraw_number'), - firstentry = this.getValue('.treedraw_first'); - - option = this.getValue('.treedraw_opt'); - - url += `&prototype="const char*,const char*,Option_t*,Long64_t,Long64_t"&varexp="${expr}"&selection="${cut}"`; - - // provide all optional arguments - default value kMaxEntries not works properly in ROOT6 - if (!nentries) - nentries = 'TTree::kMaxEntries'; // kMaxEntries available since ROOT 6.05/03 - if (!firstentry) - firstentry = '0'; - url += `&option="${option}"&nentries=${nentries}&firstentry=${firstentry}`; - } else - url += `&prototype="Option_t*"&opt="${expr}"`; - - url += `&_ret_object_=${hname}`; - - const submitDrawRequest = () => { - httpRequest(url, 'object').then(res => { - cleanup(this.drawid); - drawTreeDrawResult(this.drawid, res, option); - }); - }; - - this.draw_expr = expr; - - if (this.askey) { - // first let read tree from the file - this.askey = false; - httpRequest(this.url + '/root.json.gz?compact=3', 'text').then(submitDrawRequest); - } else - submitDrawRequest(); - }; - - player.checkResize = function(/* arg */) { - resize(this.drawid); - }; - - return player; -} - - -/** @summary function used with THttpServer to assign player for the TTree object - * @private */ -function drawTreePlayer(hpainter, itemname, askey, asleaf) { - let item = hpainter.findItem(itemname), - expr = '', leaf_cnt = 0; - const top = hpainter.getTopOnlineItem(item); - if (!item || !top) - return null; - - if (asleaf) { - expr = item._name; - while (item && !item._ttree) - item = item._parent; - if (!item) - return null; - itemname = hpainter.itemFullName(item); - } - - const url = hpainter.getOnlineItemUrl(itemname); - if (!url) - return null; - - const root_version = top._root_version || 400129, // by default use version number 6-27-01 - mdi = hpainter.getDisplay(); - if (!mdi) - return null; - - const frame = mdi.findFrame(itemname, true); - if (!frame) - return null; - - const divid = d3_select(frame).attr('id'), - player = new BasePainter(divid); - - if (item._childs && !asleaf) { - for (let n = 0; n < item._childs.length; ++n) { - const leaf = item._childs[n]; - if (isStr(leaf?._kind) && (leaf._kind.indexOf(getKindForType('TLeaf')) === 0) && (leaf_cnt < 2)) { - if (leaf_cnt++ > 0) - expr += ':'; - expr += leaf._name; - } - } - } - - createTreePlayer(player); - player.configureOnline(itemname, url, askey, root_version, expr); - player.showPlayer(); - - return player; -} - -/** @summary function used with THttpServer when tree is not yet loaded - * @private */ -function drawTreePlayerKey(hpainter, itemname) { - return drawTreePlayer(hpainter, itemname, true); -} - -/** @summary function used with THttpServer when tree is not yet loaded - * @private */ -function drawLeafPlayer(hpainter, itemname) { - return drawTreePlayer(hpainter, itemname, false, true); -} - -/** @summary function called from draw() - * @desc just envelope for real TTree::Draw method which do the main job - * Can be also used for the branch and leaf object - * @private */ -async function drawTree(dom, obj, opt) { - let tree = obj, args = opt; - - if (obj._typename === clTBranchFunc) { - // fictional object, created only in browser - args = { expr: `.${obj.func}()`, branch: obj.branch }; - if (opt && opt.indexOf('dump') === 0) - args.expr += '>>' + opt; - else if (opt) - args.expr += opt; - tree = obj.branch.$tree; - } else if (obj.$branch) { - // this is drawing of the single leaf from the branch - args = { expr: `.${obj.fName}${opt || ''}`, branch: obj.$branch }; - if ((args.branch.fType === kClonesNode) || (args.branch.fType === kSTLNode)) { - // special case of size - args.expr = opt; - args.direct_branch = true; - } - - tree = obj.$branch.$tree; - } else if (obj.$tree) { - // this is drawing of the branch - - // if generic object tried to be drawn without specifying any options, it will be just dump - if (!opt && obj.fStreamerType && (obj.fStreamerType !== kTString) && - (obj.fStreamerType >= kObject) && (obj.fStreamerType <= kAnyP)) - opt = 'dump'; - - args = { expr: opt, branch: obj }; - tree = obj.$tree; - } else { - if (!args) - args = 'player'; - if (isStr(args)) - args = { expr: args }; - } - - if (!tree) - throw Error('No TTree object available for TTree::Draw'); - - if (isStr(args.expr)) { - const p = args.expr.indexOf('player'); - if (p === 0) { - args.player = true; - args.expr = args.expr.slice(6); - if (args.expr[0] === ':') - args.expr = args.expr.slice(1); - } else if ((p >= 0) && (p === args.expr.length - 6)) { - args.player = true; - args.expr = args.expr.slice(0, p); - if ((p > 0) && (args.expr[p - 1] === ';')) - args.expr = args.expr.slice(0, p - 1); - } - } - - let painter; - - if (args.player) { - painter = new ObjectPainter(dom, obj, opt); - createTreePlayer(painter, tree); - painter.showPlayer(args); - args.drawid = painter.drawid; - } else - args.drawid = dom; - - - // use in result handling same function as for progress handling - - args.progress = treeDrawProgress.bind(args); - - let pr; - if (args.expr === 'testio') { - args.testio = true; - args.showProgress = msg => showProgress(msg, -1, () => { args._break = 1; }); - pr = treeIOTest(tree, args); - } else if (args.expr || args.branch) - pr = treeDraw(tree, args); - else - return painter; - - return pr.then(res => args.progress(res, true)); -} - -export { drawTree, drawTreePlayer, drawTreePlayerKey, drawLeafPlayer, treeDrawProgress, createTreePlayer }; diff --git a/js/modules/draw/TWebPaintingPainter.mjs b/js/modules/draw/TWebPaintingPainter.mjs deleted file mode 100644 index 42a0bb0e86864..0000000000000 --- a/js/modules/draw/TWebPaintingPainter.mjs +++ /dev/null @@ -1,227 +0,0 @@ -import { getColor } from '../base/colors.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { pointer as d3_pointer } from '../d3.mjs'; -import { urlClassPrefix } from '../core.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; - - -/** - * @summary Painter for TWebPainting classes. - * - * @private - */ - -class TWebPaintingPainter extends ObjectPainter { - - /** @summary Update TWebPainting object */ - updateObject(obj) { - if (!this.matchObjectType(obj)) - return false; - this.assignObject(obj); - return true; - } - - /** @summary Provides menu header */ - getMenuHeader() { - return this.getObject()?.fClassName || 'TWebPainting'; - } - - /** @summary Fill context menu - * @desc Create only header, items will be requested from server */ - fillContextMenu(menu) { - const cl = this.getMenuHeader(); - menu.header(cl, `${urlClassPrefix}${cl}.html`); - return true; - } - - /** @summary Mouse click handler - * @desc Redirect mouse click events to the ROOT application - * @private */ - handleMouseClick(evnt) { - const pp = this.getPadPainter(), - rect = pp?.getPadRect(); - - if (pp && rect && this.getSnapId()) { - const pos = d3_pointer(evnt, this.getG().node()); - pp.selectObjectPainter(this, { x: pos[0] + rect.x, y: pos[1] + rect.y }); - } - } - - /** @summary draw TWebPainting object */ - async redraw() { - const obj = this.getObject(), func = this.getAxisToSvgFunc(); - - if (!obj?.fOper || !func) - return this; - - let indx = 0, attr = {}, lastpath = null, lastkind = 'none', d = '', - oper, npoints, n; - - /* eslint-disable one-var */ - const g = this.createG(), - arr = obj.fOper.split(';'); - const check_attributes = kind => { - if (kind === lastkind) - return; - - if (lastpath) { - lastpath.attr('d', d); // flush previous - d = ''; - lastpath = null; - lastkind = 'none'; - } - - if (!kind) - return; - - lastkind = kind; - lastpath = g.append('svg:path').attr('d', ''); // placeholder for 'd' to have it always in front - switch (kind) { - case 'f': - lastpath.call(this.fillatt.func); - break; - case 'l': - lastpath.call(this.lineatt.func).style('fill', 'none'); - break; - case 'm': - lastpath.call(this.markeratt.func); - break; - } - }; - const read_attr = (str, names) => { - let lastp = 0; - const obj2 = { _typename: 'any' }; - for (let k = 0; k < names.length; ++k) { - const p = str.indexOf(':', lastp + 1); - obj2[names[k]] = parseInt(str.slice(lastp + 1, p > lastp ? p : undefined)); - lastp = p; - } - return obj2; - }; - const process = k => { - while (++k < arr.length) { - oper = arr[k][0]; - switch (oper) { - case 'z': - this.createAttLine({ attr: read_attr(arr[k], ['fLineColor', 'fLineStyle', 'fLineWidth']), force: true }); - check_attributes(); - continue; - case 'y': - this.createAttFill({ attr: read_attr(arr[k], ['fFillColor', 'fFillStyle']), force: true }); - check_attributes(); - continue; - case 'x': - this.createAttMarker({ attr: read_attr(arr[k], ['fMarkerColor', 'fMarkerStyle', 'fMarkerSize']), force: true }); - check_attributes(); - continue; - case 'o': - attr = read_attr(arr[k], ['fTextColor', 'fTextFont', 'fTextSize', 'fTextAlign', 'fTextAngle']); - if (attr.fTextSize < 0) - attr.fTextSize *= -0.001; - check_attributes(); - continue; - case 'r': - case 'b': { - check_attributes((oper === 'b') ? 'f' : 'l'); - - const x1 = func.x(obj.fBuf[indx++]), - y1 = func.y(obj.fBuf[indx++]), - x2 = func.x(obj.fBuf[indx++]), - y2 = func.y(obj.fBuf[indx++]); - - d += `M${x1},${y1}h${x2 - x1}v${y2 - y1}h${x1 - x2}z`; - continue; - } - case 'l': - case 'f': { - check_attributes(oper); - - npoints = parseInt(arr[k].slice(1)); - - for (n = 0; n < npoints; ++n) - d += `${(n > 0) ? 'L' : 'M'}${func.x(obj.fBuf[indx++])},${func.y(obj.fBuf[indx++])}`; - - if (oper === 'f') - d += 'Z'; - - continue; - } - - case 'm': { - check_attributes(oper); - - npoints = parseInt(arr[k].slice(1)); - - this.markeratt.resetPos(); - for (n = 0; n < npoints; ++n) - d += this.markeratt.create(func.x(obj.fBuf[indx++]), func.y(obj.fBuf[indx++])); - - continue; - } - - case 'h': - case 't': { - if (attr.fTextSize) { - check_attributes(); - - const height = (attr.fTextSize > 1) ? attr.fTextSize : this.getPadPainter().getPadHeight() * attr.fTextSize, - group = g.append('svg:g'); - - return this.startTextDrawingAsync(attr.fTextFont, height, group).then(() => { - let text = arr[k].slice(1), - angle = attr.fTextAngle; - if (angle >= 360) - angle -= Math.floor(angle / 360) * 360; - - if (oper === 'h') { - let res = ''; - for (n = 0; n < text.length; n += 2) - res += String.fromCharCode(parseInt(text.slice(n, n + 2), 16)); - text = res; - } - - // todo - correct support of angle - this.drawText({ - align: attr.fTextAlign, - x: func.x(obj.fBuf[indx++]), - y: func.y(obj.fBuf[indx++]), - rotate: -angle, - text, - color: getColor(attr.fTextColor), - latex: 0, draw_g: group - }); - - return this.finishTextDrawing(group); - }).then(() => process(k)); - } - continue; - } - - default: - console.log(`unsupported operation ${oper}`); - } - } - - return Promise.resolve(true); - }; - - - return process(-1).then(() => { - check_attributes(); - assignContextMenu(this); - if (!this.isBatchMode()) - g.on('click', evnt => this.handleMouseClick(evnt)); - return this; - }); - } - - static async draw(dom, obj) { - const painter = new TWebPaintingPainter(dom, obj); - painter.addToPadPrimitives(); - return painter.redraw(); - } - -} // class TWebPaintingPainter - - -export { TWebPaintingPainter }; diff --git a/js/modules/draw/draw3d.mjs b/js/modules/draw/draw3d.mjs deleted file mode 100644 index c8a4c8e9ccfc9..0000000000000 --- a/js/modules/draw/draw3d.mjs +++ /dev/null @@ -1,109 +0,0 @@ -import { isObject, isFunc } from '../core.mjs'; -import { createLineSegments, create3DLineMaterial } from '../base/base3d.mjs'; -import { drawDummy3DGeom } from '../geom/TGeoPainter.mjs'; -import { drawPolyMarker3D as drawPolyMarker3Dplain } from './TPolyMarker3D.mjs'; - - -/** @summary Prepare frame painter for 3D drawing - * @private */ -function before3DDraw(painter) { - const fp = painter.getFramePainter(); - - if (!fp?.mode3d || !painter.getObject()) - return null; - - if (fp?.toplevel) - return fp; - - const main = painter.getMainPainter(); - - if (main && !isFunc(main.drawExtras)) - return null; - - const pr = main ? Promise.resolve(main) : drawDummy3DGeom(painter); - - return pr.then(geop => { - const pp = painter.getPadPainter(); - if (pp) - pp.options._disable_dragging = true; - - if (geop.options.dummy && isFunc(painter.get3DBox)) - geop.extendCustomBoundingBox(painter.get3DBox()); - return geop.drawExtras(painter.getObject(), '', true, true); - }); -} - -/** @summary Function to extract 3DBox for poly marker and line - * @private */ -function get3DBox() { - const obj = this.getObject(); - if (!obj?.fP.length) - return null; - const box = { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } }; - - for (let k = 0; k < obj.fP.length; k += 3) { - const x = obj.fP[k], - y = obj.fP[k + 1], - z = obj.fP[k + 2]; - if (k === 0) { - box.min.x = box.max.x = x; - box.min.y = box.max.y = y; - box.min.z = box.max.z = z; - } else { - box.min.x = Math.min(x, box.min.x); - box.max.x = Math.max(x, box.max.x); - box.min.y = Math.min(y, box.min.y); - box.max.y = Math.max(y, box.max.y); - box.min.z = Math.min(z, box.min.z); - box.max.z = Math.max(z, box.max.z); - } - } - - return box; -} - - -/** @summary direct draw function for TPolyMarker3D object (optionally with geo painter) - * @private */ -async function drawPolyMarker3D() { - this.get3DBox = get3DBox; - - const fp = before3DDraw(this); - - if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) - return fp; - - this.$fp = fp; - - return drawPolyMarker3Dplain.bind(this)(); -} - -/** @summary Direct draw function for TPolyLine3D object - * @desc Takes into account dashed properties - * @private */ -async function drawPolyLine3D() { - this.get3DBox = get3DBox; - - const line = this.getObject(), - fp = before3DDraw(this); - - if (!isObject(fp) || !fp.grx || !fp.gry || !fp.grz) - return fp; - - const limit = 3 * line.fN, p = line.fP, pnts = []; - - for (let n = 3; n < limit; n += 3) { - pnts.push(fp.grx(p[n - 3]), fp.gry(p[n - 2]), fp.grz(p[n - 1]), - fp.grx(p[n]), fp.gry(p[n + 1]), fp.grz(p[n + 2])); - } - - const lines = createLineSegments(pnts, create3DLineMaterial(this, line)); - - fp.add3DMesh(lines, this, true); - - fp.render3D(100); - - return true; -} - -export { drawPolyMarker3D, drawPolyLine3D }; diff --git a/js/modules/draw/more.mjs b/js/modules/draw/more.mjs deleted file mode 100644 index 84d06f62ef8fc..0000000000000 --- a/js/modules/draw/more.mjs +++ /dev/null @@ -1,245 +0,0 @@ -import { BIT } from '../core.mjs'; -import { BasePainter, makeTranslate, DrawOptions } from '../base/BasePainter.mjs'; -import { addMoveHandler } from '../gui/utils.mjs'; -import { assignContextMenu } from '../gui/menu.mjs'; - - -/** @summary Draw TEllipse - * @private */ -function drawEllipse() { - const ellipse = this.getObject(), - closed_ellipse = (ellipse.fPhimin === 0) && (ellipse.fPhimax === 360), - is_crown = (ellipse._typename === 'TCrown'); - - this.createAttLine({ attr: ellipse }); - this.createAttFill({ attr: ellipse }); - - const g = this.createG(), - funcs = this.getAxisToSvgFunc(), - x = funcs.x(ellipse.fX1), - y = funcs.y(ellipse.fY1), - rx = is_crown && (ellipse.fR1 <= 0) ? (funcs.x(ellipse.fX1 + ellipse.fR2) - x) : (funcs.x(ellipse.fX1 + ellipse.fR1) - x), - ry = y - funcs.y(ellipse.fY1 + ellipse.fR2), - dr = Math.PI / 180; - - let path = ''; - - if (is_crown && (ellipse.fR1 > 0)) { - const ratio = ellipse.fYXRatio ?? 1, - rx1 = rx, ry2 = ratio * ry, - ry1 = ratio * (y - funcs.y(ellipse.fY1 + ellipse.fR1)), - rx2 = funcs.x(ellipse.fX1 + ellipse.fR2) - x; - - if (closed_ellipse) { - path = `M${-rx1},0A${rx1},${ry1},0,1,0,${rx1},0A${rx1},${ry1},0,1,0,${-rx1},0` + - `M${-rx2},0A${rx2},${ry2},0,1,0,${rx2},0A${rx2},${ry2},0,1,0,${-rx2},0`; - } else { - const large_arc = (ellipse.fPhimax - ellipse.fPhimin >= 180) ? 1 : 0, - a1 = ellipse.fPhimin * dr, a2 = ellipse.fPhimax * dr, - dx1 = Math.round(rx1 * Math.cos(a1)), dy1 = Math.round(ry1 * Math.sin(a1)), - dx2 = Math.round(rx1 * Math.cos(a2)), dy2 = Math.round(ry1 * Math.sin(a2)), - dx3 = Math.round(rx2 * Math.cos(a1)), dy3 = Math.round(ry2 * Math.sin(a1)), - dx4 = Math.round(rx2 * Math.cos(a2)), dy4 = Math.round(ry2 * Math.sin(a2)); - - path = `M${dx2},${dy2}A${rx1},${ry1},0,${large_arc},0,${dx1},${dy1}` + - `L${dx3},${dy3}A${rx2},${ry2},0,${large_arc},1,${dx4},${dy4}Z`; - } - } else if (ellipse.fTheta === 0) { - if (closed_ellipse) - path = `M${-rx},0A${rx},${ry},0,1,0,${rx},0A${rx},${ry},0,1,0,${-rx},0Z`; - else { - const x1 = Math.round(rx * Math.cos(ellipse.fPhimin * dr)), - y1 = Math.round(ry * Math.sin(ellipse.fPhimin * dr)), - x2 = Math.round(rx * Math.cos(ellipse.fPhimax * dr)), - y2 = Math.round(ry * Math.sin(ellipse.fPhimax * dr)); - path = `M0,0L${x1},${y1}A${rx},${ry},0,1,1,${x2},${y2}Z`; - } - } else { - const ct = Math.cos(ellipse.fTheta * dr), - st = Math.sin(ellipse.fTheta * dr), - phi1 = ellipse.fPhimin * dr, - phi2 = ellipse.fPhimax * dr, - np = 200, - dphi = (phi2 - phi1) / (np - (closed_ellipse ? 0 : 1)); - let lastx = 0, lasty = 0; - if (!closed_ellipse) - path = 'M0,0'; - for (let n = 0; n < np; ++n) { - const angle = phi1 + n * dphi, - dx = ellipse.fR1 * Math.cos(angle), - dy = ellipse.fR2 * Math.sin(angle), - px = funcs.x(ellipse.fX1 + dx * ct - dy * st) - x, - py = funcs.y(ellipse.fY1 + dx * st + dy * ct) - y; - if (!path) - path = `M${px},${py}`; - else if (lastx === px) - path += `v${py - lasty}`; - else if (lasty === py) - path += `h${px - lastx}`; - else - path += `l${px - lastx},${py - lasty}`; - lastx = px; - lasty = py; - } - path += 'Z'; - } - - this.x = x; - this.y = y; - - makeTranslate(g.append('svg:path'), x, y) - .attr('d', path) - .call(this.lineatt.func) - .call(this.fillatt.func); - - assignContextMenu(this); - - addMoveHandler(this); - - this.moveDrag = function(dx, dy) { - this.x += dx; - this.y += dy; - makeTranslate(this.getG().select('path'), this.x, this.y); - }; - - this.moveEnd = function(not_changed) { - if (not_changed) - return; - const ell = this.getObject(); - ell.fX1 = this.svgToAxis('x', this.x); - ell.fY1 = this.svgToAxis('y', this.y); - this.submitCanvExec(`SetX1(${ell.fX1});;SetY1(${ell.fY1});;Notify();;`); - }; -} - -/** @summary Draw TMarker - * @private */ -function drawMarker() { - const marker = this.getObject(), - kMarkerNDC = BIT(14); - - this.isndc = marker.TestBit(kMarkerNDC); - - const d = new DrawOptions(this.getDrawOpt()), - use_frame = this.isndc ? false : d.check('FRAME'), - swap_xy = use_frame && this.getFramePainter()?.swap_xy(); - - this.createAttMarker({ attr: marker }); - - const g = this.createG(use_frame ? 'frame2d' : undefined); - - let x = this.axisToSvg('x', marker.fX, this.isndc), - y = this.axisToSvg('y', marker.fY, this.isndc); - if (swap_xy) - [x, y] = [y, x]; - - const path = this.markeratt.create(x, y); - - if (path) { - g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } - - if (d.check('NO_INTERACTIVE')) - return; - - assignContextMenu(this); - - addMoveHandler(this); - - this.dx = this.dy = 0; - - this.moveDrag = function(dx, dy) { - this.dx += dx; - this.dy += dy; - if (this.getG()) - makeTranslate(this.getG().select('path'), this.dx, this.dy); - }; - - this.moveEnd = function(not_changed) { - if (not_changed || !this.getG()) - return; - const mrk = this.getObject(); - let fx = this.svgToAxis('x', this.axisToSvg('x', mrk.fX, this.isndc) + this.dx, this.isndc), - fy = this.svgToAxis('y', this.axisToSvg('y', mrk.fY, this.isndc) + this.dy, this.isndc); - if (swap_xy) - [fx, fy] = [fy, fx]; - mrk.fX = fx; - mrk.fY = fy; - this.submitCanvExec(`SetX(${fx});;SetY(${fy});;Notify();;`); - this.redraw(); - }; -} - -/** @summary Draw TPolyMarker - * @private */ -function drawPolyMarker() { - const poly = this.getObject(), - func = this.getAxisToSvgFunc(); - - this.createAttMarker({ attr: poly }); - - const g = this.createG(); - - let path = ''; - for (let n = 0; n <= poly.fLastPoint; ++n) - path += this.markeratt.create(func.x(poly.fX[n]), func.y(poly.fY[n])); - - if (path) { - g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } - - assignContextMenu(this); - - addMoveHandler(this); - - this.dx = this.dy = 0; - - this.moveDrag = function(dx, dy) { - this.dx += dx; - this.dy += dy; - if (this.getG()) - makeTranslate(this.getG().select('path'), this.dx, this.dy); - }; - - this.moveEnd = function(not_changed) { - if (not_changed || !this.getG()) - return; - const poly2 = this.getObject(), - func2 = this.getAxisToSvgFunc(); - let exec = ''; - for (let n = 0; n <= poly2.fLastPoint; ++n) { - const x = this.svgToAxis('x', func2.x(poly2.fX[n]) + this.dx), - y = this.svgToAxis('y', func2.y(poly2.fY[n]) + this.dy); - poly2.fX[n] = x; - poly2.fY[n] = y; - exec += `SetPoint(${n},${x},${y});;`; - } - this.submitCanvExec(exec + 'Notify();;'); - this.redraw(); - }; -} - -/** @summary Draw JS image - * @private */ -function drawJSImage(dom, obj, opt) { - const painter = new BasePainter(dom), - main = painter.selectDom(), - img = main.append('img').attr('src', obj.fName).attr('title', obj.fTitle || obj.fName); - - if (opt && opt.indexOf('scale') >= 0) - img.style('width', '100%').style('height', '100%'); - else if (opt && opt.indexOf('center') >= 0) { - main.style('position', 'relative'); - img.attr('style', 'margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);'); - } - - painter.setTopPainter(); - - return painter; -} - -export { drawEllipse, drawMarker, drawPolyMarker, drawJSImage }; diff --git a/js/modules/draw/v7more.mjs b/js/modules/draw/v7more.mjs deleted file mode 100644 index 74241aaaba0b7..0000000000000 --- a/js/modules/draw/v7more.mjs +++ /dev/null @@ -1,356 +0,0 @@ -import { addMethods, settings, nsREX, isFunc } from '../core.mjs'; -import { select as d3_select, rgb as d3_rgb, pointer as d3_pointer } from '../d3.mjs'; -import { makeTranslate } from '../base/BasePainter.mjs'; -import { RObjectPainter } from '../base/RObjectPainter.mjs'; -import { addDragHandler } from '../gpad/TFramePainter.mjs'; -import { ensureRCanvas } from '../gpad/RCanvasPainter.mjs'; -import { createMenu } from '../gui/menu.mjs'; - - -/** @summary draw RText object - * @private */ -function drawText() { - const text = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p = pp.getCoordinate(text.fPos, onframe), - textFont = this.v7EvalFont('text', { size: 12, color: 'black', align: 22 }); - - this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - - return this.startTextDrawingAsync(textFont, 'font').then(() => { - this.drawText({ x: p.x, y: p.y, text: text.fText, latex: 1 }); - return this.finishTextDrawing(); - }); -} - -/** @summary draw RLine object - * @private */ -function drawLine() { - const line = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p1 = pp.getCoordinate(line.fP1, onframe), - p2 = pp.getCoordinate(line.fP2, onframe), - g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - - this.createv7AttLine(); - - g.append('svg:path') - .attr('d', `M${p1.x},${p1.y}L${p2.x},${p2.y}`) - .call(this.lineatt.func); -} - -/** @summary draw RBox object - * @private */ -function drawBox() { - const box = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p1 = pp.getCoordinate(box.fP1, onframe), - p2 = pp.getCoordinate(box.fP2, onframe), - g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - - this.createv7AttLine('border_'); - - this.createv7AttFill(); - - g.append('svg:path') - .attr('d', `M${p1.x},${p1.y}H${p2.x}V${p2.y}H${p1.x}Z`) - .call(this.lineatt.func) - .call(this.fillatt.func); -} - -/** @summary draw RMarker object - * @private */ -function drawMarker() { - const marker = this.getObject(), - pp = this.getPadPainter(), - onframe = this.v7EvalAttr('onFrame', false) ? pp.getFramePainter() : null, - clipping = onframe ? this.v7EvalAttr('clipping', false) : false, - p = pp.getCoordinate(marker.fP, onframe), - g = this.createG(clipping ? 'main_layer' : (onframe ? 'upper_layer' : false)); - - this.createv7AttMarker(); - - const path = this.markeratt.create(p.x, p.y); - - if (path) { - g.append('svg:path') - .attr('d', path) - .call(this.markeratt.func); - } -} - -/** @summary painter for RPalette - * - * @private - */ - -class RPalettePainter extends RObjectPainter { - - /** @summary get palette */ - getHistPalette() { - const pal = this.getObject()?.fPalette; - - if (pal && !isFunc(pal.getColor)) - addMethods(pal, `${nsREX}RPalette`); - - return pal; - } - - /** @summary Draw palette */ - drawPalette(drag) { - const palette = this.getHistPalette(), - contour = palette.getContour(), - fp = this.getFramePainter(); - - if (!contour) - return console.log('no contour - no palette'); - - // frame painter must be there - if (!fp) - return console.log('no frame painter - no palette'); - - const zmin = contour.at(0), - zmax = contour.at(-1), - rect = fp.getFrameRect(), - pad_width = this.getPadPainter().getPadWidth(), - pad_height = this.getPadPainter().getPadHeight(), - visible = this.v7EvalAttr('visible', true), - vertical = this.v7EvalAttr('vertical', true), - g = this.getG(); - let gmin = palette.full_min, - gmax = palette.full_max, - palette_x, palette_y, palette_width, palette_height; - - if (drag) { - palette_width = drag.width; - palette_height = drag.height; - - const changes = {}; - if (vertical) { - this.v7AttrChange(changes, 'margin', (drag.x - rect.x - rect.width) / pad_width); - this.v7AttrChange(changes, 'width', palette_width / pad_width); - } else { - this.v7AttrChange(changes, 'margin', (drag.y - rect.y - rect.height) / pad_width); - this.v7AttrChange(changes, 'width', palette_height / pad_height); - } - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - } else { - if (vertical) { - const margin = this.v7EvalLength('margin', pad_width, 0.02); - palette_x = Math.round(rect.x + rect.width + margin); - palette_width = this.v7EvalLength('width', pad_width, 0.05); - palette_y = rect.y; - palette_height = rect.height; - } else { - const margin = this.v7EvalLength('margin', pad_height, 0.02); - palette_x = rect.x; - palette_width = rect.width; - palette_y = Math.round(rect.y + rect.height + margin); - palette_height = this.v7EvalLength('width', pad_height, 0.05); - } - - // x,y,width,height attributes used for drag functionality - makeTranslate(g, palette_x, palette_y); - } - - let g_btns = g.selectChild('.colbtns'); - if (g_btns.empty()) - g_btns = g.append('svg:g').attr('class', 'colbtns'); - else - g_btns.selectAll('*').remove(); - - if (!visible) - return; - - g_btns.append('svg:path') - .attr('d', `M0,0H${palette_width}V${palette_height}H0Z`) - .style('stroke', 'black') - .style('fill', 'none'); - - if ((gmin === undefined) || (gmax === undefined)) { - gmin = zmin; - gmax = zmax; - } - - if (vertical) - fp.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, true, [palette_height, 0], -palette_height, { reverse: false }); - else - fp.z_handle.configureAxis('zaxis', gmin, gmax, zmin, zmax, false, [0, palette_width], palette_width, { reverse: false }); - - for (let i = 0; i < contour.length - 1; ++i) { - const z0 = Math.round(fp.z_handle.gr(contour[i])), - z1 = Math.round(fp.z_handle.gr(contour[i + 1])), - col = palette.getContourColor((contour[i] + contour[i + 1]) / 2), - r = g_btns.append('svg:path') - .attr('d', vertical ? `M0,${z1}H${palette_width}V${z0}H0Z` : `M${z0},0V${palette_height}H${z1}V0Z`) - .style('fill', col) - .style('stroke', col) - .property('fill0', col) - .property('fill1', d3_rgb(col).darker(0.5).formatRgb()); - - if (this.isBatchMode()) - continue; - - if (this.isTooltipAllowed()) { - r.on('mouseover', function() { - d3_select(this).transition().duration(100).style('fill', d3_select(this).property('fill1')); - }).on('mouseout', function() { - d3_select(this).transition().duration(100).style('fill', d3_select(this).property('fill0')); - }).append('svg:title').text(contour[i].toFixed(2) + ' - ' + contour[i + 1].toFixed(2)); - } - - if (settings.Zooming) - r.on('dblclick', () => fp.unzoom('z')); - } - - fp.z_handle.maxTickSize = Math.round(palette_width * 0.3); - - const promise = fp.z_handle.drawAxis(g, makeTranslate(vertical ? palette_width : 0, palette_height), vertical ? -1 : 1); - - if (this.isBatchMode() || drag) - return promise; - - return promise.then(() => { - if (settings.ContextMenu) { - g.on('contextmenu', evnt => { - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - createMenu(evnt, this).then(menu => { - menu.header('Palette'); - menu.addchk(vertical, 'Vertical', flag => { - this.v7SetAttr('vertical', flag); - this.redrawPad(); - }); - fp.z_handle.fillAxisContextMenu(menu, 'z'); - menu.show(); - }); - }); - } - - addDragHandler(this, { - x: palette_x, y: palette_y, width: palette_width, height: palette_height, - minwidth: 20, minheight: 20, no_change_x: !vertical, no_change_y: vertical, redraw: d => this.drawPalette(d) - }); - - if (!settings.Zooming) - return; - - let doing_zoom = false, sel1 = 0, sel2 = 0, zoom_rect, zoom_rect_visible, moving_labels, last_pos; - - const moveRectSel = evnt => { - if (!doing_zoom) - return; - evnt.preventDefault(); - - last_pos = d3_pointer(evnt, this.getG().node()); - - if (moving_labels) - return fp.z_handle.processLabelsMove('move', last_pos); - - if (vertical) - sel2 = Math.min(Math.max(last_pos[1], 0), palette_height); - else - sel2 = Math.min(Math.max(last_pos[0], 0), palette_width); - - const sz = Math.abs(sel2 - sel1); - - if (!zoom_rect_visible && (sz > 1)) { - zoom_rect.style('display', null); - zoom_rect_visible = true; - } - - if (vertical) - zoom_rect.attr('y', Math.min(sel1, sel2)).attr('height', sz); - else - zoom_rect.attr('x', Math.min(sel1, sel2)).attr('width', sz); - }, endRectSel = evnt => { - if (!doing_zoom) - return; - - evnt.preventDefault(); - d3_select(window).on('mousemove.colzoomRect', null) - .on('mouseup.colzoomRect', null); - zoom_rect.remove(); - zoom_rect = null; - doing_zoom = false; - - if (moving_labels) - fp.z_handle.processLabelsMove('stop', last_pos); - else { - const z = fp.z_handle.func, z1 = z.invert(sel1), z2 = z.invert(sel2); - fp.zoomSingle('z', Math.min(z1, z2), Math.max(z1, z2)); - } - }, startRectSel = evnt => { - // ignore when touch selection is activated - if (doing_zoom) - return; - doing_zoom = true; - - evnt.preventDefault(); - evnt.stopPropagation(); - - last_pos = d3_pointer(evnt, this.getG().node()); - sel1 = sel2 = last_pos[vertical ? 1 : 0]; - zoom_rect_visible = false; - moving_labels = false; - zoom_rect = g_btns - .append('svg:rect') - .attr('class', 'zoom') - .attr('id', 'colzoomRect') - .style('display', 'none'); - if (vertical) - zoom_rect.attr('x', 0).attr('width', palette_width).attr('y', sel1).attr('height', 1); - else - zoom_rect.attr('x', sel1).attr('width', 1).attr('y', 0).attr('height', palette_height); - - d3_select(window).on('mousemove.colzoomRect', moveRectSel) - .on('mouseup.colzoomRect', endRectSel, true); - - setTimeout(() => { - if (!zoom_rect_visible && doing_zoom) - moving_labels = fp.z_handle.processLabelsMove('start', last_pos); - }, 500); - }, assignHandlers = () => { - this.getG().selectAll('.axis_zoom, .axis_labels') - .on('mousedown', startRectSel) - .on('dblclick', () => fp.unzoom('z')); - - if (settings.ZoomWheel) { - this.getG().on('wheel', evnt => { - evnt.stopPropagation(); - evnt.preventDefault(); - - const pos = d3_pointer(evnt, this.getG().node()), - coord = vertical ? (1 - pos[1] / palette_height) : pos[0] / palette_width, - - item = fp.z_handle.analyzeWheelEvent(evnt, coord); - if (item.changed) - fp.zoomSingle('z', item.min, item.max); - }); - } - }; - - fp.z_handle.setAfterDrawHandler(assignHandlers); - - assignHandlers(); - }); - } - - /** @summary draw RPalette object */ - static async draw(dom, palette, opt) { - const painter = new RPalettePainter(dom, palette, opt, 'palette'); - return ensureRCanvas(painter).then(() => { - painter.createG(); // just create container, real drawing will be done by histogram - return painter; - }); - } - -} // class RPalettePainter - -export { RPalettePainter, drawText, drawLine, drawBox, drawMarker }; diff --git a/js/modules/geom/TGeoPainter.mjs b/js/modules/geom/TGeoPainter.mjs deleted file mode 100644 index 732f8c8803a4d..0000000000000 --- a/js/modules/geom/TGeoPainter.mjs +++ /dev/null @@ -1,6372 +0,0 @@ -import { httpRequest, browser, source_dir, settings, internals, constants, create, clone, - findFunction, isBatchMode, isNodeJs, getDocument, isObject, isFunc, isStr, postponePromise, getPromise, - getKindForType, clTTree, clTNamed, clTList, clTAxis, clTObjArray, clTPolyMarker3D, clTPolyLine3D, - clTGeoVolume, clTGeoNode, clTGeoNodeMatrix, nsREX, nsSVG, kInspect } from '../core.mjs'; -import { showProgress, injectStyle, ToolbarIcons } from '../gui/utils.mjs'; -import { GUI } from '../gui/lil-gui.mjs'; -import { THREE, assign3DHandler, disposeThreejsObject, createOrbitControl, - createLineSegments, InteractiveControl, PointsCreator, importThreeJs, - createRender3D, beforeRender3D, afterRender3D, getRender3DKind, cleanupRender3D, - createTextGeometry } from '../base/base3d.mjs'; -import { getColor, getRootColors } from '../base/colors.mjs'; -import { DrawOptions } from '../base/BasePainter.mjs'; -import { ObjectPainter } from '../base/ObjectPainter.mjs'; -import { createMenu, closeMenu } from '../gui/menu.mjs'; -import { TAxisPainter } from '../gpad/TAxisPainter.mjs'; -import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; -import { kindGeo, kindEve, kGetMesh, kDeleteMesh, - clTGeoBBox, clTGeoCompositeShape, - geoCfg, geoBITS, ClonedNodes, testGeoBit, setGeoBit, toggleGeoBit, setInvisibleAll, - countNumShapes, getNodeKind, produceRenderOrder, createServerGeometry, - projectGeometry, numGeometryFaces, createMaterial, createFrustum, createProjectionMatrix, - getBoundingBox, provideObjectInfo, isSameStack, checkDuplicates, getObjectName, cleanupShape, getShapeIcon } from './geobase.mjs'; - - -const _ENTIRE_SCENE = 0, _BLOOM_SCENE = 1, - clTGeoManager = 'TGeoManager', clTEveGeoShapeExtract = 'TEveGeoShapeExtract', - clTGeoOverlap = 'TGeoOverlap', clTGeoVolumeAssembly = 'TGeoVolumeAssembly', - clTEveTrack = 'TEveTrack', clTEvePointSet = 'TEvePointSet', - clREveGeoShapeExtract = `${nsREX}REveGeoShapeExtract`; - -/** @summary Function used to build hierarchy of elements of overlap object - * @private */ -function buildOverlapVolume(overlap) { - const vol = create(clTGeoVolume); - - setGeoBit(vol, geoBITS.kVisDaughters, true); - vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy - vol.fName = ''; - - const node1 = create(clTGeoNodeMatrix); - node1.fName = overlap.fVolume1.fName || 'Overlap1'; - node1.fMatrix = overlap.fMatrix1; - node1.fVolume = overlap.fVolume1; - // node1.fVolume.fLineColor = 2; // color assigned with ctrl.split_colors - - const node2 = create(clTGeoNodeMatrix); - node2.fName = overlap.fVolume2.fName || 'Overlap2'; - node2.fMatrix = overlap.fMatrix2; - node2.fVolume = overlap.fVolume2; - // node2.fVolume.fLineColor = 3; // color assigned with ctrl.split_colors - - vol.fNodes = create(clTList); - vol.fNodes.Add(node1); - vol.fNodes.Add(node2); - - return vol; -} - -let $comp_col_cnt = 0; - -/** @summary Function used to build hierarchy of elements of composite shapes - * @private */ -function buildCompositeVolume(comp, maxlvl, side) { - if (maxlvl === undefined) - maxlvl = 1; - if (!side) { - $comp_col_cnt = 0; - side = ''; - } - - const vol = create(clTGeoVolume); - setGeoBit(vol, geoBITS.kVisThis, true); - setGeoBit(vol, geoBITS.kVisDaughters, true); - - if ((side && (comp._typename !== clTGeoCompositeShape)) || (maxlvl <= 0)) { - vol.fName = side; - vol.fLineColor = ($comp_col_cnt++ % 8) + 2; - vol.fShape = comp; - return vol; - } - - if (side) - side += '/'; - vol.$geoh = true; // workaround, let know browser that we are in volumes hierarchy - vol.fName = ''; - - const node1 = create(clTGeoNodeMatrix); - setGeoBit(node1, geoBITS.kVisThis, true); - setGeoBit(node1, geoBITS.kVisDaughters, true); - node1.fName = 'Left'; - node1.fMatrix = comp.fNode.fLeftMat; - node1.fVolume = buildCompositeVolume(comp.fNode.fLeft, maxlvl - 1, side + 'Left'); - - const node2 = create(clTGeoNodeMatrix); - setGeoBit(node2, geoBITS.kVisThis, true); - setGeoBit(node2, geoBITS.kVisDaughters, true); - node2.fName = 'Right'; - node2.fMatrix = comp.fNode.fRightMat; - node2.fVolume = buildCompositeVolume(comp.fNode.fRight, maxlvl - 1, side + 'Right'); - - vol.fNodes = create(clTList); - vol.fNodes.Add(node1); - vol.fNodes.Add(node2); - - if (!side) $comp_col_cnt = 0; - - return vol; -} - - -/** @summary Provides 3D rendering configuration from histogram painter - * @return {object} with scene, renderer and other attributes - * @private */ -function getHistPainter3DCfg(painter) { - const main = painter?.getFramePainter(); - if (painter?.mode3d && isFunc(main?.create3DScene) && main?.renderer) { - let scale_x = 1, scale_y = 1, scale_z = 1, - offset_x = 0, offset_y = 0, offset_z = 0; - - if (main.scale_xmax > main.scale_xmin) { - scale_x = 2 * main.size_x3d / (main.scale_xmax - main.scale_xmin); - offset_x = (main.scale_xmax + main.scale_xmin) / 2 * scale_x; - } - - if (main.scale_ymax > main.scale_ymin) { - scale_y = 2 * main.size_y3d / (main.scale_ymax - main.scale_ymin); - offset_y = (main.scale_ymax + main.scale_ymin) / 2 * scale_y; - } - - if (main.scale_zmax > main.scale_zmin) { - scale_z = 2 * main.size_z3d / (main.scale_zmax - main.scale_zmin); - offset_z = (main.scale_zmax + main.scale_zmin) / 2 * scale_z - main.size_z3d; - } - - return { - webgl: main.webgl, - scene: main.scene, - scene_width: main.scene_width, - scene_height: main.scene_height, - toplevel: main.toplevel, - renderer: main.renderer, - camera: main.camera, - scale_x, scale_y, scale_z, - offset_x, offset_y, offset_z - }; - } -} - - -/** @summary find item with geometry painter - * @private */ -function findItemWithGeoPainter(hitem, test_changes) { - while (hitem) { - if (isFunc(hitem._painter?.testGeomChanges)) { - if (test_changes) - hitem._painter.testGeomChanges(); - return hitem; - } - hitem = hitem._parent; - } - return null; -} - -/** @summary provide css style for geo object - * @private */ -function provideVisStyle(obj) { - if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) - return obj.fRnrSelf ? ' geovis_this' : ''; - - const vis = !testGeoBit(obj, geoBITS.kVisNone) && testGeoBit(obj, geoBITS.kVisThis); - let chld = testGeoBit(obj, geoBITS.kVisDaughters); - - if (chld && !obj.fNodes?.arr?.length) - chld = false; - - if (vis && chld) - return ' geovis_all'; - if (vis) - return ' geovis_this'; - if (chld) - return ' geovis_daughters'; - return ''; -} - - -/** @summary update icons - * @private */ -function updateBrowserIcons(obj, hpainter) { - if (!obj || !hpainter) - return; - - hpainter.forEachItem(m => { - // update all items with that volume - if ((obj === m._volume) || (obj === m._geoobj)) { - m._icon = m._icon.split(' ')[0] + provideVisStyle(obj); - hpainter.updateTreeNode(m); - } - }); -} - - -/** @summary Return stack for the item from list of intersection - * @private */ -function getIntersectStack(item) { - const obj = item?.object; - if (!obj) - return null; - if (obj.stack) - return obj.stack; - if (obj.stacks && item.instanceId !== undefined && item.instanceId < obj.stacks.length) - return obj.stacks[item.instanceId]; -} - -/** - * @summary Toolbar for geometry painter - * - * @private - */ - -class Toolbar { - - /** @summary constructor */ - constructor(container, bright, buttons) { - this.bright = bright; - this.buttons = buttons; - this.element = container.append('div').attr('style', 'float: left; box-sizing: border-box; position: relative; bottom: 23px; vertical-align: middle; padding-left: 5px'); - } - - /** @summary add buttons */ - createButtons() { - const buttonsNames = []; - - this.buttons.forEach(buttonConfig => { - const buttonName = buttonConfig.name; - if (!buttonName) - throw new Error('must provide button name in button config'); - if (buttonsNames.indexOf(buttonName) !== -1) - throw new Error(`button name ${buttonName} is taken`); - - buttonsNames.push(buttonName); - - const title = buttonConfig.title || buttonConfig.name; - - if (!isFunc(buttonConfig.click)) - throw new Error('must provide button click() function in button config'); - - ToolbarIcons.createSVG(this.element, ToolbarIcons[buttonConfig.icon], 16, title, this.bright) - .on('click', buttonConfig.click) - .style('position', 'relative') - .style('padding', '3px 1px'); - }); - } - - /** @summary change brightness */ - changeBrightness(bright) { - if (this.bright !== bright) { - this.element.selectAll('*').remove(); - this.bright = bright; - this.createButtons(); - } - } - - /** @summary cleanup toolbar */ - cleanup() { - this.element?.remove(); - delete this.element; - } - -} // class ToolBar - - -/** - * @summary geometry drawing control - * - * @private - */ - -class GeoDrawingControl extends InteractiveControl { - - constructor(mesh, bloom) { - super(); - this.mesh = mesh?.material ? mesh : null; - this.bloom = bloom; - } - - /** @summary set highlight */ - setHighlight(col, indx) { - return this.drawSpecial(col, indx); - } - - /** @summary draw special */ - drawSpecial(col, indx) { - const c = this.mesh; - if (!c?.material) - return; - - if (c.isInstancedMesh) { - if (c._highlight_mesh) { - c.remove(c._highlight_mesh); - delete c._highlight_mesh; - } - - if (col && indx !== undefined) { - const h = new THREE.Mesh(c.geometry, c.material.clone()); - - if (this.bloom) { - h.layers.enable(_BLOOM_SCENE); - h.material.emissive = new THREE.Color(0x00ff00); - } else { - h.material.color = new THREE.Color(col); - h.material.opacity = 1.0; - } - const m = new THREE.Matrix4(); - c.getMatrixAt(indx, m); - h.applyMatrix4(m); - c.add(h); - - h.jsroot_special = true; // exclude from intersections - - c._highlight_mesh = h; - } - return true; - } - - if (col) { - if (!c.origin) { - c.origin = { - color: c.material.color, - emissive: c.material.emissive, - opacity: c.material.opacity, - width: c.material.linewidth, - size: c.material.size - }; - } - if (this.bloom) { - c.layers.enable(_BLOOM_SCENE); - c.material.emissive = new THREE.Color(0x00ff00); - } else { - c.material.color = new THREE.Color(col); - c.material.opacity = 1.0; - } - - if (c.hightlightWidthScale && !browser.isWin) - c.material.linewidth = c.origin.width * c.hightlightWidthScale; - if (c.highlightScale) - c.material.size = c.origin.size * c.highlightScale; - return true; - } else if (c.origin) { - if (this.bloom) { - c.material.emissive = c.origin.emissive; - c.layers.enable(_ENTIRE_SCENE); - } else { - c.material.color = c.origin.color; - c.material.opacity = c.origin.opacity; - } - if (c.hightlightWidthScale) - c.material.linewidth = c.origin.width; - if (c.highlightScale) - c.material.size = c.origin.size; - return true; - } - } - -} // class GeoDrawingControl - - -const stageInit = 0, stageCollect = 1, stageWorkerCollect = 2, stageAnalyze = 3, stageCollShapes = 4, - stageStartBuild = 5, stageWorkerBuild = 6, stageBuild = 7, stageBuildReady = 8, stageWaitMain = 9, stageBuildProj = 10; - -/** - * @summary Painter class for geometries drawing - * - * @private - */ - -class TGeoPainter extends ObjectPainter { - - #geo_manager; // TGeoManager instance - if assigned in drawing - #superimpose; // set when superimposed with other 3D drawings - #complete_handler; // callback, assigned by ROOT GeomViewer - #geom_viewer; // true when processing drawing from ROOT GeomViewer - #extra_objects; // TList with extra objects configured for drawing - #webgl; // true when normal WebGL drawing is enabled - #worker; // extra Worker to run different calculations - #worker_ready; // is worker started and initialized - #worker_jobs; // number of submitted to worker jobs - #clones; // instance of ClonedNodes - #clones_owner; // is instance managed by the painter - #draw_nodes; // drawn nodes from geometry - #build_shapes; // build shapes required for drawings - #current_face_limit; // current face limit - #draw_all_nodes; // flag using in drawing - #draw_nodes_again; // flag set if drawing should be started again when completed - #drawing_ready; // if drawing completed - #drawing_log; // current drawing log information - #draw_resolveFuncs; // resolve call-backs for start drawing calls - #start_drawing_time; // time when drawing started - #start_render_tm; // time when rendering started - #first_render_tm; // first time when rendering was performed - #last_render_tm; // last time when rendering was performed - #last_render_meshes; // last value of rendered meshes - #render_resolveFuncs; // resolve call-backs from render calls - #render_tmout; // timeout used in Render3D() - #first_drawing; // true when very first drawing is performed - #full_redrawing; // if full redraw must be performed - #last_clip_cfg; // last config of clip panels - #redraw_timer; // timer used in redraw - #did_update; // flag used in update - #custom_bounding_box; // custom bounding box for extra objects - #clip_planes; // clip planes - #central_painter; // pointer on central painter - #subordinate_painters; // list of subordinate painters - #effectComposer; // extra composer for special effects, used in EVE - #bloomComposer; // extra composer for bloom highlight - #new_draw_nodes; // temporary list of new draw nodes - #new_append_nodes; // temporary list of new append nodes - #more_nodes; // more nodes from ROOT geometry viewer - #provided_more_nodes; // staged more nodes from ROOT geometry viewer - #on_pad; // true when drawing done on TPad - #last_manifest; // last node which was "manifest" via menu - #last_hidden; // last node which was "hidden" via menu - #gui; // dat.GUI instance - #toolbar; // tool buttons - #controls; // orbit control - #highlight_handlers; // highlight handlers - #animating; // set when animation started - #last_camera_position; // last camera position - #fullgeom_proj; // full geometry to produce projection - #selected_mesh; // used in highlight - #fog; // fog for the scene - #lookat; // position to which camera looks at - #scene_size; // stored scene size to control changes - #scene_width; // actual scene width - #scene_height; // actual scene width - #fit_main_area; // true if drawing must fit to DOM - #overall_size; // overall size - #scene; // three.js Scene object - #camera; // three.js camera object - #renderer; // three.js renderer object - #toplevel; // three.js top level Object3D - #camera0pos; // camera on opposite side - #vrControllers; // used in VR display - #controllersMeshes; // used in VR display - #dolly; // used in VR display - #vrDisplay; // used in VR display - #vr_camera_position; // used in VR display - #vr_camera_rotation; // used in VR display - #vr_camera_near; // used in VR display - #standingMatrix; // used in VR display - #raycasterEnd; // used in VR display - #raycasterOrigin; // used in VR display - #adjust_camera_with_render; // flag to readjust camera when rendering - #did_cleanup; // flag set after cleanup - - /** @summary Constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} obj - supported TGeo object */ - constructor(dom, obj) { - let gm; - if (obj?._typename === clTGeoManager) { - gm = obj; - obj = obj.fMasterVolume; - } - - if (obj?._typename && (obj._typename.indexOf(clTGeoVolume) === 0)) - obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; - - super(dom, obj); - - this.#superimpose = Boolean(getHistPainter3DCfg(this.getMainPainter())); - this.#geo_manager = gm; - - this._no_default_title = true; // do not set title to main DIV - this.mode3d = true; // indication of 3D mode - this.drawing_stage = stageInit; // - this.#drawing_log = 'Init'; - this.ctrl = { - clipIntersect: true, - clipVisualize: false, - clip: [{ name: 'x', enabled: false, value: 0, min: -100, max: 100, step: 1 }, - { name: 'y', enabled: false, value: 0, min: -100, max: 100, step: 1 }, - { name: 'z', enabled: false, value: 0, min: -100, max: 100, step: 1 }], - _highlight: 0, - highlight: 0, - highlight_bloom: 0, - highlight_scene: 0, - highlight_color: '#00ff00', - bloom_strength: 1.5, - more: 1, - maxfaces: 0, - vislevel: undefined, - maxnodes: undefined, - dflt_colors: false, - notoolbar: false, // used in ROOT geom viewer - - info: { num_meshes: 0, num_faces: 0, num_shapes: 0 }, - depthTest: true, - depthMethod: 'dflt', - select_in_view: false, - update_browser: true, - use_fog: false, - light: { kind: 'points', top: false, bottom: false, left: false, right: false, front: false, specular: true, power: 1 }, - lightKindItems: [ - { name: 'AmbientLight', value: 'ambient' }, - { name: 'DirectionalLight', value: 'points' }, - { name: 'HemisphereLight', value: 'hemisphere' }, - { name: 'Ambient + Point', value: 'mix' } - ], - trans_radial: 0, - trans_z: 0, - scale: new THREE.Vector3(1, 1, 1), - zoom: 1.0, rotatey: 0, rotatez: 0, - depthMethodItems: [ - { name: 'Default', value: 'dflt' }, - { name: 'Raytraicing', value: 'ray' }, - { name: 'Mesh size', value: 'size' }, - { name: 'Central point', value: 'pnt' } - ], - cameraKindItems: [ - { name: 'Perspective', value: 'perspective' }, - { name: 'Perspective (Floor XOZ)', value: 'perspXOZ' }, - { name: 'Perspective (Floor YOZ)', value: 'perspYOZ' }, - { name: 'Perspective (Floor XOY)', value: 'perspXOY' }, - { name: 'Orthographic (XOY)', value: 'orthoXOY' }, - { name: 'Orthographic (XOZ)', value: 'orthoXOZ' }, - { name: 'Orthographic (ZOY)', value: 'orthoZOY' }, - { name: 'Orthographic (ZOX)', value: 'orthoZOX' }, - { name: 'Orthographic (XnOY)', value: 'orthoXNOY' }, - { name: 'Orthographic (XnOZ)', value: 'orthoXNOZ' }, - { name: 'Orthographic (ZnOY)', value: 'orthoZNOY' }, - { name: 'Orthographic (ZnOX)', value: 'orthoZNOX' } - ], - cameraOverlayItems: [ - { name: 'None', value: 'none' }, - { name: 'Bar', value: 'bar' }, - { name: 'Axis', value: 'axis' }, - { name: 'Grid', value: 'grid' }, - { name: 'Grid background', value: 'gridb' }, - { name: 'Grid foreground', value: 'gridf' } - ], - camera_kind: 'perspective', - camera_overlay: 'gridb', - rotate: false, - background: settings.DarkMode ? '#000000' : '#ffffff', - can_rotate: true, - _axis: 0, - instancing: 0, - _count: false, - // material properties - wireframe: false, - transparency: 0, - flatShading: false, - roughness: 0.5, - metalness: 0.5, - shininess: 0, - reflectivity: 0.5, - material_kind: 'lambert', - materialKinds: [ - { name: 'MeshLambertMaterial', value: 'lambert', emissive: true, props: [{ name: 'flatShading' }] }, - { name: 'MeshBasicMaterial', value: 'basic' }, - { name: 'MeshStandardMaterial', value: 'standard', emissive: true, - props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }] }, - { name: 'MeshPhysicalMaterial', value: 'physical', emissive: true, - props: [{ name: 'flatShading' }, { name: 'roughness', min: 0, max: 1, step: 0.001 }, { name: 'metalness', min: 0, max: 1, step: 0.001 }, { name: 'reflectivity', min: 0, max: 1, step: 0.001 }] }, - { name: 'MeshPhongMaterial', value: 'phong', emissive: true, - props: [{ name: 'flatShading' }, { name: 'shininess', min: 0, max: 100, step: 0.1 }] }, - { name: 'MeshNormalMaterial', value: 'normal', props: [{ name: 'flatShading' }] }, - { name: 'MeshDepthMaterial', value: 'depth' }, - { name: 'MeshMatcapMaterial', value: 'matcap' }, - { name: 'MeshToonMaterial', value: 'toon' } - ], - getMaterialCfg() { - let cfg; - this.materialKinds.forEach(item => { - if (item.value === this.material_kind) - cfg = item; - }); - return cfg; - } - }; - - this.#draw_resolveFuncs = []; - this.#render_resolveFuncs = []; - - this.cleanup(true); - } - - /** @summary Returns scene */ - getScene() { return this.#scene; } - - /** @summary Returns camera */ - getCamera() { return this.#camera; } - - /** @summary Returns renderer */ - getRenderer() { return this.#renderer; } - - /** @summary Returns top Object3D instance */ - getTopObject3D() { return this.#toplevel; } - - /** @summary Assign geometry viewer mode - * @private */ - setGeomViewer(on) { this.#geom_viewer = on; } - - /** @summary Assign or remove subordinate painter */ - assignSubordinate(painter, do_assign = true) { - if (this.#subordinate_painters === undefined) - this.#subordinate_painters = []; - const pos = this.#subordinate_painters.indexOf(painter); - if (do_assign && pos < 0) - this.#subordinate_painters.push(painter); - else if (!do_assign && pos >= 0) - this.#subordinate_painters.splice(pos, 1); - } - - /** @summary Return list of subordinate painters */ - getSubordinates() { return this.#subordinate_painters ?? []; } - - /** @summary Assign or cleanup central painter */ - assignCentral(painter, do_assign = true) { - if (do_assign) - this.#central_painter = painter; - else if (this.#central_painter === painter) - this.#central_painter = undefined; - } - - /** @summary Returns central painter */ - getCentral() { return this.#central_painter; } - - /** @summary Returns extra objects configured for drawing */ - getExtraObjects() { return this.#extra_objects; } - - /** @summary Function called by framework when dark mode is changed - * @private */ - changeDarkMode(mode) { - if ((this.ctrl.background === '#000000') || (this.ctrl.background === '#ffffff')) - this.changedBackground((mode ?? settings.DarkMode) ? '#000000' : '#ffffff'); - } - - /** @summary Change drawing stage - * @private */ - changeStage(value, msg) { - this.drawing_stage = value; - if (!msg) { - switch (value) { - case stageInit: msg = 'Building done'; break; - case stageCollect: msg = 'collect visibles'; break; - case stageWorkerCollect: msg = 'worker collect visibles'; break; - case stageAnalyze: msg = 'Analyse visibles'; break; - case stageCollShapes: msg = 'collect shapes for building'; break; - case stageStartBuild: msg = 'Start build shapes'; break; - case stageWorkerBuild: msg = 'Worker build shapes'; break; - case stageBuild: msg = 'Build shapes'; break; - case stageBuildReady: msg = 'Build ready'; break; - case stageWaitMain: msg = 'Wait for main painter'; break; - case stageBuildProj: msg = 'Build projection'; break; - default: msg = `stage ${value}`; - } - } - this.#drawing_log = msg; - } - - /** @summary Check drawing stage */ - isStage(value) { return value === this.drawing_stage; } - - isBatchMode() { return isBatchMode() || this.batch_mode; } - - getControls() { return this.#controls; } - - /** @summary Create toolbar */ - createToolbar() { - if (this.#toolbar || !this.#webgl || this.ctrl.notoolbar || this.isBatchMode()) - return; - const buttonList = [{ - name: 'toImage', - title: 'Save as PNG', - icon: 'camera', - click: () => this.createSnapshot() - }, { - name: 'control', - title: 'Toggle control UI', - icon: 'rect', - click: () => this.showControlGui('toggle') - }, { - name: 'enlarge', - title: 'Enlarge geometry drawing', - icon: 'circle', - click: () => this.toggleEnlarge() - }]; - - // Only show VR icon if WebVR API available. - if (navigator.getVRDisplays) { - buttonList.push({ - name: 'entervr', - title: 'Enter VR (It requires a VR Headset connected)', - icon: 'vrgoggles', - click: () => this.toggleVRMode() - }); - this.initVRMode(); - } - - if (settings.ContextMenu) { - buttonList.push({ - name: 'menu', - title: 'Show context menu', - icon: 'question', - click: evnt => { - evnt.preventDefault(); - evnt.stopPropagation(); - - if (closeMenu()) - return; - - createMenu(evnt, this).then(menu => { - menu.painter.fillContextMenu(menu); - menu.show(); - }); - } - }); - } - - const bkgr = new THREE.Color(this.ctrl.background); - - this.#toolbar = new Toolbar(this.selectDom(), (bkgr.r + bkgr.g + bkgr.b) < 1, buttonList); - - this.#toolbar.createButtons(); - } - - /** @summary Initialize VR mode */ - initVRMode() { - // Dolly contains camera and controllers in VR Mode - // Allows moving the user in the scene - this.#dolly = new THREE.Group(); - this.#scene.add(this.#dolly); - this.#standingMatrix = new THREE.Matrix4(); - - // Raycaster temp variables to avoid one per frame allocation. - this.#raycasterEnd = new THREE.Vector3(); - this.#raycasterOrigin = new THREE.Vector3(); - - navigator.getVRDisplays().then(displays => { - const vrDisplay = displays[0]; - if (!vrDisplay) - return; - this.#renderer.vr.setDevice(vrDisplay); - this.#vrDisplay = vrDisplay; - if (vrDisplay.stageParameters) - this.#standingMatrix.fromArray(vrDisplay.stageParameters.sittingToStandingTransform); - - this.initVRControllersGeometry(); - }); - } - - /** @summary Init VR controllers geometry - * @private */ - initVRControllersGeometry() { - const geometry = new THREE.SphereGeometry(0.025, 18, 36), - material = new THREE.MeshBasicMaterial({ color: 'grey', vertexColors: false }), - rayMaterial = new THREE.MeshBasicMaterial({ color: 'fuchsia', vertexColors: false }), - rayGeometry = new THREE.BoxGeometry(0.001, 0.001, 2), - ray1Mesh = new THREE.Mesh(rayGeometry, rayMaterial), - ray2Mesh = new THREE.Mesh(rayGeometry, rayMaterial), - sphere1 = new THREE.Mesh(geometry, material), - sphere2 = new THREE.Mesh(geometry, material); - - this.#controllersMeshes = []; - this.#controllersMeshes.push(sphere1); - this.#controllersMeshes.push(sphere2); - ray1Mesh.position.z -= 1; - ray2Mesh.position.z -= 1; - sphere1.add(ray1Mesh); - sphere2.add(ray2Mesh); - this.#dolly.add(sphere1); - this.#dolly.add(sphere2); - // Controller mesh hidden by default - sphere1.visible = false; - sphere2.visible = false; - } - - /** @summary Update VR controllers list - * @private */ - updateVRControllersList() { - const gamepads = navigator.getGamepads && navigator.getGamepads(); - // Has controller list changed? - if (this.#vrControllers && (gamepads.length === this.#vrControllers.length)) - return; - // Hide meshes. - this.#controllersMeshes.forEach(mesh => { mesh.visible = false; }); - this.#vrControllers = []; - for (let i = 0; i < gamepads.length; ++i) { - if (!gamepads[i] || !gamepads[i].pose) - continue; - this.#vrControllers.push({ - gamepad: gamepads[i], - mesh: this.#controllersMeshes[i] - }); - this.#controllersMeshes[i].visible = true; - } - } - - /** @summary Process VR controller intersection - * @private */ - processVRControllerIntersections() { - let intersects = []; - for (let i = 0; i < this.#vrControllers.length; ++i) { - const controller = this.#vrControllers[i].mesh, - end = controller.localToWorld(this.#raycasterEnd.set(0, 0, -1)), - origin = controller.localToWorld(this.#raycasterOrigin.set(0, 0, 0)); - end.sub(origin).normalize(); - intersects = intersects.concat(this.#controls.getOriginDirectionIntersects(origin, end)); - } - // Remove duplicates. - intersects = intersects.filter((item, pos) => { return intersects.indexOf(item) === pos; }); - this.#controls.processMouseMove(intersects); - } - - /** @summary Update VR controllers - * @private */ - updateVRControllers() { - this.updateVRControllersList(); - // Update pose. - for (let i = 0; i < this.#vrControllers.length; ++i) { - const controller = this.#vrControllers[i], - orientation = controller.gamepad.pose.orientation, - position = controller.gamepad.pose.position, - controllerMesh = controller.mesh; - if (orientation) - controllerMesh.quaternion.fromArray(orientation); - if (position) - controllerMesh.position.fromArray(position); - controllerMesh.updateMatrix(); - controllerMesh.applyMatrix4(this.#standingMatrix); - controllerMesh.matrixWorldNeedsUpdate = true; - } - this.processVRControllerIntersections(); - } - - /** @summary Toggle VR mode - * @private */ - toggleVRMode() { - if (!this.#vrDisplay) - return; - // Toggle VR mode off - if (this.#vrDisplay.isPresenting) { - this.exitVRMode(); - return; - } - this.#vr_camera_position = this.#camera.position.clone(); - this.#vr_camera_rotation = this.#camera.rotation.clone(); - this.#vrDisplay.requestPresent([{ source: this.#renderer.domElement }]).then(() => { - this.#vr_camera_near = this.#camera.near; - this.#dolly.position.set(this.#camera.position.x / 4, -this.#camera.position.y / 8, -this.#camera.position.z / 4); - this.#camera.position.set(0, 0, 0); - this.#dolly.add(this.#camera); - this.#camera.near = 0.1; - this.#camera.updateProjectionMatrix(); - this.#renderer.vr.enabled = true; - this.#renderer.setAnimationLoop(() => { - this.updateVRControllers(); - this.render3D(0); - }); - }); - this.#renderer.vr.enabled = true; - - window.addEventListener('keydown', evnt => { - // Esc Key turns VR mode off - if (evnt.code === 'Escape') - this.exitVRMode(); - }); - } - - /** @summary Exit VR mode - * @private */ - exitVRMode() { - if (!this.#vrDisplay.isPresenting) - return; - this.#renderer.vr.enabled = false; - this.#dolly.remove(this.#camera); - this.#scene.add(this.#camera); - // Restore Camera pose - this.#camera.position.copy(this.#vr_camera_position); - this.#vr_camera_position = undefined; - this.#camera.rotation.copy(this.#vr_camera_rotation); - this.#vr_camera_rotation = undefined; - this.#camera.near = this.#vr_camera_near; - this.#camera.updateProjectionMatrix(); - this.#vrDisplay.exitPresent(); - } - - /** @summary Returns main geometry object */ - getGeometry() { return this.getObject(); } - - /** @summary Modify visibility of provided node by name */ - modifyVisisbility(name, sign) { - if (getNodeKind(this.getGeometry()) !== kindGeo) - return; - - if (!name) - return setGeoBit(this.getGeometry().fVolume, geoBITS.kVisThis, (sign === '+')); - - let regexp, exact = false; - - // arg.node.fVolume - if (name.indexOf('*') < 0) { - regexp = new RegExp('^' + name + '$'); - exact = true; - } else { - regexp = new RegExp('^' + name.split('*').join('.*') + '$'); - exact = false; - } - - this.findNodeWithVolume(regexp, arg => { - setInvisibleAll(arg.node.fVolume, (sign !== '+')); - return exact ? arg : null; // continue search if not exact expression provided - }); - } - - /** @summary Decode drawing options */ - decodeOptions(opt) { - if (!isStr(opt)) - opt = ''; - - if (this.#superimpose && (opt.indexOf('same') === 0)) - opt = opt.slice(4); - - const res = this.ctrl, macro = opt.indexOf('macro:'); - - if (macro >= 0) { - let separ = opt.indexOf(';', macro + 6); - if (separ < 0) - separ = opt.length; - res.script_name = opt.slice(macro + 6, separ); - opt = opt.slice(0, macro) + opt.slice(separ + 1); - } - - while (true) { - const pp = opt.indexOf('+'), pm = opt.indexOf('-'); - if ((pp < 0) && (pm < 0)) - break; - let p1 = pp, sign = '+'; - if ((p1 < 0) || ((pm >= 0) && (pm < pp))) { - p1 = pm; - sign = '-'; - } - - let p2 = p1 + 1; - const regexp = /[,; .]/; - while ((p2 < opt.length) && !regexp.test(opt[p2]) && (opt[p2] !== '+') && (opt[p2] !== '-')) - p2++; - - const name = opt.substring(p1 + 1, p2); - opt = opt.slice(0, p1) + opt.slice(p2); - - this.modifyVisisbility(name, sign); - } - - const d = new DrawOptions(opt); - - if (d.check('MAIN')) - res.is_main = true; - - if (d.check('DUMMY')) - res.dummy = true; - - if (d.check('TRACKS')) - res.tracks = true; // only for TGeoManager - if (d.check('SHOWTOP')) - res.showtop = true; // only for TGeoManager - if (d.check('NO_SCREEN')) - res.no_screen = true; // ignore kVisOnScreen bits for visibility - - if (d.check('NOINSTANCING')) - res.instancing = -1; // disable usage of InstancedMesh - if (d.check('INSTANCING')) - res.instancing = 1; // force usage of InstancedMesh - - if (d.check('ORTHO_CAMERA')) { - res.camera_kind = 'orthoXOY'; - res.can_rotate = 0; - } - if (d.check('ORTHO', true)) { - res.camera_kind = 'ortho' + d.part; - res.can_rotate = 0; - } - if (d.check('OVERLAY', true)) - res.camera_overlay = d.part.toLowerCase(); - if (d.check('CAN_ROTATE')) - res.can_rotate = true; - if (d.check('PERSPECTIVE')) { - res.camera_kind = 'perspective'; - res.can_rotate = true; - } - if (d.check('PERSP', true)) { - res.camera_kind = 'persp' + d.part; - res.can_rotate = true; - } - if (d.check('MOUSE_CLICK')) - res.mouse_click = true; - - if (d.check('DEPTHRAY') || d.check('DRAY')) - res.depthMethod = 'ray'; - if (d.check('DEPTHBOX') || d.check('DBOX')) - res.depthMethod = 'box'; - if (d.check('DEPTHPNT') || d.check('DPNT')) - res.depthMethod = 'pnt'; - if (d.check('DEPTHSIZE') || d.check('DSIZE')) - res.depthMethod = 'size'; - if (d.check('DEPTHDFLT') || d.check('DDFLT')) - res.depthMethod = 'dflt'; - - if (d.check('ZOOM', true)) - res.zoom = d.partAsFloat(0, 100) / 100; - if (d.check('ROTY', true)) - res.rotatey = d.partAsFloat(); - if (d.check('ROTZ', true)) - res.rotatez = d.partAsFloat(); - - if (d.check('PHONG')) - res.material_kind = 'phong'; - if (d.check('LAMBERT')) - res.material_kind = 'lambert'; - if (d.check('MATCAP')) - res.material_kind = 'matcap'; - if (d.check('TOON')) - res.material_kind = 'toon'; - - if (d.check('AMBIENT')) - res.light.kind = 'ambient'; - - const getCamPart = () => { - let neg = 1; - if (d.part[0] === 'N') { - neg = -1; - d.part = d.part.slice(1); - } - return neg * d.partAsFloat(); - }; - - if (d.check('CAMX', true)) - res.camx = getCamPart(); - if (d.check('CAMY', true)) - res.camy = getCamPart(); - if (d.check('CAMZ', true)) - res.camz = getCamPart(); - if (d.check('CAMLX', true)) - res.camlx = getCamPart(); - if (d.check('CAMLY', true)) - res.camly = getCamPart(); - if (d.check('CAMLZ', true)) - res.camlz = getCamPart(); - - if (d.check('BLACK')) - res.background = '#000000'; - if (d.check('WHITE')) - res.background = '#FFFFFF'; - - if (d.check('BKGR_', true)) { - let bckgr = null; - if (d.partAsInt(1) > 0) - bckgr = getColor(d.partAsInt()); - else { - for (let col = 0; col < 8; ++col) { - if (getColor(col).toUpperCase() === d.part) - bckgr = getColor(col); - } - } - if (bckgr) - res.background = '#' + new THREE.Color(bckgr).getHexString(); - } - - if (d.check('R3D_', true)) - res.Render3D = constants.Render3D.fromString(d.part.toLowerCase()); - - if (d.check('MORE', true)) - res.more = d.partAsInt(0, 2) ?? 2; - if (d.check('ALL')) { - res.more = 100; - res.vislevel = 99; - } - - if (d.check('VISLVL', true)) - res.vislevel = d.partAsInt(); - if (d.check('MAXNODES', true)) - res.maxnodes = d.partAsInt(); - if (d.check('MAXFACES', true)) - res.maxfaces = d.partAsInt(); - - if (d.check('CONTROLS') || d.check('CTRL')) - res.show_controls = true; - - if (d.check('CLIPXYZ')) - res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; - if (d.check('CLIPX')) - res.clip[0].enabled = true; - if (d.check('CLIPY')) - res.clip[1].enabled = true; - if (d.check('CLIPZ')) - res.clip[2].enabled = true; - if (d.check('CLIP')) - res.clip[0].enabled = res.clip[1].enabled = res.clip[2].enabled = true; - - if (d.check('PROJX', true)) { - res.project = 'x'; - if (d.partAsInt(1) > 0) - res.projectPos = d.partAsInt(); - res.can_rotate = 0; - } - if (d.check('PROJY', true)) { - res.project = 'y'; - if (d.partAsInt(1) > 0) - res.projectPos = d.partAsInt(); - res.can_rotate = 0; - } - if (d.check('PROJZ', true)) { - res.project = 'z'; - if (d.partAsInt(1) > 0) - res.projectPos = d.partAsInt(); - res.can_rotate = 0; - } - - if (d.check('DFLT_COLORS') || d.check('DFLT')) - res.dflt_colors = true; - d.check('SSAO'); // deprecated - if (d.check('NOBLOOM')) - res.highlight_bloom = false; - if (d.check('BLOOM')) - res.highlight_bloom = true; - if (d.check('OUTLINE')) - res.outline = true; - - if (d.check('NOWORKER')) - res.use_worker = -1; - if (d.check('WORKER')) - res.use_worker = 1; - - if (d.check('NOFOG')) - res.use_fog = false; - if (d.check('FOG')) - res.use_fog = true; - - if (d.check('NOHIGHLIGHT') || d.check('NOHIGH')) - res.highlight_scene = res.highlight = false; - if (d.check('HIGHLIGHT')) - res.highlight_scene = res.highlight = true; - if (d.check('HSCENEONLY')) { - res.highlight_scene = true; - res.highlight = false; - } - if (d.check('NOHSCENE')) - res.highlight_scene = false; - if (d.check('HSCENE')) - res.highlight_scene = true; - - if (d.check('WIREFRAME') || d.check('WIRE')) - res.wireframe = true; - if (d.check('ROTATE')) - res.rotate = true; - - if (d.check('INVX') || d.check('INVERTX')) - res.scale.x = -1; - if (d.check('INVY') || d.check('INVERTY')) - res.scale.y = -1; - if (d.check('INVZ') || d.check('INVERTZ')) - res.scale.z = -1; - - if (d.check('COUNT')) - res._count = true; - - if (d.check('TRANSP', true)) - res.transparency = d.partAsInt(0, 100) / 100; - - if (d.check('OPACITY', true)) - res.transparency = 1 - d.partAsInt(0, 100) / 100; - - if (d.check('AXISCENTER') || d.check('AXISC') || d.check('AC')) - res._axis = 2; - if (d.check('AXIS') || d.check('A')) - res._axis = 1; - - if (d.check('TRR', true)) - res.trans_radial = d.partAsInt() / 100; - if (d.check('TRZ', true)) - res.trans_z = d.partAsInt() / 100; - - if (d.check('W')) - res.wireframe = true; - if (d.check('Y')) - res._yup = true; - if (d.check('Z')) - res._yup = false; - - // when drawing geometry without TCanvas, yup = true by default - if (res._yup === undefined) - res._yup = this.getCanvSvg().empty(); - - // let reuse for storing origin options - this.setOptions(res, true); - } - - /** @summary Activate specified items in the browser */ - activateInBrowser(names, force) { - if (isStr(names)) - names = [names]; - - const h = this.getHPainter(); - - if (h) { - // show browser if it not visible - - h.activateItems(names, force); - - // if highlight in the browser disabled, suppress in few seconds - if (!this.ctrl.update_browser) - setTimeout(() => h.activateItems([]), 2000); - } - } - - /** @summary Return ClonedNodes instance from the painter */ - getClones() { return this.#clones; } - - /** @summary Assign clones, created outside. - * @desc Used by ROOT geometry painter, where clones are handled by the server */ - assignClones(clones, owner = true) { - if (this.#clones_owner) - this.#clones?.cleanup(this.#draw_nodes, this.#build_shapes); - this.#draw_nodes = undefined; - this.#build_shapes = undefined; - this.#drawing_ready = false; - - this.#clones = clones; - this.#clones_owner = owner; - } - - /** @summary method used to check matrix calculations performance with current three.js model */ - testMatrixes() { - let errcnt = 0, totalcnt = 0, totalmax = 0; - - const arg = { - domatrix: true, - func: (/* node */) => { - let m2 = this.getmatrix(); - const entry = this.copyStack(), - mesh = this.#clones.createObject3D(entry.stack, this.#toplevel, kGetMesh); - if (!mesh) - return true; - - totalcnt++; - - const m1 = mesh.matrixWorld; - if (m1.equals(m2)) - return true; - if ((m1.determinant() > 0) && (m2.determinant() < -0.9)) { - const flip = new THREE.Vector3(1, 1, -1); - m2 = m2.clone().scale(flip); - if (m1.equals(m2)) - return true; - } - - let max = 0; - for (let k = 0; k < 16; ++k) - max = Math.max(max, Math.abs(m1.elements[k] - m2.elements[k])); - - totalmax = Math.max(max, totalmax); - - if (max < 1e-4) - return true; - - console.log(`${this.#clones.resolveStack(entry.stack).name} maxdiff ${max} determ ${m1.determinant()} ${m2.determinant()}`); - - errcnt++; - - return false; - } - }, tm1 = new Date().getTime(); - - this.#clones.scanVisible(arg); - - const tm2 = new Date().getTime(); - - console.log(`Compare matrixes total ${totalcnt} errors ${errcnt} takes ${tm2 - tm1} maxdiff ${totalmax}`); - } - - /** @summary Fill context menu */ - fillContextMenu(menu) { - menu.header('Draw options'); - - menu.addchk(this.ctrl.update_browser, 'Browser update', () => { - this.ctrl.update_browser = !this.ctrl.update_browser; - if (!this.ctrl.update_browser) - this.activateInBrowser([]); - }); - menu.addchk(this.ctrl.show_controls, 'Show Controls', () => this.showControlGui('toggle')); - - menu.sub('Show axes', () => this.setAxesDraw('toggle')); - menu.addchk(this.ctrl._axis === 0, 'off', 0, arg => this.setAxesDraw(parseInt(arg))); - menu.addchk(this.ctrl._axis === 1, 'side', 1, arg => this.setAxesDraw(parseInt(arg))); - menu.addchk(this.ctrl._axis === 2, 'center', 2, arg => this.setAxesDraw(parseInt(arg))); - menu.endsub(); - - if (this.#geo_manager) - menu.addchk(this.ctrl.showtop, 'Show top volume', () => this.setShowTop(!this.ctrl.showtop)); - - menu.addchk(this.ctrl.wireframe, 'Wire frame', () => this.toggleWireFrame()); - - if (!this.getCanvPainter()) - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - - menu.sub('Highlight'); - - menu.addchk(!this.ctrl.highlight, 'Off', () => { - this.ctrl.highlight = false; - this.changedHighlight(); - }); - menu.addchk(this.ctrl.highlight && !this.ctrl.highlight_bloom, 'Normal', () => { - this.ctrl.highlight = true; - this.ctrl.highlight_bloom = false; - this.changedHighlight(); - }); - menu.addchk(this.ctrl.highlight && this.ctrl.highlight_bloom, 'Bloom', () => { - this.ctrl.highlight = true; - this.ctrl.highlight_bloom = true; - this.changedHighlight(); - }); - - menu.separator(); - - menu.addchk(this.ctrl.highlight_scene, 'Scene', flag => { - this.ctrl.highlight_scene = flag; - this.changedHighlight(); - }); - - menu.endsub(); - - menu.sub('Camera'); - menu.add('Reset position', () => this.focusCamera()); - if (!this.ctrl.project) - menu.addchk(this.ctrl.rotate, 'Autorotate', () => this.setAutoRotate(!this.ctrl.rotate)); - - if (!this.#geom_viewer) { - menu.addchk(this.canRotateCamera(), 'Can rotate', () => this.changeCanRotate(!this.ctrl.can_rotate)); - - menu.add('Get position', () => menu.info('Position (as url)', '&opt=' + this.produceCameraUrl())); - if (!this.isOrthoCamera()) { - menu.add('Absolute position', () => { - const url = this.produceCameraUrl(true), p = url.indexOf('camlx'); - menu.info('Position (as url)', '&opt=' + ((p < 0) ? url : url.slice(0, p) + '\n' + url.slice(p))); - }); - } - - menu.sub('Kind'); - this.ctrl.cameraKindItems.forEach(item => - menu.addchk(this.ctrl.camera_kind === item.value, item.name, item.value, arg => { - this.ctrl.camera_kind = arg; - this.changeCamera(); - })); - menu.endsub(); - - if (this.isOrthoCamera()) { - menu.sub('Overlay'); - this.ctrl.cameraOverlayItems.forEach(item => - menu.addchk(this.ctrl.camera_overlay === item.value, item.name, item.value, arg => { - this.ctrl.camera_overlay = arg; - this.changeCamera(); - })); - menu.endsub(); - } - } - menu.endsub(); - - menu.addchk(this.ctrl.select_in_view, 'Select in view', () => { - this.ctrl.select_in_view = !this.ctrl.select_in_view; - if (this.ctrl.select_in_view) - this.startDrawGeometry(); - }); - } - - /** @summary Method used to set transparency for all geometrical shapes - * @param {number|Function} transparency - one could provide function */ - changedGlobalTransparency(transparency) { - const func = isFunc(transparency) ? transparency : null; - if (func || (transparency === undefined)) - transparency = this.ctrl.transparency; - - this.#toplevel?.traverse(node => { - // ignore all kind of extra elements - if (node?.material?.inherentOpacity === undefined) - return; - - const t = func ? func(node) : undefined; - if (t !== undefined) - node.material.opacity = 1 - t; - else - node.material.opacity = Math.min(1 - (transparency || 0), node.material.inherentOpacity); - - node.material.depthWrite = node.material.opacity === 1; - node.material.transparent = node.material.opacity < 1; - }); - - this.render3D(); - } - - /** @summary Method used to interactively change material kinds */ - changedMaterial() { - this.#toplevel?.traverse(node => { - // ignore all kind of extra elements - if (node.material?.inherentArgs !== undefined) - node.material = createMaterial(this.ctrl, node.material.inherentArgs); - }); - - this.render3D(-1); - } - - /** @summary Change for all materials that property */ - changeMaterialProperty(name) { - const value = this.ctrl[name]; - if (value === undefined) - return console.error('No property ', name); - - this.#toplevel?.traverse(node => { - // ignore all kind of extra elements - if (node.material?.inherentArgs === undefined) - return; - - if (node.material[name] !== undefined) { - node.material[name] = value; - node.material.needsUpdate = true; - } - }); - - this.render3D(); - } - - /** @summary Reset transformation */ - resetTransformation() { - this.changedTransformation('reset'); - } - - /** @summary Method should be called when transformation parameters were changed */ - changedTransformation(arg) { - if (!this.#toplevel) - return; - - const ctrl = this.ctrl, - translation = new THREE.Matrix4(), - vect2 = new THREE.Vector3(); - - if (arg === 'reset') - ctrl.trans_z = ctrl.trans_radial = 0; - - this.#toplevel.traverse(mesh => { - if (mesh.stack !== undefined) { - const node = mesh.parent; - - if (arg === 'reset') { - if (node.matrix0) { - node.matrix.copy(node.matrix0); - node.matrix.decompose(node.position, node.quaternion, node.scale); - node.matrixWorldNeedsUpdate = true; - } - delete node.matrix0; - delete node.vect0; - delete node.vect1; - delete node.minvert; - return; - } - - if (node.vect0 === undefined) { - node.matrix0 = node.matrix.clone(); - node.minvert = new THREE.Matrix4().copy(node.matrixWorld).invert(); - - const box3 = getBoundingBox(mesh, null, true), - signz = mesh._flippedMesh ? -1 : 1; - - // real center of mesh in local coordinates - node.vect0 = new THREE.Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, signz * (box3.max.z + box3.min.z) / 2).applyMatrix4(node.matrixWorld); - node.vect1 = new THREE.Vector3(0, 0, 0).applyMatrix4(node.minvert); - } - - vect2.set(ctrl.trans_radial * node.vect0.x, ctrl.trans_radial * node.vect0.y, ctrl.trans_z * node.vect0.z).applyMatrix4(node.minvert).sub(node.vect1); - - node.matrix.multiplyMatrices(node.matrix0, translation.makeTranslation(vect2.x, vect2.y, vect2.z)); - node.matrix.decompose(node.position, node.quaternion, node.scale); - node.matrixWorldNeedsUpdate = true; - } else if (mesh.stacks !== undefined) { - mesh.instanceMatrix.needsUpdate = true; - - if (arg === 'reset') { - mesh.trans?.forEach((item, i) => { - mesh.setMatrixAt(i, item.matrix0); - }); - delete mesh.trans; - return; - } - - if (mesh.trans === undefined) { - mesh.trans = new Array(mesh.count); - - mesh.geometry.computeBoundingBox(); - - for (let i = 0; i < mesh.count; i++) { - const item = { - matrix0: new THREE.Matrix4(), - minvert: new THREE.Matrix4() - }; - - mesh.trans[i] = item; - - mesh.getMatrixAt(i, item.matrix0); - item.minvert.copy(item.matrix0).invert(); - - const box3 = new THREE.Box3().copy(mesh.geometry.boundingBox).applyMatrix4(item.matrix0); - - item.vect0 = new THREE.Vector3((box3.max.x + box3.min.x) / 2, (box3.max.y + box3.min.y) / 2, (box3.max.z + box3.min.z) / 2); - item.vect1 = new THREE.Vector3(0, 0, 0).applyMatrix4(item.minvert); - } - } - - const mm = new THREE.Matrix4(); - - mesh.trans?.forEach((item, i) => { - vect2.set(ctrl.trans_radial * item.vect0.x, ctrl.trans_radial * item.vect0.y, ctrl.trans_z * item.vect0.z).applyMatrix4(item.minvert).sub(item.vect1); - - mm.multiplyMatrices(item.matrix0, translation.makeTranslation(vect2.x, vect2.y, vect2.z)); - - mesh.setMatrixAt(i, mm); - }); - } - }); - - this.#toplevel.updateMatrixWorld(); - - // axes drawing always triggers rendering - if (arg !== 'norender') - this.drawAxesAndOverlay(); - } - - /** @summary Should be called when auto rotate property changed */ - changedAutoRotate() { - this.autorotate(2.5); - } - - /** @summary Method should be called when changing axes drawing */ - changedAxes() { - if (isStr(this.ctrl._axis)) - this.ctrl._axis = parseInt(this.ctrl._axis); - - this.drawAxesAndOverlay(); - } - - /** @summary Method should be called to change background color */ - changedBackground(val) { - if (val !== undefined) - this.ctrl.background = val; - this.#scene.background = new THREE.Color(this.ctrl.background); - this.#renderer.setClearColor(this.#scene.background, 1); - this.render3D(0); - - if (this.#toolbar) { - const bkgr = new THREE.Color(this.ctrl.background); - this.#toolbar.changeBrightness((bkgr.r + bkgr.g + bkgr.b) < 1); - } - } - - /** @summary Display control GUI */ - showControlGui(on) { - // while complete geo drawing can be removed until dat is loaded - just check and ignore callback - if (!this.ctrl) - return; - - if (on === 'toggle') - on = !this.#gui; - else if (on === undefined) - on = this.ctrl.show_controls; - - this.ctrl.show_controls = on; - - if (this.#gui) { - if (!on) { - this.#gui.destroy(); - this.#gui = undefined; - } - return; - } - - if (!on || !this.#renderer) - return; - - const main = this.selectDom(); - if (main.style('position') === 'static') - main.style('position', 'relative'); - - this.#gui = new GUI({ - container: main.node(), closeFolders: true, - width: Math.min(300, this.#scene_width / 2), - title: 'Settings' - }); - - const dom = this.#gui.domElement; - dom.style.position = 'absolute'; - dom.style.top = 0; - dom.style.right = 0; - - this.#gui.painter = this; - - const makeLil = items => { - const lil = {}; - items.forEach(i => { lil[i.name] = i.value; }); - return lil; - }; - - if (!this.ctrl.project) { - const selection = this.#gui.addFolder('Selection'); - - if (!this.ctrl.maxnodes) - this.ctrl.maxnodes = this.#clones?.getMaxVisNodes() ?? 10000; - if (!this.ctrl.vislevel) - this.ctrl.vislevel = this.#clones?.getVisLevel() ?? 3; - if (!this.ctrl.maxfaces) - this.ctrl.maxfaces = 200000 * this.ctrl.more; - this.ctrl.more = 1; // prevent autoincrease every next time drawing is started - - selection.add(this.ctrl, 'vislevel', 1, 99, 1) - .name('Visibility level') - .listen().onChange(() => this.startRedraw(500)); - selection.add(this.ctrl, 'maxnodes', 0, 500000, 1000) - .name('Visible nodes') - .listen().onChange(() => this.startRedraw(500)); - selection.add(this.ctrl, 'maxfaces', 0, 5000000, 100000) - .name('Max faces') - .listen().onChange(() => this.startRedraw(500)); - } - - if (this.ctrl.project) { - const bound = this.getGeomBoundingBox(this.getProjectionSource(), 0.01), - axis = this.ctrl.project; - - if (this.ctrl.projectPos === undefined) - this.ctrl.projectPos = (bound.min[axis] + bound.max[axis]) / 2; - - this.#gui.add(this.ctrl, 'projectPos', bound.min[axis], bound.max[axis]) - .name(axis.toUpperCase() + ' projection') - .onChange(() => this.startDrawGeometry()); - } else { - // Clipping Options - - const clipFolder = this.#gui.addFolder('Clipping'); - - for (let naxis = 0; naxis < 3; ++naxis) { - const cc = this.ctrl.clip[naxis], - axisC = cc.name.toUpperCase(); - - clipFolder.add(cc, 'enabled') - .name('Enable ' + axisC) - .listen().onChange(() => this.changedClipping(-1)); - - clipFolder.add(cc, 'value', cc.min, cc.max, cc.step) - .name(axisC + ' position') - .listen().onChange(() => this.changedClipping(naxis)); - } - - clipFolder.add(this.ctrl, 'clipIntersect').name('Clip intersection') - .onChange(() => this.changedClipping(-1)); - - clipFolder.add(this.ctrl, 'clipVisualize').name('Visualize') - .onChange(() => this.changedClipping(-1)); - } - - // Scene Options - - const scene = this.#gui.addFolder('Scene'); - // following items used in handlers and cannot be constants - let light_pnts = null, strength = null, hcolor = null, overlay = null; - - scene.add(this.ctrl.light, 'kind', makeLil(this.ctrl.lightKindItems)).name('Light') - .listen().onChange(() => { - light_pnts.show(this.ctrl.light.kind === 'mix' || this.ctrl.light.kind === 'points'); - this.changedLight(); - }); - - this.ctrl.light._pnts = this.ctrl.light.specular ? 0 : (this.ctrl.light.front ? 1 : 2); - light_pnts = scene.add(this.ctrl.light, '_pnts', { specular: 0, front: 1, box: 2 }) - .name('Positions') - .show(this.ctrl.light.kind === 'mix' || this.ctrl.light.kind === 'points') - .onChange(v => { - this.ctrl.light.specular = (v === 0); - this.ctrl.light.front = (v === 1); - this.ctrl.light.top = this.ctrl.light.bottom = this.ctrl.light.left = this.ctrl.light.right = (v === 2); - this.changedLight(); - }); - - scene.add(this.ctrl.light, 'power', 0, 10, 0.01).name('Power') - .listen().onChange(() => this.changedLight()); - - scene.add(this.ctrl, 'use_fog').name('Fog') - .listen().onChange(() => this.changedUseFog()); - - // Appearance Options - - const appearance = this.#gui.addFolder('Appearance'); - - this.ctrl._highlight = !this.ctrl.highlight ? 0 : this.ctrl.highlight_bloom ? 2 : 1; - appearance.add(this.ctrl, '_highlight', { none: 0, normal: 1, bloom: 2 }).name('Highlight Selection') - .listen().onChange(() => { - this.changedHighlight(this.ctrl._highlight); - strength.show(this.ctrl._highlight === 2); - hcolor.show(this.ctrl._highlight === 1); - }); - - hcolor = appearance.addColor(this.ctrl, 'highlight_color').name('Hightlight color') - .show(this.ctrl._highlight === 1); - strength = appearance.add(this.ctrl, 'bloom_strength', 0, 3).name('Bloom strength') - .listen().onChange(() => this.changedHighlight()) - .show(this.ctrl._highlight === 2); - - appearance.addColor(this.ctrl, 'background').name('Background') - .onChange(col => this.changedBackground(col)); - - appearance.add(this.ctrl, '_axis', { none: 0, side: 1, center: 2 }).name('Axes') - .onChange(() => this.changedAxes()); - - if (!this.ctrl.project) { - appearance.add(this.ctrl, 'rotate').name('Autorotate') - .listen().onChange(() => this.changedAutoRotate()); - } - - // Material options - - const material = this.#gui.addFolder('Material'); - let material_props = []; - - const addMaterialProp = () => { - material_props.forEach(f => f.destroy()); - material_props = []; - - const props = this.ctrl.getMaterialCfg()?.props; - if (!props) - return; - - props.forEach(prop => { - const f = material.add(this.ctrl, prop.name, prop.min, prop.max, prop.step).onChange(() => { - this.changeMaterialProperty(prop.name); - }); - material_props.push(f); - }); - }; - - material.add(this.ctrl, 'material_kind', makeLil(this.ctrl.materialKinds)).name('Kind') - .listen().onChange(() => { - addMaterialProp(); - this.ensureBloom(false); - this.changedMaterial(); - this.changedHighlight(); // for some materials bloom will not work - }); - - material.add(this.ctrl, 'transparency', 0, 1, 0.001).name('Transparency') - .listen().onChange(value => this.changedGlobalTransparency(value)); - - material.add(this.ctrl, 'wireframe').name('Wireframe') - .listen().onChange(() => this.changedWireFrame()); - - material.add(this, 'showMaterialDocu').name('Docu from threejs.org'); - - addMaterialProp(); - - - // Camera options - const camera = this.#gui.addFolder('Camera'); - - camera.add(this.ctrl, 'camera_kind', makeLil(this.ctrl.cameraKindItems)) - .name('Kind').listen().onChange(() => { - overlay.show(this.ctrl.camera_kind.indexOf('ortho') === 0); - this.changeCamera(); - }); - - camera.add(this.ctrl, 'can_rotate').name('Can rotate') - .listen().onChange(() => this.changeCanRotate()); - - camera.add(this, 'focusCamera').name('Reset position'); - - overlay = camera.add(this.ctrl, 'camera_overlay', makeLil(this.ctrl.cameraOverlayItems)) - .name('Overlay').listen().onChange(() => this.changeCamera()) - .show(this.ctrl.camera_kind.indexOf('ortho') === 0); - - // Advanced Options - if (this.#webgl) { - const advanced = this.#gui.addFolder('Advanced'); - - advanced.add(this.ctrl, 'depthTest').name('Depth test') - .listen().onChange(() => this.changedDepthTest()); - - advanced.add(this.ctrl, 'depthMethod', makeLil(this.ctrl.depthMethodItems)) - .name('Rendering order') - .onChange(method => this.changedDepthMethod(method)); - - advanced.add(this, 'resetAdvanced').name('Reset'); - } - - // Transformation Options - if (!this.ctrl.project) { - const transform = this.#gui.addFolder('Transform'); - transform.add(this.ctrl, 'trans_z', 0.0, 3.0, 0.01) - .name('Z axis') - .listen().onChange(() => this.changedTransformation()); - transform.add(this.ctrl, 'trans_radial', 0.0, 3.0, 0.01) - .name('Radial') - .listen().onChange(() => this.changedTransformation()); - - transform.add(this, 'resetTransformation').name('Reset'); - if (this.ctrl.trans_z || this.ctrl.trans_radial) - transform.open(); - } - } - - /** @summary show material documentation from https://threejs.org */ - showMaterialDocu() { - const cfg = this.ctrl.getMaterialCfg(); - if (cfg?.name && typeof window !== 'undefined') - window.open('https://threejs.org/docs/index.html#api/en/materials/' + cfg.name, '_blank'); - } - - /** @summary Should be called when configuration of highlight is changed */ - changedHighlight(arg) { - if (arg !== undefined) { - this.ctrl.highlight = Boolean(arg); - if (this.ctrl.highlight) - this.ctrl.highlight_bloom = (arg === 2); - } - - this.ensureBloom(); - - if (!this.ctrl.highlight) - this.highlightMesh(null); - - this.getSubordinates()?.forEach(p => { - p.ctrl.highlight = this.ctrl.highlight; - p.ctrl.highlight_bloom = this.ctrl.highlight_bloom; - p.ctrl.bloom_strength = this.ctrl.bloom_strength; - p.changedHighlight(); - }); - } - - /** @summary Handle change of can rotate */ - changeCanRotate(on) { - if (on !== undefined) - this.ctrl.can_rotate = on; - if (this.#controls) - this.#controls.enableRotate = this.ctrl.can_rotate; - } - - /** @summary Change use fog property */ - changedUseFog() { - this.#scene.fog = this.ctrl.use_fog ? this.#fog : null; - - this.render3D(); - } - - /** @summary Handle change of camera kind */ - changeCamera() { - // force control recreation - if (this.#controls) { - this.#controls.cleanup(); - this.#controls = undefined; - } - - this.ensureBloom(false); - - // recreate camera - this.createCamera(); - - this.createSpecialEffects(); - - // set proper position - this.adjustCameraPosition(true); - - // this.drawOverlay(); - - this.addOrbitControls(); - - this.render3D(); - - // this.#scene_size = undefined; // ensure reassign of camera position - - // this.#first_drawing = true; - // this.startDrawGeometry(true); - } - - /** @summary create bloom effect */ - ensureBloom(on) { - if (on === undefined) { - if (this.ctrl.highlight_bloom === 0) - this.ctrl.highlight_bloom = this.#webgl && !browser.android; - - on = this.ctrl.highlight_bloom && this.ctrl.getMaterialCfg()?.emissive; - } - - if (on && !this.#bloomComposer) { - this.#camera.layers.enable(_BLOOM_SCENE); - this.#bloomComposer = new THREE.EffectComposer(this.#renderer); - this.#bloomComposer.addPass(new THREE.RenderPass(this.#scene, this.#camera)); - const pass = new THREE.UnrealBloomPass(new THREE.Vector2(this.#scene_width, this.#scene_height), 1.5, 0.4, 0.85); - pass.threshold = 0; - pass.radius = 0; - pass.renderToScreen = true; - this.#bloomComposer.addPass(pass); - this.#renderer.autoClear = false; - } else if (!on && this.#bloomComposer) { - this.#bloomComposer.dispose(); - this.#bloomComposer = undefined; - if (this.#renderer) - this.#renderer.autoClear = true; - this.#camera?.layers.disable(_BLOOM_SCENE); - this.#camera?.layers.set(_ENTIRE_SCENE); - } - - if (this.#bloomComposer?.passes) - this.#bloomComposer.passes[1].strength = this.ctrl.bloom_strength; - } - - - /** @summary Show context menu for orbit control - * @private */ - orbitContext(evnt, intersects) { - createMenu(evnt, this).then(menu => { - let numitems = 0, numnodes = 0, cnt = 0; - if (intersects) { - for (let n = 0; n < intersects.length; ++n) { - if (getIntersectStack(intersects[n])) - numnodes++; - if (intersects[n].geo_name) - numitems++; - } - } - - if (numnodes + numitems === 0) - this.fillContextMenu(menu); - else { - const many = (numnodes + numitems) > 1; - - if (many) - menu.header((numitems > 0) ? 'Items' : 'Nodes'); - - for (let n = 0; n < intersects.length; ++n) { - const obj = intersects[n].object, - stack = getIntersectStack(intersects[n]); - let name, itemname, hdr; - - if (obj.geo_name) { - itemname = obj.geo_name; - if (itemname.indexOf('<prnt>') === 0) - itemname = (this.getItemName() || 'top') + itemname.slice(6); - name = itemname.slice(itemname.lastIndexOf('/') + 1); - if (!name) - name = itemname; - hdr = name; - } else if (stack) { - name = this.#clones.getStackName(stack); - itemname = this.getStackFullName(stack); - hdr = this.getItemName(); - if (name.indexOf('Nodes/') === 0) - hdr = name.slice(6); - else if (name) - hdr = name; - else if (!hdr) - hdr = 'header'; - } else - continue; - - - cnt++; - - menu.add((many ? 'sub:' : 'header:') + hdr, itemname, arg => this.activateInBrowser([arg], true)); - - menu.add('Browse', itemname, arg => this.activateInBrowser([arg], true)); - - if (this.getHPainter()) - menu.add('Inspect', itemname, arg => this.getHPainter().display(arg, kInspect)); - - if (isFunc(this.hidePhysicalNode)) { - menu.add('Hide', itemname, arg => this.hidePhysicalNode([arg])); - if (cnt > 1) { - menu.add('Hide all before', n, indx => { - const items = []; - for (let i = 0; i < indx; ++i) { - const stack2 = getIntersectStack(intersects[i]); - if (stack2) - items.push(this.getStackFullName(stack2)); - } - this.hidePhysicalNode(items); - }); - } - } else if (obj.geo_name) { - menu.add('Hide', n, indx => { - const mesh = intersects[indx].object; - mesh.visible = false; // just disable mesh - if (mesh.geo_object) - mesh.geo_object.$hidden_via_menu = true; // and hide object for further redraw - menu.painter.render3D(); - }, 'Hide this physical node'); - - if (many) - menu.endsub(); - - continue; - } - - const wireframe = this.accessObjectWireFrame(obj); - if (wireframe !== undefined) { - menu.addchk(wireframe, 'Wireframe', n, indx => { - const m = intersects[indx].object.material; - m.wireframe = !m.wireframe; - this.render3D(); - }, 'Toggle wireframe mode for the node'); - } - - if (cnt > 1) { - menu.add('Manifest', n, indx => { - if (this.#last_manifest) - this.#last_manifest.wireframe = !this.#last_manifest.wireframe; - - this.#last_hidden?.forEach(obj2 => { obj2.visible = true; }); - - this.#last_hidden = []; - - for (let i = 0; i < indx; ++i) - this.#last_hidden.push(intersects[i].object); - - this.#last_hidden.forEach(obj2 => { obj2.visible = false; }); - - this.#last_manifest = intersects[indx].object.material; - - this.#last_manifest.wireframe = !this.#last_manifest.wireframe; - - this.render3D(); - }, 'Manifest selected node'); - } - - menu.add('Focus', n, indx => { - this.focusCamera(intersects[indx].object); - }); - - if (!this.#geom_viewer) { - menu.add('Hide', n, indx => { - const resolve = this.#clones.resolveStack(intersects[indx].object.stack); - if (resolve.obj && (resolve.node.kind === kindGeo) && resolve.obj.fVolume) { - setGeoBit(resolve.obj.fVolume, geoBITS.kVisThis, false); - updateBrowserIcons(resolve.obj.fVolume, this.getHPainter()); - } else if (resolve.obj && (resolve.node.kind === kindEve)) { - resolve.obj.fRnrSelf = false; - updateBrowserIcons(resolve.obj, this.getHPainter()); - } - - this.testGeomChanges();// while many volumes may disappear, recheck all of them - }, 'Hide all logical nodes of that kind'); - menu.add('Hide only this', n, indx => { - this.#clones.setPhysNodeVisibility(getIntersectStack(intersects[indx]), false); - this.testGeomChanges(); - }, 'Hide only this physical node'); - if (n > 1) { - menu.add('Hide all before', n, indx => { - for (let k = 0; k < indx; ++k) - this.#clones.setPhysNodeVisibility(getIntersectStack(intersects[k]), false); - this.testGeomChanges(); - }, 'Hide all physical nodes before that'); - } - } - - if (many) - menu.endsub(); - } - } - menu.show(); - }); - } - - /** @summary Filter some objects from three.js intersects array */ - filterIntersects(intersects) { - if (!intersects?.length) - return intersects; - - // check redirection - for (let n = 0; n < intersects.length; ++n) { - if (intersects[n].object.geo_highlight) - intersects[n].object = intersects[n].object.geo_highlight; - } - - // remove all elements without stack - indicator that this is geometry object - // also remove all objects which are mostly transparent - for (let n = intersects.length - 1; n >= 0; --n) { - const obj = intersects[n].object; - let unique = obj.visible && (getIntersectStack(intersects[n]) || (obj.geo_name !== undefined)); - - if (unique && (obj.material?.opacity !== undefined)) - unique = obj.material.opacity >= 0.1; - - if (obj.jsroot_special) - unique = false; - - for (let k = 0; (k < n) && unique; ++k) { - if (intersects[k].object === obj) - unique = false; - } - - if (!unique) - intersects.splice(n, 1); - } - - const clip = this.ctrl.clip; - - if (clip[0].enabled || clip[1].enabled || clip[2].enabled) { - const clippedIntersects = []; - - for (let i = 0; i < intersects.length; ++i) { - const point = intersects[i].point, special = (intersects[i].object.type === 'Points'); - let clipped = true; - - if (clip[0].enabled && ((this.#clip_planes[0].normal.dot(point) > this.#clip_planes[0].constant) ^ special)) - clipped = false; - if (clip[1].enabled && ((this.#clip_planes[1].normal.dot(point) > this.#clip_planes[1].constant) ^ special)) - clipped = false; - if (clip[2].enabled && (this.#clip_planes[2].normal.dot(point) > this.#clip_planes[2].constant)) - clipped = false; - - if (!clipped) - clippedIntersects.push(intersects[i]); - } - - intersects = clippedIntersects; - } - - return intersects; - } - - /** @summary test camera position - * @desc function analyzes camera position and start redraw of geometry - * if objects in view may be changed */ - testCameraPositionChange() { - if (!this.ctrl.select_in_view || this.#draw_all_nodes) - return; - - const matrix = createProjectionMatrix(this.#camera), - frustum = createFrustum(matrix); - - // check if overall bounding box seen - if (!frustum.CheckBox(this.getGeomBoundingBox())) - this.startDrawGeometry(); - } - - /** @summary Resolve stack */ - resolveStack(stack) { - return this.#clones && stack ? this.#clones.resolveStack(stack) : null; - } - - /** @summary Returns stack full name - * @desc Includes item name of top geo object */ - getStackFullName(stack) { - const mainitemname = this.getItemName(), - sub = this.resolveStack(stack); - if (!sub || !sub.name) - return mainitemname; - return mainitemname ? mainitemname + '/' + sub.name : sub.name; - } - - /** @summary Add handler which will be called when element is highlighted in geometry drawing - * @desc Handler should have highlightMesh function with same arguments as TGeoPainter */ - addHighlightHandler(handler) { - if (!isFunc(handler?.highlightMesh)) - return; - if (!this.#highlight_handlers) - this.#highlight_handlers = []; - this.#highlight_handlers.push(handler); - } - - /** @summary perform mesh highlight */ - highlightMesh(active_mesh, color, geo_object, geo_index, geo_stack, no_recursive) { - if (geo_object) { - active_mesh = active_mesh ? [active_mesh] : []; - const extras = this.getExtrasContainer(); - if (extras) { - extras.traverse(obj3d => { - if ((obj3d.geo_object === geo_object) && (active_mesh.indexOf(obj3d) < 0)) - active_mesh.push(obj3d); - }); - } - } else if (geo_stack && this.#toplevel) { - active_mesh = []; - this.#toplevel.traverse(mesh => { - if ((mesh instanceof THREE.Mesh) && isSameStack(mesh.stack, geo_stack)) - active_mesh.push(mesh); - }); - } else - active_mesh = active_mesh ? [active_mesh] : []; - - if (!active_mesh.length) - active_mesh = null; - - if (active_mesh) { - // check if highlight is disabled for correspondent objects kinds - if (active_mesh[0].geo_object) { - if (!this.ctrl.highlight_scene) - active_mesh = null; - } else - if (!this.ctrl.highlight) - active_mesh = null; - } - - if (!no_recursive) { - // check all other painters - - if (active_mesh) { - if (!geo_object) - geo_object = active_mesh[0].geo_object; - if (!geo_stack) - geo_stack = active_mesh[0].stack; - } - - const lst = this.#highlight_handlers || this.getCentral()?.getSubordinates().concat([this.getCentral()]) || this.getSubordinates(); - - for (let k = 0; k < lst?.length; ++k) { - if (lst[k] !== this) - lst[k].highlightMesh(null, color, geo_object, geo_index, geo_stack, true); - } - } - - const curr_mesh = this.#selected_mesh; - if (!curr_mesh && !active_mesh) - return false; - - const get_ctrl = mesh => { return mesh.get_ctrl ? mesh.get_ctrl() : new GeoDrawingControl(mesh, this.ctrl.highlight_bloom && this.#bloomComposer); }; - - let same = false; - - // check if selections are the same - if (curr_mesh && active_mesh && (curr_mesh.length === active_mesh.length)) { - same = true; - for (let k = 0; (k < curr_mesh.length) && same; ++k) { - if ((curr_mesh[k] !== active_mesh[k]) || get_ctrl(curr_mesh[k]).checkHighlightIndex(geo_index)) - same = false; - } - } - if (same) - return Boolean(curr_mesh); - - curr_mesh?.forEach(mesh => get_ctrl(mesh).setHighlight()); - - this.#selected_mesh = active_mesh; - - active_mesh?.forEach(mesh => get_ctrl(mesh).setHighlight(color || new THREE.Color(this.ctrl.highlight_color), geo_index)); - - this.render3D(0); - - return Boolean(active_mesh); - } - - /** @summary handle mouse click event */ - processMouseClick(pnt, intersects, evnt) { - if (!intersects.length) - return; - - const mesh = intersects[0].object; - if (!mesh.get_ctrl) - return; - - const ctrl = mesh.get_ctrl(), - click_indx = ctrl.extractIndex(intersects[0]); - - ctrl.evnt = evnt; - - if (ctrl.setSelected('blue', click_indx)) - this.render3D(); - - ctrl.evnt = null; - } - - /** @summary Configure mouse delay, required for complex geometries */ - setMouseTmout(val) { - if (this.ctrl) - this.ctrl.mouse_tmout = val; - - if (this.#controls) - this.#controls.mouse_tmout = val; - } - - /** @summary Configure depth method, used for render order production. - * @param {string} method - Allowed values: 'ray', 'box','pnt', 'size', 'dflt' */ - setDepthMethod(method) { - if (this.ctrl) - this.ctrl.depthMethod = method; - } - - /** @summary Returns if camera can rotated */ - canRotateCamera() { - if (this.ctrl.can_rotate === false) - return false; - if (!this.ctrl.can_rotate && (this.isOrthoCamera() || this.ctrl.project)) - return false; - return true; - } - - /** @summary Add orbit control */ - addOrbitControls() { - if (this.#controls || !this.#webgl || this.isBatchMode() || this.#superimpose || isNodeJs()) - return; - - if (!this.getCanvPainter()) - this.setTooltipAllowed(settings.Tooltip); - - this.#controls = createOrbitControl(this, this.#camera, this.#scene, this.#renderer, this.#lookat); - - this.#controls.mouse_tmout = this.ctrl.mouse_tmout; // set larger timeout for geometry processing - - if (!this.canRotateCamera()) - this.#controls.enableRotate = false; - - this.#controls.contextMenu = this.orbitContext.bind(this); - - this.#controls.processMouseMove = intersects => { - // painter already cleaned up, ignore any incoming events - if (!this.ctrl || !this.#controls) - return; - - let active_mesh = null, tooltip = null, resolve = null, names = [], geo_object, geo_index, geo_stack; - - // try to find mesh from intersections - for (let k = 0; k < intersects.length; ++k) { - const obj = intersects[k].object, stack = getIntersectStack(intersects[k]); - if (!obj || !obj.visible) - continue; - let info = null; - if (obj.geo_object) - info = obj.geo_name; - else if (stack) - info = this.getStackFullName(stack); - if (!info) - continue; - - if (info.indexOf('<prnt>') === 0) - info = this.getItemName() + info.slice(6); - - names.push(info); - - if (!active_mesh) { - active_mesh = obj; - tooltip = info; - geo_object = obj.geo_object; - if (obj.get_ctrl) { - geo_index = obj.get_ctrl().extractIndex(intersects[k]); - if ((geo_index !== undefined) && isStr(tooltip)) - tooltip += ' indx:' + JSON.stringify(geo_index); - } - geo_stack = stack; - - if (geo_stack) { - resolve = this.resolveStack(geo_stack); - if (obj.stacks) - geo_index = intersects[k].instanceId; - } - } - } - - this.highlightMesh(active_mesh, undefined, geo_object, geo_index); - - if (this.ctrl.update_browser) { - if (this.ctrl.highlight && tooltip) - names = [tooltip]; - this.activateInBrowser(names); - } - - if (!resolve?.obj) - return tooltip; - - const lines = provideObjectInfo(resolve.obj); - lines.unshift(tooltip); - - return { name: resolve.obj.fName, title: resolve.obj.fTitle || resolve.obj._typename, lines }; - }; - - this.#controls.processMouseLeave = function() { - this.processMouseMove([]); // to disable highlight and reset browser - }; - - this.#controls.processDblClick = () => { - // painter already cleaned up, ignore any incoming events - if (!this.ctrl || !this.#controls) - return; - - if (this.#last_manifest) { - this.#last_manifest.wireframe = !this.#last_manifest.wireframe; - if (this.#last_hidden) - this.#last_hidden.forEach(obj => { obj.visible = true; }); - this.#last_hidden = undefined; - this.#last_manifest = undefined; - } else - this.adjustCameraPosition(true); - - this.render3D(); - }; - } - - /** @summary Main function in geometry creation loop - * @desc Returns: - * - false when nothing todo - * - true if one could perform next action immediately - * - 1 when call after short timeout required - * - 2 when call must be done from processWorkerReply */ - nextDrawAction() { - if (!this.#clones || this.isStage(stageInit)) - return false; - - if (this.isStage(stageCollect)) { - if (this.#geom_viewer) { - this.#draw_all_nodes = false; - this.changeStage(stageAnalyze); - return true; - } - - // wait until worker is really started - if (this.ctrl.use_worker > 0) { - if (!this.#worker) { - this.startWorker(); - return 1; - } - if (!this.#worker_ready) - return 1; - } - - // first copy visibility flags and check how many unique visible nodes exists - let numvis = this.#first_drawing ? this.#clones.countVisibles() : 0, - matrix = null, frustum = null; - - if (!numvis) - numvis = this.#clones.markVisibles(false, false, Boolean(this.#geo_manager) && !this.ctrl.showtop); - - if (this.ctrl.select_in_view && !this.#first_drawing) { - // extract camera projection matrix for selection - - matrix = createProjectionMatrix(this.#camera); - - frustum = createFrustum(matrix); - - // check if overall bounding box seen - if (frustum.CheckBox(this.getGeomBoundingBox())) { - matrix = null; // not use camera for the moment - frustum = null; - } - } - - this.#current_face_limit = this.ctrl.maxfaces; - if (matrix) - this.#current_face_limit *= 1.25; - - // here we decide if we need worker for the drawings - // main reason - too large geometry and large time to scan all camera positions - let need_worker = !this.isBatchMode() && browser.isChrome && ((numvis > 10000) || (matrix && (this.#clones.scanVisible() > 1e5))); - - // worker does not work when starting from file system - if (need_worker && source_dir.indexOf('file://') === 0) { - console.log('disable worker for jsroot from file system'); - need_worker = false; - } - - if (need_worker && !this.#worker && (this.ctrl.use_worker >= 0)) - this.startWorker(); // we starting worker, but it may not be ready so fast - - if (!need_worker || !this.#worker_ready) { - const res = this.#clones.collectVisibles(this.#current_face_limit, frustum); - this.#new_draw_nodes = res.lst; - this.#draw_all_nodes = res.complete; - this.changeStage(stageAnalyze); - return true; - } - - const job = { - collect: this.#current_face_limit, // indicator for the command - flags: this.#clones.getVisibleFlags(), - matrix: matrix ? matrix.elements : null, - vislevel: this.#clones.getVisLevel(), - maxvisnodes: this.#clones.getMaxVisNodes() - }; - - this.submitToWorker(job); - - this.changeStage(stageWorkerCollect); - - return 2; // we now waiting for the worker reply - } - - if (this.isStage(stageWorkerCollect)) { - // do nothing, we are waiting for worker reply - return 2; - } - - if (this.isStage(stageAnalyze)) { - // here we merge new and old list of nodes for drawing, - // normally operation is fast and can be implemented with one c - - if (this.#new_append_nodes) { - this.#new_draw_nodes = this.#draw_nodes.concat(this.#new_append_nodes); - - this.#new_append_nodes = undefined; - } else if (this.#draw_nodes) { - let del; - if (this.#geom_viewer) - del = this.#draw_nodes; - else - del = this.#clones.mergeVisibles(this.#new_draw_nodes, this.#draw_nodes); - - // remove should be fast, do it here - for (let n = 0; n < del.length; ++n) - this.#clones.createObject3D(del[n].stack, this.#toplevel, kDeleteMesh); - - if (del.length) - this.#drawing_log = `Delete ${del.length} nodes`; - } - - this.#draw_nodes = this.#new_draw_nodes; - this.#new_draw_nodes = undefined; - this.changeStage(stageCollShapes); - return true; - } - - if (this.isStage(stageCollShapes)) { - // collect shapes - const shapes = this.#clones.collectShapes(this.#draw_nodes); - - // merge old and new list with produced shapes - this.#build_shapes = this.#clones.mergeShapesLists(this.#build_shapes, shapes); - - this.changeStage(stageStartBuild); - return true; - } - - if (this.isStage(stageStartBuild)) { - // this is building of geometries, - // one can ask worker to build them or do it ourself - - if (this.canSubmitToWorker()) { - const job = { limit: this.#current_face_limit, shapes: [] }; - let cnt = 0; - for (let n = 0; n < this.#build_shapes.length; ++n) { - const item = this.#build_shapes[n]; - // only submit not-done items - if (item.ready || item.geom) { - // this is place holder for existing geometry - job.shapes.push({ id: item.id, ready: true, nfaces: numGeometryFaces(item.geom), refcnt: item.refcnt }); - } else { - job.shapes.push(clone(item, null, true)); - cnt++; - } - } - - if (cnt > 0) { - // only if some geom missing, submit job to the worker - this.submitToWorker(job); - this.changeStage(stageWorkerBuild); - return 2; - } - } - - this.changeStage(stageBuild); - } - - if (this.isStage(stageWorkerBuild)) { - // waiting shapes from the worker, worker should activate our code - return 2; - } - - if (this.isStage(stageBuild) || this.isStage(stageBuildReady)) { - if (this.isStage(stageBuild)) { - // building shapes - - const res = this.#clones.buildShapes(this.#build_shapes, this.#current_face_limit, 500); - if (res.done) { - this.ctrl.info.num_shapes = this.#build_shapes.length; - this.changeStage(stageBuildReady); - } else { - this.ctrl.info.num_shapes = res.shapes; - this.#drawing_log = `Creating: ${res.shapes} / ${this.#build_shapes.length} shapes, ${res.faces} faces`; - return true; - } - } - - // final stage, create all meshes - - const tm0 = new Date().getTime(), - toplevel = this.ctrl.project ? this.#fullgeom_proj : this.#toplevel; - let build_instanced = false, ready = true; - - if (!this.ctrl.project) - build_instanced = this.#clones.createInstancedMeshes(this.ctrl, toplevel, this.#draw_nodes, this.#build_shapes, getRootColors()); - - if (!build_instanced) { - for (let n = 0; n < this.#draw_nodes.length; ++n) { - const entry = this.#draw_nodes[n]; - if (entry.done) - continue; - - // shape can be provided with entry itself - const shape = entry.server_shape || this.#build_shapes[entry.shapeid]; - - this.createEntryMesh(entry, shape, toplevel); - - const tm1 = new Date().getTime(); - if (tm1 - tm0 > 500) { - ready = false; - break; - } - } - } - - if (ready) { - if (this.ctrl.project) { - this.changeStage(stageBuildProj); - return true; - } - this.changeStage(stageInit); - return false; - } - - if (!this.isStage(stageBuild)) - this.#drawing_log = `Building meshes ${this.ctrl.info.num_meshes} / ${this.ctrl.info.num_faces}`; - return true; - } - - if (this.isStage(stageWaitMain)) { - // wait for main painter to be ready - if (!this.getCentral()) { - this.changeStage(stageInit, 'Lost main painter'); - return false; - } - if (!this.getCentral().isDrawingReady()) - return 1; - - this.changeStage(stageBuildProj); // just do projection - } - - if (this.isStage(stageBuildProj)) { - this.doProjection(); - this.changeStage(stageInit); - return false; - } - - console.error(`never come here, stage ${this.drawing_stage}`); - - return false; - } - - /** @summary Insert appropriate mesh for given entry */ - createEntryMesh(entry, shape, toplevel) { - // workaround for the TGeoOverlap, where two branches should get predefined color - if (this.ctrl.split_colors && entry.stack) { - if (entry.stack[0] === 0) - entry.custom_color = 'green'; - else if (entry.stack[0] === 1) - entry.custom_color = 'blue'; - } - - this.#clones.createEntryMesh(this.ctrl, toplevel, entry, shape, getRootColors()); - - return true; - } - - /** @summary used by geometry viewer to show more nodes - * @desc These nodes excluded from selection logic and always inserted into the model - * Shape already should be created and assigned to the node */ - appendMoreNodes(nodes, from_drawing) { - if (!this.isStage(stageInit) && !from_drawing) { - this.#provided_more_nodes = nodes; - return; - } - - // delete old nodes - if (this.#more_nodes) { - for (let n = 0; n < this.#more_nodes.length; ++n) { - const entry = this.#more_nodes[n], - obj3d = this.#clones.createObject3D(entry.stack, this.#toplevel, kDeleteMesh); - disposeThreejsObject(obj3d); - cleanupShape(entry.server_shape); - delete entry.server_shape; - } - this.#more_nodes = undefined; - } - - if (!nodes) - return; - - const real_nodes = []; - for (let k = 0; k < nodes.length; ++k) { - const entry = nodes[k], - shape = entry.server_shape; - if (!shape?.ready) - continue; - - if (this.createEntryMesh(entry, shape, this.#toplevel)) - real_nodes.push(entry); - } - - // remember additional nodes only if they include shape - otherwise one can ignore them - if (real_nodes.length) - this.#more_nodes = real_nodes; - - if (!from_drawing) - this.render3D(); - } - - /** @summary Returns hierarchy of 3D objects used to produce projection. - * @desc Typically external master painter is used, but also internal data can be used */ - getProjectionSource() { - if (this.#clones_owner) - return this.#fullgeom_proj; - if (!this.getCentral()?.isDrawingReady()) { - console.warn('MAIN PAINTER NOT READY WHEN DO PROJECTION'); - return null; - } - return this.getCentral().#toplevel; - } - - /** @summary Extend custom geometry bounding box */ - extendCustomBoundingBox(box) { - if (!box) - return; - if (!this.#custom_bounding_box) - this.#custom_bounding_box = new THREE.Box3().makeEmpty(); - - const origin = this.#custom_bounding_box.clone(); - this.#custom_bounding_box.union(box); - - if (!this.#custom_bounding_box.equals(origin)) - this.#adjust_camera_with_render = true; - } - - /** @summary Calculate geometry bounding box */ - getGeomBoundingBox(topitem, scalar) { - const box3 = new THREE.Box3(), check_any = !this.#clones; - if (topitem === undefined) - topitem = this.#toplevel; - - box3.makeEmpty(); - - if (this.#custom_bounding_box && (topitem === this.#toplevel)) { - box3.union(this.#custom_bounding_box); - return box3; - } - - if (!topitem) { - box3.min.x = box3.min.y = box3.min.z = -1; - box3.max.x = box3.max.y = box3.max.z = 1; - return box3; - } - - topitem.traverse(mesh => { - if (check_any || (mesh.stack && (mesh instanceof THREE.Mesh)) || - (mesh.main_track && (mesh instanceof THREE.LineSegments)) || (mesh.stacks && (mesh instanceof THREE.InstancedMesh))) - getBoundingBox(mesh, box3); - }); - - if (scalar === 'original') { - box3.translate(new THREE.Vector3(-topitem.position.x, -topitem.position.y, -topitem.position.z)); - box3.min.multiply(new THREE.Vector3(1 / topitem.scale.x, 1 / topitem.scale.y, 1 / topitem.scale.z)); - box3.max.multiply(new THREE.Vector3(1 / topitem.scale.x, 1 / topitem.scale.y, 1 / topitem.scale.z)); - } else if (scalar !== undefined) - box3.expandByVector(box3.getSize(new THREE.Vector3()).multiplyScalar(scalar)); - - return box3; - } - - /** @summary Create geometry projection */ - doProjection() { - const toplevel = this.getProjectionSource(); - - if (!toplevel) - return false; - - disposeThreejsObject(this.#toplevel, true); - - // let axis = this.ctrl.project; - - if (this.ctrl.projectPos === undefined) { - const bound = this.getGeomBoundingBox(toplevel), - min = bound.min[this.ctrl.project], max = bound.max[this.ctrl.project]; - let mean = (min + max) / 2; - - if ((min < 0) && (max > 0) && (Math.abs(mean) < 0.2 * Math.max(-min, max))) - mean = 0; // if middle is around 0, use 0 - - this.ctrl.projectPos = mean; - } - - toplevel.traverse(mesh => { - if (!(mesh instanceof THREE.Mesh) || !mesh.stack) - return; - - const geom2 = projectGeometry(mesh.geometry, mesh.parent.absMatrix || mesh.parent.matrixWorld, this.ctrl.project, this.ctrl.projectPos, mesh._flippedMesh); - if (geom2) { - const mesh2 = new THREE.Mesh(geom2, mesh.material.clone()); - this.#toplevel.add(mesh2); - mesh2.stack = mesh.stack; - } - }); - - return true; - } - - /** @summary Should be invoked when light configuration changed */ - changedLight(box) { - if (!this.#camera) - return; - - const need_render = !box; - - if (!box) - box = this.getGeomBoundingBox(); - - const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - plights = [], p = (this.ctrl.light.power ?? 1) * 0.5; - - if (this.#camera._lights !== this.ctrl.light.kind) { - // remove all childs and recreate only necessary lights - disposeThreejsObject(this.#camera, true); - - this.#camera._lights = this.ctrl.light.kind; - - switch (this.#camera._lights) { - case 'ambient': - this.#camera.add(new THREE.AmbientLight(0xefefef, p)); - break; - case 'hemisphere': - this.#camera.add(new THREE.HemisphereLight(0xffffbb, 0x080820, p)); - break; - case 'mix': - this.#camera.add(new THREE.AmbientLight(0xefefef, p)); - default: // 6 point lights - for (let n = 0; n < 6; ++n) { - const l = new THREE.DirectionalLight(0xefefef, p); - this.#camera.add(l); - l._id = n; - } - } - } - - for (let k = 0; k < this.#camera.children.length; ++k) { - const light = this.#camera.children[k]; - let enabled = false; - if (light.isAmbientLight || light.isHemisphereLight) { - light.intensity = p; - continue; - } - if (!light.isDirectionalLight) - continue; - switch (light._id) { - case 0: - light.position.set(sizex / 5, sizey / 5, sizez / 5); - enabled = this.ctrl.light.specular; - break; - case 1: - light.position.set(0, 0, sizez / 2); - enabled = this.ctrl.light.front; - break; - case 2: - light.position.set(0, 2 * sizey, 0); - enabled = this.ctrl.light.top; - break; - case 3: - light.position.set(0, -2 * sizey, 0); - enabled = this.ctrl.light.bottom; - break; - case 4: - light.position.set(-2 * sizex, 0, 0); - enabled = this.ctrl.light.left; - break; - case 5: - light.position.set(2 * sizex, 0, 0); - enabled = this.ctrl.light.right; - break; - } - light.power = enabled ? p * Math.PI * 4 : 0; - if (enabled) - plights.push(light); - } - - // keep light power of all sources constant - plights.forEach(ll => { ll.power = p * 4 * Math.PI / plights.length; }); - - if (need_render) - this.render3D(); - } - - /** @summary Returns true if orthographic camera is used */ - isOrthoCamera() { - return this.ctrl.camera_kind.indexOf('ortho') === 0; - } - - /** @summary Create configured camera */ - createCamera() { - if (this.#camera) { - this.#scene.remove(this.#camera); - disposeThreejsObject(this.#camera); - this.#camera = undefined; - } - - if (this.isOrthoCamera()) - this.#camera = new THREE.OrthographicCamera(-this.#scene_width / 2, this.#scene_width / 2, this.#scene_height / 2, -this.#scene_height / 2, 1, 10000); - else { - this.#camera = new THREE.PerspectiveCamera(25, this.#scene_width / this.#scene_height, 1, 10000); - this.#camera.up = this.ctrl._yup ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(0, 0, 1); - } - - // Light - add default directional light, adjust later - const light = new THREE.DirectionalLight(0xefefef, 0.1); - light.position.set(10, 10, 10); - this.#camera.add(light); - - this.#scene.add(this.#camera); - } - - /** @summary Create special effects */ - createSpecialEffects() { - if (this.#webgl && this.ctrl.outline && isFunc(this.createOutline)) { - // code used with jsroot-based geometry drawing in EVE7, not important any longer - this.#effectComposer = new THREE.EffectComposer(this.#renderer); - this.#effectComposer.addPass(new THREE.RenderPass(this.#scene, this.#camera)); - this.createOutline(this.#scene, this.#camera, this.#scene_width, this.#scene_height); - } - - this.ensureBloom(); - } - - /** @summary Return current effect composer */ - getEffectComposer() { return this.#effectComposer; } - - /** @summary Initial scene creation */ - async createScene(w, h, render3d) { - if (this.#superimpose) { - const cfg = getHistPainter3DCfg(this.getMainPainter()); - - if (cfg?.renderer) { - this.#scene = cfg.scene; - this.#scene_width = cfg.scene_width; - this.#scene_height = cfg.scene_height; - this.#renderer = cfg.renderer; - this.#webgl = (this.#renderer.jsroot_render3d === constants.Render3D.WebGL); - - this.#toplevel = new THREE.Object3D(); - this.#scene.add(this.#toplevel); - - if (cfg.scale_x || cfg.scale_y || cfg.scale_z) - this.#toplevel.scale.set(cfg.scale_x, cfg.scale_y, cfg.scale_z); - if (cfg.offset_x || cfg.offset_y || cfg.offset_z) - this.#toplevel.position.set(cfg.offset_x, cfg.offset_y, cfg.offset_z); - this.#toplevel.updateMatrix(); - this.#toplevel.updateMatrixWorld(); - - this.#camera = cfg.camera; - } - - return this.#renderer?.jsroot_dom; - } - - return importThreeJs().then(() => { - // three.js 3D drawing - this.#scene = new THREE.Scene(); - this.#fog = new THREE.Fog(0xffffff, 1, 10000); - this.#scene.fog = this.ctrl.use_fog ? this.#fog : null; - - this.#scene.overrideMaterial = new THREE.MeshLambertMaterial({ color: 0x7000ff, vertexColors: false, transparent: true, opacity: 0.2, depthTest: false }); - - this.#scene_width = w; - this.#scene_height = h; - - this.createCamera(); - - this.#selected_mesh = null; - - this.#overall_size = 10; - - this.#toplevel = new THREE.Object3D(); - - this.#scene.add(this.#toplevel); - - this.#scene.background = new THREE.Color(this.ctrl.background); - - return createRender3D(w, h, render3d, { antialias: true, logarithmicDepthBuffer: false, preserveDrawingBuffer: true }); - }).then(r => { - this.#renderer = r; - - if (this.batch_format) - r.jsroot_image_format = this.batch_format; - - this.#webgl = (r.jsroot_render3d === constants.Render3D.WebGL); - - if (isFunc(r.setPixelRatio) && !isNodeJs() && !browser.android) - r.setPixelRatio(window.devicePixelRatio); - r.setSize(w, h, !this.#fit_main_area); - r.localClippingEnabled = true; - - r.setClearColor(this.#scene.background, 1); - - if (this.#fit_main_area && this.#webgl) { - r.domElement.style.width = '100%'; - r.domElement.style.height = '100%'; - const main = this.selectDom(); - if (main.style('position') === 'static') - main.style('position', 'relative'); - } - - this.#animating = false; - - this.ctrl.doubleside = false; // both sides need for clipping - this.createSpecialEffects(); - - if (this.#fit_main_area && !this.#webgl) { - // create top-most SVG for geometry drawings - const doc = getDocument(), - svg = doc.createElementNS(nsSVG, 'svg'); - svg.setAttribute('width', w); - svg.setAttribute('height', h); - svg.appendChild(this.#renderer.jsroot_dom); - return svg; - } - - return this.#renderer.jsroot_dom; - }); - } - - /** @summary Start geometry drawing */ - startDrawGeometry(force) { - if (!force && !this.isStage(stageInit)) { - this.#draw_nodes_again = true; - return; - } - - if (this.#clones_owner) - this.#clones?.setDefaultColors(this.ctrl.dflt_colors); - - this.#last_render_tm = this.#start_render_tm = new Date().getTime(); - this.#last_render_meshes = 0; - this.changeStage(stageCollect); - this.#drawing_ready = false; - this.ctrl.info.num_meshes = 0; - this.ctrl.info.num_faces = 0; - this.ctrl.info.num_shapes = 0; - this.#selected_mesh = null; - - if (this.ctrl.project) { - if (this.#clones_owner) { - if (this.#fullgeom_proj) - this.changeStage(stageBuildProj); - else - this.#fullgeom_proj = new THREE.Object3D(); - } else - this.changeStage(stageWaitMain); - } - - this.#last_manifest = undefined; - this.#last_hidden = undefined; // clear list of hidden objects - - this.#draw_nodes_again = undefined; // forget about such flag - - this.continueDraw(); - } - - /** @summary reset all kind of advanced features like depth test */ - resetAdvanced() { - this.ctrl.depthTest = true; - this.ctrl.clipIntersect = true; - this.ctrl.depthMethod = 'ray'; - - this.changedDepthMethod('norender'); - this.changedDepthTest(); - } - - /** @summary returns maximal dimension */ - getOverallSize(force) { - if (!this.#overall_size || force || this.#custom_bounding_box) { - const box = this.getGeomBoundingBox(); - - // if detect of coordinates fails - ignore - if (!Number.isFinite(box.min.x)) - return 1000; - - this.#overall_size = 2 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z); - } - - return this.#overall_size; - } - - /** @summary Create png image with drawing snapshot. */ - createSnapshot(filename) { - if (!this.#renderer) - return; - this.render3D(0); - const dataUrl = this.#renderer.domElement.toDataURL('image/png'); - if (filename === 'asis') - return dataUrl; - const doc = getDocument(), - link = doc.createElement('a'); - if (isStr(link.download)) { - doc.body.appendChild(link); // Firefox requires the link to be in the body - link.download = filename || 'geometry.png'; - link.href = dataUrl; - link.click(); - doc.body.removeChild(link); // remove the link when done - } - } - - /** @summary Returns url parameters defining camera position. - * @desc Either absolute position are provided (arg === true) or zoom, roty, rotz parameters */ - produceCameraUrl(arg) { - if (!this.#camera) - return ''; - - if (this.#camera.isOrthographicCamera) { - const zoom = Math.round(this.#camera.zoom * 100); - return this.ctrl.camera_kind + (zoom === 100 ? '' : `,zoom=${zoom}`); - } - - let kind = ''; - if (this.ctrl.camera_kind !== 'perspective') - kind = this.ctrl.camera_kind + ','; - - if (arg === true) { - const p = this.#camera.position, t = this.#controls?.target; - if (!p || !t) - return ''; - - const conv = v => { - let s = ''; - if (v < 0) { - s = 'n'; - v = -v; - } - return s + v.toFixed(0); - }; - - let res = `${kind}camx${conv(p.x)},camy${conv(p.y)},camz${conv(p.z)}`; - if (t.x || t.y || t.z) - res += `,camlx${conv(t.x)},camly${conv(t.y)},camlz${conv(t.z)}`; - return res; - } - - if (!this.#lookat || !this.#camera0pos) - return ''; - - const pos1 = new THREE.Vector3().add(this.#camera0pos).sub(this.#lookat), - pos2 = new THREE.Vector3().add(this.#camera.position).sub(this.#lookat), - zoom = Math.min(10000, Math.max(1, this.ctrl.zoom * pos2.length() / pos1.length() * 100)); - - pos1.normalize(); - pos2.normalize(); - - const quat = new THREE.Quaternion(), euler = new THREE.Euler(); - - quat.setFromUnitVectors(pos1, pos2); - euler.setFromQuaternion(quat, 'YZX'); - - let roty = euler.y / Math.PI * 180, - rotz = euler.z / Math.PI * 180; - - if (roty < 0) - roty += 360; - if (rotz < 0) - rotz += 360; - return `${kind}roty${roty.toFixed(0)},rotz${rotz.toFixed(0)},zoom${zoom.toFixed(0)}`; - } - - /** @summary Calculates current zoom factor */ - calculateZoom() { - if (this.#camera0pos && this.#camera && this.#lookat) { - const pos1 = new THREE.Vector3().add(this.#camera0pos).sub(this.#lookat), - pos2 = new THREE.Vector3().add(this.#camera.position).sub(this.#lookat); - return pos2.length() / pos1.length(); - } - - return 0; - } - - /** @summary Place camera to default position, - * @param {boolean|string} arg - true forces camera readjustment, 'first' is called when suppose to be first after complete drawing - * @param {boolean} keep_zoom - tries to keep zooming factor of the camera */ - adjustCameraPosition(arg, keep_zoom) { - if (!this.#toplevel || this.#superimpose) - return; - - const force = (arg === true), - first_time = (arg === 'first') || force, - only_set = (arg === 'only_set'), - box = this.getGeomBoundingBox(); - - // let box2 = new THREE.Box3().makeEmpty(); - // box2.expandByObject(this.#toplevel, true); - // console.log('min,max', box.min.x, box.max.x, box.min.y, box.max.y, box.min.z, box.max.z ); - - // if detect of coordinates fails - ignore - if (!Number.isFinite(box.min.x)) { - console.log('FAILS to get geometry bounding box'); - return; - } - - const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - midx = (box.max.x + box.min.x) / 2, - midy = (box.max.y + box.min.y) / 2, - midz = (box.max.z + box.min.z) / 2, - more = this.ctrl._axis || (this.ctrl.camera_overlay === 'bar') ? 0.2 : 0.1; - - if (this.#scene_size && !force) { - const test = (v1, v2, scale) => { - if (!scale) - scale = Math.abs((v1 + v2) / 2); - return scale <= 1e-20 ? true : Math.abs(v2 - v1) / scale > 0.01; - }, d = this.#scene_size; - if (!test(sizex, d.sizex) && !test(sizey, d.sizey) && !test(sizez, d.sizez) && - !test(midx, d.midx, d.sizex) && !test(midy, d.midy, d.sizey) && !test(midz, d.midz, d.sizez)) { - if (this.ctrl.select_in_view) - this.startDrawGeometry(); - return; - } - } - - this.#scene_size = { sizex, sizey, sizez, midx, midy, midz }; - - this.#overall_size = 2 * Math.max(sizex, sizey, sizez); - - this.#camera.near = this.#overall_size / 350; - this.#camera.far = this.#overall_size * 100; - this.#fog.near = this.#overall_size * 0.5; - this.#fog.far = this.#overall_size * 5; - - if (first_time) { - for (let naxis = 0; naxis < 3; ++naxis) { - const cc = this.ctrl.clip[naxis]; - cc.min = box.min[cc.name]; - cc.max = box.max[cc.name]; - const sz = cc.max - cc.min; - cc.max += sz * 0.01; - cc.min -= sz * 0.01; - if (sz > 100) - cc.step = 0.1; - else if (sz > 1) - cc.step = 0.001; - else - cc.step = undefined; - - if (!cc.value) - cc.value = (cc.min + cc.max) / 2; - else if (cc.value < cc.min) - cc.value = cc.min; - else if (cc.value > cc.max) - cc.value = cc.max; - } - } - - let k = 2 * this.ctrl.zoom; - const max_all = Math.max(sizex, sizey, sizez), - sign = this.ctrl.camera_kind.indexOf('N') > 0 ? -1 : 1; - - this.#lookat = new THREE.Vector3(midx, midy, midz); - this.#camera0pos = new THREE.Vector3(-2 * max_all, 0, 0); // virtual 0 position, where rotation starts - - this.#camera.updateMatrixWorld(); - this.#camera.updateProjectionMatrix(); - - if ((this.ctrl.rotatey || this.ctrl.rotatez) && this.ctrl.can_rotate) { - const prev_zoom = this.calculateZoom(); - if (keep_zoom && prev_zoom) - k = 2 * prev_zoom; - - const euler = new THREE.Euler(0, this.ctrl.rotatey / 180 * Math.PI, this.ctrl.rotatez / 180 * Math.PI, 'YZX'); - - this.#camera.position.set(-k * max_all, 0, 0); - this.#camera.position.applyEuler(euler); - this.#camera.position.add(new THREE.Vector3(midx, midy, midz)); - - if (keep_zoom && prev_zoom) { - const actual_zoom = this.calculateZoom(); - k *= prev_zoom / actual_zoom; - - this.#camera.position.set(-k * max_all, 0, 0); - this.#camera.position.applyEuler(euler); - this.#camera.position.add(new THREE.Vector3(midx, midy, midz)); - } - } else if (this.ctrl.camx !== undefined && this.ctrl.camy !== undefined && this.ctrl.camz !== undefined) { - this.#camera.position.set(this.ctrl.camx, this.ctrl.camy, this.ctrl.camz); - this.#lookat.set(this.ctrl.camlx || 0, this.ctrl.camly || 0, this.ctrl.camlz || 0); - this.ctrl.camx = this.ctrl.camy = this.ctrl.camz = this.ctrl.camlx = this.ctrl.camly = this.ctrl.camlz = undefined; - } else if ((this.ctrl.camera_kind === 'orthoXOY') || (this.ctrl.camera_kind === 'orthoXNOY')) { - this.#camera.up.set(0, 1, 0); - this.#camera.position.set(sign < 0 ? midx * 2 : 0, 0, midz + sign * sizez * 2); - this.#lookat.set(sign < 0 ? midx * 2 : 0, 0, midz); - this.#camera.left = box.min.x - more * sizex; - this.#camera.right = box.max.x + more * sizex; - this.#camera.top = box.max.y + more * sizey; - this.#camera.bottom = box.min.y - more * sizey; - if (!keep_zoom) - this.#camera.zoom = this.ctrl.zoom || 1; - this.#camera.orthoSign = sign; - this.#camera.orthoZ = [midz, sizez / 2]; - } else if ((this.ctrl.camera_kind === 'orthoXOZ') || (this.ctrl.camera_kind === 'orthoXNOZ')) { - this.#camera.up.set(0, 0, 1); - this.#camera.position.set(sign < 0 ? midx * 2 : 0, midy - sign * sizey * 2, 0); - this.#lookat.set(sign < 0 ? midx * 2 : 0, midy, 0); - this.#camera.left = box.min.x - more * sizex; - this.#camera.right = box.max.x + more * sizex; - this.#camera.top = box.max.z + more * sizez; - this.#camera.bottom = box.min.z - more * sizez; - if (!keep_zoom) - this.#camera.zoom = this.ctrl.zoom || 1; - this.#camera.orthoIndicies = [0, 2, 1]; - this.#camera.orthoRotation = geom => geom.rotateX(Math.PI / 2); - this.#camera.orthoSign = sign; - this.#camera.orthoZ = [midy, -sizey / 2]; - } else if ((this.ctrl.camera_kind === 'orthoZOY') || (this.ctrl.camera_kind === 'orthoZNOY')) { - this.#camera.up.set(0, 1, 0); - this.#camera.position.set(midx - sign * sizex * 2, 0, sign < 0 ? midz * 2 : 0); - this.#lookat.set(midx, 0, sign < 0 ? midz * 2 : 0); - this.#camera.left = box.min.z - more * sizez; - this.#camera.right = box.max.z + more * sizez; - this.#camera.top = box.max.y + more * sizey; - this.#camera.bottom = box.min.y - more * sizey; - if (!keep_zoom) - this.#camera.zoom = this.ctrl.zoom || 1; - this.#camera.orthoIndicies = [2, 1, 0]; - this.#camera.orthoRotation = geom => geom.rotateY(-Math.PI / 2); - this.#camera.orthoSign = sign; - this.#camera.orthoZ = [midx, -sizex / 2]; - } else if ((this.ctrl.camera_kind === 'orthoZOX') || (this.ctrl.camera_kind === 'orthoZNOX')) { - this.#camera.up.set(1, 0, 0); - this.#camera.position.set(0, midy - sign * sizey * 2, sign > 0 ? midz * 2 : 0); - this.#lookat.set(0, midy, sign > 0 ? midz * 2 : 0); - this.#camera.left = box.min.z - more * sizez; - this.#camera.right = box.max.z + more * sizez; - this.#camera.top = box.max.x + more * sizex; - this.#camera.bottom = box.min.x - more * sizex; - if (!keep_zoom) - this.#camera.zoom = this.ctrl.zoom || 1; - this.#camera.orthoIndicies = [2, 0, 1]; - this.#camera.orthoRotation = geom => geom.rotateX(Math.PI / 2).rotateY(Math.PI / 2); - this.#camera.orthoSign = sign; - this.#camera.orthoZ = [midy, -sizey / 2]; - } else if (this.ctrl.project) { - switch (this.ctrl.project) { - case 'x': this.#camera.position.set(k * 1.5 * Math.max(sizey, sizez), 0, 0); break; - case 'y': this.#camera.position.set(0, k * 1.5 * Math.max(sizex, sizez), 0); break; - case 'z': this.#camera.position.set(0, 0, k * 1.5 * Math.max(sizex, sizey)); break; - } - } else if (this.ctrl.camera_kind === 'perspXOZ') { - this.#camera.up.set(0, 1, 0); - this.#camera.position.set(midx - 3 * max_all, midy, midz); - } else if (this.ctrl.camera_kind === 'perspYOZ') { - this.#camera.up.set(1, 0, 0); - this.#camera.position.set(midx, midy - 3 * max_all, midz); - } else if (this.ctrl.camera_kind === 'perspXOY') { - this.#camera.up.set(0, 0, 1); - this.#camera.position.set(midx - 3 * max_all, midy, midz); - } else if (this.ctrl._yup) { - this.#camera.up.set(0, 1, 0); - this.#camera.position.set(midx - k * Math.max(sizex, sizez), midy + k * sizey, midz - k * Math.max(sizex, sizez)); - } else { - this.#camera.up.set(0, 0, 1); - this.#camera.position.set(midx - k * Math.max(sizex, sizey), midy - k * Math.max(sizex, sizey), midz + k * sizez); - } - - if (this.#camera.isOrthographicCamera && this.isOrthoCamera() && this.#scene_width && this.#scene_height) { - const screen_ratio = this.#scene_width / this.#scene_height, - szx = this.#camera.right - this.#camera.left, szy = this.#camera.top - this.#camera.bottom; - - if (screen_ratio > szx / szy) { - // screen wider than actual geometry - const m = (this.#camera.right + this.#camera.left) / 2; - this.#camera.left = m - szy * screen_ratio / 2; - this.#camera.right = m + szy * screen_ratio / 2; - } else { - // screen higher than actual geometry - const m = (this.#camera.top + this.#camera.bottom) / 2; - this.#camera.top = m + szx / screen_ratio / 2; - this.#camera.bottom = m - szx / screen_ratio / 2; - } - } - - this.#camera.lookAt(this.#lookat); - this.#camera.updateProjectionMatrix(); - - this.changedLight(box); - - if (this.#controls) { - this.#controls.target.copy(this.#lookat); - if (!only_set) - this.#controls.update(); - } - - // recheck which elements to draw - if (this.ctrl.select_in_view && !only_set) - this.startDrawGeometry(); - } - - /** @summary Specifies camera position as rotation around geometry center */ - setCameraPosition(rotatey, rotatez, zoom) { - if (!this.ctrl) - return; - this.ctrl.rotatey = rotatey || 0; - this.ctrl.rotatez = rotatez || 0; - let preserve_zoom = false; - if (zoom && Number.isFinite(zoom)) - this.ctrl.zoom = zoom; - else - preserve_zoom = true; - - this.adjustCameraPosition(false, preserve_zoom); - } - - /** @summary Specifies camera position and point to which it looks to - @desc Both specified in absolute coordinates */ - setCameraPositionAndLook(camx, camy, camz, lookx, looky, lookz) { - if (!this.ctrl) - return; - this.ctrl.camx = camx; - this.ctrl.camy = camy; - this.ctrl.camz = camz; - this.ctrl.camlx = lookx; - this.ctrl.camly = looky; - this.ctrl.camlz = lookz; - this.adjustCameraPosition(false); - } - - /** @summary focus on item */ - focusOnItem(itemname) { - if (!itemname || !this.#clones) - return; - - const stack = this.#clones.findStackByName(itemname); - - if (stack) - this.focusCamera(this.#clones.resolveStack(stack, true), false); - } - - /** @summary focus camera on specified position */ - focusCamera(focus, autoClip) { - if (this.ctrl.project || this.isOrthoCamera()) { - this.adjustCameraPosition(true); - return this.render3D(); - } - - let box = new THREE.Box3(); - if (focus === undefined) - box = this.getGeomBoundingBox(); - else if (focus instanceof THREE.Mesh) - box.setFromObject(focus); - else { - const center = new THREE.Vector3().setFromMatrixPosition(focus.matrix), - node = focus.node, - halfDelta = new THREE.Vector3(node.fDX, node.fDY, node.fDZ).multiplyScalar(0.5); - box.min = center.clone().sub(halfDelta); - box.max = center.clone().add(halfDelta); - } - - const sizex = box.max.x - box.min.x, - sizey = box.max.y - box.min.y, - sizez = box.max.z - box.min.z, - midx = (box.max.x + box.min.x) / 2, - midy = (box.max.y + box.min.y) / 2, - midz = (box.max.z + box.min.z) / 2; - - let position, frames = 50, step = 0; - if (this.ctrl._yup) - position = new THREE.Vector3(midx - 2 * Math.max(sizex, sizez), midy + 2 * sizey, midz - 2 * Math.max(sizex, sizez)); - else - position = new THREE.Vector3(midx - 2 * Math.max(sizex, sizey), midy - 2 * Math.max(sizex, sizey), midz + 2 * sizez); - - const target = new THREE.Vector3(midx, midy, midz), - oldTarget = this.#controls.target, - // Amount to change camera position at each step - posIncrement = position.sub(this.#camera.position).divideScalar(frames), - // Amount to change 'lookAt' so it will end pointed at target - targetIncrement = target.sub(oldTarget).divideScalar(frames); - - autoClip = autoClip && this.#webgl; - - // Automatic Clipping - if (autoClip) { - for (let axis = 0; axis < 3; ++axis) { - const cc = this.ctrl.clip[axis]; - if (!cc.enabled) { - cc.value = cc.min; - cc.enabled = true; - } - cc.inc = ((cc.min + cc.max) / 2 - cc.value) / frames; - } - this.updateClipping(); - } - - this.#animating = true; - - const animate = () => { - if (this.#animating === undefined) - return; - - if (this.#animating) - requestAnimationFrame(animate); - else if (!this.#geom_viewer) - this.startDrawGeometry(); - const smoothFactor = -Math.cos((2.0 * Math.PI * step) / frames) + 1.0; - this.#camera.position.add(posIncrement.clone().multiplyScalar(smoothFactor)); - oldTarget.add(targetIncrement.clone().multiplyScalar(smoothFactor)); - this.#lookat = oldTarget; - this.#camera.lookAt(this.#lookat); - this.#camera.updateProjectionMatrix(); - - const tm1 = new Date().getTime(); - if (autoClip) { - for (let axis = 0; axis < 3; ++axis) - this.ctrl.clip[axis].value += this.ctrl.clip[axis].inc * smoothFactor; - this.updateClipping(); - } else - this.render3D(0); - - const tm2 = new Date().getTime(); - if ((step === 0) && (tm2 - tm1 > 200)) - frames = 20; - step++; - this.#animating = step < frames; - }; - - animate(); - - // this.#controls.update(); - } - - /** @summary activate auto rotate */ - autorotate(speed) { - const rotSpeed = (speed === undefined) ? 2.0 : speed; - let last = new Date(); - - const animate = () => { - if (!this.#renderer || !this.ctrl) - return; - - const current = new Date(); - - if (this.ctrl.rotate) - requestAnimationFrame(animate); - - if (this.#controls) { - this.#controls.autoRotate = this.ctrl.rotate; - this.#controls.autoRotateSpeed = rotSpeed * (current.getTime() - last.getTime()) / 16.6666; - this.#controls.update(); - } - last = new Date(); - this.render3D(0); - }; - - if (this.#webgl) - animate(); - } - - /** @summary called at the end of scene drawing */ - completeScene() {} - - /** @summary Drawing with 'count' option - * @desc Scans hierarchy and check for unique nodes - * @return {Promise} with object drawing ready */ - async drawCount(unqievis, clonetm) { - const makeTime = tm => (this.isBatchMode() ? 'anytime' : tm.toString()) + ' ms', - - res = ['Unique nodes: ' + this.#clones.nodes.length, - 'Unique visible: ' + unqievis, - 'Time to clone: ' + makeTime(clonetm)]; - - // need to fill cached value line numvischld - this.#clones.scanVisible(); - - let nshapes = 0; - const arg = { - clones: this.#clones, - cnt: [], - func(node) { - if (this.cnt[this.last] === undefined) - this.cnt[this.last] = 1; - else - this.cnt[this.last]++; - - nshapes += countNumShapes(this.clones.getNodeShape(node.id)); - return true; - } - }; - - let tm1 = new Date().getTime(), - numvis = this.#clones.scanVisible(arg), - tm2 = new Date().getTime(); - - res.push(`Total visible nodes: ${numvis}`, `Total shapes: ${nshapes}`); - - for (let lvl = 0; lvl < arg.cnt.length; ++lvl) { - if (arg.cnt[lvl] !== undefined) - res.push(` lvl${lvl}: ${arg.cnt[lvl]}`); - } - - res.push(`Time to scan: ${makeTime(tm2 - tm1)}`, '', 'Check timing for matrix calculations ...'); - - const elem = this.selectDom().style('overflow', 'auto'); - - if (this.isBatchMode()) - elem.property('_json_object_', res); - else - res.forEach(str => elem.append('p').text(str)); - - return postponePromise(() => { - arg.domatrix = true; - tm1 = new Date().getTime(); - numvis = this.#clones.scanVisible(arg); - tm2 = new Date().getTime(); - - const last_str = `Time to scan with matrix: ${makeTime(tm2 - tm1)}`; - if (this.isBatchMode()) - res.push(last_str); - else - elem.append('p').text(last_str); - return this; - }, 100); - } - - /** @summary Handle drop operation - * @desc opt parameter can include function name like 'func_name$opt' - * Such function should be possible to find via {@link findFunction} - * Function has to return Promise with objects to draw on geometry - * By default function with name 'extract_geo_tracks' is checked - * @return {Promise} handling of drop operation */ - async performDrop(obj, itemname, hitem, opt) { - if (obj?.$kind === clTTree) { - // drop tree means function call which must extract tracks from provided tree - - let funcname = 'extract_geo_tracks'; - - if (opt && opt.indexOf('$') > 0) { - funcname = opt.slice(0, opt.indexOf('$')); - opt = opt.slice(opt.indexOf('$') + 1); - } - - const func = findFunction(funcname); - - if (!func) - return Promise.reject(Error(`Function ${funcname} not found`)); - - return func(obj, opt).then(tracks => { - if (!tracks) - return this; - - // FIXME: probably tracks should be remembered? - return this.drawExtras(tracks, '', false).then(() => { - this.updateClipping(true); - return this.render3D(100); - }); - }); - } - - return this.drawExtras(obj, itemname).then(is_any => { - if (!is_any) - return this; - - if (hitem) - hitem._painter = this; // set for the browser item back pointer - - return this.render3D(100); - }); - } - - /** @summary function called when mouse is going over the item in the browser */ - mouseOverHierarchy(on, itemname, hitem) { - if (!this.ctrl) - return; // protection for cleaned-up painter - - const obj = hitem._obj; - - // let's highlight tracks and hits only for the time being - if (!obj || (obj._typename !== clTEveTrack && obj._typename !== clTEvePointSet && obj._typename !== clTPolyMarker3D)) - return; - - this.highlightMesh(null, 0x00ff00, on ? obj : null); - } - - /** @summary clear extra drawn objects like tracks or hits */ - clearExtras() { - this.getExtrasContainer('delete'); - this.#extra_objects = undefined; // workaround, later will be normal function - this.render3D(); - } - - /** @summary Register extra objects like tracks or hits - * @desc Rendered after main geometry volumes are created - * Check if object already exists to prevent duplication */ - addExtra(obj, itemname) { - if (!this.#extra_objects) - this.#extra_objects = create(clTList); - - if (this.#extra_objects.arr.indexOf(obj) >= 0) - return false; - - this.#extra_objects.Add(obj, itemname); - - delete obj.$hidden_via_menu; // remove previous hidden property - - return true; - } - - /** @summary manipulate visibility of extra objects, used for HierarchyPainter - * @private */ - extraObjectVisible(hpainter, hitem, toggle) { - if (!this.#extra_objects) - return; - - const itemname = hpainter.itemFullName(hitem); - let indx = this.#extra_objects.opt.indexOf(itemname); - - if ((indx < 0) && hitem._obj) { - indx = this.#extra_objects.arr.indexOf(hitem._obj); - // workaround - if object found, replace its name - if (indx >= 0) - this.#extra_objects.opt[indx] = itemname; - } - - if (indx < 0) - return; - - const obj = this.#extra_objects.arr[indx]; - let res = Boolean(obj.$hidden_via_menu); - - if (toggle) { - obj.$hidden_via_menu = res; - res = !res; - - let mesh = null; - // either found painted object or just draw once again - this.#toplevel.traverse(node => { - if (node.geo_object === obj) - mesh = node; - }); - - if (mesh) { - mesh.visible = res; - this.render3D(); - } else if (res) { - this.drawExtras(obj, '', false).then(() => { - this.updateClipping(true); - this.render3D(); - }); - } - } - - return res; - } - - /** @summary Draw extra object like tracks - * @return {Promise} for ready */ - async drawExtras(obj, itemname, add_objects, not_wait_render) { - // if object was hidden via menu, do not redraw it with next draw call - if (!obj?._typename || (!add_objects && obj.$hidden_via_menu)) - return false; - - let do_render = false; - if (add_objects === undefined) { - add_objects = true; - do_render = true; - } else if (not_wait_render) - do_render = true; - - - let promise = false; - - if ((obj._typename === clTList) || (obj._typename === clTObjArray)) { - if (!obj.arr) - return false; - const parr = []; - for (let n = 0; n < obj.arr.length; ++n) { - const sobj = obj.arr[n]; - let sname = obj.opt ? obj.opt[n] : ''; - if (!sname) - sname = (itemname || '<prnt>') + `/[${n}]`; - parr.push(this.drawExtras(sobj, sname, add_objects)); - } - promise = Promise.all(parr).then(ress => ress.indexOf(true) >= 0); - } else if (obj._typename === 'Mesh') { - // adding mesh as is - this.addToExtrasContainer(obj); - promise = Promise.resolve(true); - } else if (obj._typename === 'TGeoTrack') { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawGeoTrack(obj, itemname); - } else if (obj._typename === clTPolyLine3D) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawPolyLine(obj, itemname); - } else if ((obj._typename === clTEveTrack) || (obj._typename === `${nsREX}REveTrack`)) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawEveTrack(obj, itemname); - } else if ((obj._typename === clTEvePointSet) || (obj._typename === `${nsREX}REvePointSet`) || (obj._typename === clTPolyMarker3D)) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawHit(obj, itemname); - } else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { - if (!add_objects || this.addExtra(obj, itemname)) - promise = this.drawExtraShape(obj, itemname); - } - - return getPromise(promise).then(is_any => { - if (!is_any || !do_render) - return is_any; - - this.updateClipping(true); - - const pr = this.render3D(100, not_wait_render ? 'nopromise' : false); - - return not_wait_render ? this : pr; - }); - } - - /** @summary returns container for extra objects */ - getExtrasContainer(action, name) { - if (!this.#toplevel) - return null; - - if (!name) - name = 'tracks'; - - let extras = null; - const lst = []; - for (let n = 0; n < this.#toplevel.children.length; ++n) { - const chld = this.#toplevel.children[n]; - if (!chld._extras) - continue; - if (action === 'collect') { - lst.push(chld); - continue; - } - if (chld._extras === name) { - extras = chld; - break; - } - } - - if (action === 'collect') { - for (let k = 0; k < lst.length; ++k) - this.#toplevel.remove(lst[k]); - return lst; - } - - if (action === 'delete') { - if (extras) - this.#toplevel.remove(extras); - disposeThreejsObject(extras); - return null; - } - - if ((action !== 'get') && !extras) { - extras = new THREE.Object3D(); - extras._extras = name; - this.#toplevel.add(extras); - } - - return extras; - } - - /** @summary add object to extras container. - * @desc If fail, dispose object */ - addToExtrasContainer(obj, name) { - const container = this.getExtrasContainer('', name); - if (container) - container.add(obj); - else { - console.warn('Fail to add object to extras'); - disposeThreejsObject(obj); - } - } - - /** @summary drawing TGeoTrack */ - drawGeoTrack(track, itemname) { - if (!track?.fNpoints) - return false; - - const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), // line width not supported on windows - color = getColor(track.fLineColor) || '#ff00ff', - npoints = Math.round(track.fNpoints / 4), // each track point has [x,y,z,t] coordinate - buf = new Float32Array((npoints - 1) * 6), - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'); - - for (let k = 0, pos = 0; k < npoints - 1; ++k, pos += 6) { - buf[pos] = projx ? projv : track.fPoints[k * 4]; - buf[pos + 1] = projy ? projv : track.fPoints[k * 4 + 1]; - buf[pos + 2] = projz ? projv : track.fPoints[k * 4 + 2]; - buf[pos + 3] = projx ? projv : track.fPoints[k * 4 + 4]; - buf[pos + 4] = projy ? projv : track.fPoints[k * 4 + 5]; - buf[pos + 5] = projz ? projv : track.fPoints[k * 4 + 6]; - } - - const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), - line = createLineSegments(buf, lineMaterial); - - line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front - line.geo_name = itemname; - line.geo_object = track; - line.hightlightWidthScale = 2; - - if (itemname?.indexOf('<prnt>/Tracks') === 0) - line.main_track = true; - - this.addToExtrasContainer(line); - - return true; - } - - /** @summary drawing TPolyLine3D */ - drawPolyLine(line, itemname) { - if (!line) - return false; - - const linewidth = browser.isWin ? 1 : (line.fLineWidth || 1), - color = getColor(line.fLineColor) || '#ff00ff', - npoints = line.fN, - fP = line.fP, - buf = new Float32Array((npoints - 1) * 6), - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'); - - for (let k = 0, pos = 0; k < npoints - 1; ++k, pos += 6) { - buf[pos] = projx ? projv : fP[k * 3]; - buf[pos + 1] = projy ? projv : fP[k * 3 + 1]; - buf[pos + 2] = projz ? projv : fP[k * 3 + 2]; - buf[pos + 3] = projx ? projv : fP[k * 3 + 3]; - buf[pos + 4] = projy ? projv : fP[k * 3 + 4]; - buf[pos + 5] = projz ? projv : fP[k * 3 + 5]; - } - - const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), - line3d = createLineSegments(buf, lineMaterial); - - line3d.defaultOrder = line3d.renderOrder = 1000000; // to bring line to the front - line3d.geo_name = itemname; - line3d.geo_object = line; - line3d.hightlightWidthScale = 2; - - this.addToExtrasContainer(line3d); - - return true; - } - - /** @summary Drawing TEveTrack */ - drawEveTrack(track, itemname) { - if (!track || (track.fN <= 0)) - return false; - - const linewidth = browser.isWin ? 1 : (track.fLineWidth || 1), - color = getColor(track.fLineColor) || '#ff00ff', - buf = new Float32Array((track.fN - 1) * 6), - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'); - - for (let k = 0, pos = 0; k < track.fN - 1; ++k, pos += 6) { - buf[pos] = projx ? projv : track.fP[k * 3]; - buf[pos + 1] = projy ? projv : track.fP[k * 3 + 1]; - buf[pos + 2] = projz ? projv : track.fP[k * 3 + 2]; - buf[pos + 3] = projx ? projv : track.fP[k * 3 + 3]; - buf[pos + 4] = projy ? projv : track.fP[k * 3 + 4]; - buf[pos + 5] = projz ? projv : track.fP[k * 3 + 5]; - } - - const lineMaterial = new THREE.LineBasicMaterial({ color, linewidth }), - line = createLineSegments(buf, lineMaterial); - - line.defaultOrder = line.renderOrder = 1000000; // to bring line to the front - line.geo_name = itemname; - line.geo_object = track; - line.hightlightWidthScale = 2; - - this.addToExtrasContainer(line); - - return true; - } - - /** @summary Drawing different hits types like TPolyMarker3D */ - async drawHit(hit, itemname) { - if (!hit || !hit.fN || (hit.fN < 0)) - return false; - - // make hit size scaling factor of overall geometry size - // otherwise it is not possible to correctly see hits at all - const nhits = hit.fN, - projv = this.ctrl.projectPos, - projx = (this.ctrl.project === 'x'), - projy = (this.ctrl.project === 'y'), - projz = (this.ctrl.project === 'z'), - hit_scale = Math.max(hit.fMarkerSize * this.getOverallSize() * (this.getOptions().dummy ? 0.015 : 0.005), 0.2), - pnts = new PointsCreator(nhits, this.#webgl, hit_scale); - - for (let i = 0; i < nhits; i++) { - pnts.addPoint(projx ? projv : hit.fP[i * 3], - projy ? projv : hit.fP[i * 3 + 1], - projz ? projv : hit.fP[i * 3 + 2]); - } - - return pnts.createPoints({ color: getColor(hit.fMarkerColor) || '#0000ff', style: hit.fMarkerStyle }).then(mesh => { - mesh.defaultOrder = mesh.renderOrder = 1000000; // to bring points to the front - mesh.highlightScale = 2; - mesh.geo_name = itemname; - mesh.geo_object = hit; - this.addToExtrasContainer(mesh); - return true; // indicate that rendering should be done - }); - } - - /** @summary Draw extra shape on the geometry */ - drawExtraShape(obj, itemname) { - // eslint-disable-next-line no-use-before-define - const mesh = build(obj); - if (!mesh) - return false; - - mesh.geo_name = itemname; - mesh.geo_object = obj; - - this.addToExtrasContainer(mesh); - return true; - } - - /** @summary Search for specified node - * @private */ - findNodeWithVolume(name, action, prnt, itemname, volumes) { - let first_level = false, res = null; - - if (!prnt) { - prnt = this.getGeometry(); - if (!prnt && (getNodeKind(prnt) !== kindGeo)) - return null; - itemname = this.#geo_manager ? prnt.fName : ''; - first_level = true; - volumes = []; - } else { - if (itemname) - itemname += '/'; - itemname += prnt.fName; - } - - if (!prnt.fVolume || prnt.fVolume._searched) - return null; - - if (name.test(prnt.fVolume.fName)) { - res = action({ node: prnt, item: itemname }); - if (res) - return res; - } - - prnt.fVolume._searched = true; - volumes.push(prnt.fVolume); - - if (prnt.fVolume.fNodes) { - for (let n = 0, len = prnt.fVolume.fNodes.arr.length; n < len; ++n) { - res = this.findNodeWithVolume(name, action, prnt.fVolume.fNodes.arr[n], itemname, volumes); - if (res) - break; - } - } - - if (first_level) { - for (let n = 0, len = volumes.length; n < len; ++n) - delete volumes[n]._searched; - } - - return res; - } - - /** @summary Search for created shape for nodeid - * @desc Used in ROOT geometry painter */ - findNodeShape(nodeid) { - if ((nodeid !== undefined) && this.#draw_nodes) { - for (let k = 0; k < this.#draw_nodes.length; ++k) { - const item = this.#draw_nodes[k]; - if ((item.nodeid === nodeid) && item.server_shape) - return item.server_shape; - } - } - } - - /** @summary Process script option - load and execute some gGeoManager-related calls */ - async loadMacro(script_name) { - const result = { obj: this.getGeometry(), prefix: '' }; - - if (this.#geo_manager) - result.prefix = result.obj.fName; - - if (!script_name || (script_name.length < 3) || (getNodeKind(result.obj) !== kindGeo)) - return result; - - const mgr = { - GetVolume: name => { - const regexp = new RegExp('^' + name + '$'), - currnode = this.findNodeWithVolume(regexp, arg => arg); - - if (!currnode) - console.log(`Did not found ${name} volume`); - - // return proxy object with several methods, typically used in ROOT geom scripts - return { - found: currnode, - fVolume: currnode?.node?.fVolume, - InvisibleAll(flag) { - setInvisibleAll(this.fVolume, flag); - }, - Draw() { - if (!this.found || !this.fVolume) - return; - result.obj = this.found.node; - result.prefix = this.found.item; - console.log(`Select volume for drawing ${this.fVolume.fName} ${result.prefix}`); - }, - SetTransparency(lvl) { - if (this.fVolume?.fMedium?.fMaterial) - this.fVolume.fMedium.fMaterial.fFillStyle = 3000 + lvl; - }, - SetLineColor(col) { - if (this.fVolume) - this.fVolume.fLineColor = col; - } - }; - }, - - DefaultColors: () => { - this.ctrl.dflt_colors = true; - }, - - SetMaxVisNodes: limit => { - if (!this.ctrl.maxnodes) - this.ctrl.maxnodes = parseInt(limit) || 0; - }, - - SetVisLevel: limit => { - if (!this.ctrl.vislevel) - this.ctrl.vislevel = parseInt(limit) || 0; - } - }; - - showProgress(`Loading macro ${script_name}`); - - return httpRequest(script_name, 'text').then(script => { - const lines = script.split('\n'); - let indx = 0; - - while (indx < lines.length) { - let line = lines[indx++].trim(); - - if (line.indexOf('//') === 0) - continue; - - if (line.indexOf('gGeoManager') < 0) - continue; - line = line.replace('->GetVolume', '.GetVolume'); - line = line.replace('->InvisibleAll', '.InvisibleAll'); - line = line.replace('->SetMaxVisNodes', '.SetMaxVisNodes'); - line = line.replace('->DefaultColors', '.DefaultColors'); - line = line.replace('->Draw', '.Draw'); - line = line.replace('->SetTransparency', '.SetTransparency'); - line = line.replace('->SetLineColor', '.SetLineColor'); - line = line.replace('->SetVisLevel', '.SetVisLevel'); - if (line.indexOf('->') >= 0) - continue; - - try { - const func = new Function('gGeoManager', line); - func(mgr); - } catch { - console.error(`Problem by processing ${line}`); - } - } - - return result; - }).catch(() => { - console.error(`Fail to load ${script_name}`); - return result; - }); - } - - /** @summary Extract shapes from draw message of geometry painter - * @desc For the moment used in batch production */ - extractRawShapes(draw_msg, recreate) { - let nodes = null, old_gradpersegm = 0; - - // array for descriptors for each node - // if array too large (>1M), use JS object while only ~1K nodes are expected to be used - if (recreate) - nodes = (draw_msg.numnodes > 1e6) ? { length: draw_msg.numnodes } : new Array(draw_msg.numnodes); // array for all nodes - - draw_msg.nodes.forEach(node => { - node = ClonedNodes.formatServerElement(node); - if (nodes) - nodes[node.id] = node; - else - this.#clones.updateNode(node); - }); - - if (recreate) { - this.assignClones(new ClonedNodes(null, nodes), true); - this.#clones.name_prefix = this.#clones.getNodeName(0); - this.#clones.setConfig(this.ctrl); - - // normally only need when making selection, not used in geo viewer - // this.geo#clones.setMaxVisNodes(draw_msg.maxvisnodes); - // this.geo#clones.setVisLevel(draw_msg.vislevel); - // TODO: provide from server - this.#clones.maxdepth = 20; - } - - let nsegm = 0; - if (draw_msg.cfg) - nsegm = draw_msg.cfg.nsegm; - - if (nsegm) { - old_gradpersegm = geoCfg('GradPerSegm'); - geoCfg('GradPerSegm', 360 / Math.max(nsegm, 6)); - } - - for (let cnt = 0; cnt < draw_msg.visibles.length; ++cnt) { - const item = draw_msg.visibles[cnt], rd = item.ri; - - // entry may be provided without shape - it is ok - if (rd) - item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); - } - - if (old_gradpersegm) - geoCfg('GradPerSegm', old_gradpersegm); - - return true; - } - - /** @summary Prepare drawings - * @desc Return value used as promise for painter */ - async prepareObjectDraw(draw_obj, name_prefix) { - // if did cleanup - ignore all kind of activity - if (this.#did_cleanup) - return null; - - if (name_prefix === '__geom_viewer_append__') { - this.#new_append_nodes = draw_obj; - this.ctrl.use_worker = 0; - this.setGeomViewer(true); // indicate that working with geom viewer - } else if ((name_prefix === '__geom_viewer_selection__') && this.#clones) { - // these are selection done from geom viewer - this.#new_draw_nodes = draw_obj; - this.ctrl.use_worker = 0; - this.setGeomViewer(true); // indicate that working with geom viewer - } else if (this.getCentral()) - this.assignClones(this.getCentral().getClones(), false); - else if (!draw_obj) - this.assignClones(undefined, undefined); - else { - this.#start_drawing_time = new Date().getTime(); - this.assignClones(new ClonedNodes(draw_obj), true); - let lvl = this.ctrl.vislevel, maxnodes = this.ctrl.maxnodes; - if (this.#geo_manager) { - if (!lvl && this.#geo_manager.fVisLevel) - lvl = this.#geo_manager.fVisLevel; - if (!maxnodes) - maxnodes = this.#geo_manager.fMaxVisNodes; - } - - this.#clones.setVisLevel(lvl); - this.#clones.setMaxVisNodes(maxnodes, this.ctrl.more); - this.#clones.setConfig(this.ctrl); - - this.#clones.name_prefix = name_prefix; - - const hide_top_volume = Boolean(this.#geo_manager) && !this.ctrl.showtop; - let uniquevis = this.ctrl.no_screen ? 0 : this.#clones.markVisibles(true, false, hide_top_volume); - - if (uniquevis <= 0) - uniquevis = this.#clones.markVisibles(false, false, hide_top_volume); - else - uniquevis = this.#clones.markVisibles(true, true, hide_top_volume); // copy bits once and use normal visibility bits - - this.#clones.produceIdShifts(); - - const spent = new Date().getTime() - this.#start_drawing_time; - - if (!this.#scene && (settings.Debug || spent > 1000)) - console.log(`Creating clones ${this.#clones.nodes.length} takes ${spent} ms uniquevis ${uniquevis}`); - - if (this.ctrl._count) - return this.drawCount(uniquevis, spent); - } - - let promise = Promise.resolve(true); - - if (!this.#scene) { - this.#first_drawing = true; - - const pp = this.getPadPainter(); - - this.#on_pad = Boolean(pp); - - if (this.#on_pad) { - let size, render3d, fp; - promise = ensureTCanvas(this, '3d').then(() => { - if (pp.fillatt?.color) - this.ctrl.background = pp.fillatt.color; - fp = this.getFramePainter(); - - this.batch_mode = pp.isBatchMode(); - - render3d = getRender3DKind(undefined, this.batch_mode); - assign3DHandler(fp); - fp.mode3d = true; - - size = fp.getSizeFor3d(undefined, render3d); - - this.#fit_main_area = (size.can3d === -1); - - return this.createScene(size.width, size.height, render3d) - .then(dom => fp.add3dCanvas(size, dom, render3d === constants.Render3D.WebGL)); - }); - } else { - const dom = this.selectDom('origin'); - - this.batch_mode = isBatchMode() || (!dom.empty() && dom.property('_batch_mode')); - this.batch_format = dom.property('_batch_format'); - - const render3d = getRender3DKind(this.getOptions().Render3D, this.batch_mode); - - // activate worker - if ((this.ctrl.use_worker > 0) && !this.batch_mode) - this.startWorker(); - - assign3DHandler(this); - - const size = this.getSizeFor3d(undefined, render3d); - - this.#fit_main_area = (size.can3d === -1); - - promise = this.createScene(size.width, size.height, render3d) - .then(dom2 => this.add3dCanvas(size, dom2, this.#webgl)); - } - } - - return promise.then(() => { - // this is limit for the visible faces, number of volumes does not matter - if (this.#first_drawing && !this.ctrl.maxfaces) - this.ctrl.maxfaces = 200000 * this.ctrl.more; - - // set top painter only when first child exists - this.setAsMainPainter(); - - this.createToolbar(); - - // just draw extras and complete drawing if there are no main model - if (!this.#clones) - return this.completeDraw(); - - return new Promise(resolveFunc => { - this.#draw_resolveFuncs.push(resolveFunc); - this.showDrawInfo('Drawing geometry'); - this.startDrawGeometry(true); - }); - }); - } - - /** @summary methods show info when first geometry drawing is performed */ - showDrawInfo(msg) { - if (this.isBatchMode() || !this.#first_drawing || !this.#start_drawing_time) - return; - - const main = this.#renderer.domElement.parentNode; - if (!main) - return; - - let info = main.querySelector('.geo_info'); - - if (!msg) - info?.remove(); - else { - const spent = (new Date().getTime() - this.#start_drawing_time) * 1e-3; - if (!info) { - info = getDocument().createElement('p'); - info.setAttribute('class', 'geo_info'); - info.setAttribute('style', 'position: absolute; text-align: center; vertical-align: middle; top: 45%; left: 40%; color: red; font-size: 150%;'); - main.append(info); - } - info.innerText = `${msg}, ${spent.toFixed(1)}s`; - } - } - - /** @summary Reentrant method to perform geometry drawing step by step */ - continueDraw() { - // nothing to do - exit - if (this.isStage(stageInit)) - return; - - const tm0 = new Date().getTime(), - interval = this.#first_drawing ? 1000 : 200; - let now = tm0; - - while (true) { - const res = this.nextDrawAction(); - if (!res) - break; - - now = new Date().getTime(); - - // stop creation after 100 sec, render as is - if (now - this.#start_render_tm > 1e5) { - this.changeStage(stageInit, 'Abort build after 100s'); - break; - } - - // if we are that fast, do next action - if ((res === true) && (now - tm0 < interval)) - continue; - - if ((now - tm0 > interval) || (res === 1) || (res === 2)) { - showProgress(this.#drawing_log); - - this.showDrawInfo(this.#drawing_log); - - if (this.#first_drawing && this.#webgl && (this.ctrl.info.num_meshes - this.#last_render_meshes > 100) && (now - this.#last_render_tm > 2.5 * interval)) { - this.adjustCameraPosition(); - this.render3D(-1); - this.#last_render_meshes = this.ctrl.info.num_meshes; - } - if (res !== 2) - setTimeout(() => this.continueDraw(), (res === 1) ? 100 : 1); - - return; - } - } - - const take_time = now - this.#start_render_tm; - - if ((this.#first_drawing || this.#full_redrawing) && (settings.Debug || take_time > 1000)) - console.log(`Create tm = ${take_time} meshes ${this.ctrl.info.num_meshes} faces ${this.ctrl.info.num_faces}`); - - if (take_time > 300) { - showProgress('Rendering geometry'); - this.showDrawInfo('Rendering'); - return setTimeout(() => this.completeDraw(true), 10); - } - - this.completeDraw(true); - } - - /** @summary Checks camera position and recalculate rendering order if needed - * @param {boolean} [force] - if specified, forces calculations of render order */ - testCameraPosition(force) { - this.#camera.updateMatrixWorld(); - - this.drawOverlay(); - - const origin = this.#camera.position.clone(); - if (!force && this.#last_camera_position) { - // if camera position does not changed a lot, ignore such change - const dist = this.#last_camera_position.distanceTo(origin); - if (dist < (this.#overall_size || 1000) * 1e-4) - return; - } - - this.#last_camera_position = origin; // remember current camera position - - if (this.ctrl._axis) { - const vect = (this.#controls?.target || this.#lookat).clone().sub(this.#camera.position).normalize(); - this.getExtrasContainer('get', 'axis')?.traverse(obj3d => { - if (isFunc(obj3d._axis_flip)) - obj3d._axis_flip(vect); - }); - } - - if (!this.ctrl.project) - produceRenderOrder(this.#toplevel, origin, this.ctrl.depthMethod, this.#clones); - } - - /** @summary Call 3D rendering of the geometry - * @param {number} tmout - specifies delay, after which actual rendering will be invoked - * @param {boolean} [measure] - when true, for the first time printout rendering time - * @return {Promise} when tmout bigger than 0 is specified - * @desc Timeout used to avoid multiple rendering of the picture when several 3D drawings - * superimposed with each other. If tmout <= 0, rendering performed immediately - * Several special values are used: - * -1 - force recheck of rendering order based on camera position */ - render3D(tmout, measure) { - if (!this.#renderer) { - if (this.#did_cleanup) - console.warn('try to render after cleanup'); - else - console.warn('renderer object not exists - check code'); - return this; - } - - const ret_promise = (tmout !== undefined) && (tmout > 0) && (measure !== 'nopromise'); - - if (tmout === undefined) - tmout = 5; // by default, rendering happens with timeout - - if ((tmout > 0) && this.#webgl) { - if (this.isBatchMode()) - tmout = 1; // use minimal timeout in batch mode - if (ret_promise) { - return new Promise(resolveFunc => { - this.#render_resolveFuncs.push(resolveFunc); - if (!this.#render_tmout) - this.#render_tmout = setTimeout(() => this.render3D(0), tmout); - }); - } - - if (!this.#render_tmout) - this.#render_tmout = setTimeout(() => this.render3D(0), tmout); - return this; - } - - if (this.#render_tmout) { - clearTimeout(this.#render_tmout); - this.#render_tmout = undefined; - } - - beforeRender3D(this.#renderer); - - const tm1 = new Date(); - - if (this.#adjust_camera_with_render) { - this.adjustCameraPosition('only_set'); - this.#adjust_camera_with_render = undefined; - } - - this.testCameraPosition(tmout === -1); - - // its needed for outlinePass - do rendering, most consuming time - if (this.#webgl && this.#effectComposer?.passes) - this.#effectComposer.render(); - else if (this.#webgl && this.#bloomComposer?.passes) { - this.#renderer.clear(); - this.#camera.layers.set(_BLOOM_SCENE); - this.#bloomComposer.render(); - this.#renderer.clearDepth(); - this.#camera.layers.set(_ENTIRE_SCENE); - this.#renderer.render(this.#scene, this.#camera); - } else - this.#renderer.render(this.#scene, this.#camera); - - const tm2 = new Date(); - - this.#last_render_tm = tm2.getTime(); - - if ((this.#first_render_tm === 0) && (measure === true)) { - this.#first_render_tm = tm2.getTime() - tm1.getTime(); - if (this.#first_render_tm > 500) - console.log(`three.js r${THREE.REVISION}, first render tm = ${this.#first_render_tm}`); - } - - afterRender3D(this.#renderer); - - const arr = this.#render_resolveFuncs; - this.#render_resolveFuncs = []; - arr?.forEach(func => func(this)); - } - - /** @summary Start geo worker */ - async startWorker() { - if (this.#worker) - return; - - this.#worker_ready = false; - this.#worker_jobs = 0; // counter how many requests send to worker - - let pr; - - if (isNodeJs()) { - pr = import('node:worker_threads').then(h => { - const wrk = new h.Worker(source_dir.slice(7) + 'modules/geom/nodeworker.mjs', { type: 'module' }); - wrk.on('message', msg => this.processWorkerReply(msg)); - return wrk; - }); - } else { - // Finally use ES6 module, see https://www.codedread.com/blog/archives/2017/10/19/web-workers-can-be-es6-modules-too/ - const wrk = new Worker(source_dir + 'modules/geom/geoworker.mjs', { type: 'module' }); - wrk.onmessage = e => this.processWorkerReply(e?.data); - pr = Promise.resolve(wrk); - } - - return pr.then(wrk => { - this.#worker = wrk; - // send initialization message with clones - wrk.postMessage({ - init: true, // indicate init command for worker - tm0: new Date().getTime(), - vislevel: this.#clones.getVisLevel(), - maxvisnodes: this.#clones.getMaxVisNodes(), - clones: this.#clones.nodes, - sortmap: this.#clones.sortmap - }); - }); - } - - /** @summary check if one can submit request to worker - * @private */ - canSubmitToWorker(force) { - return this.#worker ? this.#worker_ready && ((this.#worker_jobs === 0) || force) : false; - } - - /** @summary submit request to worker - * @private */ - submitToWorker(job) { - if (!this.#worker) - return false; - - this.#worker_jobs++; - job.tm0 = new Date().getTime(); - this.#worker.postMessage(job); - } - - /** @summary process reply from worker - * @private */ - processWorkerReply(job) { - if (!job || !isObject(job)) - return; - - if (job.log) - return console.log(`geo: ${job.log}`); - - if (job.progress) - return showProgress(job.progress); - - job.tm3 = new Date().getTime(); - - if (job.init) { - this.#worker_ready = true; - return; - } - - this.#worker_jobs--; - - if ('collect' in job) { - this.#new_draw_nodes = job.new_nodes; - this.#draw_all_nodes = job.complete; - this.changeStage(stageAnalyze); - // invoke methods immediately - return this.continueDraw(); - } - - if ('shapes' in job) { - for (let n = 0; n < job.shapes.length; ++n) { - const item = job.shapes[n], - origin = this.#build_shapes[n]; - - if (item.buf_pos && item.buf_norm) { - if (!item.buf_pos.length) - origin.geom = null; - else if (item.buf_pos.length !== item.buf_norm.length) { - console.error(`item.buf_pos.length ${item.buf_pos.length} !== item.buf_norm.length ${item.buf_norm.length}`); - origin.geom = null; - } else { - origin.geom = new THREE.BufferGeometry(); - - origin.geom.setAttribute('position', new THREE.BufferAttribute(item.buf_pos, 3)); - origin.geom.setAttribute('normal', new THREE.BufferAttribute(item.buf_norm, 3)); - } - - origin.ready = true; - origin.nfaces = item.nfaces; - } - } - - job.tm4 = new Date().getTime(); - - this.changeStage(stageBuild); // first check which shapes are used, than build meshes - - // invoke methods immediately - return this.continueDraw(); - } - } - - /** @summary start draw geometries on central and all subordinate painters - * @private */ - testGeomChanges() { - if (this.getCentral()) { - console.warn('Get testGeomChanges call for subordinate painter'); - return this.getCentral().testGeomChanges(); - } - this.startDrawGeometry(); - this.getSubordinates()?.forEach(p => p.startDrawGeometry()); - } - - /** @summary Draw axes and camera overlay */ - drawAxesAndOverlay(norender) { - const res1 = this.drawAxes(), - res2 = this.drawOverlay(); - - if (!res1 && !res2) - return norender ? null : this.render3D(); - - return this.changedDepthMethod(norender ? 'norender' : undefined); - } - - /** @summary Draw overlay for the orthographic cameras */ - drawOverlay() { - this.getExtrasContainer('delete', 'overlay'); - if (!this.isOrthoCamera() || (this.ctrl.camera_overlay === 'none')) - return false; - - const zoom = 0.5 / this.#camera.zoom, - midx = (this.#camera.left + this.#camera.right) / 2, - midy = (this.#camera.bottom + this.#camera.top) / 2, - xmin = midx - (this.#camera.right - this.#camera.left) * zoom, - xmax = midx + (this.#camera.right - this.#camera.left) * zoom, - ymin = midy - (this.#camera.top - this.#camera.bottom) * zoom, - ymax = midy + (this.#camera.top - this.#camera.bottom) * zoom, - tick_size = (ymax - ymin) * 0.02, - text_size = (ymax - ymin) * 0.015, - grid_gap = (ymax - ymin) * 0.001, - x1 = xmin + text_size * 5, x2 = xmax - text_size * 5, - y1 = ymin + text_size * 3, y2 = ymax - text_size * 3, - x_handle = new TAxisPainter(null, create(clTAxis)); - - x_handle.configureAxis('xaxis', x1, x2, x1, x2, false, [x1, x2], - { log: 0, reverse: false }); - const y_handle = new TAxisPainter(null, create(clTAxis)); - y_handle.configureAxis('yaxis', y1, y2, y1, y2, false, [y1, y2], - { log: 0, reverse: false }); - - const ii = this.#camera.orthoIndicies ?? [0, 1, 2]; - let buf, pos, midZ = 0, gridZ = 0; - - if (this.#camera.orthoZ) - gridZ = midZ = this.#camera.orthoZ[0]; - - const addPoint = (x, y, z) => { - buf[pos + ii[0]] = x; - buf[pos + ii[1]] = y; - buf[pos + ii[2]] = z ?? gridZ; - pos += 3; - }, createText = (lbl, size) => { - const text3d = createTextGeometry(lbl, size); - text3d.computeBoundingBox(); - text3d._width = text3d.boundingBox.max.x - text3d.boundingBox.min.x; - text3d._height = text3d.boundingBox.max.y - text3d.boundingBox.min.y; - - text3d.translate(-text3d._width / 2, -text3d._height / 2, 0); - if (this.#camera.orthoSign < 0) - text3d.rotateY(Math.PI); - - if (isFunc(this.#camera.orthoRotation)) - this.#camera.orthoRotation(text3d); - - return text3d; - }, createTextMesh = (geom, material, x, y, z) => { - const tgt = [0, 0, 0]; - tgt[ii[0]] = x; - tgt[ii[1]] = y; - tgt[ii[2]] = z ?? gridZ; - const mesh = new THREE.Mesh(geom, material); - mesh.translateX(tgt[0]).translateY(tgt[1]).translateZ(tgt[2]); - return mesh; - }; - - if (this.ctrl.camera_overlay === 'bar') { - const container = this.getExtrasContainer('create', 'overlay'); - - let ox1 = xmin * 0.15 + xmax * 0.85, - ox2 = xmin * 0.05 + xmax * 0.95; - const oy1 = ymax * 0.9 + ymin * 0.1, - oy2 = ymax * 0.86 + ymin * 0.14, - ticks = x_handle.createTicks(); - - if (ticks.major?.length > 1) { - ox1 = ticks.major.at(-2); - ox2 = ticks.major.at(-1); - } - - buf = new Float32Array(3 * 6); - pos = 0; - - addPoint(ox1, oy1, midZ); - addPoint(ox1, oy2, midZ); - - addPoint(ox1, (oy1 + oy2) / 2, midZ); - addPoint(ox2, (oy1 + oy2) / 2, midZ); - - addPoint(ox2, oy1, midZ); - addPoint(ox2, oy2, midZ); - - const lineMaterial = new THREE.LineBasicMaterial({ color: 'green' }), - textMaterial = new THREE.MeshBasicMaterial({ color: 'green', vertexColors: false }); - - container.add(createLineSegments(buf, lineMaterial)); - - const text3d = createText(x_handle.format(ox2 - ox1, true), Math.abs(oy2 - oy1)); - - container.add(createTextMesh(text3d, textMaterial, (ox2 + ox1) / 2, (oy1 + oy2) / 2 + text3d._height * 0.8, midZ)); - return true; - } - - const show_grid = this.ctrl.camera_overlay.indexOf('grid') === 0; - - if (show_grid && this.#camera.orthoZ) { - if (this.ctrl.camera_overlay === 'gridf') - gridZ += this.#camera.orthoSign * this.#camera.orthoZ[1]; - else if (this.ctrl.camera_overlay === 'gridb') - gridZ -= this.#camera.orthoSign * this.#camera.orthoZ[1]; - } - - if ((this.ctrl.camera_overlay === 'axis') || show_grid) { - const container = this.getExtrasContainer('create', 'overlay'), - lineMaterial = new THREE.LineBasicMaterial({ color: new THREE.Color('black') }), - gridMaterial1 = show_grid ? new THREE.LineBasicMaterial({ color: new THREE.Color(0xbbbbbb) }) : null, - gridMaterial2 = show_grid ? new THREE.LineDashedMaterial({ color: new THREE.Color(0xdddddd), dashSize: grid_gap, gapSize: grid_gap }) : null, - textMaterial = new THREE.MeshBasicMaterial({ color: 'black', vertexColors: false }), - xticks = x_handle.createTicks(); - - while (xticks.next()) { - const x = xticks.tick, k = (xticks.kind === 1) ? 1.0 : 0.6; - - if (show_grid) { - buf = new Float32Array(2 * 3); - pos = 0; - addPoint(x, ymax - k * tick_size - grid_gap); - addPoint(x, ymin + k * tick_size + grid_gap); - container.add(createLineSegments(buf, xticks.kind === 1 ? gridMaterial1 : gridMaterial2)); - } - - buf = new Float32Array(4 * 3); - pos = 0; - addPoint(x, ymax); - addPoint(x, ymax - k * tick_size); - addPoint(x, ymin); - addPoint(x, ymin + k * tick_size); - - container.add(createLineSegments(buf, lineMaterial)); - - if (xticks.kind !== 1) - continue; - - const text3d = createText(x_handle.format(x, true), text_size); - - container.add(createTextMesh(text3d, textMaterial, x, ymax - tick_size - text_size / 2 - text3d._height / 2)); - - container.add(createTextMesh(text3d, textMaterial, x, ymin + tick_size + text_size / 2 + text3d._height / 2)); - } - - const yticks = y_handle.createTicks(); - - while (yticks.next()) { - const y = yticks.tick, k = (yticks.kind === 1) ? 1.0 : 0.6; - - if (show_grid) { - buf = new Float32Array(2 * 3); - pos = 0; - addPoint(xmin + k * tick_size + grid_gap, y); - addPoint(xmax - k * tick_size - grid_gap, y); - container.add(createLineSegments(buf, yticks.kind === 1 ? gridMaterial1 : gridMaterial2)); - } - - buf = new Float32Array(4 * 3); - pos = 0; - addPoint(xmin, y); - addPoint(xmin + k * tick_size, y); - addPoint(xmax, y); - addPoint(xmax - k * tick_size, y); - - container.add(createLineSegments(buf, lineMaterial)); - - if (yticks.kind !== 1) - continue; - - const text3d = createText(y_handle.format(y, true), text_size); - - container.add(createTextMesh(text3d, textMaterial, xmin + tick_size + text_size / 2 + text3d._width / 2, y)); - - container.add(createTextMesh(text3d, textMaterial, xmax - tick_size - text_size / 2 - text3d._width / 2, y)); - } - - return true; - } - - return false; - } - - /** @summary Draw axes if configured, otherwise just remove completely */ - drawAxes() { - this.getExtrasContainer('delete', 'axis'); - - if (!this.ctrl._axis) - return false; - - const box = this.getGeomBoundingBox(this.#toplevel, this.#superimpose ? 'original' : undefined), - container = this.getExtrasContainer('create', 'axis'), - text_size = 0.02 * Math.max(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z), - center = [0, 0, 0], - names = ['x', 'y', 'z'], - labels = ['X', 'Y', 'Z'], - colors = ['red', 'green', 'blue'], - ortho = this.isOrthoCamera(), - ckind = this.ctrl.camera_kind ?? 'perspective'; - - if (this.ctrl._axis === 2) { - for (let naxis = 0; naxis < 3; ++naxis) { - const name = names[naxis]; - if ((box.min[name] <= 0) && (box.max[name] >= 0)) - continue; - center[naxis] = (box.min[name] + box.max[name]) / 2; - } - } - - for (let naxis = 0; naxis < 3; ++naxis) { - // exclude axis which is not seen - if (ortho && ckind.indexOf(labels[naxis]) < 0) - continue; - - const buf = new Float32Array(6), - color = colors[naxis], - name = names[naxis], - valueToString = val => { - if (!val) - return '0'; - const lg = Math.log10(Math.abs(val)); - if (lg < 0) { - if (lg > -1) - return val.toFixed(2); - if (lg > -2) - return val.toFixed(3); - } else { - if (lg < 2) - return val.toFixed(1); - if (lg < 4) - return val.toFixed(0); - } - return val.toExponential(2); - }, - lbl = valueToString(box.max[name]) + ' ' + labels[naxis]; - - buf[0] = box.min.x; - buf[1] = box.min.y; - buf[2] = box.min.z; - - buf[3] = box.min.x; - buf[4] = box.min.y; - buf[5] = box.min.z; - - switch (naxis) { - case 0: buf[3] = box.max.x; break; - case 1: buf[4] = box.max.y; break; - case 2: buf[5] = box.max.z; break; - } - - if (this.ctrl._axis === 2) { - for (let k = 0; k < 6; ++k) { - if ((k % 3) !== naxis) - buf[k] = center[k % 3]; - } - } - - const lineMaterial = new THREE.LineBasicMaterial({ color }); - let mesh = createLineSegments(buf, lineMaterial); - - mesh._no_clip = true; // skip from clipping - - container.add(mesh); - - const textMaterial = new THREE.MeshBasicMaterial({ color, vertexColors: false }); - - if ((center[naxis] === 0) && (center[naxis] >= box.min[name]) && (center[naxis] <= box.max[name])) { - if ((this.ctrl._axis !== 2) || (naxis === 0)) { - const geom = ortho ? new THREE.CircleGeometry(text_size * 0.25) : new THREE.SphereGeometry(text_size * 0.25); - mesh = new THREE.Mesh(geom, textMaterial); - mesh.translateX(naxis === 0 ? center[0] : buf[0]); - mesh.translateY(naxis === 1 ? center[1] : buf[1]); - mesh.translateZ(naxis === 2 ? center[2] : buf[2]); - mesh._no_clip = true; - container.add(mesh); - } - } - - let text3d = createTextGeometry(lbl, text_size); - mesh = new THREE.Mesh(text3d, textMaterial); - mesh._no_clip = true; // skip from clipping - - function setSideRotation(mesh2, normal) { - mesh2._other_side = false; - mesh2._axis_norm = normal ?? new THREE.Vector3(1, 0, 0); - mesh2._axis_flip = function(vect) { - const other_side = vect.dot(this._axis_norm) < 0; - if (this._other_side !== other_side) { - this._other_side = other_side; - this.rotateY(Math.PI); - } - }; - } - - function setTopRotation(mesh2, first_angle = -1) { - mesh2._last_angle = first_angle; - mesh2._axis_flip = function(vect) { - let angle; - switch (this._axis_name) { - case 'x': angle = -Math.atan2(vect.y, vect.z); break; - case 'y': angle = -Math.atan2(vect.z, vect.x); break; - default: angle = Math.atan2(vect.y, vect.x); - } - angle = Math.round(angle / Math.PI * 2 + 2) % 4; - if (this._last_angle !== angle) { - this.rotateX((angle - this._last_angle) * Math.PI / 2); - this._last_angle = angle; - } - }; - } - - let textbox = new THREE.Box3().setFromObject(mesh); - - text3d.translate(-textbox.max.x * 0.5, -textbox.max.y / 2, 0); - - mesh.translateX(buf[3]); - mesh.translateY(buf[4]); - mesh.translateZ(buf[5]); - - mesh._axis_name = name; - - if (naxis === 0) { - if (ortho && ckind.indexOf('OX') > 0) - setTopRotation(mesh, 0); - else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) - setSideRotation(mesh, new THREE.Vector3(0, 0, -1)); - else { - setSideRotation(mesh, new THREE.Vector3(0, 1, 0)); - mesh.rotateX(Math.PI / 2); - } - - mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); - } else if (naxis === 1) { - if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { - setTopRotation(mesh, 2); - mesh.rotateX(-Math.PI / 2); - mesh.rotateY(-Math.PI / 2); - mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); - } else { - setSideRotation(mesh); - mesh.rotateX(Math.PI / 2); - mesh.rotateY(-Math.PI / 2); - mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); - } - } else if (naxis === 2) { - if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { - const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); - setSideRotation(mesh, zox ? new THREE.Vector3(0, -1, 0) : undefined); - mesh.rotateY(-Math.PI / 2); - if (zox) - mesh.rotateX(-Math.PI / 2); - } else { - setTopRotation(mesh); - mesh.rotateX(Math.PI / 2); - mesh.rotateZ(Math.PI / 2); - } - mesh.translateX(text_size * 0.5 + textbox.max.x * 0.5); - } - - container.add(mesh); - - text3d = createTextGeometry(valueToString(box.min[name]), text_size); - - mesh = new THREE.Mesh(text3d, textMaterial); - mesh._no_clip = true; // skip from clipping - textbox = new THREE.Box3().setFromObject(mesh); - - text3d.translate(-textbox.max.x * 0.5, -textbox.max.y / 2, 0); - - mesh._axis_name = name; - - mesh.translateX(buf[0]); - mesh.translateY(buf[1]); - mesh.translateZ(buf[2]); - - if (naxis === 0) { - if (ortho && ckind.indexOf('OX') > 0) - setTopRotation(mesh, 0); - else if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) - setSideRotation(mesh, new THREE.Vector3(0, 0, -1)); - else { - setSideRotation(mesh, new THREE.Vector3(0, 1, 0)); - mesh.rotateX(Math.PI / 2); - } - mesh.translateX(-text_size * 0.5 - textbox.max.x * 0.5); - } else if (naxis === 1) { - if (ortho ? ckind.indexOf('OY') > 0 : this.ctrl._yup) { - setTopRotation(mesh, 2); - mesh.rotateX(-Math.PI / 2); - mesh.rotateY(-Math.PI / 2); - mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); - } else { - setSideRotation(mesh); - mesh.rotateX(Math.PI / 2); - mesh.rotateY(-Math.PI / 2); - mesh.translateX(textbox.max.x * 0.5 + text_size * 0.5); - } - } else if (naxis === 2) { - if (ortho ? ckind.indexOf('OZ') < 0 : this.ctrl._yup) { - const zox = ortho && (ckind.indexOf('ZOX') > 0 || ckind.indexOf('ZNOX') > 0); - setSideRotation(mesh, zox ? new THREE.Vector3(0, -1, 0) : undefined); - mesh.rotateY(-Math.PI / 2); - if (zox) - mesh.rotateX(-Math.PI / 2); - } else { - setTopRotation(mesh); - mesh.rotateX(Math.PI / 2); - mesh.rotateZ(Math.PI / 2); - } - mesh.translateX(-textbox.max.x * 0.5 - text_size * 0.5); - } - - container.add(mesh); - } - - // after creating axes trigger rendering and recalculation of depth - return true; - } - - /** @summary Set axes visibility 0 - off, 1 - on, 2 - centered */ - setAxesDraw(on) { - if (on === 'toggle') - this.ctrl._axis = this.ctrl._axis ? 0 : 1; - else - this.ctrl._axis = (typeof on === 'number') ? on : (on ? 1 : 0); - return this.drawAxesAndOverlay(); - } - - /** @summary Set auto rotate mode */ - setAutoRotate(on) { - if (this.ctrl.project) - return; - if (on !== undefined) - this.ctrl.rotate = on; - this.autorotate(2.5); - } - - /** @summary Toggle wireframe mode */ - toggleWireFrame() { - this.ctrl.wireframe = !this.ctrl.wireframe; - this.changedWireFrame(); - } - - /** @summary Specify wireframe mode */ - setWireFrame(on) { - this.ctrl.wireframe = Boolean(on); - this.changedWireFrame(); - } - - /** @summary Specify showtop draw options, relevant only for TGeoManager */ - setShowTop(on) { - this.ctrl.showtop = Boolean(on); - return this.startRedraw(); - } - - /** @summary Should be called when configuration of particular axis is changed */ - changedClipping(naxis = -1) { - if ((naxis < 0) || this.ctrl.clip[naxis]?.enabled) - this.updateClipping(false, true); - } - - /** @summary Should be called when depth test flag is changed */ - changedDepthTest() { - if (!this.#toplevel) - return; - const flag = this.ctrl.depthTest; - this.#toplevel.traverse(node => { - if (node instanceof THREE.Mesh) - node.material.depthTest = flag; - }); - - this.render3D(0); - } - - /** @summary Should be called when depth method is changed */ - changedDepthMethod(arg) { - // force recalculation of render order - this.#last_camera_position = undefined; - if (arg !== 'norender') - return this.render3D(); - } - - /** @summary Assign clipping attributes to the meshes - supported only for webgl */ - updateClipping(without_render, force_traverse) { - // do not try clipping with SVG renderer - if (this.#renderer?.jsroot_render3d === constants.Render3D.SVG) - return; - - if (!this.#clip_planes) { - this.#clip_planes = [new THREE.Plane(new THREE.Vector3(1, 0, 0), 0), - new THREE.Plane(new THREE.Vector3(0, this.ctrl._yup ? -1 : 1, 0), 0), - new THREE.Plane(new THREE.Vector3(0, 0, this.ctrl._yup ? 1 : -1), 0)]; - } - - const clip = this.ctrl.clip, - clip_constants = [-1 * clip[0].value, clip[1].value, (this.ctrl._yup ? -1 : 1) * clip[2].value], - container = this.getExtrasContainer(this.ctrl.clipVisualize ? '' : 'delete', 'clipping'); - let panels = [], changed = false, - clip_cfg = this.ctrl.clipIntersect ? 16 : 0; - - for (let k = 0; k < 3; ++k) { - if (clip[k].enabled) - clip_cfg += 2 << k; - if (this.#clip_planes[k].constant !== clip_constants[k]) { - if (clip[k].enabled) - changed = true; - this.#clip_planes[k].constant = clip_constants[k]; - } - if (clip[k].enabled) - panels.push(this.#clip_planes[k]); - - if (container && clip[k].enabled) { - const helper = new THREE.PlaneHelper(this.#clip_planes[k], (clip[k].max - clip[k].min)); - helper._no_clip = true; - container.add(helper); - } - } - if (!panels.length) - panels = null; - - if (this.#last_clip_cfg !== clip_cfg) - changed = true; - - this.#last_clip_cfg = clip_cfg; - - const any_clipping = Boolean(panels), ci = this.ctrl.clipIntersect, - material_side = any_clipping ? THREE.DoubleSide : THREE.FrontSide; - - if (force_traverse || changed) { - this.#scene.traverse(node => { - if (!node._no_clip && (node.material?.clippingPlanes !== undefined)) { - if (node.material.clippingPlanes !== panels) { - node.material.clipIntersection = ci; - node.material.clippingPlanes = panels; - node.material.needsUpdate = true; - } - - if (node.material.emissive !== undefined) { - if (node.material.side !== material_side) { - node.material.side = material_side; - node.material.needsUpdate = true; - } - } - } - }); - } - - this.ctrl.doubleside = any_clipping; - - if (!without_render) - this.render3D(0); - - return changed; - } - - /** @summary Assign callback, invoked every time when drawing is completed - * @desc Used together with web-based geometry viewer - * @private */ - setCompleteHandler(callback) { - this.#complete_handler = callback; - } - - /** @summary Completes drawing procedure - * @return {Promise} for ready */ - async completeDraw(close_progress) { - let first_time = false, full_redraw = false, check_extras = true; - - if (!this.ctrl) { - console.warn('ctrl object does not exist in completeDraw - something went wrong'); - return this; - } - - let promise; - - if (!this.#clones) { - check_extras = false; - // if extra object where append, redraw them at the end - this.getExtrasContainer('delete'); // delete old container - promise = this.drawExtras(this.getCentral()?.getExtraObjects() || this.getExtraObjects(), '', false); - } else if ((this.#first_drawing || this.#full_redrawing) && this.ctrl.tracks && this.#geo_manager) - promise = this.drawExtras(this.#geo_manager.fTracks, '<prnt>/Tracks'); - else - promise = Promise.resolve(true); - - return promise.then(() => { - if (this.#full_redrawing) { - this.adjustCameraPosition('first'); - this.#full_redrawing = false; - full_redraw = true; - this.changedDepthMethod('norender'); - } - - if (this.#first_drawing) { - this.adjustCameraPosition('first'); - this.showDrawInfo(); - this.#first_drawing = false; - first_time = true; - full_redraw = true; - } - - if (first_time) - this.completeScene(); - - if (full_redraw && (this.ctrl.trans_radial || this.ctrl.trans_z)) - this.changedTransformation('norender'); - - if (full_redraw) - return this.drawAxesAndOverlay(true); - }).then(() => { - this.#scene.overrideMaterial = null; - - if (this.#provided_more_nodes !== undefined) { - this.appendMoreNodes(this.#provided_more_nodes, true); - this.#provided_more_nodes = undefined; - } - - if (check_extras) { - // if extra object where append, redraw them at the end - this.getExtrasContainer('delete'); // delete old container - return this.drawExtras(this.getCentral()?.getExtraObjects() || this.getExtraObjects(), '', false); - } - }).then(() => { - this.updateClipping(true); // do not render - - this.render3D(0, true); - - if (close_progress) - showProgress(); - - this.addOrbitControls(); - - if (first_time && !this.isBatchMode()) { - // after first draw check if highlight can be enabled - if (this.ctrl.highlight === 0) - this.ctrl.highlight = (this.#first_render_tm < 1000); - - // also highlight of scene object can be assigned at the first draw - if (this.ctrl.highlight_scene === 0) - this.ctrl.highlight_scene = this.ctrl.highlight; - - // if rotation was enabled, do it - if (this.#webgl && this.ctrl.rotate && !this.ctrl.project) - this.autorotate(2.5); - if (this.#webgl && this.ctrl.show_controls) - this.showControlGui(true); - } - - this.setAsMainPainter(); - - const arr = this.#draw_resolveFuncs; - this.#draw_resolveFuncs = []; - arr?.forEach(func => func(this)); - - if (isFunc(this.#complete_handler)) - this.#complete_handler(this); - - if (this.#draw_nodes_again) - this.startDrawGeometry(); // relaunch drawing - else - this.#drawing_ready = true; // indicate that drawing is completed - - return this; - }); - } - - /** @summary Returns true if geometry drawing is completed */ - isDrawingReady() { return this.#drawing_ready ?? false; } - - /** @summary Remove already drawn node. Used by geom viewer */ - removeDrawnNode(nodeid) { - if (!this.#draw_nodes) - return; - - const new_nodes = []; - - for (let n = 0; n < this.#draw_nodes.length; ++n) { - const entry = this.#draw_nodes[n]; - if ((entry.nodeid === nodeid) || this.#clones.isIdInStack(nodeid, entry.stack)) - this.#clones.createObject3D(entry.stack, this.#toplevel, kDeleteMesh); - else - new_nodes.push(entry); - } - - if (new_nodes.length < this.#draw_nodes.length) { - this.#draw_nodes = new_nodes; - this.render3D(); - } - } - - /** @summary Cleanup geometry painter */ - cleanup(first_time) { - if (!first_time) { - let can3d = 0; - - if (!this.#superimpose) { - this.clearTopPainter(); // remove as pointer - - if (this.#on_pad) { - const fp = this.getFramePainter(); - if (fp?.mode3d) { - fp.clear3dCanvas(); - fp.mode3d = false; - } - } else if (isFunc(this.clear3dCanvas)) - can3d = this.clear3dCanvas(); // remove 3d canvas from main HTML element - - disposeThreejsObject(this.#scene); - } - - this.#toolbar?.cleanup(); // remove toolbar - - disposeThreejsObject(this.#fullgeom_proj); - - this.#controls?.cleanup(); - - this.#gui?.destroy(); - - this.#worker?.terminate(); - - this.#animating = undefined; - - const obj = this.getGeometry(); - if (obj && this.ctrl.is_main) { - if (obj.$geo_painter === this) - delete obj.$geo_painter; - else if (obj.fVolume?.$geo_painter === this) - delete obj.fVolume.$geo_painter; - } - - this.#central_painter?.assignSubordinate(this, false); - this.#subordinate_painters?.forEach(p => { - p.assignCentral(this, false); - if (p.getClones() === this.getClones()) - p.assignClones(undefined, undefined); - }); - - this.#geo_manager = undefined; - this.#highlight_handlers = undefined; - - super.cleanup(); - - delete this.ctrl; - - this.#did_cleanup = true; - - if (can3d < 0) - this.selectDom().html(''); - } - - this.#central_painter = undefined; - this.#subordinate_painters = []; - - const arr = this.#render_resolveFuncs; - this.#render_resolveFuncs = []; - arr?.forEach(func => func(this)); - - if (!this.#superimpose) - cleanupRender3D(this.#renderer); - - this.ensureBloom(false); - this.#effectComposer = undefined; - - this.#scene = undefined; - this.#scene_size = undefined; - this.#scene_width = 0; - this.#scene_height = 0; - this.#renderer = null; - this.#toplevel = null; - this.#fullgeom_proj = undefined; - this.#fog = undefined; - this.#camera = undefined; - this.#camera0pos = undefined; - this.#lookat = undefined; - this.#selected_mesh = undefined; - this.#custom_bounding_box = undefined; - - this.assignClones(undefined, undefined); - this.#new_draw_nodes = undefined; - this.#new_append_nodes = undefined; - this.#last_camera_position = undefined; - - this.#on_pad = undefined; - - this.#start_render_tm = 0; - this.#first_render_tm = 0; // time needed for first rendering - this.#last_render_tm = 0; - - this.changeStage(stageInit, 'cleanup'); - this.#drawing_log = undefined; - - this.#gui = undefined; - this.#controls = undefined; - this.#toolbar = undefined; - - this.#worker = undefined; - } - - /** @summary perform resize */ - performResize(width, height) { - if ((this.#scene_width === width) && (this.#scene_height === height)) - return false; - if ((width < 10) || (height < 10)) - return false; - - this.#scene_width = width; - this.#scene_height = height; - - if (this.#camera && this.#renderer) { - if (this.#camera.isPerspectiveCamera) - this.#camera.aspect = this.#scene_width / this.#scene_height; - else if (this.#camera.isOrthographicCamera) - this.adjustCameraPosition(true, true); - this.#camera.updateProjectionMatrix(); - this.#renderer.setSize(this.#scene_width, this.#scene_height, !this.#fit_main_area); - this.#effectComposer?.setSize(this.#scene_width, this.#scene_height); - this.#bloomComposer?.setSize(this.#scene_width, this.#scene_height); - - if (this.isStage(stageInit)) - this.render3D(); - } - - return true; - } - - /** @summary Check if HTML element was resized and drawing need to be adjusted */ - checkResize(arg) { - const cp = this.getCanvPainter(); - - // firefox is the only browser which correctly supports resize of embedded canvas, - // for others we should force canvas redrawing at every step - if (cp && !cp.checkCanvasResize(arg)) - return false; - - const sz = this.getSizeFor3d(); - return this.performResize(sz.width, sz.height); - } - - /** @summary Toggle enlarge state */ - toggleEnlarge() { - if (this.enlargeMain('toggle')) - this.checkResize(); - } - - /** @summary either change mesh wireframe or return current value - * @return undefined when wireframe cannot be accessed - * @private */ - accessObjectWireFrame(obj, on) { - if (!obj?.material) - return; - - if ((on !== undefined) && obj.stack) - obj.material.wireframe = on; - - return obj.material.wireframe; - } - - /** @summary handle wireframe flag change in GUI - * @private */ - changedWireFrame() { - this.#scene?.traverse(obj => this.accessObjectWireFrame(obj, this.ctrl.wireframe)); - - this.render3D(); - } - - /** @summary Update object in geo painter */ - updateObject(obj) { - if ((obj === 'same') || !obj?._typename) - return false; - if (obj === this.getObject()) - return true; - - let gm; - if (obj._typename === clTGeoManager) { - gm = obj; - obj = obj.fMasterVolume; - } - - if (obj._typename.indexOf(clTGeoVolume) === 0) - obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; - - if (this.#geo_manager && gm) { - this.#geo_manager = gm; - this.assignObject(obj); - this.#did_update = true; - return true; - } - - if (!this.matchObjectType(obj._typename)) - return false; - - this.assignObject(obj); - this.#did_update = true; - return true; - } - - /** @summary Cleanup TGeo drawings */ - clearDrawings() { - this.assignClones(undefined, undefined); - - this.#extra_objects = undefined; - this.#last_clip_cfg = undefined; - - // only remove all childs from top level object - disposeThreejsObject(this.#toplevel, true); - - this.#full_redrawing = true; - } - - /** @summary Redraw TGeo object inside TPad */ - redraw() { - if (this.#superimpose) { - const cfg = getHistPainter3DCfg(this.getMainPainter()); - - if (cfg) { - this.#toplevel.scale.set(cfg.scale_x ?? 1, cfg.scale_y ?? 1, cfg.scale_z ?? 1); - this.#toplevel.position.set(cfg.offset_x ?? 0, cfg.offset_y ?? 0, cfg.offset_z ?? 0); - this.#toplevel.updateMatrix(); - this.#toplevel.updateMatrixWorld(); - } - } - - if (this.#did_update) - return this.startRedraw(); - - const fp = this.#on_pad ? this.getFramePainter() : null; - if (!fp) - return Promise.resolve(false); - const sz = fp.getSizeFor3d(fp.access3dKind()); - fp.apply3dSize(sz); - return this.performResize(sz.width, sz.height); - } - - /** @summary Redraw TGeo object */ - redrawObject(obj, opt) { - if (!this.updateObject(obj, opt)) - return false; - - return this.startRedraw(); - } - - /** @summary Start geometry redraw */ - startRedraw(tmout) { - if (this.#redraw_timer) { - clearTimeout(this.#redraw_timer); - this.#redraw_timer = undefined; - } - - if (tmout) { - this.#redraw_timer = setTimeout(() => this.startRedraw(), tmout); - return; - } - - this.#did_update = undefined; - - this.clearDrawings(); - const draw_obj = this.getGeometry(), - name_prefix = this.#geo_manager ? draw_obj.fName : ''; - return this.prepareObjectDraw(draw_obj, name_prefix); - } - - /** @summary Build three.js object */ - static build3d(obj, sopt) { - if (!obj) - return null; - - let opt = sopt || {}; - if (isStr(sopt)) { - const painter = new TGeoPainter(null, obj); - painter.decodeOptions(sopt); - opt = painter.ctrl; - opt.numfaces = opt.maxfaces; - opt.numnodes = opt.maxnodes; - } - - if (!opt.numfaces) - opt.numfaces = 100000; - if (!opt.numnodes) - opt.numnodes = 1000; - if (!opt.frustum) - opt.frustum = null; - - opt.res_mesh = opt.res_faces = 0; - - if (opt.instancing === undefined) - opt.instancing = -1; - - opt.info = { num_meshes: 0, num_faces: 0 }; - - let clones, visibles; - - if (obj.visibles && obj.nodes && obj.numnodes) { - // case of draw message from geometry viewer - - const nodes = obj.numnodes > 1e6 ? { length: obj.numnodes } : new Array(obj.numnodes); - - obj.nodes.forEach(node => { - nodes[node.id] = ClonedNodes.formatServerElement(node); - }); - - clones = new ClonedNodes(null, nodes); - clones.name_prefix = clones.getNodeName(0); - - // normally only need when making selection, not used in geo viewer - // this.geo_clones.setMaxVisNodes(draw_msg.maxvisnodes); - // this.geo_clones.setVisLevel(draw_msg.vislevel); - // TODO: provide from server - clones.maxdepth = 20; - - const nsegm = obj.cfg?.nsegm || 30; - - for (let cnt = 0; cnt < obj.visibles.length; ++cnt) { - const item = obj.visibles[cnt], rd = item.ri; - - // entry may be provided without shape - it is ok - if (rd) - item.server_shape = rd.server_shape = createServerGeometry(rd, nsegm); - } - - visibles = obj.visibles; - } else { - let shape = null, hide_top = false; - - if (('fShapeBits' in obj) && ('fShapeId' in obj)) { - shape = obj; - obj = null; - } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) - shape = obj.fShape; - else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) - shape = obj.fShape; - else if (obj._typename === clTGeoManager) { - obj = obj.fMasterVolume; - hide_top = !opt.showtop; - shape = obj.fShape; - } else if (obj.fVolume) - shape = obj.fVolume.fShape; - else - obj = null; - - if (opt.composite && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) - obj = buildCompositeVolume(shape); - - if (!obj && shape) - obj = Object.assign(create(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); - - if (!obj) - return null; - - if (obj._typename.indexOf(clTGeoVolume) === 0) - obj = { _typename: clTGeoNode, fVolume: obj, fName: obj.fName, $geoh: obj.$geoh, _proxy: true }; - - clones = new ClonedNodes(obj); - clones.setVisLevel(opt.vislevel); - clones.setMaxVisNodes(opt.numnodes); - - if (opt.dflt_colors) - clones.setDefaultColors(true); - - const uniquevis = opt.no_screen ? 0 : clones.markVisibles(true); - if (uniquevis <= 0) - clones.markVisibles(false, false, hide_top); - else - clones.markVisibles(true, true, hide_top); // copy bits once and use normal visibility bits - - clones.produceIdShifts(); - - // collect visible nodes - const res = clones.collectVisibles(opt.numfaces, opt.frustum); - - visibles = res.lst; - } - - if (!opt.material_kind) - opt.material_kind = 'lambert'; - if (opt.set_names === undefined) - opt.set_names = true; - - clones.setConfig(opt); - - // collect shapes - const shapes = clones.collectShapes(visibles); - - clones.buildShapes(shapes, opt.numfaces); - - const toplevel = new THREE.Object3D(); - toplevel.clones = clones; // keep reference on JSROOT data - - const colors = getRootColors(); - - if (clones.createInstancedMeshes(opt, toplevel, visibles, shapes, colors)) - return toplevel; - - for (let n = 0; n < visibles.length; ++n) { - const entry = visibles[n]; - if (entry.done) - continue; - - const shape = entry.server_shape || shapes[entry.shapeid]; - if (!shape.ready) { - console.warn('shape marked as not ready when it should'); - break; - } - - clones.createEntryMesh(opt, toplevel, entry, shape, colors); - } - - return toplevel; - } - - /** @summary draw TGeo object */ - static async draw(dom, obj, opt) { - if (!obj) - return null; - - let shape = null, extras = null, extras_path = '', is_eve = false; - - if (('fShapeBits' in obj) && ('fShapeId' in obj)) { - shape = obj; - obj = null; - } else if ((obj._typename === clTGeoVolumeAssembly) || (obj._typename === clTGeoVolume)) - shape = obj.fShape; - else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { - shape = obj.fShape; - is_eve = true; - } else if (obj._typename === clTGeoManager) - shape = obj.fMasterVolume.fShape; - else if (obj._typename === clTGeoOverlap) { - extras = obj.fMarker; - extras_path = '<prnt>/Marker'; - obj = buildOverlapVolume(obj); - if (!opt) - opt = 'wire'; - } else if ('fVolume' in obj) - shape = obj.fVolume?.fShape; - else - obj = null; - - if (isStr(opt) && opt.indexOf('comp') === 0 && shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { - let maxlvl = 1; - opt = opt.slice(4); - if (opt[0] === 'x') { - maxlvl = 999; - opt = opt.slice(1) + '_vislvl999'; - } - obj = buildCompositeVolume(shape, maxlvl); - } - - if (!obj && shape) - obj = Object.assign(create(clTNamed), { _typename: clTEveGeoShapeExtract, fTrans: null, fShape: shape, fRGBA: [0, 1, 0, 1], fElements: null, fRnrSelf: true }); - - if (!obj) - return null; - - // eslint-disable-next-line no-use-before-define - const painter = createGeoPainter(dom, obj, opt); - - if (painter.ctrl.is_main && !obj.$geo_painter) - obj.$geo_painter = painter; - - if (!painter.ctrl.is_main && painter.ctrl.project && obj.$geo_painter) { - painter.assignCentral(obj.$geo_painter); - obj.$geo_painter.assignSubordinate(painter); - } - - if (is_eve && (!painter.ctrl.vislevel || (painter.ctrl.vislevel < 9))) - painter.ctrl.vislevel = 9; - - if (extras) { - painter.ctrl.split_colors = true; - painter.addExtra(extras, extras_path); - } - - return painter.loadMacro(painter.ctrl.script_name).then(arg => painter.prepareObjectDraw(arg.obj, arg.prefix)); - } - -} // class TGeoPainter - - -let add_settings = false; - -/** @summary Get icon for the browser - * @private */ -function getBrowserIcon(hitem, hpainter) { - let icon = ''; - switch (hitem._kind) { - case getKindForType(clTEveTrack): icon = 'img_evetrack'; break; - case getKindForType(clTEvePointSet): icon = 'img_evepoints'; break; - case getKindForType(clTPolyMarker3D): icon = 'img_evepoints'; break; - } - if (icon) { - const drawitem = findItemWithGeoPainter(hitem); - if (drawitem?._painter?.extraObjectVisible(hpainter, hitem)) - icon += ' geovis_this'; - } - return icon; -} - -/** @summary handle click on browser icon - * @private */ -function browserIconClick(hitem, hpainter) { - if (hitem._volume) { - if (hitem._more && hitem._volume.fNodes?.arr?.length) - toggleGeoBit(hitem._volume, geoBITS.kVisDaughters); - else - toggleGeoBit(hitem._volume, geoBITS.kVisThis); - - updateBrowserIcons(hitem._volume, hpainter); - - findItemWithGeoPainter(hitem, true); - return false; // no need to update icon - we did it ourself - } - - if (hitem._geoobj && ((hitem._geoobj._typename === clTEveGeoShapeExtract) || (hitem._geoobj._typename === clREveGeoShapeExtract))) { - hitem._geoobj.fRnrSelf = !hitem._geoobj.fRnrSelf; - - updateBrowserIcons(hitem._geoobj, hpainter); - findItemWithGeoPainter(hitem, true); - return false; // no need to update icon - we did it ourself - } - - // first check that geo painter assigned with the item - const drawitem = findItemWithGeoPainter(hitem), - newstate = drawitem?._painter?.extraObjectVisible(hpainter, hitem, true); - - // return true means browser should update icon for the item - return newstate !== undefined; -} - - -/** @summary Create geo-related css entries - * @private */ -function injectGeoStyle() { - if (!add_settings && isFunc(internals.addDrawFunc)) { - add_settings = true; - // indication that draw and hierarchy is loaded, create css - internals.addDrawFunc({ name: clTEvePointSet, icon_get: getBrowserIcon, icon_click: browserIconClick }); - internals.addDrawFunc({ name: clTEveTrack, icon_get: getBrowserIcon, icon_click: browserIconClick }); - } - - function img(name, code) { - return `.jsroot .img_${name} { display: inline-block; height: 16px; width: 16px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQ${code}'); }`; - } - - injectStyle(` -${img('geoarb8', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB1SURBVBjTdY6rEYAwEETTy6lzK8/Fo+Jj18dTAjUgaQGfGiggtRDE8RtY93Zu514If2nzk2ux9c5TZkwXbiWTUavzws69oBfpYBrMT4r0Jhsw+QfRgQSw+CaKRsKsnV+SaF8MN49RBSgPUxO85PMl5n4tfGUH2gghs2uPAeQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geocombi', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAlUlEQVQoz5VQMQ4CMQyzEUNnBqT7Bo+4nZUH8gj+welWJsQDkHoCEYakTXMHSFiq2jqu4xRAEl2A7w4myWzpzCSZRZ658ldKu1hPnFsequBIc/hcLli3l52MAIANtpWrDsv8waGTW6BPuFtsdZArXyFuj33TQpazGEQF38phipnLgItxRcAoOeNpzv4PTXnC42fb//AGI5YqfQAU8dkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geocone', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACRSURBVBjTdY+xDcNACEVvEm/ggo6Olva37IB0C3iEzJABvAHFTXBDeJRwthMnUvylk44vPjxK+afeokX0flQhJO7L4pafSOMxzaxIKc/Tc7SIjNLyieyZSjBzc4DqMZI0HTMonWPBNlogOLeuewbg9c0hOiIqH7DKmTCuFykjHe4XOzQ58XVMGxzt575tKzd6AX9yMkcWyPlsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geogtra', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACCSURBVBjTVc+hDQMxDAVQD1FyqCQk0MwsCwQEG3+eCW6B0FvheDboFMGepTlVitPP/Cz5y0S/mNkw8pySU9INJDDH4vM4Usm5OrQXasXtkA+tQF+zxfcDY8EVwgNeiwmA37TEccK5oLOwQtuCj7BM2Fq7iGrxVqJbSsH+GzXs+798AThwKMh3/6jDAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geomedium', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABVQTFRFAAAAAAAAMDAww8PDWKj/////gICAG0/C4AAAAAF0Uk5TAEDm2GYAAAABYktHRAX4b+nHAAAACXBIWXMAAABIAAAASABGyWs+AAAAXElEQVQI102MwRGAMAgEuQ6IDwvQCjQdhAl/H7ED038JHhkd3dcOLAgESFARaAqnEB3yrj6QSEym1RbbOKinN+8q2Esui1GaX7VXSi4RUbxHRbER8X6O5Pg/fLgBBzMN8HfXD3AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geopara', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABtSURBVBjTY2DADq5MT7+CzD9kaKjp+QhJYIWqublhMbKAgpOnZxWSQJdsVJTndCSBKoWoAM/VSALpqlEBAYeQBKJAAsi2BGgCBZDdEWUYFZCOLFBlGOWJ7AyGFeaotjIccopageK3R12PGHABACTYHWd0tGw6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('georotation', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAiklEQVQoz2NgYGBgYGDg+A/BmIAFIvyDEbs0AwMTAwHACLPiB5QVBTdpGSOSCZjScDcgc4z+32BgYGBgEGIQw3QDLkdCTZD8/xJFeBfDVxQT/j9n/MeIrMCNIRBJwX8GRuzGM/yHKMAljeILNFOuMTyEisEUMKIqucrwB2oyIhyQpH8y/MZrLWkAAHFzIHIc0Q5yAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotranslation', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABESURBVBjTY2DgYGAAYzjgAAIQgSLAgSwAAcrWUUCAJBAVhSpgBAQumALGCJPAAsriHIS0IAQ4UAU4cGphQBWwZSAOAADGJBKdZk/rHQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geotrd2', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABsSURBVBjTbY+xDcAwCARZx6UraiaAmpoRvIIb75PWI2QITxIiRQKk0CCO/xcA/NZ9LRs7RkJEYg3QxczUwoGsXiMAoe8lAelqRWFNKpiNXZLAalRDd0f3TMgeMckABKsCDmu+442RddeHz9cf9jUkW8smGn8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geovolume', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAB5QTFRFAAAAMDAw///Ay8uc/7+Q/4BgmJh4gIDgAAD/////CZb2ugAAAAF0Uk5TAEDm2GYAAAABYktHRAnx2aXsAAAACXBIWXMAAABIAAAASABGyWs+AAAAR0lEQVQI12NggAEBIBAEQgYGQUYQAyIGIhgwAZMSGCgwMJuEKimFOhswsKWAGG4JDGxJIBk1EEO9o6NIDVkEpgauC24ODAAASQ8Pkj/retYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geoassembly', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAA9QTFRFAAAAMDAw/wAAAAD/////jEo0BQAAAAF0Uk5TAEDm2GYAAAABYktHRASPaNlRAAAACXBIWXMAAABIAAAASABGyWs+AAAAOklEQVQI12NggAFGRgEgEBRgEBSAMhgYGQQEgAR+oARGDIwCIAYjUL0A2DQQg9nY2ABVBKoGrgsDAADxzgNboMz8zQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geocomposite', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABuSURBVBjTY2AgF2hqgQCCr+0V4O7hFmgCF7CJyKysKkmxhfGNLaw9SppqAi2gfMuY5Agrl+ZaC6iAUXRJZX6Ic0klTMA5urapPFY5NRcmYKFqWl8S5RobBRNg0PbNT3a1dDGH8RlM3LysTRjIBwAG6xrzJt11BAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geoctub', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACESURBVBjTdc+xDcMwDARA7cKKHTuWX37LHaw+vQbQAJomA7j2DB7FhCMFCZB8pxPwJEv5kQcZW+3HencRBekak4aaMQIi8YJdAQ1CMeE0UBkuaLMETklQ9Alhka0JzzXWqLVBuQYPpWcVuBbZjZafNRYcDk9o/b07bvhINz+/zxu1/M0FSRcmAk/HaIcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geohype', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACKSURBVBjTbU+rFQQhDKQSDDISEYuMREfHx6eHKMpYuf5qoIQt5bgDblfcuJk3nySEhSvceDV3c/ejT66lspopE9pXyIlkCrHMBACpu1DClekQAREi/loviCnF/NhRwJLaQ6hVhPjB8bOCsjlnNnNl0FWJVWxAqGzHONRHpu5Ml+nQ+8GzNW9n+Is3eg80Nk0iiwoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geomixture', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAACFQTFRFAAAAAAAAKysrVVUA//8B//8AgICAqqpV398gv79A////VYJtlwAAAAF0Uk5TAEDm2GYAAAABYktHRApo0PRWAAAACXBIWXMAAABIAAAASABGyWs+AAAAXklEQVQI12NgwASCQsJCgoZAhoADq1tKIJAhEpDGxpYIZKgxsLElgBhibAkOCY4gKTaGkPRGIEPUIYEBrEaAIY0tDawmgYWNgREkkjCVjRWkWCUhLY0FJCIIBljsBgCZTAykgaRiRwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geopcon', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACJSURBVBjTdc+hGcQwCIZhhjl/rkgWiECj8XgGyAbZoD5LdIRMkEnKkV575n75Pp8AgLU54dmh6mauelyAL2Qzxfe2sklioq6FacFAcRFXYhwJHdU5rDD2hEYB/CmoJVRMiIJqgtENuoqA8ltAlYAqRH4d1tGkwzTqN2gA7Nv+fUwkgZ/3mg34txM+szzATJS1HQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geosphere', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACFSURBVBjTdY+xEcQwCAQp5QNFjpQ5vZACFBFTADFFfKYCXINzlUAJruXll2ekxDAEt9zcANFbXb2mqm56dxsymAH0yccAJaeNi0h5QGyfxGJmivMPjj0nmLsbRmyFCss3rlbpcUjfS8wLUNRcJyCF6uqg2IvYCnoKC7f1kSbA6riTz7evfwj3Ml+H3KBqAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotrap', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB5SURBVBjTbY+hFYAwDETZB1OJi4yNPp0JqjtAZ2AELL5DdABmIS2PtLxHXH7u7l2W5W+uHMHpGiCHLYR1yw4SCZMIXBOJWVSjK7QDDAu4g8OBmAKK4sAEDdR3rw8YmcUcrEijKKhl7lN1IQPn9ExlgU6/WEyc75+5AYK0KY5oHBDfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotubeseg', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACBSURBVBjTdc+hEcQwDARA12P6QFBQ9LDwcXEVkA7SQTr4BlJBakgpsWdsh/wfux3NSCrlV86Mlrxmz1pBWq3bAHwETohxABVmDZADQp1BE+wDNnGywzHgmHDOreJNTDH3Xn3CVX0dpu2MHcIFBkYp/gKsQ8SCQ72V+36/+2aWf3kAQfgshnpXF0wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geoxtru', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABcSURBVBjTY2AgEmhpeZV56vmWwQW00QUYwAJlSAI6XmVqukh8PT1bT03PchhXX09Pr9wQIQDiJ+ZowgWAXD3bck+QQDlCQTkDQgCoxA/ERBKwhbDglgA1lDMQDwCc/Rvq8nYsWgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geobbox', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTVc+hEYAwDAXQLlNRF1tVGxn9NRswQiSSCdgDyQBM0FlIIb2WuL77uf6E8E0N02wKYRwDciTKREVvB04GuZSyOMCABRB1WGzF3uDNQTvs/RcDtJXT4fSEXA5XoiQt0ttVSm8Co2psIOvoimjAOqBmFtH5wEP2373TPIvTK1nrpULXAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geoconeseg', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB4SURBVBjTdc6hEcAgDAXQbFNZXHQkFlkd/30myAIMwAws0gmYpVzvoFyv/S5P/B+izzQ387ZA2pkDnvsU1SQLVIFrOM4JFmEaYp2gCQbmPEGODhJ8jt7Am47hwgrzInGAifa/elUZnQLY00iU30BZAV+BWi2VfnIBv1osbHH8jX0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTUtMTItMDJUMTQ6MjY6MjkrMDE6MDDARtd2AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE0LTExLTEyVDA4OjM5OjE5KzAxOjAwO3ydwwAAAABJRU5ErkJggg==')} -${img('geoeltu', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTdY+hFYUwDEU7xq9CIXC4uNjY6KczQXeoYgVMR2ABRmCGjvIp/6dgiEruueedvBDuOR57LQnKyc8CJmKO+N8bieIUPtmBWjIIx8XDBHYCipsnql1g2D0UP2OoDqwBncf+RdZmzFMHizRjog7KZYzawd4Ay93lEAPWR7WAvNbwMl/XwSxBV8qCjgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geomaterial', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAbElEQVQoz62QMRbAIAhDP319Xon7j54qHSyCtaMZFCUkRjgDIdRU9yZUCfg8ut5aAHdcxtoNurmgA3ABNKIR9KimhSukPe2qxcCYC0pfFXx/aFWo7i42KKItOpopqvvnLzJmtlZTS7EfGAfwAM4EQbLIGV0sAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geoparab', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTbY+xDYAwDAQ9UAp3X7p0m9o9dUZgA9oMwAjpMwMzMAnYBAQSX9mn9+tN9KOtzsWsLOvYCziUGNX3nnCLJRzKPgeYrhPW7FJNLUB3YJazYKQKTnBaxgXRzNmJcrt7XCHQp9kEB1wfELEir/KGj4Foh8A+/zW1nf51AFabKZuWK+mNAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geopgon', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAABwSURBVBjTY2AgDlwAAzh3sX1sPRDEeuwDc+8V2dsHgQQ8LCzq74HkLSzs7Yva2tLt7S3sN4MNiDUGKQmysCi6BzWkzcI+PdY+aDPCljZlj1iFOUjW1tvHLjYuQhJIt5/DcAFZYLH9YnSn7iPST9gAACbsJth21haFAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotorus', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTjY+hFcMwDEQ9SkFggXGIoejhw+LiGkBDlHoAr+AhgjNL5byChuXeE7gvPelUyjOds/f5Zw0ggfj5KVCPMBWeyx+SbQ1XUriAC2XfpWWxjQQEZasRtRHiCUAj3qN4JaolUJppzh4q7dUTdHFXW/tH9OuswWm3nI7tc08+/eGLl758ey9KpKrNOQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('geotrd1', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAAB/SURBVBjTbc6xDQMhDAVQ9qH6lUtal65/zQ5IDMAMmYAZrmKGm4FJzlEQQUo+bvwkG4fwm9lbodV7w40Y4WGfSxQiXiJlQfZOjWRb8Ioi3tKuBQMCo7+9N72BzPsfAuoTdUP9QN8wgOQwvsfWmHzpeT5BKydMNW0nhJGvGf7mAc5WKO9e5N2dAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTAyVDE0OjI2OjI5KzAxOjAwwEbXdgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0xMS0xMlQwODozOToxOSswMTowMDt8ncMAAAAASUVORK5CYII=')} -${img('geotube', 'CAAAAAA6mKC9AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJ0Uk5TAAB2k804AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAAAEgAAABIAEbJaz4AAACGSURBVBjTRc+tEcAwCAXgLFNbWeSzSDQazw5doWNUZIOM0BEyS/NHy10E30HyklKvWnJ+0le3sJoKn3X2z7GRuvG++YRyMMDt0IIKUXMzxbnugJi5m9K1gNnGBOUFElAWGMaKIKI4xoQggl00gT+A9hXWgDwnfqgsHRAx2m+8bfjfdyrx5AtsSjpwu+M2RgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0xMi0wMlQxNDoyNjoyOSswMTowMMBG13YAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMTEtMTJUMDg6Mzk6MTkrMDE6MDA7fJ3DAAAAAElFTkSuQmCC')} -${img('evepoints', 'BAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAABJQTFRF////n4mJcEdKRDMzcEdH////lLE/CwAAAAF0Uk5TAEDm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAABIAAAASABGyWs+AAAAI0lEQVQI12NgIAowIpgKEJIZLiAgAKWZGQzQ9UGlWIizBQgAN4IAvGtVrTcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTYtMDktMDJUMTU6MDQ6MzgrMDI6MDDPyc7hAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE2LTA5LTAyVDE1OjA0OjM4KzAyOjAwvpR2XQAAAABJRU5ErkJggg==')} -${img('evetrack', 'CAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAJiS0dEAP+Hj8y/AAAACXBIWXMAAABIAAAASABGyWs+AAAAqElEQVQoz32RMQrCQBBFf4IgSMB0IpGkMpVHCFh7BbHIGTyVhU0K8QYewEKsbVJZaCUiPAsXV8Puzhaz7H8zs5+JUDjikLilQr5zpCRl5xMXZNScQE5gSMGaz70jjUAJcw5c3UBMTsUe+9Kzf065SbropeLXimWfDIgoab/tOyPGzOhz53+oSWcSGh7UdB2ZNKXBZdgAuUdEKJYmrEILyVgG6pE2tEHgDfe42rbjYzSHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA5LTAyVDE1OjA0OjQ3KzAyOjAwM0S3EQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0wOS0wMlQxNTowNDo0NyswMjowMEIZD60AAAAASUVORK5CYII=')} -.jsroot .geovis_this { background-color: lightgreen; } -.jsroot .geovis_daughters { background-color: lightblue; } -.jsroot .geovis_all { background-color: yellow; }`); -} - - -/** @summary Create geo painter - * @private */ -function createGeoPainter(dom, obj, opt) { - injectGeoStyle(); - - geoCfg('GradPerSegm', settings.GeoGradPerSegm); - geoCfg('CompressComp', settings.GeoCompressComp); - - const painter = new TGeoPainter(dom, obj); - - painter.decodeOptions(opt); // indicator of initialization - - return painter; -} - - -/** @summary provide menu for geo object - * @private */ -function provideMenu(menu, item, hpainter) { - if (!item._geoobj) - return false; - - const obj = item._geoobj, vol = item._volume, - iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)); - - if (!vol && !iseve) - return false; - - menu.separator(); - - const scanEveVisible = (obj2, arg, skip_this) => { - if (!arg) - arg = { visible: 0, hidden: 0 }; - - if (!skip_this) { - if (arg.assign !== undefined) - obj2.fRnrSelf = arg.assign; - else if (obj2.fRnrSelf) - arg.vis++; - else - arg.hidden++; - } - - if (obj2.fElements) { - for (let n = 0; n < obj2.fElements.arr.length; ++n) - scanEveVisible(obj2.fElements.arr[n], arg, false); - } - - return arg; - }, toggleEveVisibility = arg => { - if (arg === 'self') { - obj.fRnrSelf = !obj.fRnrSelf; - item._icon = item._icon.split(' ')[0] + provideVisStyle(obj); - hpainter.updateTreeNode(item); - } else { - scanEveVisible(obj, { assign: (arg === 'true') }, true); - hpainter.forEachItem(m => { - // update all child items - if (m._geoobj && m._icon) { - m._icon = item._icon.split(' ')[0] + provideVisStyle(m._geoobj); - hpainter.updateTreeNode(m); - } - }, item); - } - - findItemWithGeoPainter(item, true); - }, toggleMenuBit = arg => { - toggleGeoBit(vol, arg); - const newname = item._icon.split(' ')[0] + provideVisStyle(vol); - hpainter.forEachItem(m => { - // update all items with that volume - if (item._volume === m._volume) { - m._icon = newname; - hpainter.updateTreeNode(m); - } - }); - - hpainter.updateTreeNode(item); - findItemWithGeoPainter(item, true); - }, drawitem = findItemWithGeoPainter(item), - fullname = drawitem ? hpainter.itemFullName(item, drawitem) : ''; - - if ((item._geoobj._typename.indexOf(clTGeoNode) === 0) && drawitem) { - menu.add('Focus', () => { - if (drawitem && isFunc(drawitem._painter?.focusOnItem)) - drawitem._painter.focusOnItem(fullname); - }); - } - - if (iseve) { - menu.addchk(obj.fRnrSelf, 'Visible', 'self', toggleEveVisibility); - const res = scanEveVisible(obj, undefined, true); - if (res.hidden + res.visible > 0) - menu.addchk((res.hidden === 0), 'Daughters', res.hidden ? 'true' : 'false', toggleEveVisibility); - } else { - const stack = drawitem?._painter?.getClones()?.findStackByName(fullname), - phys_vis = stack ? drawitem._painter.getClones().getPhysNodeVisibility(stack) : null, - is_visible = testGeoBit(vol, geoBITS.kVisThis); - - menu.addchk(testGeoBit(vol, geoBITS.kVisNone), 'Invisible', geoBITS.kVisNone, toggleMenuBit); - if (stack) { - const changePhysVis = arg => { - drawitem._painter.getClones().setPhysNodeVisibility(stack, (arg === 'off') ? false : arg); - findItemWithGeoPainter(item, true); - }; - - menu.sub('Physical vis', 'Physical node visibility - only for this instance'); - menu.addchk(phys_vis?.visible, 'on', 'on', changePhysVis, 'Enable visibility of phys node'); - menu.addchk(phys_vis && !phys_vis.visible, 'off', 'off', changePhysVis, 'Disable visibility of physical node'); - menu.add('reset', 'clear', changePhysVis, 'Reset custom visibility of physical node'); - menu.add('reset all', 'clearall', changePhysVis, 'Reset all custom settings for all nodes'); - menu.endsub(); - } - - menu.addchk(is_visible, 'Logical vis', - geoBITS.kVisThis, toggleMenuBit, 'Logical node visibility - all instances'); - menu.addchk(testGeoBit(vol, geoBITS.kVisDaughters), 'Daughters', - geoBITS.kVisDaughters, toggleMenuBit, 'Logical node daugthers visibility'); - } - - return true; -} - -let createItem = null; - -/** @summary create list entity for geo object - * @private */ -function createList(parent, lst, name, title) { - if (!lst?.arr?.length) - return; - - const list_item = { - _name: name, - _kind: getKindForType(clTList), - _title: title, - _more: true, - _geoobj: lst, - _parent: parent, - _get(item /* , itemname */) { - return Promise.resolve(item._geoobj || null); - }, - _expand(node, lst2) { - // only childs - - if (lst2.fVolume) - lst2 = lst2.fVolume.fNodes; - - if (!lst2.arr) - return false; - - node._childs = []; - - checkDuplicates(null, lst2.arr); - - for (const n in lst2.arr) - createItem(node, lst2.arr[n]); - - return true; - } - }; - - if (!parent._childs) - parent._childs = []; - parent._childs.push(list_item); -} - - -/** @summary Expand geo object - * @private */ -function expandGeoObject(parent, obj) { - injectGeoStyle(); - - if (!parent || !obj) - return false; - - const isnode = (obj._typename.indexOf(clTGeoNode) === 0), - isvolume = (obj._typename.indexOf(clTGeoVolume) === 0), - ismanager = (obj._typename === clTGeoManager), - iseve = ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)), - isoverlap = (obj._typename === clTGeoOverlap); - - if (!isnode && !isvolume && !ismanager && !iseve && !isoverlap) - return false; - - if (parent._childs) - return true; - - if (ismanager) { - createList(parent, obj.fMaterials, 'Materials', 'list of materials'); - createList(parent, obj.fMedia, 'Media', 'list of media'); - createList(parent, obj.fTracks, 'Tracks', 'list of tracks'); - createList(parent, obj.fOverlaps, 'Overlaps', 'list of detected overlaps'); - createItem(parent, obj.fMasterVolume); - return true; - } - - if (isoverlap) { - createItem(parent, obj.fVolume1); - createItem(parent, obj.fVolume2); - createItem(parent, obj.fMarker, 'Marker'); - return true; - } - - let volume, subnodes, shape; - - if (iseve) { - subnodes = obj.fElements?.arr; - shape = obj.fShape; - } else { - volume = isnode ? obj.fVolume : obj; - subnodes = volume?.fNodes?.arr; - shape = volume?.fShape; - } - - if (!subnodes && (shape?._typename === clTGeoCompositeShape) && shape?.fNode) { - if (!parent._childs) { - createItem(parent, shape.fNode.fLeft, 'Left'); - createItem(parent, shape.fNode.fRight, 'Right'); - } - - return true; - } - - if (!subnodes) - return false; - - checkDuplicates(obj, subnodes); - - for (let i = 0; i < subnodes.length; ++i) - createItem(parent, subnodes[i]); - - return true; -} - - -/** @summary create hierarchy item for geo object - * @private */ -createItem = function(node, obj, name) { - const sub = { - _kind: getKindForType(obj._typename), - _name: name || getObjectName(obj), - _title: obj.fTitle, - _parent: node, - _geoobj: obj, - _get(item /* ,itemname */) { - // mark object as belong to the hierarchy, require to - if (item._geoobj) - item._geoobj.$geoh = true; - return Promise.resolve(item._geoobj); - } - }; - let volume, shape, subnodes, iseve = false; - - if (obj._typename === 'TGeoMaterial') - sub._icon = 'img_geomaterial'; - else if (obj._typename === 'TGeoMedium') - sub._icon = 'img_geomedium'; - else if (obj._typename === 'TGeoMixture') - sub._icon = 'img_geomixture'; - else if ((obj._typename.indexOf(clTGeoNode) === 0) && obj.fVolume) { - sub._title = 'node:' + obj._typename; - if (obj.fTitle) - sub._title += ' ' + obj.fTitle; - volume = obj.fVolume; - } else if (obj._typename.indexOf(clTGeoVolume) === 0) - volume = obj; - else if ((obj._typename === clTEveGeoShapeExtract) || (obj._typename === clREveGeoShapeExtract)) { - iseve = true; - shape = obj.fShape; - subnodes = obj.fElements ? obj.fElements.arr : null; - } else if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) - shape = obj; - - if (volume) { - shape = volume.fShape; - subnodes = volume.fNodes ? volume.fNodes.arr : null; - } - - if (volume || shape || subnodes) { - if (volume) - sub._volume = volume; - - if (subnodes) { - sub._more = true; - sub._expand = expandGeoObject; - } else if (shape && (shape._typename === clTGeoCompositeShape) && shape.fNode) { - sub._more = true; - sub._shape = shape; - sub._expand = function(node2 /* , obj */) { - createItem(node2, node2._shape.fNode.fLeft, 'Left'); - createItem(node2, node2._shape.fNode.fRight, 'Right'); - return true; - }; - } - - if (!sub._title && (obj._typename !== clTGeoVolume)) - sub._title = obj._typename; - - if (shape) { - if (sub._title === '') - sub._title = shape._typename; - - sub._icon = getShapeIcon(shape); - } else - sub._icon = sub._more ? 'img_geocombi' : 'img_geobbox'; - - if (volume) - sub._icon += provideVisStyle(volume); - else if (iseve) - sub._icon += provideVisStyle(obj); - - sub._menu = provideMenu; - sub._icon_click = browserIconClick; - } - - if (!node._childs) - node._childs = []; - - if (!sub._name) { - if (isStr(node._name)) { - sub._name = node._name; - if (sub._name.at(-1) === 's') - sub._name = sub._name.slice(0, sub._name.length - 1); - sub._name += '_' + node._childs.length; - } else - sub._name = 'item_' + node._childs.length; - } - - node._childs.push(sub); - - return sub; -}; - -/** @summary Draw dummy geometry - * @private */ -async function drawDummy3DGeom(painter) { - const shape = create(clTNamed); - shape._typename = clTGeoBBox; - shape.fDX = 1e-10; - shape.fDY = 1e-10; - shape.fDZ = 1e-10; - shape.fShapeId = 1; - shape.fShapeBits = 0; - shape.fOrigin = [0, 0, 0]; - - const obj = Object.assign(create(clTNamed), { - _typename: clTEveGeoShapeExtract, - fTrans: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], - fShape: shape, fRGBA: [0, 0, 0, 0], fElements: null, fRnrSelf: false - }), - pp = painter.getPadPainter(), - pad = pp?.getRootPad(true), - opt = 'dummy;' + (pad?.fFillColor && (pad?.fFillStyle > 1000) ? 'bkgr_' + pad.fFillColor : ''); - - return TGeoPainter.draw(pp || painter.getDom(), obj, opt); -} - -/** @summary Direct draw function for TAxis3D - * @private */ -function drawAxis3D() { - const main = this.getMainPainter(); - - if (isFunc(main?.setAxesDraw)) - return main.setAxesDraw(true); - - console.error('no geometry painter found to toggle TAxis3D drawing'); -} - -/** @summary Build three.js model for given geometry object - * @param {object} obj - TGeo-related object - * @param {object} [opt] - options - * @param {number} [opt.vislevel] - visibility level like TGeoManager, when not specified - show all - * @param {number} [opt.numnodes=1000] - maximal number of visible nodes - * @param {number} [opt.numfaces=100000] - approx maximal number of created triangles - * @param {number} [opt.instancing=-1] - <0 disable use of InstancedMesh, =0 only for large geometries, >0 enforce usage of InstancedMesh - * @param {boolean} [opt.doubleside=false] - use double-side material - * @param {boolean} [opt.wireframe=false] - show wireframe for created shapes - * @param {boolean} [opt.transparency=0] - make nodes transparent - * @param {boolean} [opt.dflt_colors=false] - use default ROOT colors - * @param {boolean} [opt.set_names=true] - set names to all Object3D instances - * @param {boolean} [opt.set_origin=false] - set TGeoNode/TGeoVolume as Object3D.userData - * @return {object} Object3D with created model - * @example - * import { build } from 'https://root.cern/js/latest/modules/geom/TGeoPainter.mjs'; - * let obj3d = build(obj); - * // this is three.js object and can be now inserted in the scene - */ -function build(obj, opt) { - return TGeoPainter.build3d(obj, opt); -} - -export { ClonedNodes, build, TGeoPainter, GeoDrawingControl, - expandGeoObject, createGeoPainter, drawAxis3D, drawDummy3DGeom, produceRenderOrder }; diff --git a/js/modules/geom/bundle.mjs b/js/modules/geom/bundle.mjs deleted file mode 100644 index b2d425e744b60..0000000000000 --- a/js/modules/geom/bundle.mjs +++ /dev/null @@ -1,5 +0,0 @@ -// Only build function to create three.js model - -export { version, parse, settings, httpRequest } from '../core.mjs'; -export { openFile } from '../io.mjs'; -export { build, produceRenderOrder } from './TGeoPainter.mjs'; diff --git a/js/modules/geom/csg.mjs b/js/modules/geom/csg.mjs deleted file mode 100644 index 54df921476080..0000000000000 --- a/js/modules/geom/csg.mjs +++ /dev/null @@ -1,854 +0,0 @@ -// CSG library for THREE.js - -import { THREE } from '../base/base3d.mjs'; - -const EPSILON = 1e-5, - COPLANAR = 0, - FRONT = 1, - BACK = 2, - SPANNING = FRONT | BACK; - -class Vertex { - - constructor(x, y, z, nx, ny, nz) { - this.x = x; - this.y = y; - this.z = z; - this.nx = nx; - this.ny = ny; - this.nz = nz; - } - - setnormal(nx, ny, nz) { - this.nx = nx; - this.ny = ny; - this.nz = nz; - } - - clone() { - return new Vertex(this.x, this.y, this.z, this.nx, this.ny, this.nz); - } - - add(vertex) { - this.x += vertex.x; - this.y += vertex.y; - this.z += vertex.z; - return this; - } - - subtract(vertex) { - this.x -= vertex.x; - this.y -= vertex.y; - this.z -= vertex.z; - return this; - } - - // multiplyScalar( scalar ) { - // this.x *= scalar; - // this.y *= scalar; - // this.z *= scalar; - // return this; - // } - - // cross( vertex ) { - // let x = this.x, y = this.y, z = this.z, - // vx = vertex.x, vy = vertex.y, vz = vertex.z; - // - // this.x = y * vz - z * vy; - // this.y = z * vx - x * vz; - // this.z = x * vy - y * vx; - // - // return this; - // } - - cross3(vx, vy, vz) { - const x = this.x, y = this.y, z = this.z; - - this.x = y * vz - z * vy; - this.y = z * vx - x * vz; - this.z = x * vy - y * vx; - - return this; - } - - - normalize() { - const length = Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2); - - this.x /= length; - this.y /= length; - this.z /= length; - - return this; - } - - dot(vertex) { - return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; - } - - diff(vertex) { - const dx = (this.x - vertex.x), - dy = (this.y - vertex.y), - dz = (this.z - vertex.z), - len2 = this.x ** 2 + this.y ** 2 + this.z ** 2; - - return (dx ** 2 + dy ** 2 + dz ** 2) / (len2 > 0 ? len2 : 1e-10); - } - - /* - lerp( a, t ) { - this.add( - a.clone().subtract( this ).multiplyScalar( t ) - ); - - this.normal.add( - a.normal.clone().sub( this.normal ).multiplyScalar( t ) - ); - - //this.uv.add( - // a.uv.clone().sub( this.uv ).multiplyScalar( t ) - //); - - return this; - }; - - interpolate( other, t ) { - return this.clone().lerp( other, t ); - }; -*/ - - interpolate(a, t) { - const t1 = 1 - t; - return new Vertex(this.x * t1 + a.x * t, this.y * t1 + a.y * t, this.z * t1 + a.z * t, - this.nx * t1 + a.nx * t, this.ny * t1 + a.ny * t, this.nz * t1 + a.nz * t); - } - - applyMatrix4(m) { - // input: Matrix4 affine matrix - - let x = this.x, y = this.y, z = this.z; - const e = m.elements; - - this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; - this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; - this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; - - x = this.nx; - y = this.ny; - z = this.nz; - - this.nx = e[0] * x + e[4] * y + e[8] * z; - this.ny = e[1] * x + e[5] * y + e[9] * z; - this.nz = e[2] * x + e[6] * y + e[10] * z; - - return this; - } - -} // class Vertex - - -class Polygon { - - constructor(vertices, parent, more) { - this.vertices = vertices || []; - this.nsign = 1; - if (parent) - this.copyProperties(parent, more); - else if (this.vertices.length) - this.calculateProperties(); - } - - copyProperties(parent, more) { - this.normal = parent.normal; // .clone(); - this.w = parent.w; - this.nsign = parent.nsign; - if (more && (parent.id !== undefined)) { - this.id = parent.id; - this.parent = parent; - } - return this; - } - - calculateProperties(force) { - if (this.normal && !force) - return; - - const a = this.vertices[0], - b = this.vertices[1], - c = this.vertices[2]; - - this.nsign = 1; - - // this.normal = b.clone().subtract(a).cross(c.clone().subtract(a)).normalize(); - - this.normal = new Vertex(b.x - a.x, b.y - a.y, b.z - a.z, 0, 0, 0).cross3(c.x - a.x, c.y - a.y, c.z - a.z).normalize(); - - this.w = this.normal.dot(a); - return this; - } - - clone() { - const vertice_count = this.vertices.length, - vertices = []; - - for (let i = 0; i < vertice_count; ++i) - vertices.push(this.vertices[i].clone()); - - return new Polygon(vertices, this); - } - - flip() { - // normal is not changed, only sign variable - // this.normal.multiplyScalar( -1 ); - // this.w *= -1; - - this.nsign *= -1; - - this.vertices.reverse(); - - return this; - } - - classifyVertex(vertex) { - const side_value = this.nsign * (this.normal.dot(vertex) - this.w); - if (side_value < -EPSILON) - return BACK; - if (side_value > EPSILON) - return FRONT; - return COPLANAR; - } - - classifySide(polygon) { - let num_positive = 0, num_negative = 0; - const vertice_count = polygon.vertices.length; - - for (let i = 0; i < vertice_count; ++i) { - const classification = this.classifyVertex(polygon.vertices[i]); - if (classification === FRONT) - ++num_positive; - else if (classification === BACK) - ++num_negative; - } - - if (num_positive > 0 && num_negative === 0) - return FRONT; - if (num_positive === 0 && num_negative > 0) - return BACK; - if (num_positive === 0 && num_negative === 0) - return COPLANAR; - return SPANNING; - } - - splitPolygon(polygon, coplanar_front, coplanar_back, front, back) { - const classification = this.classifySide(polygon); - - if (classification === COPLANAR) - - ((this.nsign * polygon.nsign * this.normal.dot(polygon.normal) > 0) ? coplanar_front : coplanar_back).push(polygon); - - else if (classification === FRONT) - - front.push(polygon); - - else if (classification === BACK) - - back.push(polygon); - - else { - const vertice_count = polygon.vertices.length, - nnx = this.normal.x, - nny = this.normal.y, - nnz = this.normal.z, - f = [], b = []; - let i, j, ti, tj, vi, vj, t, v; - - for (i = 0; i < vertice_count; ++i) { - j = (i + 1) % vertice_count; - vi = polygon.vertices[i]; - vj = polygon.vertices[j]; - ti = this.classifyVertex(vi); - tj = this.classifyVertex(vj); - - if (ti !== BACK) - f.push(vi); - if (ti !== FRONT) - b.push(vi); - if ((ti | tj) === SPANNING) { - // t = (this.w - this.normal.dot(vi))/this.normal.dot(vj.clone().subtract(vi)); - // v = vi.clone().lerp( vj, t ); - - t = (this.w - (nnx * vi.x + nny * vi.y + nnz * vi.z)) / (nnx * (vj.x - vi.x) + nny * (vj.y - vi.y) + nnz * (vj.z - vi.z)); - - v = vi.interpolate(vj, t); - f.push(v); - b.push(v); - } - } - - // if ( f.length >= 3 ) - // front.push(new Polygon(f).calculateProperties()); - // if ( b.length >= 3 ) - // back.push(new Polygon(b).calculateProperties()); - if (f.length >= 3) - front.push(new Polygon(f, polygon, true)); - if (b.length >= 3) - back.push(new Polygon(b, polygon, true)); - } - } - -} // class Polygon - - -class Node { - - constructor(polygons, nodeid) { - this.polygons = []; - this.front = this.back = undefined; - - if (!polygons) - return; - - this.divider = polygons[0].clone(); - - const polygon_count = polygons.length, - front = [], back = []; - - for (let i = 0; i < polygon_count; ++i) { - if (nodeid !== undefined) { - polygons[i].id = nodeid++; - delete polygons[i].parent; - } - - // by definition polygon should be COPLANAR for itself - if (i === 0) - this.polygons.push(polygons[0]); - else - this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); - } - - if (nodeid !== undefined) - this.maxnodeid = nodeid; - - if (front.length) - this.front = new Node(front); - - if (back.length) - this.back = new Node(back); - } - - // isConvex(polygons) { - // let i, j, len = polygons.length; - // for ( i = 0; i < len; ++i ) - // for ( j = 0; j < len; ++j ) - // if ( i !== j && polygons[i].classifySide( polygons[j] ) !== BACK ) - // return false; - // return true; - // } - - build(polygons) { - const polygon_count = polygons.length, - front = [], back = []; - let first = 0; - - if (!this.divider) { - this.divider = polygons[0].clone(); - this.polygons.push(polygons[0]); - first = 1; - } - - for (let i = first; i < polygon_count; ++i) - this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); - - if (front.length) { - if (!this.front) - this.front = new Node(); - this.front.build(front); - } - - if (back.length) { - if (!this.back) - this.back = new Node(); - this.back.build(back); - } - } - - collectPolygons(arr) { - if (arr === undefined) - arr = []; - const len = this.polygons.length; - for (let i = 0; i < len; ++i) - arr.push(this.polygons[i]); - this.front?.collectPolygons(arr); - this.back?.collectPolygons(arr); - return arr; - } - - numPolygons() { - return this.polygons.length + (this.front?.numPolygons() || 0) + (this.back?.numPolygons() || 0); - } - - clone() { - const node = new Node(); - - node.divider = this.divider?.clone(); - node.polygons = this.polygons.map(polygon => polygon.clone()); - node.front = this.front?.clone(); - node.back = this.back?.clone(); - - return node; - } - - invert() { - const polygon_count = this.polygons.length; - - for (let i = 0; i < polygon_count; ++i) - this.polygons[i].flip(); - - this.divider.flip(); - if (this.front) - this.front.invert(); - if (this.back) - this.back.invert(); - - const temp = this.front; - this.front = this.back; - this.back = temp; - - return this; - } - - clipPolygons(polygons) { - if (!this.divider) - return polygons.slice(); - - const polygon_count = polygons.length; - let front = [], back = []; - - for (let i = 0; i < polygon_count; ++i) - this.divider.splitPolygon(polygons[i], front, back, front, back); - - if (this.front) - front = this.front.clipPolygons(front); - - back = this.back?.clipPolygons(back) ?? []; - - return front.concat(back); - } - - clipTo(node) { - this.polygons = node.clipPolygons(this.polygons); - this.front?.clipTo(node); - this.back?.clipTo(node); - } - -} // class Node - - -function createBufferGeometry(polygons) { - const polygon_count = polygons.length; - let i, j, buf_size = 0; - - for (i = 0; i < polygon_count; ++i) - buf_size += (polygons[i].vertices.length - 2) * 9; - - const positions_buf = new Float32Array(buf_size), - normals_buf = new Float32Array(buf_size); - let iii = 0, polygon; - - function CopyVertex(vertex) { - positions_buf[iii] = vertex.x; - positions_buf[iii + 1] = vertex.y; - positions_buf[iii + 2] = vertex.z; - - normals_buf[iii] = polygon.nsign * vertex.nx; - normals_buf[iii + 1] = polygon.nsign * vertex.ny; - normals_buf[iii + 2] = polygon.nsign * vertex.nz; - iii += 3; - } - - for (i = 0; i < polygon_count; ++i) { - polygon = polygons[i]; - for (j = 2; j < polygon.vertices.length; ++j) { - CopyVertex(polygon.vertices[0]); - CopyVertex(polygon.vertices[j - 1]); - CopyVertex(polygon.vertices[j]); - } - } - - const geometry = new THREE.BufferGeometry(); - geometry.setAttribute('position', new THREE.BufferAttribute(positions_buf, 3)); - geometry.setAttribute('normal', new THREE.BufferAttribute(normals_buf, 3)); - - // geometry.computeVertexNormals(); - return geometry; -} - - -class Geometry { - - constructor(geometry, transfer_matrix, nodeid, flippedMesh) { - // Convert BufferGeometry to ThreeBSP - - if (geometry instanceof THREE.Mesh) { - // #todo: add hierarchy support - geometry.updateMatrix(); - transfer_matrix = this.matrix = geometry.matrix.clone(); - geometry = geometry.geometry; - } else if (geometry instanceof Node) { - this.tree = geometry; - this.matrix = null; // new Matrix4; - return; - } else if (geometry instanceof THREE.BufferGeometry) { - const pos_buf = geometry.getAttribute('position').array, - norm_buf = geometry.getAttribute('normal').array, - polygons = []; - let polygon, vert1, vert2, vert3; - - for (let i = 0; i < pos_buf.length; i += 9) { - polygon = new Polygon(); - - vert1 = new Vertex(pos_buf[i], pos_buf[i + 1], pos_buf[i + 2], norm_buf[i], norm_buf[i + 1], norm_buf[i + 2]); - if (transfer_matrix) - vert1.applyMatrix4(transfer_matrix); - - vert2 = new Vertex(pos_buf[i + 3], pos_buf[i + 4], pos_buf[i + 5], norm_buf[i + 3], norm_buf[i + 4], norm_buf[i + 5]); - if (transfer_matrix) - vert2.applyMatrix4(transfer_matrix); - - vert3 = new Vertex(pos_buf[i + 6], pos_buf[i + 7], pos_buf[i + 8], norm_buf[i + 6], norm_buf[i + 7], norm_buf[i + 8]); - if (transfer_matrix) - vert3.applyMatrix4(transfer_matrix); - - if (flippedMesh) - polygon.vertices.push(vert1, vert3, vert2); - else - polygon.vertices.push(vert1, vert2, vert3); - - polygon.calculateProperties(true); - polygons.push(polygon); - } - - this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) - this.maxid = this.tree.maxnodeid; - return; - } else if (geometry.polygons && (geometry.polygons[0] instanceof Polygon)) { - const polygons = geometry.polygons; - - for (let i = 0; i < polygons.length; ++i) { - const polygon = polygons[i]; - if (transfer_matrix) { - const new_vertices = []; - - for (let n = 0; n < polygon.vertices.length; ++n) - new_vertices.push(polygon.vertices[n].clone().applyMatrix4(transfer_matrix)); - - polygon.vertices = new_vertices; - } - - polygon.calculateProperties(transfer_matrix); - } - - this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) - this.maxid = this.tree.maxnodeid; - return; - } else - throw Error('ThreeBSP: Given geometry is unsupported'); - - const polygons = [], nfaces = geometry.faces.length; - let face, polygon, vertex, normal, useVertexNormals; - - for (let i = 0; i < nfaces; ++i) { - face = geometry.faces[i]; - normal = face.normal; - // faceVertexUvs = geometry.faceVertexUvs[0][i]; - polygon = new Polygon(); - - useVertexNormals = face.vertexNormals && (face.vertexNormals.length === 3); - - vertex = geometry.vertices[face.a]; - if (useVertexNormals) - normal = face.vertexNormals[0]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; - vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) - vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - vertex = geometry.vertices[face.b]; - if (useVertexNormals) - normal = face.vertexNormals[1]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; - vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) - vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - vertex = geometry.vertices[face.c]; - if (useVertexNormals) - normal = face.vertexNormals[2]; - // uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; - vertex = new Vertex(vertex.x, vertex.y, vertex.z, normal.x, normal.y, normal.z /* face.normal, uvs */); - if (transfer_matrix) - vertex.applyMatrix4(transfer_matrix); - polygon.vertices.push(vertex); - - polygon.calculateProperties(true); - polygons.push(polygon); - } - - this.tree = new Node(polygons, nodeid); - if (nodeid !== undefined) - this.maxid = this.tree.maxnodeid; - } - - subtract(other_tree) { - let a = this.tree.clone(); - const b = other_tree.tree.clone(); - - a.invert(); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons()); - a.invert(); - a = new Geometry(a); - a.matrix = this.matrix; - return a; - } - - union(other_tree) { - let a = this.tree.clone(); - const b = other_tree.tree.clone(); - - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons()); - a = new Geometry(a); - a.matrix = this.matrix; - return a; - } - - intersect(other_tree) { - let a = this.tree.clone(); - const b = other_tree.tree.clone(); - - a.invert(); - b.clipTo(a); - b.invert(); - a.clipTo(b); - b.clipTo(a); - a.build(b.collectPolygons()); - a.invert(); - a = new Geometry(a); - a.matrix = this.matrix; - return a; - } - - tryToCompress(polygons) { - if (this.maxid === undefined) - return; - - const arr = []; - let parts, foundpair, - nreduce = 0, n, len = polygons.length, - p, p1, p2, i1, i2; - - // sort out polygons - for (n = 0; n < len; ++n) { - p = polygons[n]; - if (p.id === undefined) - continue; - if (arr[p.id] === undefined) - arr[p.id] = []; - - arr[p.id].push(p); - } - - for (n = 0; n < arr.length; ++n) { - parts = arr[n]; - if (parts === undefined) - continue; - - len = parts.length; - - foundpair = (len > 1); - - while (foundpair) { - foundpair = false; - - for (i1 = 0; i1 < len - 1; ++i1) { - p1 = parts[i1]; - if (!p1?.parent) - continue; - for (i2 = i1 + 1; i2 < len; ++i2) { - p2 = parts[i2]; - if (p2 && (p1.parent === p2.parent) && (p1.nsign === p2.nsign)) { - if (p1.nsign !== p1.parent.nsign) - p1.parent.flip(); - nreduce++; - parts[i1] = p1.parent; - parts[i2] = null; - if (p1.parent.vertices.length < 3) - console.log('something wrong with parent'); - foundpair = true; - break; - } - } - } - } - } - - if (nreduce > 0) { - polygons.splice(0, polygons.length); - - for (n = 0; n < arr.length; ++n) { - parts = arr[n]; - if (parts !== undefined) { - for (i1 = 0, len = parts.length; i1 < len; ++i1) { - if (parts[i1]) - polygons.push(parts[i1]); - } - } - } - } - } - - direct_subtract(other_tree) { - const a = this.tree, - b = other_tree.tree; - a.invert(); - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons()); - a.invert(); - return this; - } - - direct_union(other_tree) { - const a = this.tree, - b = other_tree.tree; - - a.clipTo(b); - b.clipTo(a); - b.invert(); - b.clipTo(a); - b.invert(); - a.build(b.collectPolygons()); - return this; - } - - direct_intersect(other_tree) { - const a = this.tree, - b = other_tree.tree; - - a.invert(); - b.clipTo(a); - b.invert(); - a.clipTo(b); - b.clipTo(a); - a.build(b.collectPolygons()); - a.invert(); - return this; - } - - cut_from_plane(other_tree) { - // just cut peaces from second geometry, which just simple plane - - const a = this.tree, - b = other_tree.tree; - - a.invert(); - b.clipTo(a); - - return this; - } - - scale(x, y, z) { - // try to scale as BufferGeometry - const polygons = this.tree.collectPolygons(); - - for (let i = 0; i < polygons.length; ++i) { - const polygon = polygons[i]; - for (let k = 0; k < polygon.vertices.length; ++k) { - const v = polygon.vertices[k]; - v.x *= x; - v.y *= y; - v.z *= z; - } - polygon.calculateProperties(true); - } - } - - toPolygons() { - const polygons = this.tree.collectPolygons(); - - this.tryToCompress(polygons); - - for (let i = 0; i < polygons.length; ++i) { - delete polygons[i].id; - delete polygons[i].parent; - } - - return polygons; - } - - toBufferGeometry() { - return createBufferGeometry(this.toPolygons()); - } - - toMesh(material) { - const geometry = this.toBufferGeometry(), - mesh = new THREE.Mesh(geometry, material); - - if (this.matrix) { - mesh.position.setFromMatrixPosition(this.matrix); - mesh.rotation.setFromRotationMatrix(this.matrix); - } - - return mesh; - } - -} // class Geometry - -/** @summary create geometry to make cut on specified axis - * @private */ -function createNormal(axis_name, pos, size) { - if (!size || (size < 10000)) - size = 10000; - - let vertices; - - switch (axis_name) { - case 'x': - vertices = [new Vertex(pos, -3 * size, size, 1, 0, 0), - new Vertex(pos, size, -3 * size, 1, 0, 0), - new Vertex(pos, size, size, 1, 0, 0)]; - break; - case 'y': - vertices = [new Vertex(-3 * size, pos, size, 0, 1, 0), - new Vertex(size, pos, size, 0, 1, 0), - new Vertex(size, pos, -3 * size, 0, 1, 0)]; - break; - // case 'z': - default: - vertices = [new Vertex(-3 * size, size, pos, 0, 0, 1), - new Vertex(size, -3 * size, pos, 0, 0, 1), - new Vertex(size, size, pos, 0, 0, 1)]; - } - - const node = new Node([new Polygon(vertices)]); - - return new Geometry(node); -} - -export { createBufferGeometry, createNormal, Vertex, Geometry, Polygon }; diff --git a/js/modules/geom/geobase.mjs b/js/modules/geom/geobase.mjs deleted file mode 100644 index 97e51dd99f0ca..0000000000000 --- a/js/modules/geom/geobase.mjs +++ /dev/null @@ -1,4353 +0,0 @@ -import { isObject, isFunc, isStr, BIT } from '../core.mjs'; -import { getRootColors } from '../base/colors.mjs'; -import { THREE } from '../base/base3d.mjs'; -import { createBufferGeometry, createNormal, - Vertex as CsgVertex, Geometry as CsgGeometry, Polygon as CsgPolygon } from './csg.mjs'; - -const _cfg = { - GradPerSegm: 6, // grad per segment in cylinder/spherical symmetry shapes - CompressComp: true // use faces compression in composite shapes -}, kShapeType = '$$Shape$$'; - -/** @summary Returns or set geometry config values - * @desc Supported 'GradPerSegm' and 'CompressComp' - * @private */ - -function geoCfg(name, value) { - if (value === undefined) - return _cfg[name]; - - _cfg[name] = value; -} - -const kindGeo = 0, // TGeoNode / TGeoShape - kindEve = 1, // TEveShape / TEveGeoShapeExtract - kindShape = 2, // special kind for single shape handling - - kGetMesh = 'mesh', // return mesh from createObject3D - kDeleteMesh = 'delete_mesh', // delete mesh in createObject3D - - /** @summary TGeo-related bits - * @private */ - geoBITS = { - kVisOverride: BIT(0), // volume's vis. attributes are overwritten - kVisNone: BIT(1), // the volume/node is invisible, as well as daughters - kVisThis: BIT(2), // this volume/node is visible - kVisDaughters: BIT(3), // all leaves are visible - kVisOneLevel: BIT(4), // first level daughters are visible (not used) - kVisStreamed: BIT(5), // true if attributes have been streamed - kVisTouched: BIT(6), // true if attributes are changed after closing geom - kVisOnScreen: BIT(7), // true if volume is visible on screen - kVisContainers: BIT(12), // all containers visible - kVisOnly: BIT(13), // just this visible - kVisBranch: BIT(14), // only a given branch visible - kVisRaytrace: BIT(15) // raytracing flag - }, - - clTGeoBBox = 'TGeoBBox', - clTGeoArb8 = 'TGeoArb8', - clTGeoCone = 'TGeoCone', - clTGeoConeSeg = 'TGeoConeSeg', - clTGeoTube = 'TGeoTube', - clTGeoTubeSeg = 'TGeoTubeSeg', - clTGeoCtub = 'TGeoCtub', - clTGeoTrd1 = 'TGeoTrd1', - clTGeoTrd2 = 'TGeoTrd2', - clTGeoPara = 'TGeoPara', - clTGeoParaboloid = 'TGeoParaboloid', - clTGeoPcon = 'TGeoPcon', - clTGeoPgon = 'TGeoPgon', - clTGeoShapeAssembly = 'TGeoShapeAssembly', - clTGeoSphere = 'TGeoSphere', - clTGeoTorus = 'TGeoTorus', - clTGeoXtru = 'TGeoXtru', - clTGeoTrap = 'TGeoTrap', - clTGeoGtra = 'TGeoGtra', - clTGeoEltu = 'TGeoEltu', - clTGeoHype = 'TGeoHype', - clTGeoCompositeShape = 'TGeoCompositeShape', - clTGeoHalfSpace = 'TGeoHalfSpace', - clTGeoScaledShape = 'TGeoScaledShape'; - -/** @summary Test fGeoAtt bits - * @private */ -function testGeoBit(volume, f) { - const att = volume.fGeoAtt; - return att === undefined ? false : Boolean(att & f); -} - -/** @summary Set fGeoAtt bit - * @private */ -function setGeoBit(volume, f, value) { - if (volume.fGeoAtt !== undefined) - volume.fGeoAtt = value ? (volume.fGeoAtt | f) : (volume.fGeoAtt & ~f); -} - -/** @summary Toggle fGeoAttBit - * @private */ -function toggleGeoBit(volume, f) { - if (volume.fGeoAtt !== undefined) - volume.fGeoAtt ^= f & 0xffffff; -} - -/** @summary Implementation of TGeoVolume::InvisibleAll - * @private */ -function setInvisibleAll(volume, flag = true) { - setGeoBit(volume, geoBITS.kVisThis, !flag); - // setGeoBit(this, geoBITS.kVisDaughters, !flag); - - if (volume.fNodes) { - for (let n = 0; n < volume.fNodes.arr.length; ++n) { - const sub = volume.fNodes.arr[n].fVolume; - setGeoBit(sub, geoBITS.kVisThis, !flag); - // setGeoBit(sub, geoBITS.kVisDaughters, !flag); - } - } -} - -const _warn_msgs = {}; - -/** @summary method used to avoid duplication of warnings - * @private */ -function geoWarn(msg) { - if (_warn_msgs[msg] === undefined) { - _warn_msgs[msg] = true; - console.warn(msg); - } -} - -/** @summary Analyze TGeo node kind - * @desc 0 - TGeoNode - * 1 - TEveGeoNode - * -1 - unsupported - * @return detected node kind - * @private */ -function getNodeKind(obj) { - if (!isObject(obj)) - return -1; - return ('fShape' in obj) && ('fTrans' in obj) ? kindEve : kindGeo; -} - -/** @summary Returns number of shapes - * @desc Used to count total shapes number in composites - * @private */ -function countNumShapes(shape) { - if (!shape) - return 0; - if (shape._typename !== clTGeoCompositeShape) - return 1; - return countNumShapes(shape.fNode.fLeft) + countNumShapes(shape.fNode.fRight); -} - - -/** @summary Returns geo object name - * @desc Can appends some special suffixes - * @private */ -function getObjectName(obj) { - return obj?.fName ? (obj.fName + (obj.$geo_suffix || '')) : ''; -} - -/** @summary Check duplicates - * @private */ -function checkDuplicates(parent, chlds) { - if (parent) { - if (parent.$geo_checked) - return; - parent.$geo_checked = true; - } - - const names = [], cnts = []; - for (let k = 0; k < chlds.length; ++k) { - const chld = chlds[k]; - if (!chld?.fName) - continue; - if (!chld.$geo_suffix) { - const indx = names.indexOf(chld.fName); - if (indx >= 0) { - let cnt = cnts[indx] || 1; - while (names.indexOf(chld.fName + '#' + cnt) >= 0) ++cnt; - chld.$geo_suffix = '#' + cnt; - cnts[indx] = cnt + 1; - } - } - names.push(getObjectName(chld)); - } -} - - -/** @summary Create normal to plane, defined with three points - * @private */ -function produceNormal(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - const pA = new THREE.Vector3(x1, y1, z1), - pB = new THREE.Vector3(x2, y2, z2), - pC = new THREE.Vector3(x3, y3, z3), - cb = new THREE.Vector3(), - ab = new THREE.Vector3(); - - cb.subVectors(pC, pB); - ab.subVectors(pA, pB); - cb.cross(ab); - - return cb; -} - -// ========================================================================== - -/** - * @summary Helper class for geometry creation - * - * @private - */ - -class GeometryCreator { - - /** @summary Constructor - * @param {number} numfaces - number of faces */ - constructor(numfaces) { - this.nfaces = numfaces; - this.indx = 0; - this.pos = new Float32Array(numfaces * 9); - this.norm = new Float32Array(numfaces * 9); - } - - /** @summary Add face with 3 vertices */ - addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - const indx = this.indx, pos = this.pos; - pos[indx] = x1; - pos[indx + 1] = y1; - pos[indx + 2] = z1; - pos[indx + 3] = x2; - pos[indx + 4] = y2; - pos[indx + 5] = z2; - pos[indx + 6] = x3; - pos[indx + 7] = y3; - pos[indx + 8] = z3; - this.last4 = false; - this.indx = indx + 9; - } - - /** @summary Start polygon */ - startPolygon() {} - - /** @summary Stop polygon */ - stopPolygon() {} - - /** @summary Add face with 4 vertices - * @desc From four vertices one normally creates two faces (1,2,3) and (1,3,4) - * if (reduce === 1), first face is reduced - * if (reduce === 2), second face is reduced */ - addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce) { - let indx = this.indx; - const pos = this.pos; - - if (reduce !== 1) { - pos[indx] = x1; - pos[indx + 1] = y1; - pos[indx + 2] = z1; - pos[indx + 3] = x2; - pos[indx + 4] = y2; - pos[indx + 5] = z2; - pos[indx + 6] = x3; - pos[indx + 7] = y3; - pos[indx + 8] = z3; - indx += 9; - } - - if (reduce !== 2) { - pos[indx] = x1; - pos[indx + 1] = y1; - pos[indx + 2] = z1; - pos[indx + 3] = x3; - pos[indx + 4] = y3; - pos[indx + 5] = z3; - pos[indx + 6] = x4; - pos[indx + 7] = y4; - pos[indx + 8] = z4; - indx += 9; - } - - this.last4 = (indx !== this.indx + 9); - this.indx = indx; - } - - /** @summary Specify normal for face with 4 vertices - * @desc same as addFace4, assign normals for each individual vertex - * reduce has same meaning and should be the same */ - setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4, reduce) { - if (this.last4 && reduce) - return console.error('missmatch between addFace4 and setNormal4 calls'); - - let indx = this.indx - (this.last4 ? 18 : 9); - const norm = this.norm; - - if (reduce !== 1) { - norm[indx] = nx1; - norm[indx + 1] = ny1; - norm[indx + 2] = nz1; - norm[indx + 3] = nx2; - norm[indx + 4] = ny2; - norm[indx + 5] = nz2; - norm[indx + 6] = nx3; - norm[indx + 7] = ny3; - norm[indx + 8] = nz3; - indx += 9; - } - - if (reduce !== 2) { - norm[indx] = nx1; - norm[indx + 1] = ny1; - norm[indx + 2] = nz1; - norm[indx + 3] = nx3; - norm[indx + 4] = ny3; - norm[indx + 5] = nz3; - norm[indx + 6] = nx4; - norm[indx + 7] = ny4; - norm[indx + 8] = nz4; - } - } - - /** @summary Recalculate Z with provided func */ - recalcZ(func) { - const pos = this.pos, - last = this.indx; - let indx = last - (this.last4 ? 18 : 9); - - while (indx < last) { - pos[indx + 2] = func(pos[indx], pos[indx + 1], pos[indx + 2]); - indx += 3; - } - } - - /** @summary Calculate normal */ - calcNormal() { - if (!this.cb) { - this.pA = new THREE.Vector3(); - this.pB = new THREE.Vector3(); - this.pC = new THREE.Vector3(); - this.cb = new THREE.Vector3(); - this.ab = new THREE.Vector3(); - } - - this.pA.fromArray(this.pos, this.indx - 9); - this.pB.fromArray(this.pos, this.indx - 6); - this.pC.fromArray(this.pos, this.indx - 3); - - this.cb.subVectors(this.pC, this.pB); - this.ab.subVectors(this.pA, this.pB); - this.cb.cross(this.ab); - - this.setNormal(this.cb.x, this.cb.y, this.cb.z); - } - - /** @summary Set normal */ - setNormal(nx, ny, nz) { - let indx = this.indx - 9; - const norm = this.norm; - - norm[indx] = norm[indx + 3] = norm[indx + 6] = nx; - norm[indx + 1] = norm[indx + 4] = norm[indx + 7] = ny; - norm[indx + 2] = norm[indx + 5] = norm[indx + 8] = nz; - - if (this.last4) { - indx -= 9; - norm[indx] = norm[indx + 3] = norm[indx + 6] = nx; - norm[indx + 1] = norm[indx + 4] = norm[indx + 7] = ny; - norm[indx + 2] = norm[indx + 5] = norm[indx + 8] = nz; - } - } - - /** @summary Set normal - * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ - setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34, reduce = 0) { - let indx = this.indx - ((reduce > 0) ? 9 : 18); - const norm = this.norm; - - if (reduce !== 1) { - norm[indx] = nx12; - norm[indx + 1] = ny12; - norm[indx + 2] = nz12; - norm[indx + 3] = nx12; - norm[indx + 4] = ny12; - norm[indx + 5] = nz12; - norm[indx + 6] = nx34; - norm[indx + 7] = ny34; - norm[indx + 8] = nz34; - indx += 9; - } - - if (reduce !== 2) { - norm[indx] = nx12; - norm[indx + 1] = ny12; - norm[indx + 2] = nz12; - norm[indx + 3] = nx34; - norm[indx + 4] = ny34; - norm[indx + 5] = nz34; - norm[indx + 6] = nx34; - norm[indx + 7] = ny34; - norm[indx + 8] = nz34; - } - } - - /** @summary Create geometry */ - create() { - if (this.nfaces !== this.indx / 9) - console.error(`Mismatch with created ${this.nfaces} and filled ${this.indx / 9} number of faces`); - - const geometry = new THREE.BufferGeometry(); - geometry.setAttribute('position', new THREE.BufferAttribute(this.pos, 3)); - geometry.setAttribute('normal', new THREE.BufferAttribute(this.norm, 3)); - return geometry; - } - -} - -// ================================================================================ - -/** @summary Helper class for CsgGeometry creation - * - * @private - */ - -class PolygonsCreator { - - /** @summary constructor */ - constructor() { - this.polygons = []; - } - - /** @summary Start polygon */ - startPolygon(normal) { - this.multi = 1; - this.mnormal = normal; - } - - /** @summary Stop polygon */ - stopPolygon() { - if (!this.multi) - return; - this.multi = 0; - console.error('Polygon should be already closed at this moment'); - } - - /** @summary Add face with 3 vertices */ - addFace3(x1, y1, z1, x2, y2, z2, x3, y3, z3) { - this.addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x3, y3, z3, 2); - } - - /** @summary Add face with 4 vertices - * @desc From four vertices one normally creates two faces (1,2,3) and (1,3,4) - * if (reduce === 1), first face is reduced - * if (reduce === 2), second face is reduced */ - addFace4(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, reduce = 0) { - this.v1 = new CsgVertex(x1, y1, z1, 0, 0, 0); - this.v2 = (reduce === 1) ? null : new CsgVertex(x2, y2, z2, 0, 0, 0); - this.v3 = new CsgVertex(x3, y3, z3, 0, 0, 0); - this.v4 = (reduce === 2) ? null : new CsgVertex(x4, y4, z4, 0, 0, 0); - this.reduce = reduce; - - if (this.multi) { - if (reduce !== 2) - console.error('polygon not supported for not-reduced faces'); - - let polygon; - - if (this.multi++ === 1) { - polygon = new CsgPolygon(); - - polygon.vertices.push(this.mnormal ? this.v2 : this.v3); - this.polygons.push(polygon); - } else { - polygon = this.polygons.at(-1); - // check that last vertex equals to v2 - const last = this.mnormal ? polygon.vertices.at(-1) : polygon.vertices.at(0), - comp = this.mnormal ? this.v2 : this.v3; - - if (comp.diff(last) > 1e-12) - console.error('vertex missmatch when building polygon'); - } - - const first = this.mnormal ? polygon.vertices[0] : polygon.vertices.at(-1), - next = this.mnormal ? this.v3 : this.v2; - - if (next.diff(first) < 1e-12) - this.multi = 0; - else if (this.mnormal) - polygon.vertices.push(this.v3); - else - polygon.vertices.unshift(this.v2); - } else { - const polygon = new CsgPolygon(); - switch (reduce) { - case 0: polygon.vertices.push(this.v1, this.v2, this.v3, this.v4); break; - case 1: polygon.vertices.push(this.v1, this.v3, this.v4); break; - case 2: polygon.vertices.push(this.v1, this.v2, this.v3); break; - } - this.polygons.push(polygon); - } - } - - /** @summary Specify normal for face with 4 vertices - * @desc same as addFace4, assign normals for each individual vertex - * reduce has same meaning and should be the same */ - setNormal4(nx1, ny1, nz1, nx2, ny2, nz2, nx3, ny3, nz3, nx4, ny4, nz4) { - this.v1.setnormal(nx1, ny1, nz1); - this.v2?.setnormal(nx2, ny2, nz2); - this.v3.setnormal(nx3, ny3, nz3); - this.v4?.setnormal(nx4, ny4, nz4); - } - - /** @summary Set normal - * @desc special shortcut, when same normals can be applied for 1-2 point and 3-4 point */ - setNormal_12_34(nx12, ny12, nz12, nx34, ny34, nz34) { - this.v1.setnormal(nx12, ny12, nz12); - this.v2?.setnormal(nx12, ny12, nz12); - this.v3.setnormal(nx34, ny34, nz34); - this.v4?.setnormal(nx34, ny34, nz34); - } - - /** @summary Calculate normal */ - calcNormal() { - if (!this.cb) { - this.pA = new THREE.Vector3(); - this.pB = new THREE.Vector3(); - this.pC = new THREE.Vector3(); - this.cb = new THREE.Vector3(); - this.ab = new THREE.Vector3(); - } - - this.pA.set(this.v1.x, this.v1.y, this.v1.z); - - if (this.reduce !== 1) { - this.pB.set(this.v2.x, this.v2.y, this.v2.z); - this.pC.set(this.v3.x, this.v3.y, this.v3.z); - } else { - this.pB.set(this.v3.x, this.v3.y, this.v3.z); - this.pC.set(this.v4.x, this.v4.y, this.v4.z); - } - - this.cb.subVectors(this.pC, this.pB); - this.ab.subVectors(this.pA, this.pB); - this.cb.cross(this.ab); - - this.setNormal(this.cb.x, this.cb.y, this.cb.z); - } - - /** @summary Set normal */ - setNormal(nx, ny, nz) { - this.v1.setnormal(nx, ny, nz); - this.v2?.setnormal(nx, ny, nz); - this.v3.setnormal(nx, ny, nz); - this.v4?.setnormal(nx, ny, nz); - } - - /** @summary Recalculate Z with provided func */ - recalcZ(func) { - this.v1.z = func(this.v1.x, this.v1.y, this.v1.z); - if (this.v2) - this.v2.z = func(this.v2.x, this.v2.y, this.v2.z); - this.v3.z = func(this.v3.x, this.v3.y, this.v3.z); - if (this.v4) - this.v4.z = func(this.v4.x, this.v4.y, this.v4.z); - } - - /** @summary Create geometry - * @private */ - create() { - return { polygons: this.polygons }; - } - -} - -// ================= all functions to create geometry =================================== - -/** @summary Creates cube geometry - * @private */ -function createCubeBuffer(shape, faces_limit) { - if (faces_limit < 0) - return 12; - - const dx = shape.fDX, dy = shape.fDY, dz = shape.fDZ, - creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(12); - - creator.addFace4(dx, dy, dz, dx, -dy, dz, dx, -dy, -dz, dx, dy, -dz); - creator.setNormal(1, 0, 0); - - creator.addFace4(-dx, dy, -dz, -dx, -dy, -dz, -dx, -dy, dz, -dx, dy, dz); - creator.setNormal(-1, 0, 0); - - creator.addFace4(-dx, dy, -dz, -dx, dy, dz, dx, dy, dz, dx, dy, -dz); - creator.setNormal(0, 1, 0); - - creator.addFace4(-dx, -dy, dz, -dx, -dy, -dz, dx, -dy, -dz, dx, -dy, dz); - creator.setNormal(0, -1, 0); - - creator.addFace4(-dx, dy, dz, -dx, -dy, dz, dx, -dy, dz, dx, dy, dz); - creator.setNormal(0, 0, 1); - - creator.addFace4(dx, dy, -dz, dx, -dy, -dz, -dx, -dy, -dz, -dx, dy, -dz); - creator.setNormal(0, 0, -1); - - return creator.create(); -} - -/** @summary Creates 8 edges geometry - * @private */ -function create8edgesBuffer(v, faces_limit) { - const indicies = [4, 7, 6, 5, 0, 3, 7, 4, 4, 5, 1, 0, 6, 2, 1, 5, 7, 3, 2, 6, 1, 2, 3, 0], - creator = (faces_limit > 0) ? new PolygonsCreator() : new GeometryCreator(12); - - for (let n = 0; n < indicies.length; n += 4) { - const i1 = indicies[n] * 3, - i2 = indicies[n + 1] * 3, - i3 = indicies[n + 2] * 3, - i4 = indicies[n + 3] * 3; - creator.addFace4(v[i1], v[i1 + 1], v[i1 + 2], v[i2], v[i2 + 1], v[i2 + 2], - v[i3], v[i3 + 1], v[i3 + 2], v[i4], v[i4 + 1], v[i4 + 2]); - if (n === 0) - creator.setNormal(0, 0, 1); - else if (n === 20) - creator.setNormal(0, 0, -1); - else - creator.calcNormal(); - } - - return creator.create(); -} - -/** @summary Creates PARA geometry - * @private */ -function createParaBuffer(shape, faces_limit) { - if (faces_limit < 0) - return 12; - - const txy = shape.fTxy, txz = shape.fTxz, tyz = shape.fTyz, v = [ - -shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, - -shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, - -shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY - shape.fZ * tyz, -shape.fZ, - -shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY - shape.fZ * tyz, -shape.fZ, - shape.fZ * txz - txy * shape.fY - shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ, - shape.fZ * txz + txy * shape.fY - shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, - shape.fZ * txz + txy * shape.fY + shape.fX, shape.fY + shape.fZ * tyz, shape.fZ, - shape.fZ * txz - txy * shape.fY + shape.fX, -shape.fY + shape.fZ * tyz, shape.fZ]; - - return create8edgesBuffer(v, faces_limit); -} - -/** @summary Creates trapezoid geometry - * @private */ -function createTrapezoidBuffer(shape, faces_limit) { - if (faces_limit < 0) - return 12; - - let y1, y2; - if (shape._typename === clTGeoTrd1) - y1 = y2 = shape.fDY; - else { - y1 = shape.fDy1; - y2 = shape.fDy2; - } - - const v = [ - -shape.fDx1, y1, -shape.fDZ, - shape.fDx1, y1, -shape.fDZ, - shape.fDx1, -y1, -shape.fDZ, - -shape.fDx1, -y1, -shape.fDZ, - -shape.fDx2, y2, shape.fDZ, - shape.fDx2, y2, shape.fDZ, - shape.fDx2, -y2, shape.fDZ, - -shape.fDx2, -y2, shape.fDZ - ]; - - return create8edgesBuffer(v, faces_limit); -} - - -/** @summary Creates arb8 geometry - * @private */ -function createArb8Buffer(shape, faces_limit) { - if (faces_limit < 0) - return 12; - - const vertices = [ - shape.fXY[0][0], shape.fXY[0][1], -shape.fDZ, - shape.fXY[1][0], shape.fXY[1][1], -shape.fDZ, - shape.fXY[2][0], shape.fXY[2][1], -shape.fDZ, - shape.fXY[3][0], shape.fXY[3][1], -shape.fDZ, - shape.fXY[4][0], shape.fXY[4][1], shape.fDZ, - shape.fXY[5][0], shape.fXY[5][1], shape.fDZ, - shape.fXY[6][0], shape.fXY[6][1], shape.fDZ, - shape.fXY[7][0], shape.fXY[7][1], shape.fDZ - ], - indicies = [ - 4, 7, 6, 6, 5, 4, 3, 7, 4, 4, 0, 3, - 5, 1, 0, 0, 4, 5, 6, 2, 1, 1, 5, 6, - 7, 3, 2, 2, 6, 7, 1, 2, 3, 3, 0, 1]; - - // detect same vertices on both Z-layers - for (let side = 0; side < vertices.length; side += vertices.length / 2) { - for (let n1 = side; n1 < side + vertices.length / 2 - 3; n1 += 3) { - for (let n2 = n1 + 3; n2 < side + vertices.length / 2; n2 += 3) { - if ((vertices[n1] === vertices[n2]) && - (vertices[n1 + 1] === vertices[n2 + 1]) && - (vertices[n1 + 2] === vertices[n2 + 2])) { - for (let k = 0; k < indicies.length; ++k) { - if (indicies[k] === n2 / 3) - indicies[k] = n1 / 3; - } - } - } - } - } - - const map = []; // list of existing faces (with all rotations) - let numfaces = 0; - - for (let k = 0; k < indicies.length; k += 3) { - const id1 = indicies[k] * 100 + indicies[k + 1] * 10 + indicies[k + 2], - id2 = indicies[k + 1] * 100 + indicies[k + 2] * 10 + indicies[k], - id3 = indicies[k + 2] * 100 + indicies[k] * 10 + indicies[k + 1]; - - if ((indicies[k] === indicies[k + 1]) || (indicies[k] === indicies[k + 2]) || (indicies[k + 1] === indicies[k + 2]) || - (map.indexOf(id1) >= 0) || (map.indexOf(id2) >= 0) || (map.indexOf(id3) >= 0)) - indicies[k] = indicies[k + 1] = indicies[k + 2] = -1; - else { - map.push(id1, id2, id3); - numfaces++; - } - } - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); - - for (let n = 0; n < indicies.length; n += 6) { - const i1 = indicies[n] * 3, - i2 = indicies[n + 1] * 3, - i3 = indicies[n + 2] * 3, - i4 = indicies[n + 3] * 3, - i5 = indicies[n + 4] * 3, - i6 = indicies[n + 5] * 3; - let norm = null; - - if ((i1 >= 0) && (i4 >= 0) && faces_limit) { - // try to identify two faces with same normal - very useful if one can create face4 - if (n === 0) - norm = new THREE.Vector3(0, 0, 1); - else if (n === 30) - norm = new THREE.Vector3(0, 0, -1); - else { - const norm1 = produceNormal(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], - vertices[i2], vertices[i2 + 1], vertices[i2 + 2], - vertices[i3], vertices[i3 + 1], vertices[i3 + 2]); - - norm1.normalize(); - - const norm2 = produceNormal(vertices[i4], vertices[i4 + 1], vertices[i4 + 2], - vertices[i5], vertices[i5 + 1], vertices[i5 + 2], - vertices[i6], vertices[i6 + 1], vertices[i6 + 2]); - - norm2.normalize(); - - if (norm1.distanceToSquared(norm2) < 1e-12) - norm = norm1; - } - } - - if (norm !== null) { - creator.addFace4(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], - vertices[i2], vertices[i2 + 1], vertices[i2 + 2], - vertices[i3], vertices[i3 + 1], vertices[i3 + 2], - vertices[i5], vertices[i5 + 1], vertices[i5 + 2]); - creator.setNormal(norm.x, norm.y, norm.z); - } else { - if (i1 >= 0) { - creator.addFace3(vertices[i1], vertices[i1 + 1], vertices[i1 + 2], - vertices[i2], vertices[i2 + 1], vertices[i2 + 2], - vertices[i3], vertices[i3 + 1], vertices[i3 + 2]); - creator.calcNormal(); - } - if (i4 >= 0) { - creator.addFace3(vertices[i4], vertices[i4 + 1], vertices[i4 + 2], - vertices[i5], vertices[i5 + 1], vertices[i5 + 2], - vertices[i6], vertices[i6 + 1], vertices[i6 + 2]); - creator.calcNormal(); - } - } - } - - return creator.create(); -} - -/** @summary Creates sphere geometry - * @private */ -function createSphereBuffer(shape, faces_limit) { - const radius = [shape.fRmax, shape.fRmin], - phiStart = shape.fPhi1, - phiLength = shape.fPhi2 - shape.fPhi1, - thetaStart = shape.fTheta1, - thetaLength = shape.fTheta2 - shape.fTheta1, - noInside = (radius[1] <= 0); - let widthSegments = shape.fNseg, - heightSegments = shape.fNz; - - if (faces_limit > 0) { - const fact = (noInside ? 2 : 4) * widthSegments * heightSegments / faces_limit; - - if (fact > 1.0) { - widthSegments = Math.max(4, Math.floor(widthSegments / Math.sqrt(fact))); - heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(fact))); - } - } - - let numoutside = widthSegments * heightSegments * 2, - numtop = widthSegments * (noInside ? 1 : 2), - numbottom = widthSegments * (noInside ? 1 : 2); - const numcut = (phiLength === 360) ? 0 : heightSegments * (noInside ? 2 : 4), - epsilon = 1e-10; - - if (faces_limit < 0) - return numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut; - - const _sinp = new Float32Array(widthSegments + 1), - _cosp = new Float32Array(widthSegments + 1), - _sint = new Float32Array(heightSegments + 1), - _cost = new Float32Array(heightSegments + 1); - - for (let n = 0; n <= heightSegments; ++n) { - const theta = (thetaStart + thetaLength / heightSegments * n) * Math.PI / 180; - _sint[n] = Math.sin(theta); - _cost[n] = Math.cos(theta); - } - - for (let n = 0; n <= widthSegments; ++n) { - const phi = (phiStart + phiLength / widthSegments * n) * Math.PI / 180; - _sinp[n] = Math.sin(phi); - _cosp[n] = Math.cos(phi); - } - - if (Math.abs(_sint[0]) <= epsilon) { - numoutside -= widthSegments; - numtop = 0; - } - if (Math.abs(_sint[heightSegments]) <= epsilon) { - numoutside -= widthSegments; - numbottom = 0; - } - - const numfaces = numoutside * (noInside ? 1 : 2) + numtop + numbottom + numcut, - creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); - - for (let side = 0; side < 2; ++side) { - if ((side === 1) && noInside) - break; - - const r = radius[side], - s = (side === 0) ? 1 : -1, - d1 = 1 - side, d2 = 1 - d1; - - // use direct algorithm for the sphere - here normals and position can be calculated directly - for (let k = 0; k < heightSegments; ++k) { - const k1 = k + d1, k2 = k + d2; - let skip = 0; - if (Math.abs(_sint[k1]) <= epsilon) - skip = 1; - else if (Math.abs(_sint[k2]) <= epsilon) - skip = 2; - - for (let n = 0; n < widthSegments; ++n) { - creator.addFace4( - r * _sint[k1] * _cosp[n], r * _sint[k1] * _sinp[n], r * _cost[k1], - r * _sint[k1] * _cosp[n + 1], r * _sint[k1] * _sinp[n + 1], r * _cost[k1], - r * _sint[k2] * _cosp[n + 1], r * _sint[k2] * _sinp[n + 1], r * _cost[k2], - r * _sint[k2] * _cosp[n], r * _sint[k2] * _sinp[n], r * _cost[k2], - skip); - creator.setNormal4( - s * _sint[k1] * _cosp[n], s * _sint[k1] * _sinp[n], s * _cost[k1], - s * _sint[k1] * _cosp[n + 1], s * _sint[k1] * _sinp[n + 1], s * _cost[k1], - s * _sint[k2] * _cosp[n + 1], s * _sint[k2] * _sinp[n + 1], s * _cost[k2], - s * _sint[k2] * _cosp[n], s * _sint[k2] * _sinp[n], s * _cost[k2], - skip); - } - } - } - - // top/bottom - for (let side = 0; side <= heightSegments; side += heightSegments) { - if (Math.abs(_sint[side]) >= epsilon) { - const ss = _sint[side], cc = _cost[side], - d1 = (side === 0) ? 0 : 1, d2 = 1 - d1; - for (let n = 0; n < widthSegments; ++n) { - creator.addFace4( - radius[1] * ss * _cosp[n + d1], radius[1] * ss * _sinp[n + d1], radius[1] * cc, - radius[0] * ss * _cosp[n + d1], radius[0] * ss * _sinp[n + d1], radius[0] * cc, - radius[0] * ss * _cosp[n + d2], radius[0] * ss * _sinp[n + d2], radius[0] * cc, - radius[1] * ss * _cosp[n + d2], radius[1] * ss * _sinp[n + d2], radius[1] * cc, - noInside ? 2 : 0); - creator.calcNormal(); - } - } - } - - // cut left/right sides - if (phiLength < 360) { - for (let side = 0; side <= widthSegments; side += widthSegments) { - const ss = _sinp[side], cc = _cosp[side], - d1 = (side === 0) ? 1 : 0, d2 = 1 - d1; - - for (let k = 0; k < heightSegments; ++k) { - creator.addFace4( - radius[1] * _sint[k + d1] * cc, radius[1] * _sint[k + d1] * ss, radius[1] * _cost[k + d1], - radius[0] * _sint[k + d1] * cc, radius[0] * _sint[k + d1] * ss, radius[0] * _cost[k + d1], - radius[0] * _sint[k + d2] * cc, radius[0] * _sint[k + d2] * ss, radius[0] * _cost[k + d2], - radius[1] * _sint[k + d2] * cc, radius[1] * _sint[k + d2] * ss, radius[1] * _cost[k + d2], - noInside ? 2 : 0); - creator.calcNormal(); - } - } - } - - return creator.create(); -} - -/** @summary Creates tube geometry - * @private */ -function createTubeBuffer(shape, faces_limit) { - let outerR, innerR; // inner/outer tube radius - if ((shape._typename === clTGeoCone) || (shape._typename === clTGeoConeSeg)) { - outerR = [shape.fRmax2, shape.fRmax1]; - innerR = [shape.fRmin2, shape.fRmin1]; - } else { - outerR = [shape.fRmax, shape.fRmax]; - innerR = [shape.fRmin, shape.fRmin]; - } - - const hasrmin = (innerR[0] > 0) || (innerR[1] > 0); - let thetaStart = 0, thetaLength = 360; - - if ((shape._typename === clTGeoConeSeg) || (shape._typename === clTGeoTubeSeg) || (shape._typename === clTGeoCtub)) { - thetaStart = shape.fPhi1; - thetaLength = shape.fPhi2 - shape.fPhi1; - } - - const radiusSegments = Math.max(4, Math.round(thetaLength / _cfg.GradPerSegm)); - - // external surface - let numfaces = radiusSegments * (((outerR[0] <= 0) || (outerR[1] <= 0)) ? 1 : 2); - - // internal surface - if (hasrmin) - numfaces += radiusSegments * (((innerR[0] <= 0) || (innerR[1] <= 0)) ? 1 : 2); - - // upper cap - if (outerR[0] > 0) - numfaces += radiusSegments * ((innerR[0] > 0) ? 2 : 1); - // bottom cup - if (outerR[1] > 0) - numfaces += radiusSegments * ((innerR[1] > 0) ? 2 : 1); - - if (thetaLength < 360) - numfaces += ((outerR[0] > innerR[0]) ? 2 : 0) + ((outerR[1] > innerR[1]) ? 2 : 0); - - if (faces_limit < 0) - return numfaces; - - const phi0 = thetaStart * Math.PI / 180, - dphi = thetaLength / radiusSegments * Math.PI / 180, - _sin = new Float32Array(radiusSegments + 1), - _cos = new Float32Array(radiusSegments + 1); - - for (let seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(phi0 + seg * dphi); - _sin[seg] = Math.sin(phi0 + seg * dphi); - } - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), - calcZ = (shape._typename !== clTGeoCtub) ? null : (x, y, z) => { - const arr = z < 0 ? shape.fNlow : shape.fNhigh; - return (z < 0 ? -shape.fDz : shape.fDz) - (x * arr[0] + y * arr[1]) / arr[2]; - }; - - // create outer/inner tube - for (let side = 0; side < 2; ++side) { - if ((side === 1) && !hasrmin) - break; - - const R = (side === 0) ? outerR : innerR, d1 = side, d2 = 1 - side; - let nxy = 1, nz = 0; - - if (R[0] !== R[1]) { - const angle = Math.atan2((R[1] - R[0]), 2 * shape.fDz); - nxy = Math.cos(angle); - nz = Math.sin(angle); - } - - if (side === 1) { - nxy *= -1; - nz *= -1; - } - - const reduce = (R[0] <= 0) ? 2 : ((R[1] <= 0) ? 1 : 0); - - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4( - R[0] * _cos[seg + d1], R[0] * _sin[seg + d1], shape.fDz, - R[1] * _cos[seg + d1], R[1] * _sin[seg + d1], -shape.fDz, - R[1] * _cos[seg + d2], R[1] * _sin[seg + d2], -shape.fDz, - R[0] * _cos[seg + d2], R[0] * _sin[seg + d2], shape.fDz, - reduce); - - if (calcZ) - creator.recalcZ(calcZ); - - creator.setNormal_12_34(nxy * _cos[seg + d1], nxy * _sin[seg + d1], nz, - nxy * _cos[seg + d2], nxy * _sin[seg + d2], nz, - reduce); - } - } - - // create upper/bottom part - for (let side = 0; side < 2; ++side) { - if (outerR[side] <= 0) - continue; - - const d1 = side, d2 = 1 - side, - sign = (side === 0) ? 1 : -1, - reduce = (innerR[side] <= 0) ? 2 : 0; - if ((reduce === 2) && (thetaLength === 360) && !calcZ) - creator.startPolygon(side === 0); - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4( - innerR[side] * _cos[seg + d1], innerR[side] * _sin[seg + d1], sign * shape.fDz, - outerR[side] * _cos[seg + d1], outerR[side] * _sin[seg + d1], sign * shape.fDz, - outerR[side] * _cos[seg + d2], outerR[side] * _sin[seg + d2], sign * shape.fDz, - innerR[side] * _cos[seg + d2], innerR[side] * _sin[seg + d2], sign * shape.fDz, - reduce); - if (calcZ) { - creator.recalcZ(calcZ); - creator.calcNormal(); - } else - creator.setNormal(0, 0, sign); - } - - creator.stopPolygon(); - } - - // create cut surfaces - if (thetaLength < 360) { - creator.addFace4(innerR[1] * _cos[0], innerR[1] * _sin[0], -shape.fDz, - outerR[1] * _cos[0], outerR[1] * _sin[0], -shape.fDz, - outerR[0] * _cos[0], outerR[0] * _sin[0], shape.fDz, - innerR[0] * _cos[0], innerR[0] * _sin[0], shape.fDz, - (outerR[0] === innerR[0]) ? 2 : ((innerR[1] === outerR[1]) ? 1 : 0)); - if (calcZ) - creator.recalcZ(calcZ); - creator.calcNormal(); - - creator.addFace4(innerR[0] * _cos[radiusSegments], innerR[0] * _sin[radiusSegments], shape.fDz, - outerR[0] * _cos[radiusSegments], outerR[0] * _sin[radiusSegments], shape.fDz, - outerR[1] * _cos[radiusSegments], outerR[1] * _sin[radiusSegments], -shape.fDz, - innerR[1] * _cos[radiusSegments], innerR[1] * _sin[radiusSegments], -shape.fDz, - (outerR[0] === innerR[0]) ? 1 : ((innerR[1] === outerR[1]) ? 2 : 0)); - - if (calcZ) - creator.recalcZ(calcZ); - creator.calcNormal(); - } - - return creator.create(); -} - -/** @summary Creates eltu geometry - * @private */ -function createEltuBuffer(shape, faces_limit) { - const radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)); - - if (faces_limit < 0) - return radiusSegments * 4; - - // calculate all sin/cos tables in advance - const x = new Float32Array(radiusSegments + 1), - y = new Float32Array(radiusSegments + 1); - for (let seg = 0; seg <= radiusSegments; ++seg) { - const phi = seg / radiusSegments * 2 * Math.PI; - x[seg] = shape.fRmin * Math.cos(phi); - y[seg] = shape.fRmax * Math.sin(phi); - } - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(radiusSegments * 4); - let nx1, ny1, nx2 = 1, ny2 = 0; - - // create tube faces - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4(x[seg], y[seg], shape.fDZ, - x[seg], y[seg], -shape.fDZ, - x[seg + 1], y[seg + 1], -shape.fDZ, - x[seg + 1], y[seg + 1], shape.fDZ); - - // calculate normals ourself - nx1 = nx2; - ny1 = ny2; - nx2 = x[seg + 1] * shape.fRmax / shape.fRmin; - ny2 = y[seg + 1] * shape.fRmin / shape.fRmax; - const dist = Math.sqrt(nx2 ** 2 + ny2 ** 2); - nx2 /= dist; - ny2 /= dist; - - creator.setNormal_12_34(nx1, ny1, 0, nx2, ny2, 0); - } - - // create top/bottom sides - for (let side = 0; side < 2; ++side) { - const sign = (side === 0) ? 1 : -1, d1 = side, d2 = 1 - side; - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace3(0, 0, sign * shape.fDZ, - x[seg + d1], y[seg + d1], sign * shape.fDZ, - x[seg + d2], y[seg + d2], sign * shape.fDZ); - creator.setNormal(0, 0, sign); - } - } - - return creator.create(); -} - -/** @summary Creates torus geometry - * @private */ -function createTorusBuffer(shape, faces_limit) { - const radius = shape.fR; - let radialSegments = Math.max(6, Math.round(360 / _cfg.GradPerSegm)), - tubularSegments = Math.max(8, Math.round(shape.fDphi / _cfg.GradPerSegm)), - numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); - - if (faces_limit < 0) - return numfaces; - - if ((faces_limit > 0) && (numfaces > faces_limit)) { - radialSegments = Math.floor(radialSegments / Math.sqrt(numfaces / faces_limit)); - tubularSegments = Math.floor(tubularSegments / Math.sqrt(numfaces / faces_limit)); - numfaces = (shape.fRmin > 0 ? 4 : 2) * radialSegments * (tubularSegments + (shape.fDphi !== 360 ? 1 : 0)); - } - - const _sinr = new Float32Array(radialSegments + 1), - _cosr = new Float32Array(radialSegments + 1), - _sint = new Float32Array(tubularSegments + 1), - _cost = new Float32Array(tubularSegments + 1); - - for (let n = 0; n <= radialSegments; ++n) { - _sinr[n] = Math.sin(n / radialSegments * 2 * Math.PI); - _cosr[n] = Math.cos(n / radialSegments * 2 * Math.PI); - } - - for (let t = 0; t <= tubularSegments; ++t) { - const angle = (shape.fPhi1 + shape.fDphi * t / tubularSegments) / 180 * Math.PI; - _sint[t] = Math.sin(angle); - _cost[t] = Math.cos(angle); - } - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces), - // use vectors for normals calculation - p1 = new THREE.Vector3(), p2 = new THREE.Vector3(), p3 = new THREE.Vector3(), p4 = new THREE.Vector3(), - n1 = new THREE.Vector3(), n2 = new THREE.Vector3(), n3 = new THREE.Vector3(), n4 = new THREE.Vector3(), - center1 = new THREE.Vector3(), center2 = new THREE.Vector3(); - - for (let side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) - break; - const tube = (side > 0) ? shape.fRmin : shape.fRmax, - d1 = 1 - side, d2 = 1 - d1, ns = side > 0 ? -1 : 1; - - for (let t = 0; t < tubularSegments; ++t) { - const t1 = t + d1, t2 = t + d2; - center1.x = radius * _cost[t1]; - center1.y = radius * _sint[t1]; - center2.x = radius * _cost[t2]; - center2.y = radius * _sint[t2]; - - for (let n = 0; n < radialSegments; ++n) { - p1.x = (radius + tube * _cosr[n]) * _cost[t1]; - p1.y = (radius + tube * _cosr[n]) * _sint[t1]; - p1.z = tube * _sinr[n]; - p2.x = (radius + tube * _cosr[n + 1]) * _cost[t1]; - p2.y = (radius + tube * _cosr[n + 1]) * _sint[t1]; - p2.z = tube * _sinr[n + 1]; - p3.x = (radius + tube * _cosr[n + 1]) * _cost[t2]; - p3.y = (radius + tube * _cosr[n + 1]) * _sint[t2]; - p3.z = tube * _sinr[n + 1]; - p4.x = (radius + tube * _cosr[n]) * _cost[t2]; - p4.y = (radius + tube * _cosr[n]) * _sint[t2]; - p4.z = tube * _sinr[n]; - - creator.addFace4(p1.x, p1.y, p1.z, - p2.x, p2.y, p2.z, - p3.x, p3.y, p3.z, - p4.x, p4.y, p4.z); - - n1.subVectors(p1, center1).normalize(); - n2.subVectors(p2, center1).normalize(); - n3.subVectors(p3, center2).normalize(); - n4.subVectors(p4, center2).normalize(); - - creator.setNormal4(ns * n1.x, ns * n1.y, ns * n1.z, - ns * n2.x, ns * n2.y, ns * n2.z, - ns * n3.x, ns * n3.y, ns * n3.z, - ns * n4.x, ns * n4.y, ns * n4.z); - } - } - } - - if (shape.fDphi !== 360) { - for (let t = 0; t <= tubularSegments; t += tubularSegments) { - const tube1 = shape.fRmax, tube2 = shape.fRmin, - d1 = t > 0 ? 0 : 1, d2 = 1 - d1, - skip = shape.fRmin > 0 ? 0 : 1, - nsign = t > 0 ? 1 : -1; - for (let n = 0; n < radialSegments; ++n) { - creator.addFace4((radius + tube1 * _cosr[n + d1]) * _cost[t], (radius + tube1 * _cosr[n + d1]) * _sint[t], tube1 * _sinr[n + d1], - (radius + tube2 * _cosr[n + d1]) * _cost[t], (radius + tube2 * _cosr[n + d1]) * _sint[t], tube2 * _sinr[n + d1], - (radius + tube2 * _cosr[n + d2]) * _cost[t], (radius + tube2 * _cosr[n + d2]) * _sint[t], tube2 * _sinr[n + d2], - (radius + tube1 * _cosr[n + d2]) * _cost[t], (radius + tube1 * _cosr[n + d2]) * _sint[t], tube1 * _sinr[n + d2], skip); - creator.setNormal(-nsign * _sint[t], nsign * _cost[t], 0); - } - } - } - - return creator.create(); -} - - -/** @summary Creates polygon geometry - * @private */ -function createPolygonBuffer(shape, faces_limit) { - const thetaStart = shape.fPhi1, - thetaLength = shape.fDphi; - let radiusSegments, factor; - - if (shape._typename === clTGeoPgon) { - radiusSegments = shape.fNedges; - factor = 1.0 / Math.cos(Math.PI / 180 * thetaLength / radiusSegments / 2); - } else { - radiusSegments = Math.max(5, Math.round(thetaLength / _cfg.GradPerSegm)); - factor = 1; - } - - const usage = new Int16Array(2 * shape.fNz); - let numusedlayers = 0, hasrmin = false; - - for (let layer = 0; layer < shape.fNz; ++layer) - hasrmin = hasrmin || (shape.fRmin[layer] > 0); - - // return very rough estimation, number of faces may be much less - if (faces_limit < 0) - return (hasrmin ? 4 : 2) * radiusSegments * (shape.fNz - 1); - - // coordinate of point on cut edge (x,z) - const pnts = (thetaLength === 360) ? null : []; - - // first analyze levels - if we need to create all of them - for (let side = 0; side < 2; ++side) { - const rside = (side === 0) ? 'fRmax' : 'fRmin'; - - for (let layer = 0; layer < shape.fNz; ++layer) { - // first create points for the layer - const layerz = shape.fZ[layer], rad = shape[rside][layer]; - - usage[layer * 2 + side] = 0; - - if ((layer > 0) && (layer < shape.fNz - 1)) { - if (((shape.fZ[layer - 1] === layerz) && (shape[rside][layer - 1] === rad)) || - ((shape[rside][layer + 1] === rad) && (shape[rside][layer - 1] === rad))) { - // same Z and R as before - ignore - // or same R before and after - continue; - } - } - - if ((layer > 0) && ((side === 0) || hasrmin)) { - usage[layer * 2 + side] = 1; - numusedlayers++; - } - - if (pnts !== null) { - if (side === 0) - pnts.push(new THREE.Vector2(factor * rad, layerz)); - else if (rad < shape.fRmax[layer]) - pnts.unshift(new THREE.Vector2(factor * rad, layerz)); - } - } - } - - let numfaces = numusedlayers * radiusSegments * 2; - if (shape.fRmin[0] !== shape.fRmax[0]) - numfaces += radiusSegments * (hasrmin ? 2 : 1); - if (shape.fRmin[shape.fNz - 1] !== shape.fRmax[shape.fNz - 1]) - numfaces += radiusSegments * (hasrmin ? 2 : 1); - - let cut_faces = null; - - if (pnts !== null) { - if (pnts.length === shape.fNz * 2) { - // special case - all layers are there, create faces ourself - cut_faces = []; - for (let layer = shape.fNz - 1; layer > 0; --layer) { - if (shape.fZ[layer] === shape.fZ[layer - 1]) - continue; - const right = 2 * shape.fNz - 1 - layer; - cut_faces.push([right, layer - 1, layer]); - cut_faces.push([right, right + 1, layer - 1]); - } - } else { - // let three.js calculate our faces - cut_faces = THREE.ShapeUtils.triangulateShape(pnts, []); - } - numfaces += cut_faces.length * 2; - } - - const phi0 = thetaStart * Math.PI / 180, - dphi = thetaLength / radiusSegments * Math.PI / 180, - // calculate all sin/cos tables in advance - _sin = new Float32Array(radiusSegments + 1), - _cos = new Float32Array(radiusSegments + 1); - for (let seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(phi0 + seg * dphi); - _sin[seg] = Math.sin(phi0 + seg * dphi); - } - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); - - // add sides - for (let side = 0; side < 2; ++side) { - const rside = (side === 0) ? 'fRmax' : 'fRmin', - d1 = 1 - side, d2 = side; - let z1 = shape.fZ[0], r1 = factor * shape[rside][0]; - - for (let layer = 0; layer < shape.fNz; ++layer) { - if (usage[layer * 2 + side] === 0) - continue; - - const z2 = shape.fZ[layer], r2 = factor * shape[rside][layer]; - let nxy = 1, nz = 0; - - if ((r2 !== r1)) { - const angle = Math.atan2((r2 - r1), (z2 - z1)); - nxy = Math.cos(angle); - nz = Math.sin(angle); - } - - if (side > 0) { - nxy *= -1; - nz *= -1; - } - - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z1, - r2 * _cos[seg + d1], r2 * _sin[seg + d1], z2, - r2 * _cos[seg + d2], r2 * _sin[seg + d2], z2, - r1 * _cos[seg + d2], r1 * _sin[seg + d2], z1); - creator.setNormal_12_34(nxy * _cos[seg + d1], nxy * _sin[seg + d1], nz, nxy * _cos[seg + d2], nxy * _sin[seg + d2], nz); - } - - z1 = z2; - r1 = r2; - } - } - - // add top/bottom - for (let layer = 0; layer < shape.fNz; layer += (shape.fNz - 1)) { - const rmin = factor * shape.fRmin[layer], rmax = factor * shape.fRmax[layer]; - - if (rmin === rmax) - continue; - - const layerz = shape.fZ[layer], - d1 = (layer === 0) ? 1 : 0, d2 = 1 - d1, - normalz = (layer === 0) ? -1 : 1; - - if (!hasrmin && !cut_faces) - creator.startPolygon(layer > 0); - - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4(rmin * _cos[seg + d1], rmin * _sin[seg + d1], layerz, - rmax * _cos[seg + d1], rmax * _sin[seg + d1], layerz, - rmax * _cos[seg + d2], rmax * _sin[seg + d2], layerz, - rmin * _cos[seg + d2], rmin * _sin[seg + d2], layerz, - hasrmin ? 0 : 2); - creator.setNormal(0, 0, normalz); - } - - creator.stopPolygon(); - } - - if (cut_faces) { - for (let seg = 0; seg <= radiusSegments; seg += radiusSegments) { - const d1 = (seg === 0) ? 1 : 2, d2 = 3 - d1; - for (let n = 0; n < cut_faces.length; ++n) { - const a = pnts[cut_faces[n][0]], - b = pnts[cut_faces[n][d1]], - c = pnts[cut_faces[n][d2]]; - - creator.addFace3(a.x * _cos[seg], a.x * _sin[seg], a.y, - b.x * _cos[seg], b.x * _sin[seg], b.y, - c.x * _cos[seg], c.x * _sin[seg], c.y); - - creator.calcNormal(); - } - } - } - - return creator.create(); -} - -/** @summary Creates xtru geometry - * @private */ -function createXtruBuffer(shape, faces_limit) { - let nfaces = (shape.fNz - 1) * shape.fNvert * 2; - - if (faces_limit < 0) - return nfaces + shape.fNvert * 3; - - // create points - const pnts = []; - for (let vert = 0; vert < shape.fNvert; ++vert) - pnts.push(new THREE.Vector2(shape.fX[vert], shape.fY[vert])); - - let faces = THREE.ShapeUtils.triangulateShape(pnts, []); - if (faces.length < pnts.length - 2) { - geoWarn(`Problem with XTRU shape ${shape.fName} with ${pnts.length} vertices`); - faces = []; - } else - nfaces += faces.length * 2; - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(nfaces); - - for (let layer = 0; layer < shape.fNz - 1; ++layer) { - const z1 = shape.fZ[layer], scale1 = shape.fScale[layer], - z2 = shape.fZ[layer + 1], scale2 = shape.fScale[layer + 1], - x01 = shape.fX0[layer], x02 = shape.fX0[layer + 1], - y01 = shape.fY0[layer], y02 = shape.fY0[layer + 1]; - - for (let vert1 = 0; vert1 < shape.fNvert; ++vert1) { - const vert2 = (vert1 + 1) % shape.fNvert; - creator.addFace4(scale1 * shape.fX[vert1] + x01, scale1 * shape.fY[vert1] + y01, z1, - scale2 * shape.fX[vert1] + x02, scale2 * shape.fY[vert1] + y02, z2, - scale2 * shape.fX[vert2] + x02, scale2 * shape.fY[vert2] + y02, z2, - scale1 * shape.fX[vert2] + x01, scale1 * shape.fY[vert2] + y01, z1); - creator.calcNormal(); - } - } - - for (let layer = 0; layer <= shape.fNz - 1; layer += (shape.fNz - 1)) { - const z = shape.fZ[layer], scale = shape.fScale[layer], - x0 = shape.fX0[layer], y0 = shape.fY0[layer]; - - for (let n = 0; n < faces.length; ++n) { - const face = faces[n], - pnt1 = pnts[face[0]], - pnt2 = pnts[face[layer === 0 ? 2 : 1]], - pnt3 = pnts[face[layer === 0 ? 1 : 2]]; - - creator.addFace3(scale * pnt1.x + x0, scale * pnt1.y + y0, z, - scale * pnt2.x + x0, scale * pnt2.y + y0, z, - scale * pnt3.x + x0, scale * pnt3.y + y0, z); - creator.setNormal(0, 0, layer === 0 ? -1 : 1); - } - } - - return creator.create(); -} - -/** @summary Creates para geometry - * @private */ -function createParaboloidBuffer(shape, faces_limit) { - let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)), - heightSegments = 30; - - if (faces_limit > 0) { - const fact = 2 * radiusSegments * (heightSegments + 1) / faces_limit; - if (fact > 1.0) { - radiusSegments = Math.max(5, Math.floor(radiusSegments / Math.sqrt(fact))); - heightSegments = Math.max(5, Math.floor(heightSegments / Math.sqrt(fact))); - } - } - - const rmin = shape.fRlo, rmax = shape.fRhi; - let numfaces = (heightSegments + 1) * radiusSegments * 2; - - if (rmin === 0) - numfaces -= radiusSegments * 2; // complete layer - if (rmax === 0) - numfaces -= radiusSegments * 2; // complete layer - - if (faces_limit < 0) - return numfaces; - - let zmin = -shape.fDZ, zmax = shape.fDZ; - - // if no radius at -z, find intersection - if (shape.fA >= 0) - zmin = Math.max(zmin, shape.fB); - else - zmax = Math.min(shape.fB, zmax); - - const ttmin = Math.atan2(zmin, rmin), - ttmax = Math.atan2(zmax, rmax), - // calculate all sin/cos tables in advance - _sin = new Float32Array(radiusSegments + 1), - _cos = new Float32Array(radiusSegments + 1); - for (let seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); - _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); - } - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); - let lastz = zmin, lastr = 0, lastnxy = 0, lastnz = -1; - - for (let layer = 0; layer <= heightSegments + 1; ++layer) { - if ((layer === 0) && (rmin === 0)) - continue; - - if ((layer === heightSegments + 1) && (lastr === 0)) - break; - - let layerz, radius; - - switch (layer) { - case 0: - layerz = zmin; - radius = rmin; - break; - case heightSegments: - layerz = zmax; - radius = rmax; - break; - case heightSegments + 1: - layerz = zmax; - radius = 0; - break; - default: { - const tt = Math.tan(ttmin + (ttmax - ttmin) * layer / heightSegments), - delta = tt ** 2 - 4 * shape.fA * shape.fB; // should be always positive (a*b < 0) - radius = 0.5 * (tt + Math.sqrt(delta)) / shape.fA; - if (radius < 1e-6) - radius = 0; - layerz = radius * tt; - break; - } - } - - const nxy = shape.fA * radius, - nz = (shape.fA > 0) ? -1 : 1, - skip = (lastr === 0) ? 1 : ((radius === 0) ? 2 : 0); - - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4(radius * _cos[seg], radius * _sin[seg], layerz, - lastr * _cos[seg], lastr * _sin[seg], lastz, - lastr * _cos[seg + 1], lastr * _sin[seg + 1], lastz, - radius * _cos[seg + 1], radius * _sin[seg + 1], layerz, skip); - - // use analytic normal values when open/closing paraboloid around 0 - // cut faces (top or bottom) set with simple normal - if ((skip === 0) || ((layer === 1) && (rmin === 0)) || ((layer === heightSegments + 1) && (rmax === 0))) { - creator.setNormal4(nxy * _cos[seg], nxy * _sin[seg], nz, - lastnxy * _cos[seg], lastnxy * _sin[seg], lastnz, - lastnxy * _cos[seg + 1], lastnxy * _sin[seg + 1], lastnz, - nxy * _cos[seg + 1], nxy * _sin[seg + 1], nz, skip); - } else - creator.setNormal(0, 0, (layer < heightSegments) ? -1 : 1); - } - - lastz = layerz; - lastr = radius; - lastnxy = nxy; - lastnz = nz; - } - - return creator.create(); -} - -/** @summary Creates hype geometry - * @private */ -function createHypeBuffer(shape, faces_limit) { - if ((shape.fTin === 0) && (shape.fTout === 0)) - return createTubeBuffer(shape, faces_limit); - - let radiusSegments = Math.max(4, Math.round(360 / _cfg.GradPerSegm)), - heightSegments = 30, - numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); - - if (faces_limit < 0) - return numfaces; - - if ((faces_limit > 0) && (faces_limit > numfaces)) { - radiusSegments = Math.max(4, Math.floor(radiusSegments / Math.sqrt(numfaces / faces_limit))); - heightSegments = Math.max(4, Math.floor(heightSegments / Math.sqrt(numfaces / faces_limit))); - numfaces = radiusSegments * (heightSegments + 1) * ((shape.fRmin > 0) ? 4 : 2); - } - - // calculate all sin/cos tables in advance - const _sin = new Float32Array(radiusSegments + 1), - _cos = new Float32Array(radiusSegments + 1); - for (let seg = 0; seg <= radiusSegments; ++seg) { - _cos[seg] = Math.cos(seg / radiusSegments * 2 * Math.PI); - _sin[seg] = Math.sin(seg / radiusSegments * 2 * Math.PI); - } - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); - - // in-out side - for (let side = 0; side < 2; ++side) { - if ((side > 0) && (shape.fRmin <= 0)) - break; - - const r0 = (side > 0) ? shape.fRmin : shape.fRmax, - tsq = (side > 0) ? shape.fTinsq : shape.fToutsq, - d1 = 1 - side, d2 = 1 - d1; - - // vertical layers - for (let layer = 0; layer < heightSegments; ++layer) { - const z1 = -shape.fDz + layer / heightSegments * 2 * shape.fDz, - z2 = -shape.fDz + (layer + 1) / heightSegments * 2 * shape.fDz, - r1 = Math.sqrt(r0 ** 2 + tsq * z1 ** 2), - r2 = Math.sqrt(r0 ** 2 + tsq * z2 ** 2); - - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z1, - r2 * _cos[seg + d1], r2 * _sin[seg + d1], z2, - r2 * _cos[seg + d2], r2 * _sin[seg + d2], z2, - r1 * _cos[seg + d2], r1 * _sin[seg + d2], z1); - creator.calcNormal(); - } - } - } - - // add caps - for (let layer = 0; layer < 2; ++layer) { - const z = (layer === 0) ? shape.fDz : -shape.fDz, - r1 = Math.sqrt(shape.fRmax ** 2 + shape.fToutsq * z ** 2), - r2 = (shape.fRmin > 0) ? Math.sqrt(shape.fRmin ** 2 + shape.fTinsq * z ** 2) : 0, - skip = (shape.fRmin > 0) ? 0 : 1, - d1 = 1 - layer, d2 = 1 - d1; - for (let seg = 0; seg < radiusSegments; ++seg) { - creator.addFace4(r1 * _cos[seg + d1], r1 * _sin[seg + d1], z, - r2 * _cos[seg + d1], r2 * _sin[seg + d1], z, - r2 * _cos[seg + d2], r2 * _sin[seg + d2], z, - r1 * _cos[seg + d2], r1 * _sin[seg + d2], z, skip); - creator.setNormal(0, 0, (layer === 0) ? 1 : -1); - } - } - - return creator.create(); -} - -/** @summary Creates tessellated geometry - * @private */ -function createTessellatedBuffer(shape, faces_limit) { - let numfaces = 0; - for (let i = 0; i < shape.fFacets.length; ++i) - numfaces += (shape.fFacets[i].fNvert === 4) ? 2 : 1; - if (faces_limit < 0) - return numfaces; - - const creator = faces_limit ? new PolygonsCreator() : new GeometryCreator(numfaces); - - for (let i = 0; i < shape.fFacets.length; ++i) { - const f = shape.fFacets[i], - v0 = shape.fVertices[f.fIvert[0]].fVec, - v1 = shape.fVertices[f.fIvert[1]].fVec, - v2 = shape.fVertices[f.fIvert[2]].fVec; - - if (f.fNvert === 4) { - const v3 = shape.fVertices[f.fIvert[3]].fVec; - creator.addFace4(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], v3[0], v3[1], v3[2]); - creator.calcNormal(); - } else { - creator.addFace3(v0[0], v0[1], v0[2], v1[0], v1[1], v1[2], v2[0], v2[1], v2[2]); - creator.calcNormal(); - } - } - - return creator.create(); -} - -/** @summary Creates Matrix4 from TGeoMatrix - * @private */ -function createMatrix(matrix) { - if (!matrix) - return null; - - let translation, rotation, scale; - - switch (matrix._typename) { - case 'TGeoTranslation': - translation = matrix.fTranslation; - break; - case 'TGeoRotation': - rotation = matrix.fRotationMatrix; - break; - case 'TGeoScale': - scale = matrix.fScale; - break; - case 'TGeoGenTrans': - scale = matrix.fScale; // no break, translation and rotation follows - case 'TGeoCombiTrans': - translation = matrix.fTranslation; - rotation = matrix.fRotation?.fRotationMatrix; - break; - case 'TGeoHMatrix': - translation = matrix.fTranslation; - rotation = matrix.fRotationMatrix; - scale = matrix.fScale; - break; - case 'TGeoIdentity': - break; - default: - console.warn(`unsupported matrix ${matrix._typename}`); - } - - if (!translation && !rotation && !scale) - return null; - - const res = new THREE.Matrix4(); - - if (rotation) { - res.set(rotation[0], rotation[1], rotation[2], 0, - rotation[3], rotation[4], rotation[5], 0, - rotation[6], rotation[7], rotation[8], 0, - 0, 0, 0, 1); - } - - if (translation) - res.setPosition(translation[0], translation[1], translation[2]); - - if (scale) - res.scale(new THREE.Vector3(scale[0], scale[1], scale[2])); - - return res; -} - -/** @summary Creates transformation matrix for TGeoNode - * @desc created after node visibility flag is checked and volume cut is performed - * @private */ -function getNodeMatrix(kind, node) { - let matrix = null; - - if (kind === kindEve) { - // special handling for EVE nodes - - matrix = new THREE.Matrix4(); - if (node.fTrans) { - matrix.set(node.fTrans[0], node.fTrans[4], node.fTrans[8], 0, - node.fTrans[1], node.fTrans[5], node.fTrans[9], 0, - node.fTrans[2], node.fTrans[6], node.fTrans[10], 0, - 0, 0, 0, 1); - // second - set position with proper sign - matrix.setPosition(node.fTrans[12], node.fTrans[13], node.fTrans[14]); - } - } else if (node.fMatrix) - matrix = createMatrix(node.fMatrix); - else if ((node._typename === 'TGeoNodeOffset') && node.fFinder) { - const kPatternReflected = BIT(14), - finder = node.fFinder, - typ = finder._typename; - if (finder.fBits & kPatternReflected) - geoWarn(`Unsupported reflected pattern ${typ}`); - if (typ.indexOf('TGeoPattern') !== 0) - geoWarn(`Abnormal pattern type ${typ}`); - const part = typ.slice(11); - matrix = new THREE.Matrix4(); - switch (part) { - case 'X': - case 'Y': - case 'Z': - case 'ParaX': - case 'ParaY': - case 'ParaZ': { - const _shift = finder.fStart + (node.fIndex + 0.5) * finder.fStep; - switch (part.at(-1)) { - case 'X': matrix.setPosition(_shift, 0, 0); break; - case 'Y': matrix.setPosition(0, _shift, 0); break; - case 'Z': matrix.setPosition(0, 0, _shift); break; - } - break; - } - case 'CylPhi': { - const phi = (Math.PI / 180) * (finder.fStart + (node.fIndex + 0.5) * finder.fStep), - _cos = Math.cos(phi), _sin = Math.sin(phi); - matrix.set(_cos, -_sin, 0, 0, - _sin, _cos, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - break; - } - case 'CylR': - // seems to be, require no transformation - break; - case 'TrapZ': { - const dz = finder.fStart + (node.fIndex + 0.5) * finder.fStep; - matrix.setPosition(finder.fTxz * dz, finder.fTyz * dz, dz); - break; - } - // case 'CylR': break; - // case 'SphR': break; - // case 'SphTheta': break; - // case 'SphPhi': break; - // case 'Honeycomb': break; - default: - geoWarn(`Unsupported pattern type ${typ}`); - break; - } - } - - return matrix; -} - -/** @summary Returns number of faces for provided geometry - * @param {object} geom - can be BufferGeometry, CsgGeometry or interim array of polygons - * @private */ -function numGeometryFaces(geom) { - if (!geom) - return 0; - - if (geom instanceof CsgGeometry) - return geom.tree.numPolygons(); - - // special array of polygons - if (geom.polygons) - return geom.polygons.length; - - const attr = geom.getAttribute('position'); - return attr?.count ? Math.round(attr.count / 3) : 0; -} - -/** @summary Returns number of faces for provided geometry - * @param {object} geom - can be BufferGeometry, CsgGeometry or interim array of polygons - * @private */ -function numGeometryVertices(geom) { - if (!geom) - return 0; - - if (geom instanceof CsgGeometry) - return geom.tree.numPolygons() * 3; - - if (geom.polygons) - return geom.polygons.length * 4; - - return geom.getAttribute('position')?.count || 0; -} - -/** @summary Returns geometry bounding box - * @private */ -function geomBoundingBox(geom) { - if (!geom) - return null; - - let polygons = null; - - if (geom instanceof CsgGeometry) - polygons = geom.tree.collectPolygons(); - else if (geom.polygons) - polygons = geom.polygons; - - if (polygons !== null) { - const box = new THREE.Box3(); - for (let n = 0; n < polygons.length; ++n) { - const polygon = polygons[n], nvert = polygon.vertices.length; - for (let k = 0; k < nvert; ++k) - box.expandByPoint(polygon.vertices[k]); - } - return box; - } - - if (!geom.boundingBox) - geom.computeBoundingBox(); - - return geom.boundingBox.clone(); -} - -/** @summary Creates half-space geometry for given shape - * @desc Just big-enough triangle to make BSP calculations - * @private */ -function createHalfSpace(shape, geom) { - if (!shape?.fN || !shape?.fP) - return null; - - const vertex = new THREE.Vector3(shape.fP[0], shape.fP[1], shape.fP[2]), - normal = new THREE.Vector3(shape.fN[0], shape.fN[1], shape.fN[2]); - - normal.normalize(); - - let sz = 1e10; - if (geom) { - // using real size of other geometry, we probably improve precision - const box = geomBoundingBox(geom); - if (box) - sz = box.getSize(new THREE.Vector3()).length() * 1000; - } - - const v0 = new THREE.Vector3(-sz, -sz / 2, 0), - v1 = new THREE.Vector3(0, sz, 0), - v2 = new THREE.Vector3(sz, -sz / 2, 0), - v3 = new THREE.Vector3(0, 0, -sz), - geometry = new THREE.BufferGeometry(), - positions = new Float32Array([v0.x, v0.y, v0.z, v2.x, v2.y, v2.z, v1.x, v1.y, v1.z, - v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v3.x, v3.y, v3.z, - v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z, - v2.x, v2.y, v2.z, v0.x, v0.y, v0.z, v3.x, v3.y, v3.z]); - geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); - geometry.computeVertexNormals(); - - geometry.lookAt(normal); - geometry.computeVertexNormals(); - - for (let k = 0; k < positions.length; k += 3) { - positions[k] += vertex.x; - positions[k + 1] += vertex.y; - positions[k + 2] += vertex.z; - } - - return geometry; -} - -let createGeometry = null; - -/** @summary Creates geometry for composite shape - * @private */ -function createComposite(shape, faces_limit) { - if (faces_limit < 0) { - return createGeometry(shape.fNode.fLeft, -1) + - createGeometry(shape.fNode.fRight, -1); - } - - let geom1, geom2, return_bsp = false; - const matrix1 = createMatrix(shape.fNode.fLeftMat), - matrix2 = createMatrix(shape.fNode.fRightMat); - - if (faces_limit === 0) - faces_limit = 4000; - else - return_bsp = true; - - if (matrix1 && (matrix1.determinant() < -0.9)) - geoWarn('Axis reflection in left composite shape - not supported'); - - if (matrix2 && (matrix2.determinant() < -0.9)) - geoWarn('Axis reflections in right composite shape - not supported'); - - if (shape.fNode.fLeft._typename === clTGeoHalfSpace) - geom1 = createHalfSpace(shape.fNode.fLeft); - else - geom1 = createGeometry(shape.fNode.fLeft, faces_limit); - - if (!geom1) - return null; - - let n1 = numGeometryFaces(geom1), n2 = 0; - if (geom1._exceed_limit) - n1 += faces_limit; - - if (n1 < faces_limit) { - if (shape.fNode.fRight._typename === clTGeoHalfSpace) - geom2 = createHalfSpace(shape.fNode.fRight, geom1); - else - geom2 = createGeometry(shape.fNode.fRight, faces_limit); - - - n2 = numGeometryFaces(geom2); - } - - if ((n1 + n2 >= faces_limit) || !geom2) { - if (geom1.polygons) - geom1 = createBufferGeometry(geom1.polygons); - if (matrix1) - geom1.applyMatrix4(matrix1); - geom1._exceed_limit = true; - return geom1; - } - - let bsp1 = new CsgGeometry(geom1, matrix1, _cfg.CompressComp ? 0 : undefined); - - const bsp2 = new CsgGeometry(geom2, matrix2, bsp1.maxid); - - // take over maxid from both geometries - bsp1.maxid = bsp2.maxid; - - switch (shape.fNode._typename) { - case 'TGeoIntersection': // '*' - bsp1.direct_intersect(bsp2); - break; - case 'TGeoUnion': // '+' - bsp1.direct_union(bsp2); - break; - case 'TGeoSubtraction': // '/' - bsp1.direct_subtract(bsp2); - break; - default: - geoWarn(`unsupported bool operation ${shape.fNode._typename}, use first geom`); - break; - } - - if (numGeometryFaces(bsp1) === 0) { - geoWarn('Zero faces in comp shape' + - ` left: ${shape.fNode.fLeft._typename} ${numGeometryFaces(geom1)} faces` + - ` right: ${shape.fNode.fRight._typename} ${numGeometryFaces(geom2)} faces` + - ' use first'); - bsp1 = new CsgGeometry(geom1, matrix1); - } - - return return_bsp ? { polygons: bsp1.toPolygons() } : bsp1.toBufferGeometry(); -} - -/** @summary Try to create projected geometry - * @private */ -function projectGeometry(geom, matrix, projection, position = 0, flippedMesh = false) { - if (!geom.boundingBox) - geom.computeBoundingBox(); - - const box = geom.boundingBox.clone(); - - box.applyMatrix4(matrix); - - if (((box.min[projection] >= position) && (box.max[projection] >= position)) || - ((box.min[projection] <= position) && (box.max[projection] <= position))) - return null; // not interesting - - - const bsp1 = new CsgGeometry(geom, matrix, 0, flippedMesh), - sizex = 2 * Math.max(Math.abs(box.min.x), Math.abs(box.max.x)), - sizey = 2 * Math.max(Math.abs(box.min.y), Math.abs(box.max.y)), - sizez = 2 * Math.max(Math.abs(box.min.z), Math.abs(box.max.z)); - let size = 10000; - - switch (projection) { - case 'x': size = Math.max(sizey, sizez); break; - case 'y': size = Math.max(sizex, sizez); break; - case 'z': size = Math.max(sizex, sizey); break; - } - - const bsp2 = createNormal(projection, position, size); - - bsp1.cut_from_plane(bsp2); - - return bsp2.toBufferGeometry(); -} - -/** @summary Creates geometry model for the provided shape - * @param {object} shape - instance of TGeoShape object - * @param {number} limit - defines return value, see details - * @desc - * - if limit === 0 returns BufferGeometry - * - if limit < 0 just returns estimated number of faces - * - if limit > 0 return list of CsgPolygons (used only for composite shapes) - * @private */ -createGeometry = function(shape, limit = 0) { - try { - switch (shape._typename) { - case clTGeoBBox: return createCubeBuffer(shape, limit); - case clTGeoPara: return createParaBuffer(shape, limit); - case clTGeoTrd1: - case clTGeoTrd2: return createTrapezoidBuffer(shape, limit); - case clTGeoArb8: - case clTGeoTrap: - case clTGeoGtra: return createArb8Buffer(shape, limit); - case clTGeoSphere: return createSphereBuffer(shape, limit); - case clTGeoCone: - case clTGeoConeSeg: - case clTGeoTube: - case clTGeoTubeSeg: - case clTGeoCtub: return createTubeBuffer(shape, limit); - case clTGeoEltu: return createEltuBuffer(shape, limit); - case clTGeoTorus: return createTorusBuffer(shape, limit); - case clTGeoPcon: - case clTGeoPgon: return createPolygonBuffer(shape, limit); - case clTGeoXtru: return createXtruBuffer(shape, limit); - case clTGeoParaboloid: return createParaboloidBuffer(shape, limit); - case clTGeoHype: return createHypeBuffer(shape, limit); - case 'TGeoTessellated': return createTessellatedBuffer(shape, limit); - case clTGeoCompositeShape: return createComposite(shape, limit); - case clTGeoShapeAssembly: break; - case clTGeoScaledShape: { - const res = createGeometry(shape.fShape, limit); - if (shape.fScale && (limit >= 0) && isFunc(res?.scale)) - res.scale(shape.fScale.fScale[0], shape.fScale.fScale[1], shape.fScale.fScale[2]); - return res; - } - case clTGeoHalfSpace: - if (limit < 0) - return 1; // half space if just plane used in composite - default: - geoWarn(`unsupported shape type ${shape._typename}`); - } - } catch (e) { - let place = ''; - if (e.stack !== undefined) { - place = e.stack.split('\n')[0]; - place = place.indexOf(e.message) >= 0 ? e.stack.split('\n')[1] : 'at: ' + place; - } - geoWarn(`${shape._typename} err: ${e.message} ${place}`); - } - - return limit < 0 ? 0 : null; -}; - - -/** @summary Create single shape from EVE7 render date - * @private */ -function makeEveGeometry(rd) { - let off = 0; - - if (rd.sz[0]) { - rd.vtxBuff = new Float32Array(rd.raw.buffer, off, rd.sz[0]); - off += rd.sz[0] * 4; - } - - if (rd.sz[1]) { - // normals were not used - // rd.nrmBuff = new Float32Array(rd.raw.buffer, off, rd.sz[1]); - off += rd.sz[1] * 4; - } - - if (rd.sz[2]) { - // these are special values in the buffer begin - rd.prefixBuf = new Uint32Array(rd.raw.buffer, off, 2); - off += 2 * 4; - rd.idxBuff = new Uint32Array(rd.raw.buffer, off, rd.sz[2] - 2); - // off += (rd.sz[2]-2)*4; - } - - const GL_TRIANGLES = 4; // same as in EVE7 - - if (rd.prefixBuf[0] !== GL_TRIANGLES) - throw Error('Expect triangles first.'); - - const nVert = 3 * rd.prefixBuf[1]; // number of vertices to draw - - if (rd.idxBuff.length !== nVert) - throw Error('Expect single list of triangles in index buffer.'); - - const body = new THREE.BufferGeometry(); - body.setAttribute('position', new THREE.BufferAttribute(rd.vtxBuff, 3)); - body.setIndex(new THREE.BufferAttribute(rd.idxBuff, 1)); - body.computeVertexNormals(); - - return body; -} - -/** @summary Create single shape from geometry viewer render date - * @private */ -function makeViewerGeometry(rd) { - const vtxBuff = new Float32Array(rd.raw.buffer, 0, rd.raw.buffer.byteLength / 4), - body = new THREE.BufferGeometry(); - body.setAttribute('position', new THREE.BufferAttribute(vtxBuff, 3)); - body.setIndex(new THREE.BufferAttribute(new Uint32Array(rd.idx), 1)); - body.computeVertexNormals(); - return body; -} - -/** @summary Create single shape from provided raw data from web viewer. - * @desc If nsegm changed, shape will be recreated - * @private */ -function createServerGeometry(rd, nsegm) { - if (rd.server_shape && ((rd.nsegm === nsegm) || !rd.shape)) - return rd.server_shape; - - rd.nsegm = nsegm; - - let geom; - - if (rd.shape) { - // case when TGeoShape provided as is - geom = createGeometry(rd.shape); - } else { - if (!rd.raw?.buffer) { - console.error('No raw data at all'); - return null; - } - - geom = rd.sz ? makeEveGeometry(rd) : makeViewerGeometry(rd); - } - - // shape handle is similar to created in TGeoPainter - return { - _typename: kShapeType, // indicate that shape can be used as is - ready: true, - geom, - nfaces: numGeometryFaces(geom) - }; -} - -/** @summary Provides info about geo object, used for tooltip info - * @param {object} obj - any kind of TGeo-related object like shape or node or volume - * @private */ -function provideObjectInfo(obj) { - let info = [], shape = null; - - if (obj.fVolume !== undefined) - shape = obj.fVolume.fShape; - else if (obj.fShape !== undefined) - shape = obj.fShape; - else if ((obj.fShapeBits !== undefined) && (obj.fShapeId !== undefined)) - shape = obj; - - if (!shape) { - info.push(obj._typename); - return info; - } - - const sz = Math.max(shape.fDX, shape.fDY, shape.fDZ), - useexp = (sz > 1e7) || (sz < 1e-7), - conv = v => { - if (v === undefined) - return '???'; - if ((v === Math.round(v) && v < 1e7)) - return Math.round(v); - return useexp ? v.toExponential(4) : v.toPrecision(7); - }; - - info.push(shape._typename); - - info.push(`DX=${conv(shape.fDX)} DY=${conv(shape.fDY)} DZ=${conv(shape.fDZ)}`); - - switch (shape._typename) { - case clTGeoPara: - info.push(`Alpha=${shape.fAlpha} Phi=${shape.fPhi} Theta=${shape.fTheta}`); - break; - case clTGeoTrd2: - info.push(`Dy1=${conv(shape.fDy1)} Dy2=${conv(shape.fDy2)}`); // no break - case clTGeoTrd1: - info.push(`Dx1=${conv(shape.fDx1)} Dx2=${conv(shape.fDx2)}`); - break; - case clTGeoSphere: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, - `Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`, - `Theta1=${shape.fTheta1} Theta2=${shape.fTheta2}`); - break; - case clTGeoConeSeg: - info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); - case clTGeoCone: - info.push(`Rmin1=${conv(shape.fRmin1)} Rmax1=${conv(shape.fRmax1)}`, - `Rmin2=${conv(shape.fRmin2)} Rmax2=${conv(shape.fRmax2)}`); - break; - case clTGeoCtub: - case clTGeoTubeSeg: - info.push(`Phi1=${shape.fPhi1} Phi2=${shape.fPhi2}`); - case clTGeoEltu: - case clTGeoTube: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`); - break; - case clTGeoTorus: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, - `Phi1=${shape.fPhi1} Dphi=${shape.fDphi}`); - break; - case clTGeoParaboloid: - info.push(`Rlo=${conv(shape.fRlo)} Rhi=${conv(shape.fRhi)}`, - `A=${conv(shape.fA)} B=${conv(shape.fB)}`); - break; - case clTGeoHype: - info.push(`Rmin=${conv(shape.fRmin)} Rmax=${conv(shape.fRmax)}`, - `StIn=${conv(shape.fStIn)} StOut=${conv(shape.fStOut)}`); - break; - case clTGeoScaledShape: - info = provideObjectInfo(shape.fShape); - if (shape.fScale) - info.unshift(`Scale X=${shape.fScale.fScale[0]} Y=${shape.fScale.fScale[1]} Z=${shape.fScale.fScale[2]}`); - break; - case clTGeoPcon: - case clTGeoPgon: - info.push(`Phi1=${conv(shape.fPhi1)} Dphi=${conv(shape.fDphi)}`, - `Nz=${shape.fNz}`); - break; - case clTGeoXtru: - info.push(`Nz=${shape.fNz} Nvert=${shape.fNvert}`); - break; - case clTGeoCompositeShape: - info.push(`Type ${shape.fNode?._typename}`, - `Left=${shape.fNode?.fLeft?._typename} Right=${shape.fNode?.fRight?._typename}`); - break; - case clTGeoBBox: - info.push(`Origin=[${conv(shape.fOrigin[0])},${conv(shape.fOrigin[1])},${conv(shape.fOrigin[2])}]`); - break; - case clTGeoArb8: - break; - case clTGeoGtra: - info.push(`TwistAngle=${conv(shape.fTwistAngle)}`); - case clTGeoTrap: - info.push(`Phi=${conv(shape.fPhi)} Theta=${conv(shape.fTheta)}`); - break; - case clTGeoShapeAssembly: - break; - } - - return info; -} - -/** @summary Creates projection matrix for the camera - * @private */ -function createProjectionMatrix(camera) { - const cameraProjectionMatrix = new THREE.Matrix4(); - - camera.updateMatrixWorld(); - - camera.matrixWorldInverse.copy(camera.matrixWorld).invert(); - cameraProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - - return cameraProjectionMatrix; -} - -/** @summary Creates frustum - * @private */ -function createFrustum(source) { - if (!source) - return null; - - if (source instanceof THREE.PerspectiveCamera) - source = createProjectionMatrix(source); - - const frustum = new THREE.Frustum(); - frustum.setFromProjectionMatrix(source); - - frustum.corners = new Float32Array([ - 1, 1, 1, - 1, 1, -1, - 1, -1, 1, - 1, -1, -1, - -1, 1, 1, - -1, 1, -1, - -1, -1, 1, - -1, -1, -1, - 0, 0, 0 // also check center of the shape - ]); - - frustum.test = new THREE.Vector3(0, 0, 0); - - frustum.CheckShape = function(matrix, shape) { - const pnt = this.test, len = this.corners.length, corners = this.corners; - - for (let i = 0; i < len; i += 3) { - pnt.x = corners[i] * shape.fDX; - pnt.y = corners[i + 1] * shape.fDY; - pnt.z = corners[i + 2] * shape.fDZ; - if (this.containsPoint(pnt.applyMatrix4(matrix))) - return true; - } - - return false; - }; - - frustum.CheckPoint = function(x, y, z) { - return this.containsPoint(this.test.set(x, y, z)) ? 1 : 0; - }; - - frustum.CheckBox = function(box) { - // only if 6 edges and more are seen, we think that box is fully visible - return this.CheckPoint(box.min.x, box.min.y, box.min.z) + - this.CheckPoint(box.min.x, box.min.y, box.max.z) + - this.CheckPoint(box.min.x, box.max.y, box.min.z) + - this.CheckPoint(box.min.x, box.max.y, box.max.z) + - this.CheckPoint(box.max.x, box.max.y, box.max.z) + - this.CheckPoint(box.max.x, box.min.y, box.max.z) + - this.CheckPoint(box.max.x, box.max.y, box.min.z) + - this.CheckPoint(box.max.x, box.max.y, box.max.z) > 5; - }; - - return frustum; -} - -/** @summary Create node material - * @private */ -function createMaterial(cfg, args0) { - if (!cfg) - cfg = { material_kind: 'lambert' }; - - const args = Object.assign({}, args0); - - if (args.opacity === undefined) - args.opacity = 1; - - if (cfg.transparency) - args.opacity = Math.min(1 - cfg.transparency, args.opacity); - - args.wireframe = cfg.wireframe ?? false; - if (!args.color) - args.color = 'red'; - args.side = THREE.FrontSide; - args.transparent = args.opacity < 1; - args.depthWrite = args.opacity === 1; - - let material; - - if (cfg.material_kind === 'basic') - material = new THREE.MeshBasicMaterial(args); - else if (cfg.material_kind === 'depth') { - delete args.color; - material = new THREE.MeshDepthMaterial(args); - } else if (cfg.material_kind === 'toon') - material = new THREE.MeshToonMaterial(args); - else if (cfg.material_kind === 'matcap') { - delete args.wireframe; - material = new THREE.MeshMatcapMaterial(args); - } else if (cfg.material_kind === 'standard') { - args.metalness = cfg.metalness ?? 0.5; - args.roughness = cfg.roughness ?? 0.1; - material = new THREE.MeshStandardMaterial(args); - } else if (cfg.material_kind === 'normal') { - delete args.color; - material = new THREE.MeshNormalMaterial(args); - } else if (cfg.material_kind === 'physical') { - args.metalness = cfg.metalness ?? 0.5; - args.roughness = cfg.roughness ?? 0.1; - args.reflectivity = cfg.reflectivity ?? 0.5; - args.emissive = args.color; - material = new THREE.MeshPhysicalMaterial(args); - } else if (cfg.material_kind === 'phong') { - args.shininess = cfg.shininess ?? 0.9; - material = new THREE.MeshPhongMaterial(args); - } else { - args.vertexColors = false; - material = new THREE.MeshLambertMaterial(args); - } - - if ((material.flatShading !== undefined) && (cfg.flatShading !== undefined)) - material.flatShading = cfg.flatShading; - material.inherentOpacity = args0.opacity ?? 1; - material.inherentArgs = args0; - - return material; -} - -/** @summary Compares two stacks. - * @return {number} 0 if same, -1 when stack1 < stack2, +1 when stack1 > stack2 - * @private */ -function compare_stacks(stack1, stack2) { - if (stack1 === stack2) - return 0; - const len1 = stack1?.length ?? 0, - len2 = stack2?.length ?? 0, - len = (len1 < len2) ? len1 : len2; - let indx = 0; - while (indx < len) { - if (stack1[indx] < stack2[indx]) - return -1; - if (stack1[indx] > stack2[indx]) - return 1; - ++indx; - } - - return (len1 < len2) ? -1 : ((len1 > len2) ? 1 : 0); -} - -/** @summary Checks if two stack arrays are identical - * @private */ -function isSameStack(stack1, stack2) { - if (!stack1 || !stack2) - return false; - if (stack1 === stack2) - return true; - if (stack1.length !== stack2.length) - return false; - for (let k = 0; k < stack1.length; ++k) { - if (stack1[k] !== stack2[k]) - return false; - } - return true; -} - - -function createFlippedGeom(geom) { - let pos = geom.getAttribute('position').array, - norm = geom.getAttribute('normal').array; - const index = geom.getIndex(); - - if (index) { - // we need to unfold all points to - const arr = index.array, - i0 = geom.drawRange.start; - let ilen = geom.drawRange.count; - if (i0 + ilen > arr.length) - ilen = arr.length - i0; - - const dpos = new Float32Array(ilen * 3), - dnorm = new Float32Array(ilen * 3); - for (let ii = 0; ii < ilen; ++ii) { - const k = arr[i0 + ii]; - if ((k < 0) || (k * 3 >= pos.length)) - console.log(`strange index ${k * 3} totallen = ${pos.length}`); - dpos[ii * 3] = pos[k * 3]; - dpos[ii * 3 + 1] = pos[k * 3 + 1]; - dpos[ii * 3 + 2] = pos[k * 3 + 2]; - dnorm[ii * 3] = norm[k * 3]; - dnorm[ii * 3 + 1] = norm[k * 3 + 1]; - dnorm[ii * 3 + 2] = norm[k * 3 + 2]; - } - - pos = dpos; - norm = dnorm; - } - - const len = pos.length, - newpos = new Float32Array(len), - newnorm = new Float32Array(len); - - // we should swap second and third point in each face - for (let n = 0, shift = 0; n < len; n += 3) { - newpos[n] = pos[n + shift]; - newpos[n + 1] = pos[n + 1 + shift]; - newpos[n + 2] = -pos[n + 2 + shift]; - - newnorm[n] = norm[n + shift]; - newnorm[n + 1] = norm[n + 1 + shift]; - newnorm[n + 2] = -norm[n + 2 + shift]; - - shift += 3; - if (shift === 6) - shift = -3; // values 0,3,-3 - } - - const geomZ = new THREE.BufferGeometry(); - geomZ.setAttribute('position', new THREE.BufferAttribute(newpos, 3)); - geomZ.setAttribute('normal', new THREE.BufferAttribute(newnorm, 3)); - - return geomZ; -} - - -/** @summary Create flipped mesh for the shape - * @desc When transformation matrix includes one or several inversion of axis, - * one should inverse geometry object, otherwise three.js cannot correctly draw it - * @param {object} shape - TGeoShape object - * @param {object} material - material - * @private */ -function createFlippedMesh(shape, material) { - if (shape.geomZ === undefined) - shape.geomZ = createFlippedGeom(shape.geom); - - const mesh = new THREE.Mesh(shape.geomZ, material); - mesh.scale.copy(new THREE.Vector3(1, 1, -1)); - mesh.updateMatrix(); - - mesh._flippedMesh = true; - - return mesh; -} - - -/** - * @summary class for working with cloned nodes - * - * @private - */ - -class ClonedNodes { - - /** @summary Constructor */ - constructor(obj, clones) { - this.toplevel = true; // indicate if object creates top-level structure with Nodes and Volumes folder - this.name_prefix = ''; // name prefix used for nodes names - this.maxdepth = 1; // maximal hierarchy depth, required for transparency - this.vislevel = 4; // maximal depth of nodes visibility aka gGeoManager->SetVisLevel, same default - this.maxnodes = 10000; // maximal number of visible nodes aka gGeoManager->fMaxVisNodes - - if (obj) { - if (obj.$geoh) - this.toplevel = false; - this.createClones(obj); - } else if (clones) - this.nodes = clones; - } - - /** @summary Set maximal depth for nodes visibility */ - setVisLevel(lvl) { - this.vislevel = lvl && Number.isInteger(lvl) ? lvl : 4; - } - - /** @summary Returns maximal depth for nodes visibility */ - getVisLevel() { - return this.vislevel; - } - - /** @summary Set maximal number of visible nodes - * @desc By default 10000 nodes will be visualized */ - setMaxVisNodes(v, more) { - this.maxnodes = (v === Infinity) ? 1e9 : (Number.isFinite(v) ? v : 10000); - if (more && Number.isFinite(more)) - this.maxnodes *= more; - } - - /** @summary Returns configured maximal number of visible nodes */ - getMaxVisNodes() { - return this.maxnodes; - } - - /** @summary Set geo painter configuration - used for material creation */ - setConfig(cfg) { - this._cfg = cfg; - } - - /** @summary Insert node into existing array */ - updateNode(node) { - if (node && Number.isInteger(node.id) && (node.id < this.nodes.length)) - this.nodes[node.id] = node; - } - - /** @summary Returns TGeoShape for element with given indx */ - getNodeShape(indx) { - if (!this.origin || !this.nodes) - return null; - const obj = this.origin[indx], clone = this.nodes[indx]; - if (!obj || !clone) - return null; - return clone.kind === kindGeo ? obj.fVolume?.fShape : obj.fShape; - } - - /** @summary function to cleanup as much as possible structures - * @desc Provided parameters drawnodes and drawshapes are arrays created during building of geometry */ - cleanup(drawnodes, drawshapes) { - if (drawnodes) { - for (let n = 0; n < drawnodes.length; ++n) { - delete drawnodes[n].stack; - drawnodes[n] = undefined; - } - } - - if (drawshapes) { - for (let n = 0; n < drawshapes.length; ++n) { - delete drawshapes[n].geom; - drawshapes[n] = undefined; - } - } - - if (this.nodes) { - for (let n = 0; n < this.nodes.length; ++n) { - if (this.nodes[n]) - delete this.nodes[n].chlds; - } - } - - delete this.nodes; - delete this.origin; - - delete this.sortmap; - } - - /** @summary Create complete description for provided Geo object */ - createClones(obj, sublevel, kind) { - if (!sublevel) { - if (obj?._typename === kShapeType) - return this.createClonesForShape(obj); - - this.origin = []; - sublevel = 1; - kind = getNodeKind(obj); - } - - if ((kind < 0) || !obj || ('_refid' in obj)) - return; - - obj._refid = this.origin.length; - this.origin.push(obj); - this.maxdepth = Math.max(this.maxdepth, sublevel); - - let chlds; - if (kind === kindGeo) - chlds = obj.fVolume?.fNodes?.arr || null; - else - chlds = obj.fElements?.arr || null; - - if (chlds !== null) { - checkDuplicates(obj, chlds); - for (let i = 0; i < chlds.length; ++i) - this.createClones(chlds[i], sublevel + 1, kind); - } - - if (sublevel > 1) - return; - - this.nodes = []; - - const sortarr = []; - - // first create nodes objects - for (let id = 0; id < this.origin.length; ++id) { - // let obj = this.origin[id]; - const node = { id, kind, vol: 0, nfaces: 0 }; - this.nodes.push(node); - sortarr.push(node); // array use to produce sortmap - } - - // than fill children lists - for (let n = 0; n < this.origin.length; ++n) { - const obj2 = this.origin[n], - clone = this.nodes[n], - shape = kind === kindEve ? obj2.fShape : obj2.fVolume.fShape, - chlds2 = kind === kindEve ? obj2.fElements?.arr : obj2.fVolume.fNodes?.arr, - matrix = getNodeMatrix(kind, obj2); - - if (matrix) { - clone.matrix = matrix.elements; // take only matrix elements, matrix will be constructed in worker - if (clone.matrix && (clone.matrix[0] === 1)) { - let issimple = true; - for (let k = 1; (k < clone.matrix.length) && issimple; ++k) - issimple = (clone.matrix[k] === ((k === 5) || (k === 10) || (k === 15) ? 1 : 0)); - if (issimple) - delete clone.matrix; - } - if (clone.matrix && (kind === kindEve)) - clone.abs_matrix = true; - } - if (shape) { - clone.fDX = shape.fDX; - clone.fDY = shape.fDY; - clone.fDZ = shape.fDZ; - clone.vol = Math.sqrt(shape.fDX ** 2 + shape.fDY ** 2 + shape.fDZ ** 2); - if (shape.$nfaces === undefined) - shape.$nfaces = createGeometry(shape, -1); - clone.nfaces = shape.$nfaces; - if (clone.nfaces <= 0) - clone.vol = 0; - } - - if (chlds2) { - // in cloned object children is only list of ids - clone.chlds = new Array(chlds2.length); - for (let k = 0; k < chlds2.length; ++k) - clone.chlds[k] = chlds2[k]._refid; - } - } - - // remove _refid identifiers from original objects - for (let n = 0; n < this.origin.length; ++n) - delete this.origin[n]._refid; - - // do sorting once - sortarr.sort((a, b) => b.vol - a.vol); - - // remember sort map and also sortid - this.sortmap = new Array(this.nodes.length); - for (let n = 0; n < this.nodes.length; ++n) { - this.sortmap[n] = sortarr[n].id; - sortarr[n].sortid = n; - } - } - - /** @summary Create elementary item with single already existing shape - * @desc used by details view of geometry shape */ - createClonesForShape(obj) { - this.origin = []; - - // indicate that just plain shape is used - this.plain_shape = obj; - - this.nodes = [{ - id: 0, sortid: 0, kind: kindShape, - name: 'Shape', - nfaces: obj.nfaces, - fDX: 1, fDY: 1, fDZ: 1, vol: 1, - vis: true - }]; - } - - /** @summary Count all visible nodes */ - countVisibles() { - const len = this.nodes?.length || 0; - let cnt = 0; - for (let k = 0; k < len; ++k) { - if (this.nodes[k].vis) - cnt++; - } - return cnt; - } - - /** @summary Mark visible nodes. - * @desc Set only basic flags, actual visibility depends from hierarchy */ - markVisibles(on_screen, copy_bits, hide_top_volume) { - if (this.plain_shape) - return 1; - if (!this.origin || !this.nodes) - return 0; - - let res = 0; - - for (let n = 0; n < this.nodes.length; ++n) { - const clone = this.nodes[n], obj = this.origin[n]; - - clone.vis = 0; // 1 - only with last level - delete clone.nochlds; - - if (clone.kind === kindGeo) { - if (obj.fVolume) { - if (on_screen) { - // on screen bits used always, childs always checked - clone.vis = testGeoBit(obj.fVolume, geoBITS.kVisOnScreen) ? 99 : 0; - - if ((n === 0) && clone.vis && hide_top_volume) - clone.vis = 0; - - if (copy_bits) { - setGeoBit(obj.fVolume, geoBITS.kVisNone, false); - setGeoBit(obj.fVolume, geoBITS.kVisThis, (clone.vis > 0)); - setGeoBit(obj.fVolume, geoBITS.kVisDaughters, true); - setGeoBit(obj, geoBITS.kVisDaughters, true); - } - } else { - clone.vis = !testGeoBit(obj.fVolume, geoBITS.kVisNone) && testGeoBit(obj.fVolume, geoBITS.kVisThis) ? 99 : 0; - - if (!testGeoBit(obj, geoBITS.kVisDaughters) || !testGeoBit(obj.fVolume, geoBITS.kVisDaughters)) - clone.nochlds = true; - - // node with childs only shown in case if it is last level in hierarchy - if ((clone.vis > 0) && clone.chlds && !clone.nochlds) - clone.vis = 1; - - // special handling for top node - if (n === 0) { - if (hide_top_volume) - clone.vis = 0; - delete clone.nochlds; - } - } - } - } else { - clone.vis = obj.fRnrSelf ? 99 : 0; - - // when the only node is selected, draw it - if ((n === 0) && (this.nodes.length === 1)) - clone.vis = 99; - - this.vislevel = 9999; // automatically take all volumes - } - - // shape with zero volume or without faces will not be observed - if ((clone.vol <= 0) || (clone.nfaces <= 0)) - clone.vis = 0; - - if (clone.vis) - res++; - } - - return res; - } - - /** @summary After visibility flags is set, produce id shifts for all nodes as it would be maximum level */ - produceIdShifts() { - for (let k = 0; k < this.nodes.length; ++k) - this.nodes[k].idshift = -1; - - function scan_func(nodes, node) { - if (node.idshift < 0) { - node.idshift = 0; - if (node.chlds) { - for (let k = 0; k < node.chlds.length; ++k) - node.idshift += scan_func(nodes, nodes[node.chlds[k]]); - } - } - - return node.idshift + 1; - } - - scan_func(this.nodes, this.nodes[0]); - } - - /** @summary Extract only visibility flags - * @desc Used to transfer them to the worker */ - getVisibleFlags() { - const res = new Array(this.nodes.length); - for (let n = 0; n < this.nodes.length; ++n) - res[n] = { vis: this.nodes[n].vis, nochlds: this.nodes[n].nochlds }; - return res; - } - - /** @summary Assign only visibility flags, extracted with getVisibleFlags */ - setVisibleFlags(flags) { - if (!this.nodes || (flags?.length !== this.nodes.length)) - return 0; - - let res = 0; - for (let n = 0; n < this.nodes.length; ++n) { - const clone = this.nodes[n]; - clone.vis = flags[n].vis; - clone.nochlds = flags[n].nochlds; - if (clone.vis) - res++; - } - - return res; - } - - /** @summary Set visibility flag for physical node - * @desc Trying to reimplement functionality in the RGeomViewer */ - setPhysNodeVisibility(stack, on) { - let do_clear = false; - if (on === 'clearall') { - delete this.fVisibility; - return; - } else if (on === 'clear') { - do_clear = true; - if (!this.fVisibility) - return; - } else - on = Boolean(on); - if (!stack) - return; - - if (!this.fVisibility) - this.fVisibility = []; - - for (let indx = 0; indx < this.fVisibility.length; ++indx) { - const item = this.fVisibility[indx], - res = compare_stacks(item.stack, stack); - - if (res === 0) { - if (do_clear) { - this.fVisibility.splice(indx, 1); - if (!this.fVisibility.length) - delete this.fVisibility; - } else - item.visible = on; - - return; - } - - if (res > 0) { - if (!do_clear) - this.fVisibility.splice(indx, 0, { visible: on, stack }); - return; - } - } - - if (!do_clear) - this.fVisibility.push({ visible: on, stack }); - } - - /** @summary Get visibility item for physical node */ - getPhysNodeVisibility(stack) { - if (!stack || !this.fVisibility) - return null; - for (let indx = 0; indx < this.fVisibility.length; ++indx) { - const item = this.fVisibility[indx], - res = compare_stacks(item.stack, stack); - if (res === 0) - return item; - if (res > 0) - return null; - } - - return null; - } - - /** @summary Scan visible nodes in hierarchy, starting from nodeid - * @desc Each entry in hierarchy get its unique id, which is not changed with visibility flags */ - scanVisible(arg, vislvl) { - if (!this.nodes) - return 0; - - if (vislvl === undefined) { - if (!arg) - arg = {}; - - vislvl = arg.vislvl || this.vislevel || 4; // default 3 in ROOT - if (vislvl > 88) - vislvl = 88; - - arg.stack = new Array(100); // current stack - arg.nodeid = 0; - arg.counter = 0; // sequence ID of the node, used to identify it later - arg.last = 0; - arg.copyStack = function(factor) { - const entry = { nodeid: this.nodeid, seqid: this.counter, stack: new Array(this.last) }; - if (factor) - entry.factor = factor; // factor used to indicate importance of entry, will be built as first - for (let n = 0; n < this.last; ++n) - entry.stack[n] = this.stack[n + 1]; // copy stack - return entry; - }; - - if (arg.domatrix) { - arg.matrices = []; - arg.mpool = [new THREE.Matrix4()]; // pool of Matrix objects to avoid permanent creation - arg.getmatrix = function() { return this.matrices[this.last]; }; - } - - if (this.fVisibility?.length) { - arg.vindx = 0; - arg.varray = this.fVisibility; - arg.vstack = arg.varray[arg.vindx].stack; - arg.testPhysVis = function() { - if (!this.vstack || (this.vstack?.length !== this.last)) - return undefined; - for (let n = 0; n < this.last; ++n) { - if (this.vstack[n] !== this.stack[n + 1]) - return undefined; - } - const res = this.varray[this.vindx++].visible; - this.vstack = this.vindx < this.varray.length ? this.varray[this.vindx].stack : null; - return res; - }; - } - } - - const node = this.nodes[arg.nodeid]; - let res = 0; - - if (arg.domatrix) { - if (!arg.mpool[arg.last + 1]) - arg.mpool[arg.last + 1] = new THREE.Matrix4(); - - const prnt = (arg.last > 0) ? arg.matrices[arg.last - 1] : new THREE.Matrix4(); - if (node.matrix) { - arg.matrices[arg.last] = arg.mpool[arg.last].fromArray(prnt.elements); - arg.matrices[arg.last].multiply(arg.mpool[arg.last + 1].fromArray(node.matrix)); - } else - arg.matrices[arg.last] = prnt; - } - - let node_vis = node.vis, node_nochlds = node.nochlds; - - if ((arg.nodeid === 0) && arg.main_visible) - node_vis = vislvl + 1; - else if (arg.testPhysVis) { - const res2 = arg.testPhysVis(); - if (res2 !== undefined) { - node_vis = res2 && !node.chlds ? vislvl + 1 : 0; - node_nochlds = !res2; - } - } - - if (node_nochlds) - vislvl = 0; - - if (node_vis > vislvl) { - if (!arg.func || arg.func(node)) - res++; - } - - arg.counter++; - - if ((vislvl > 0) && node.chlds) { - arg.last++; - for (let i = 0; i < node.chlds.length; ++i) { - arg.nodeid = node.chlds[i]; - arg.stack[arg.last] = i; // in the stack one store index of child, it is path in the hierarchy - res += this.scanVisible(arg, vislvl - 1); - } - arg.last--; - } else - arg.counter += (node.idshift || 0); - - - if (arg.last === 0) { - delete arg.last; - delete arg.stack; - delete arg.copyStack; - delete arg.counter; - delete arg.matrices; - delete arg.mpool; - delete arg.getmatrix; - delete arg.vindx; - delete arg.varray; - delete arg.vstack; - delete arg.testPhysVis; - } - - return res; - } - - /** @summary Return node name with given id. - * @desc Either original object or description is used */ - getNodeName(nodeid) { - if (this.origin) { - const obj = this.origin[nodeid]; - return obj ? getObjectName(obj) : ''; - } - const node = this.nodes[nodeid]; - return node ? node.name : ''; - } - - /** @summary Returns description for provided stack - * @desc If specified, absolute matrix is also calculated */ - resolveStack(stack, withmatrix) { - const res = { id: 0, obj: null, node: this.nodes[0], name: this.name_prefix || '' }; - - if (withmatrix) { - res.matrix = new THREE.Matrix4(); - if (res.node.matrix) - res.matrix.fromArray(res.node.matrix); - } - - if (this.origin) - res.obj = this.origin[0]; - - // if (!res.name) - // res.name = this.getNodeName(0); - - if (stack) { - for (let lvl = 0; lvl < stack.length; ++lvl) { - res.id = res.node.chlds[stack[lvl]]; - res.node = this.nodes[res.id]; - - if (this.origin) - res.obj = this.origin[res.id]; - - const subname = this.getNodeName(res.id); - if (subname) { - if (res.name) - res.name += '/'; - res.name += subname; - } - - if (withmatrix && res.node.matrix) - res.matrix.multiply(new THREE.Matrix4().fromArray(res.node.matrix)); - } - } - - return res; - } - - /** @summary Provide stack name - * @desc Stack name includes full path to the physical node which is identified by stack */ - getStackName(stack) { - return this.resolveStack(stack).name; - } - - /** @summary Create stack array based on nodes ids array. - * @desc Ids list should correspond to existing nodes hierarchy */ - buildStackByIds(ids) { - if (!ids) - return null; - - if (ids[0]) { - console.error('wrong ids - first should be 0'); - return null; - } - - let node = this.nodes[0]; - const stack = []; - - for (let k = 1; k < ids.length; ++k) { - const nodeid = ids[k]; - if (!node) - return null; - const chindx = node.chlds.indexOf(nodeid); - if (chindx < 0) { - console.error(`wrong nodes ids ${ids[k]} is not child of ${ids[k - 1]}`); - return null; - } - - stack.push(chindx); - node = this.nodes[nodeid]; - } - - return stack; - } - - /** @summary Returns ids array which correspond to the stack */ - buildIdsByStack(stack) { - if (!stack) - return null; - let node = this.nodes[0]; - const ids = [0]; - for (let k = 0; k < stack.length; ++k) { - const id = node.chlds[stack[k]]; - ids.push(id); - node = this.nodes[id]; - } - return ids; - } - - /** @summary Returns node id by stack */ - getNodeIdByStack(stack) { - if (!stack || !this.nodes) - return -1; - let node = this.nodes[0], id = 0; - for (let k = 0; k < stack.length; ++k) { - id = node.chlds[stack[k]]; - node = this.nodes[id]; - } - return id; - } - - /** @summary Returns true if stack includes at any place provided nodeid */ - isIdInStack(nodeid, stack) { - if (!nodeid) - return true; - - let node = this.nodes[0]; - - for (let lvl = 0; lvl < stack.length; ++lvl) { - const id = node.chlds[stack[lvl]]; - if (id === nodeid) - return true; - node = this.nodes[id]; - } - - return false; - } - - /** @summary Find stack by name which include names of all parents */ - findStackByName(fullname) { - const names = fullname.split('/'), stack = []; - let currid = 0; - - if (this.getNodeName(currid) !== names[0]) - return null; - - for (let n = 1; n < names.length; ++n) { - const node = this.nodes[currid]; - if (!node.chlds) - return null; - - for (let k = 0; k < node.chlds.length; ++k) { - const chldid = node.chlds[k]; - if (this.getNodeName(chldid) === names[n]) { - stack.push(k); - currid = chldid; - break; - } - } - - // no new entry - not found stack - if (stack.length === n - 1) - return null; - } - - return stack; - } - - /** @summary Set usage of default ROOT colors */ - setDefaultColors(on) { - this.use_dflt_colors = on; - if (this.use_dflt_colors && !this.dflt_table) { - const nmax = 110, col = [], dflt = { kWhite: 0, kBlack: 1, kGray: 920, - kRed: 632, kGreen: 416, kBlue: 600, kYellow: 400, kMagenta: 616, kCyan: 432, - kOrange: 800, kSpring: 820, kTeal: 840, kAzure: 860, kViolet: 880, kPink: 900 }; - for (let i = 0; i < nmax; i++) - col.push(dflt.kGray); - - // here we should create a new TColor with the same rgb as in the default - // ROOT colors used below - col[3] = dflt.kYellow - 10; - col[4] = col[5] = dflt.kGreen - 10; - col[6] = col[7] = dflt.kBlue - 7; - col[8] = col[9] = dflt.kMagenta - 3; - col[10] = col[11] = dflt.kRed - 10; - col[12] = dflt.kGray + 1; - col[13] = dflt.kBlue - 10; - col[14] = dflt.kOrange + 7; - col[16] = dflt.kYellow + 1; - col[20] = dflt.kYellow - 10; - col[24] = col[25] = col[26] = dflt.kBlue - 8; - col[29] = dflt.kOrange + 9; - col[79] = dflt.kOrange - 2; - - this.dflt_table = col; - } - } - - /** @summary Provide different properties of draw entry nodeid - * @desc Only if node visible, material will be created */ - getDrawEntryProperties(entry, root_colors) { - const clone = this.nodes[entry.nodeid], visible = true; - - if (clone.kind === kindShape) { - const prop = { name: clone.name, nname: clone.name, shape: null, material: null, chlds: null }, - opacity = entry.opacity || 1, col = entry.color || '#0000FF'; - prop.fillcolor = new THREE.Color(col[0] === '#' ? col : `rgb(${col})`); - prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); - return prop; - } - - if (!this.origin) { - console.error(`origin not there - kind ${clone.kind} id ${entry.nodeid}`); - return null; - } - - const node = this.origin[entry.nodeid]; - - if (clone.kind === kindEve) { - // special handling for EVE nodes - - const prop = { name: getObjectName(node), nname: getObjectName(node), shape: node.fShape, material: null, chlds: null }; - - if (node.fElements !== null) - prop.chlds = node.fElements.arr; - - if (visible) { - const opacity = Math.min(1, node.fRGBA[3]); - prop.fillcolor = new THREE.Color(node.fRGBA[0], node.fRGBA[1], node.fRGBA[2]); - prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); - } - - return prop; - } - - const volume = node.fVolume, prop = { - name: getObjectName(volume), nname: getObjectName(node), volume, shape: volume.fShape, material: null, - chlds: volume.fNodes?.arr, linewidth: volume.fLineWidth - }; - - if (visible) { - let opacity = 1.0; - if (!root_colors) - root_colors = getRootColors(); - - if (entry.custom_color) - prop.fillcolor = entry.custom_color; - else if ((volume.fFillColor > 1) && (volume.fLineColor === 1)) - prop.fillcolor = root_colors[volume.fFillColor]; - else if (volume.fLineColor >= 0) - prop.fillcolor = root_colors[volume.fLineColor]; - - const mat = volume.fMedium?.fMaterial; - - if (mat) { - const fillstyle = mat.fFillStyle; - let transparency = (fillstyle >= 3000 && fillstyle <= 3100) ? fillstyle - 3000 : 0; - - if (this.use_dflt_colors) { - const matZ = Math.round(mat.fZ), icol = this.dflt_table[matZ]; - prop.fillcolor = root_colors[icol]; - if (mat.fDensity < 0.1) - transparency = 60; - } - - if (transparency > 0) - opacity = (100 - transparency) / 100; - if (prop.fillcolor === undefined) - prop.fillcolor = root_colors[mat.fFillColor]; - } - if (prop.fillcolor === undefined) - prop.fillcolor = 'lightgrey'; - - prop.material = createMaterial(this._cfg, { opacity, color: prop.fillcolor }); - } - - return prop; - } - - /** @summary Creates hierarchy of Object3D for given stack entry - * @desc Such hierarchy repeats hierarchy of TGeoNodes and set matrix for the objects drawing - * also set renderOrder, required to handle transparency */ - createObject3D(stack, toplevel, options) { - let node = this.nodes[0], three_prnt = toplevel, draw_depth = 0; - const force = isObject(options) || (options === 'force'); - - for (let lvl = 0; lvl <= stack.length; ++lvl) { - const nchld = (lvl > 0) ? stack[lvl - 1] : 0, - // extract current node - child = (lvl > 0) ? this.nodes[node.chlds[nchld]] : node; - if (!child) { - console.error(`Wrong stack ${JSON.stringify(stack)} for nodes at level ${lvl}, node.id ${node.id}, numnodes ${this.nodes.length}, nchld ${nchld}, numchilds ${node.chlds.length}, chldid ${node.chlds[nchld]}`); - return null; - } - - node = child; - - let obj3d; - - if (three_prnt.children) { - for (let i = 0; i < three_prnt.children.length; ++i) { - if (three_prnt.children[i].nchld === nchld) { - obj3d = three_prnt.children[i]; - break; - } - } - } - - if (obj3d) { - three_prnt = obj3d; - if (obj3d.$jsroot_drawable) - draw_depth++; - continue; - } - - if (!force) - return null; - - obj3d = new THREE.Object3D(); - - if (this._cfg?.set_names) - obj3d.name = this.getNodeName(node.id); - - if (this._cfg?.set_origin && this.origin) - obj3d.userData = this.origin[node.id]; - - if (node.abs_matrix) { - obj3d.absMatrix = new THREE.Matrix4(); - obj3d.absMatrix.fromArray(node.matrix); - } else if (node.matrix) { - obj3d.matrix.fromArray(node.matrix); - obj3d.matrix.decompose(obj3d.position, obj3d.quaternion, obj3d.scale); - } - - // this.accountNodes(obj3d); - obj3d.nchld = nchld; // mark index to find it again later - - // add the mesh to the scene - three_prnt.add(obj3d); - - // this is only for debugging - test inversion of whole geometry - if ((lvl === 0) && isObject(options) && options.scale) { - if ((options.scale.x < 0) || (options.scale.y < 0) || (options.scale.z < 0)) { - obj3d.scale.copy(options.scale); - obj3d.updateMatrix(); - } - } - - obj3d.updateMatrixWorld(); - - three_prnt = obj3d; - } - - if ((options === kGetMesh) || (options === kDeleteMesh)) { - let mesh = null; - if (three_prnt) { - for (let n = 0; (n < three_prnt.children.length) && !mesh; ++n) { - const chld = three_prnt.children[n]; - if ((chld.type === 'Mesh') && (chld.nchld === undefined)) - mesh = chld; - } - } - - if ((options === kGetMesh) || !mesh) - return mesh; - - const res = three_prnt; - while (mesh && (mesh !== toplevel)) { - three_prnt = mesh.parent; - three_prnt.remove(mesh); - mesh = !three_prnt.children.length ? three_prnt : null; - } - - return res; - } - - if (three_prnt) { - three_prnt.$jsroot_drawable = true; - three_prnt.$jsroot_depth = draw_depth; - } - - return three_prnt; - } - - /** @summary Create mesh for single physical node */ - createEntryMesh(ctrl, toplevel, entry, shape, colors) { - if (!shape || !shape.ready) - return null; - - entry.done = true; // mark entry is created - shape.used = true; // indicate that shape was used in building - - if (!shape.geom || !shape.nfaces) { - // node is visible, but shape does not created - this.createObject3D(entry.stack, toplevel, kDeleteMesh); - return null; - } - - const prop = this.getDrawEntryProperties(entry, colors), - obj3d = this.createObject3D(entry.stack, toplevel, ctrl), - matrix = obj3d.absMatrix || obj3d.matrixWorld; - - prop.material.wireframe = ctrl.wireframe; - - prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide; - - let mesh; - if (matrix.determinant() > -0.9) - mesh = new THREE.Mesh(shape.geom, prop.material); - else - mesh = createFlippedMesh(shape, prop.material); - - obj3d.add(mesh); - - if (obj3d.absMatrix) { - mesh.matrix.copy(obj3d.absMatrix); - mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale); - mesh.updateMatrixWorld(); - } - - // keep full stack of nodes - mesh.stack = entry.stack; - mesh.renderOrder = this.maxdepth - entry.stack.length; // order of transparency handling - - if (ctrl.set_names) - mesh.name = this.getNodeName(entry.nodeid); - - if (ctrl.set_origin) - mesh.userData = prop.volume; - - // keep hierarchy level - mesh.$jsroot_order = obj3d.$jsroot_depth; - - if (ctrl.info?.num_meshes !== undefined) { - ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces; - } - - // set initial render order, when camera moves, one must refine it - // mesh.$jsroot_order = mesh.renderOrder = - // this._clones.maxdepth - ((obj3d.$jsroot_depth !== undefined) ? obj3d.$jsroot_depth : entry.stack.length); - - return mesh; - } - - /** @summary Check if instancing can be used for the nodes */ - createInstancedMeshes(ctrl, toplevel, draw_nodes, build_shapes, colors) { - if (ctrl.instancing < 0) - return false; - - // first delete previous data - const used_shapes = []; - let max_entries = 1; - - for (let n = 0; n < draw_nodes.length; ++n) { - const entry = draw_nodes[n]; - if (entry.done) - continue; - - // shape can be provided with entry itself - const shape = entry.server_shape || build_shapes[entry.shapeid]; - if (!shape || !shape.ready) { - console.warn(`Problem with shape id ${entry.shapeid} when building`); - return false; - } - - // ignore shape without geometry - if (!shape.geom || !shape.nfaces) - continue; - - if (shape.instances === undefined) { - shape.instances = []; - used_shapes.push(shape); - } - - const instance = shape.instances.find(i => i.nodeid === entry.nodeid); - - if (instance) { - instance.entries.push(entry); - max_entries = Math.max(max_entries, instance.entries.length); - } else - shape.instances.push({ nodeid: entry.nodeid, entries: [entry] }); - } - - const make_sense = ctrl.instancing > 0 ? (max_entries > 2) : (draw_nodes.length > 10000) && (max_entries > 10); - - if (!make_sense) { - used_shapes.forEach(shape => { delete shape.instances; }); - return false; - } - - used_shapes.forEach(shape => { - shape.used = true; - shape.instances.forEach(instance => { - const entry0 = instance.entries[0], - prop = this.getDrawEntryProperties(entry0, colors); - - prop.material.wireframe = ctrl.wireframe; - - prop.material.side = ctrl.doubleside ? THREE.DoubleSide : THREE.FrontSide; - - if (instance.entries.length === 1) - this.createEntryMesh(ctrl, toplevel, entry0, shape, colors); - else { - const arr1 = [], arr2 = [], stacks1 = [], stacks2 = [], names1 = [], names2 = []; - - instance.entries.forEach(entry => { - const info = this.resolveStack(entry.stack, true); - // FIXME: probably here must be > 0 - if (info.matrix.determinant() > -0.9) { - arr1.push(info.matrix); - stacks1.push(entry.stack); - names1.push(this.getNodeName(entry.nodeid)); - } else { - arr2.push(info.matrix); - stacks2.push(entry.stack); - names2.push(this.getNodeName(entry.nodeid)); - } - entry.done = true; - }); - - if (arr1.length) { - const mesh1 = new THREE.InstancedMesh(shape.geom, prop.material, arr1.length); - - mesh1.stacks = stacks1; - arr1.forEach((matrix, i) => mesh1.setMatrixAt(i, matrix)); - - toplevel.add(mesh1); - - mesh1.renderOrder = 1; - - if (ctrl.set_names) { - mesh1.name = names1[0]; - mesh1.names = names1; - } - - if (ctrl.set_origin) - mesh1.userData = prop.volume; - - mesh1.$jsroot_order = 1; - ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces * arr1.length; - } - - if (arr2.length) { - if (shape.geomZ === undefined) - shape.geomZ = createFlippedGeom(shape.geom); - - const mesh2 = new THREE.InstancedMesh(shape.geomZ, prop.material, arr2.length); - - mesh2.stacks = stacks2; - const m = new THREE.Matrix4().makeScale(1, 1, -1); - arr2.forEach((matrix, i) => { - mesh2.setMatrixAt(i, matrix.multiply(m)); - }); - mesh2._flippedMesh = true; - - toplevel.add(mesh2); - - mesh2.renderOrder = 1; - if (ctrl.set_names) { - mesh2.name = names2[0]; - mesh2.names = names2; - } - if (ctrl.set_origin) - mesh2.userData = prop.volume; - - mesh2.$jsroot_order = 1; - ctrl.info.num_meshes++; - ctrl.info.num_faces += shape.nfaces * arr2.length; - } - } - }); - - delete shape.instances; - }); - - return true; - } - - /** @summary Get volume boundary */ - getVolumeBoundary(viscnt, facelimit, nodeslimit) { - const result = { min: 0, max: 1, sortidcut: 0 }; - - if (!this.sortmap) { - console.error('sorting map do not exist'); - return result; - } - - let maxNode, currNode, cnt = 0, facecnt = 0; - - for (let n = 0; (n < this.sortmap.length) && (cnt < nodeslimit) && (facecnt < facelimit); ++n) { - const id = this.sortmap[n]; - if (viscnt[id] === 0) - continue; - currNode = this.nodes[id]; - if (!maxNode) - maxNode = currNode; - cnt += viscnt[id]; - facecnt += viscnt[id] * currNode.nfaces; - } - - if (!currNode) { - console.error('no volumes selected'); - return result; - } - - result.max = maxNode.vol; - result.min = currNode.vol; - result.sortidcut = currNode.sortid; // latest node is not included - return result; - } - - /** @summary Collects visible nodes, using maxlimit - * @desc One can use map to define cut based on the volume or serious of cuts */ - collectVisibles(maxnumfaces, frustum) { - // in simple case shape as it is - if (this.plain_shape) - return { lst: [{ nodeid: 0, seqid: 0, stack: [], factor: 1, shapeid: 0, server_shape: this.plain_shape }], complete: true }; - - const arg = { - facecnt: 0, - viscnt: new Array(this.nodes.length), // counter for each node - vislvl: this.getVisLevel(), - reset() { - this.total = 0; - this.facecnt = 0; - this.viscnt.fill(0); - }, - func(node) { - this.total++; - this.facecnt += node.nfaces; - this.viscnt[node.id]++; - return true; - } - }; - - arg.reset(); - - let total = this.scanVisible(arg); - if ((total === 0) && (this.nodes[0].vis < 2) && !this.nodes[0].nochlds) { - // try to draw only main node by default - arg.reset(); - arg.main_visible = true; - total = this.scanVisible(arg); - } - - const maxnumnodes = this.getMaxVisNodes(); - - if (maxnumnodes > 0) { - while ((total > maxnumnodes) && (arg.vislvl > 1)) { - arg.vislvl--; - arg.reset(); - total = this.scanVisible(arg); - } - } - - this.actual_level = arg.vislvl; // not used, can be shown somewhere in the gui - - let minVol = 0, maxVol, camVol = -1, camFact = 10, sortidcut = this.nodes.length + 1; - - if (arg.facecnt > maxnumfaces) { - const bignumfaces = maxnumfaces * (frustum ? 0.8 : 1.0), - bignumnodes = maxnumnodes * (frustum ? 0.8 : 1.0), - // define minimal volume, which always to shown - boundary = this.getVolumeBoundary(arg.viscnt, bignumfaces, bignumnodes); - - minVol = boundary.min; - maxVol = boundary.max; - sortidcut = boundary.sortidcut; - - if (frustum) { - arg.domatrix = true; - arg.frustum = frustum; - arg.totalcam = 0; - arg.func = function(node) { - if (node.vol <= minVol) { - // only small volumes are interesting - if (this.frustum.CheckShape(this.getmatrix(), node)) { - this.viscnt[node.id]++; - this.totalcam += node.nfaces; - } - } - - return true; - }; - - for (let n = 0; n < arg.viscnt.length; ++n) - arg.viscnt[n] = 0; - - this.scanVisible(arg); - - if (arg.totalcam > maxnumfaces * 0.2) - camVol = this.getVolumeBoundary(arg.viscnt, maxnumfaces * 0.2, maxnumnodes * 0.2).min; - else - camVol = 0; - - camFact = maxVol / (camVol > 0 ? camVol : minVol); - } - } - - arg.items = []; - - arg.func = function(node) { - if (node.sortid < sortidcut) - this.items.push(this.copyStack()); - else if ((camVol >= 0) && (node.vol > camVol)) { - if (this.frustum.CheckShape(this.getmatrix(), node)) - this.items.push(this.copyStack(camFact)); - } - return true; - }; - - this.scanVisible(arg); - - return { lst: arg.items, complete: minVol === 0 }; - } - - /** @summary Merge list of drawn objects - * @desc In current list we should mark if object already exists - * from previous list we should collect objects which are not there */ - mergeVisibles(current, prev) { - let indx2 = 0; - const del = []; - for (let indx1 = 0; (indx1 < current.length) && (indx2 < prev.length); ++indx1) { - while ((indx2 < prev.length) && (prev[indx2].seqid < current[indx1].seqid)) - del.push(prev[indx2++]); // this entry should be removed - - - if ((indx2 < prev.length) && (prev[indx2].seqid === current[indx1].seqid)) { - if (prev[indx2].done) - current[indx1].done = true; // copy ready flag - indx2++; - } - } - - // remove rest - while (indx2 < prev.length) - del.push(prev[indx2++]); - - return del; - } - - /** @summary Collect all uniques shapes which should be built - * @desc Check if same shape used many times for drawing */ - collectShapes(lst) { - // nothing else - just that single shape - if (this.plain_shape) - return [this.plain_shape]; - - const shapes = []; - - for (let i = 0; i < lst.length; ++i) { - const entry = lst[i], - shape = this.getNodeShape(entry.nodeid); - - if (!shape) - continue; // strange, but avoid misleading - - if (shape._id === undefined) { - shape._id = shapes.length; - - shapes.push({ id: shape._id, shape, vol: this.nodes[entry.nodeid].vol, refcnt: 1, factor: 1, ready: false }); - - // shapes.push( { obj: shape, vol: this.nodes[entry.nodeid].vol }); - } else - shapes[shape._id].refcnt++; - - - entry.shape = shapes[shape._id]; // remember shape used - - // use maximal importance factor to push element to the front - if (entry.factor && (entry.factor > entry.shape.factor)) - entry.shape.factor = entry.factor; - } - - // now sort shapes in volume decrease order - shapes.sort((a, b) => b.vol * b.factor - a.vol * a.factor); - - // now set new shape ids according to the sorted order and delete temporary field - for (let n = 0; n < shapes.length; ++n) { - const item = shapes[n]; - item.id = n; // set new ID - delete item.shape._id; // remove temporary field - } - - // as last action set current shape id to each entry - for (let i = 0; i < lst.length; ++i) { - const entry = lst[i]; - if (entry.shape) { - entry.shapeid = entry.shape.id; // keep only id for the entry - delete entry.shape; // remove direct references - } - } - - return shapes; - } - - /** @summary Merge shape lists */ - mergeShapesLists(oldlst, newlst) { - if (!oldlst) - return newlst; - - // set geometry to shape object itself - for (let n = 0; n < oldlst.length; ++n) { - const item = oldlst[n]; - - item.shape._geom = item.geom; - delete item.geom; - - if (item.geomZ !== undefined) { - item.shape._geomZ = item.geomZ; - delete item.geomZ; - } - } - - // take from shape (if match) - for (let n = 0; n < newlst.length; ++n) { - const item = newlst[n]; - - if (item.shape._geom !== undefined) { - item.geom = item.shape._geom; - delete item.shape._geom; - } - - if (item.shape._geomZ !== undefined) { - item.geomZ = item.shape._geomZ; - delete item.shape._geomZ; - } - } - - // now delete all unused geometries - for (let n = 0; n < oldlst.length; ++n) { - const item = oldlst[n]; - delete item.shape._geom; - delete item.shape._geomZ; - } - - return newlst; - } - - /** @summary Build shapes */ - buildShapes(lst, limit, timelimit) { - let created = 0; - const tm1 = new Date().getTime(), - res = { done: false, shapes: 0, faces: 0, notusedshapes: 0 }; - - for (let n = 0; n < lst.length; ++n) { - const item = lst[n]; - - // if enough faces are produced, nothing else is required - if (res.done) { - item.ready = true; - continue; - } - - if (!item.ready) { - item._typename = kShapeType; // let reuse item for direct drawing - item.ready = true; - if (item.geom === undefined) { - item.geom = createGeometry(item.shape); - if (item.geom) - created++; // indicate that at least one shape was created - } - item.nfaces = numGeometryFaces(item.geom); - } - - res.shapes++; - if (!item.used) - res.notusedshapes++; - res.faces += item.nfaces * item.refcnt; - - if (res.faces >= limit) - res.done = true; - else if ((created > 0.01 * lst.length) && (timelimit !== undefined)) { - const tm2 = new Date().getTime(); - if (tm2 - tm1 > timelimit) - return res; - } - } - - res.done = true; - - return res; - } - - /** @summary Format REveGeomNode data to be able use it in list of clones - * @private */ - static formatServerElement(elem) { - elem.kind = 2; // special element for geom viewer, used in TGeoPainter - elem.vis = 2; // visibility is always on - const m = elem.matr; - delete elem.matr; - if (!m?.length) - return elem; - - if (m.length === 16) - elem.matrix = m; - else { - const nm = elem.matrix = new Array(16); - nm.fill(0); - nm[0] = nm[5] = nm[10] = nm[15] = 1; - - if (m.length === 3) { - // translation matrix - nm[12] = m[0]; - nm[13] = m[1]; - nm[14] = m[2]; - } else if (m.length === 4) { - // scale matrix - nm[0] = m[0]; - nm[5] = m[1]; - nm[10] = m[2]; - nm[15] = m[3]; - } else if (m.length === 9) { - // rotation matrix - nm[0] = m[0]; - nm[4] = m[1]; - nm[8] = m[2]; - nm[1] = m[3]; - nm[5] = m[4]; - nm[9] = m[5]; - nm[2] = m[6]; - nm[6] = m[7]; - nm[10] = m[8]; - } else - console.error(`wrong number of elements ${m.length} in the matrix`); - } - return elem; - } - -} // class ClonedNodes - -/** @summary extract code of Box3.expandByObject - * @desc Major difference - do not traverse hierarchy, support InstancedMesh - * @private */ -function getBoundingBox(node, box3, local_coordinates) { - if (!node?.geometry) - return box3; - - if (!box3) - box3 = new THREE.Box3().makeEmpty(); - - if (node.isInstancedMesh) { - const m = new THREE.Matrix4(), b = new THREE.Box3().makeEmpty(); - - node.geometry.computeBoundingBox(); - - for (let i = 0; i < node.count; i++) { - node.getMatrixAt(i, m); - b.copy(node.geometry.boundingBox).applyMatrix4(m); - box3.union(b); - } - return box3; - } - - if (!local_coordinates) - node.updateWorldMatrix(false, false); - - const v1 = new THREE.Vector3(), attribute = node.geometry.attributes?.position; - - if (attribute !== undefined) { - for (let i = 0, l = attribute.count; i < l; i++) { - // v1.fromAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); - v1.fromBufferAttribute(attribute, i); - if (!local_coordinates) - v1.applyMatrix4(node.matrixWorld); - box3.expandByPoint(v1); - } - } - - return box3; -} - -/** @summary Cleanup shape entity - * @private */ -function cleanupShape(shape) { - if (!shape) - return; - - if (isFunc(shape.geom?.dispose)) - shape.geom.dispose(); - - if (isFunc(shape.geomZ?.dispose)) - shape.geomZ.dispose(); - - delete shape.geom; - delete shape.geomZ; -} - -/** @summary Set rendering order for created hierarchy - * @desc depending from provided method sort differently objects - * @param {object} toplevel - top element - * @param {object} origin - camera position used to provide sorting - * @param {string} method - name of sorting method like 'pnt', 'ray', 'size', 'dflt' */ -function produceRenderOrder(toplevel, origin, method /* , clones */) { - const raycast = new THREE.Raycaster(); - - function setdefaults(top) { - top?.traverse(obj => { - obj.renderOrder = obj.defaultOrder || 0; - if (obj.material) - obj.material.depthWrite = true; // by default depthWriting enabled - }); - } - - function traverse(obj, lvl, arr) { - // traverse hierarchy and extract all children of given level - // if (obj.$jsroot_depth === undefined) - // return; - - if (!obj.children) - return; - - for (let k = 0; k < obj.children.length; ++k) { - const chld = obj.children[k]; - if (chld.$jsroot_order === lvl) { - if (chld.material) { - if (chld.material.transparent) { - chld.material.depthWrite = false; // disable depth writing for transparent - arr.push(chld); - } else - setdefaults(chld); - } - } else if ((obj.$jsroot_depth === undefined) || (obj.$jsroot_depth < lvl)) - traverse(chld, lvl, arr); - } - } - - function sort(arr, minorder, maxorder) { - // resort meshes using ray caster and camera position - // idea to identify meshes which are in front or behind - - if (arr.length > 1000) { - // too many of them, just set basic level and exit - for (let i = 0; i < arr.length; ++i) - arr[i].renderOrder = (minorder + maxorder) / 2; - return false; - } - - const tmp_vect = new THREE.Vector3(); - - // first calculate distance to the camera - // it gives preliminary order of volumes - for (let i = 0; i < arr.length; ++i) { - const mesh = arr[i]; - let box3 = mesh.$jsroot_box3; - - if (!box3) - mesh.$jsroot_box3 = box3 = getBoundingBox(mesh); - - if (method === 'size') { - const sz = box3.getSize(new THREE.Vector3()); - mesh.$jsroot_distance = sz.x * sz.y * sz.z; - continue; - } - - if (method === 'pnt') { - mesh.$jsroot_distance = origin.distanceTo(box3.getCenter(tmp_vect)); - continue; - } - - let dist = Math.min(origin.distanceTo(box3.min), origin.distanceTo(box3.max)); - const pnt = new THREE.Vector3(box3.min.x, box3.min.y, box3.max.z); - - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.min.x, box3.max.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.max.x, box3.min.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.max.x, box3.max.y, box3.min.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.max.x, box3.min.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - pnt.set(box3.min.x, box3.max.y, box3.max.z); - dist = Math.min(dist, origin.distanceTo(pnt)); - - mesh.$jsroot_distance = dist; - } - - arr.sort((a, b) => a.$jsroot_distance - b.$jsroot_distance); - - const resort = new Array(arr.length); - - for (let i = 0; i < arr.length; ++i) { - arr[i].$jsroot_index = i; - resort[i] = arr[i]; - } - - if (method === 'ray') { - for (let i = arr.length - 1; i >= 0; --i) { - const mesh = arr[i], box3 = mesh.$jsroot_box3; - let intersects, direction = box3.getCenter(tmp_vect); - - for (let ntry = 0; ntry < 2; ++ntry) { - direction.sub(origin).normalize(); - - raycast.set(origin, direction); - - intersects = raycast.intersectObjects(arr, false) || []; // only plain array - const unique = []; - - for (let k1 = 0; k1 < intersects.length; ++k1) { - if (unique.indexOf(intersects[k1].object) < 0) - unique.push(intersects[k1].object); - } - - intersects = unique; - - if ((intersects.indexOf(mesh) >= 0) || (ntry > 0)) - break; - - const pos = mesh.geometry.attributes.position.array; - - direction = new THREE.Vector3((pos[0] + pos[3] + pos[6]) / 3, (pos[1] + pos[4] + pos[7]) / 3, (pos[2] + pos[5] + pos[8]) / 3); - - direction.applyMatrix4(mesh.matrixWorld); - } - - // now push first object in intersects to the front - for (let k1 = 0; k1 < intersects.length - 1; ++k1) { - const mesh1 = intersects[k1], mesh2 = intersects[k1 + 1], - i1 = mesh1.$jsroot_index, i2 = mesh2.$jsroot_index; - if (i1 < i2) - continue; - for (let ii = i2; ii < i1; ++ii) { - resort[ii] = resort[ii + 1]; - resort[ii].$jsroot_index = ii; - } - resort[i1] = mesh2; - mesh2.$jsroot_index = i1; - } - } - } - - for (let i = 0; i < resort.length; ++i) { - resort[i].renderOrder = Math.round(maxorder - (i + 1) / (resort.length + 1) * (maxorder - minorder)); - delete resort[i].$jsroot_index; - delete resort[i].$jsroot_distance; - } - - return true; - } - - function process(obj, lvl, minorder, maxorder) { - const arr = []; - let did_sort = false; - - traverse(obj, lvl, arr); - - if (!arr.length) - return; - - if (minorder === maxorder) { - for (let k = 0; k < arr.length; ++k) - arr[k].renderOrder = minorder; - } else { - did_sort = sort(arr, minorder, maxorder); - if (!did_sort) - minorder = maxorder = (minorder + maxorder) / 2; - } - - for (let k = 0; k < arr.length; ++k) { - const next = arr[k].parent; - let min = minorder, max = maxorder; - - if (did_sort) { - max = arr[k].renderOrder; - min = max - (maxorder - minorder) / (arr.length + 2); - } - - process(next, lvl + 1, min, max); - } - } - - if (!method || (method === 'dflt')) - setdefaults(toplevel); - else - process(toplevel, 0, 1, 1000000); -} - -/** @summary provide icon name for the shape - * @private */ -function getShapeIcon(shape) { - switch (shape._typename) { - case clTGeoArb8: return 'img_geoarb8'; - case clTGeoCone: return 'img_geocone'; - case clTGeoConeSeg: return 'img_geoconeseg'; - case clTGeoCompositeShape: return 'img_geocomposite'; - case clTGeoTube: return 'img_geotube'; - case clTGeoTubeSeg: return 'img_geotubeseg'; - case clTGeoPara: return 'img_geopara'; - case clTGeoParaboloid: return 'img_geoparab'; - case clTGeoPcon: return 'img_geopcon'; - case clTGeoPgon: return 'img_geopgon'; - case clTGeoShapeAssembly: return 'img_geoassembly'; - case clTGeoSphere: return 'img_geosphere'; - case clTGeoTorus: return 'img_geotorus'; - case clTGeoTrd1: return 'img_geotrd1'; - case clTGeoTrd2: return 'img_geotrd2'; - case clTGeoXtru: return 'img_geoxtru'; - case clTGeoTrap: return 'img_geotrap'; - case clTGeoGtra: return 'img_geogtra'; - case clTGeoEltu: return 'img_geoeltu'; - case clTGeoHype: return 'img_geohype'; - case clTGeoCtub: return 'img_geoctub'; - } - return 'img_geotube'; -} - -function runGeoWorker(ctxt, data, doPost) { - if (isStr(data)) { - console.log(`Worker get message ${data}`); - return; - } - - if (!isObject(data)) - return; - - data.tm1 = new Date().getTime(); - - if (data.init) { - if (data.clones) { - ctxt.clones = new ClonedNodes(null, data.clones); - ctxt.clones.setVisLevel(data.vislevel); - ctxt.clones.setMaxVisNodes(data.maxvisnodes); - ctxt.clones.sortmap = data.sortmap; - delete data.clones; - } - - data.tm2 = new Date().getTime(); - - return doPost(data); - } - - if (data.shapes) { - // this is task to create geometries in the worker - - const shapes = data.shapes, transferables = []; - - // build all shapes up to specified limit, also limit execution time - for (let n = 0; n < 100; ++n) { - const res = ctxt.clones.buildShapes(shapes, data.limit, 1000); - if (res.done) - break; - doPost({ progress: `Worker creating: ${res.shapes} / ${shapes.length} shapes, ${res.faces} faces` }); - } - - for (let n = 0; n < shapes.length; ++n) { - const item = shapes[n]; - - if (item.geom) { - let bufgeom; - if (item.geom instanceof THREE.BufferGeometry) - bufgeom = item.geom; - else - bufgeom = new THREE.BufferGeometry().fromGeometry(item.geom); - - item.buf_pos = bufgeom.attributes.position.array; - item.buf_norm = bufgeom.attributes.normal.array; - - // use nice feature of HTML workers with transferable - // we allow to take ownership of buffer from local array - // therefore buffer content not need to be copied - transferables.push(item.buf_pos.buffer, item.buf_norm.buffer); - - delete item.geom; - } - - delete item.shape; // no need to send back shape - } - - data.tm2 = new Date().getTime(); - - return doPost(data, transferables); - } - - if (data.collect !== undefined) { - // this is task to collect visible nodes using camera position - - // first mark all visible flags - ctxt.clones.setVisibleFlags(data.flags); - ctxt.clones.setVisLevel(data.vislevel); - ctxt.clones.setMaxVisNodes(data.maxvisnodes); - - delete data.flags; - - ctxt.clones.produceIdShifts(); - - let matrix = null; - if (data.matrix) - matrix = new THREE.Matrix4().fromArray(data.matrix); - delete data.matrix; - - const res = ctxt.clones.collectVisibles(data.collect, createFrustum(matrix)); - - data.new_nodes = res.lst; - data.complete = res.complete; // inform if all nodes are selected - - data.tm2 = new Date().getTime(); - - return doPost(data); - } -} - -export { kindGeo, kindEve, kindShape, kGetMesh, kDeleteMesh, - clTGeoBBox, clTGeoCompositeShape, - geoCfg, geoBITS, ClonedNodes, isSameStack, checkDuplicates, getObjectName, testGeoBit, setGeoBit, toggleGeoBit, - setInvisibleAll, countNumShapes, getNodeKind, produceRenderOrder, createFlippedGeom, createFlippedMesh, cleanupShape, - createGeometry, numGeometryFaces, numGeometryVertices, createServerGeometry, createMaterial, - projectGeometry, createFrustum, createProjectionMatrix, - getBoundingBox, provideObjectInfo, getShapeIcon, runGeoWorker }; diff --git a/js/modules/geom/geoworker.mjs b/js/modules/geom/geoworker.mjs deleted file mode 100644 index e74a75c53404e..0000000000000 --- a/js/modules/geom/geoworker.mjs +++ /dev/null @@ -1,15 +0,0 @@ -import { runGeoWorker } from './geobase.mjs'; - -const ctxt = {}; - -onmessage = function(e) { - if (!e?.data) - return; - - if (typeof e.data === 'string') { - console.log(`Worker get message ${e.data}`); - return; - } - - runGeoWorker(ctxt, e.data, postMessage); -}; diff --git a/js/modules/geom/nodeworker.mjs b/js/modules/geom/nodeworker.mjs deleted file mode 100644 index d81d1e045da56..0000000000000 --- a/js/modules/geom/nodeworker.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { runGeoWorker } from './geobase.mjs'; -import { parentPort } from 'node:worker_threads'; - -const ctxt = {}; - -parentPort.on('message', msg => runGeoWorker(ctxt, msg, reply => parentPort.postMessage(reply))); diff --git a/js/modules/gpad/RAxisPainter.mjs b/js/modules/gpad/RAxisPainter.mjs deleted file mode 100644 index 992bcf3150db9..0000000000000 --- a/js/modules/gpad/RAxisPainter.mjs +++ /dev/null @@ -1,1179 +0,0 @@ -import { select as d3_select, pointer as d3_pointer, - drag as d3_drag, timeFormat as d3_timeFormat, - scaleTime as d3_scaleTime, scaleSymlog as d3_scaleSymlog, - scaleLog as d3_scaleLog, scaleLinear as d3_scaleLinear } from '../d3.mjs'; -import { settings, isFunc, urlClassPrefix } from '../core.mjs'; -import { makeTranslate, addHighlightStyle } from '../base/BasePainter.mjs'; -import { AxisPainterMethods, chooseTimeFormat } from './TAxisPainter.mjs'; -import { createMenu } from '../gui/menu.mjs'; -import { addDragHandler } from './TFramePainter.mjs'; -import { kAxisLabels, kAxisNormal, kAxisTime } from '../base/ObjectPainter.mjs'; -import { RObjectPainter } from '../base/RObjectPainter.mjs'; - - -/** - * @summary Axis painter for v7 - * - * @private - */ - -class RAxisPainter extends RObjectPainter { - - /** @summary constructor */ - constructor(dom, arg1, axis, cssprefix) { - const drawable = cssprefix ? arg1.getObject() : arg1; - super(dom, drawable, '', cssprefix ? arg1.csstype : 'axis'); - Object.assign(this, AxisPainterMethods); - this.initAxisPainter(); - - this.axis = axis; - if (cssprefix) { // drawing from the frame - this.embedded = true; // indicate that painter embedded into the histo painter - // this.csstype = arg1.csstype; // for the moment only via frame one can set axis attributes - this.cssprefix = cssprefix; - this.rstyle = arg1.rstyle; - } else { - // this.csstype = 'axis'; - this.cssprefix = 'axis_'; - } - } - - /** @summary cleanup painter */ - cleanup() { - delete this.axis; - delete this.axis_g; - this.cleanupAxisPainter(); - super.cleanup(); - } - - /** @summary Use in GED to identify kind of axis */ - getAxisType() { return 'RAttrAxis'; } - - /** @summary Configure only base parameters, later same handle will be used for drawing */ - configureZAxis(name, fp) { - this.name = name; - this.kind = kAxisNormal; - this.log = false; - const _log = this.v7EvalAttr('log', 0); - if (_log) { - this.log = true; - this.logbase = 10; - if (Math.abs(_log - Math.exp(1)) < 0.1) - this.logbase = Math.exp(1); - else if (_log > 1.9) - this.logbase = Math.round(_log); - } - fp.logz = this.log; - } - - /** @summary Configure axis painter - * @desc Axis can be drawn inside frame <g> group with offset to 0 point for the frame - * Therefore one should distinguish when calculated coordinates used for axis drawing itself or for calculation of frame coordinates - * @private */ - configureAxis(name, min, max, smin, smax, vertical, frame_range, axis_range, opts) { - if (!opts) - opts = {}; - this.name = name; - this.full_min = min; - this.full_max = max; - this.kind = kAxisNormal; - this.vertical = vertical; - this.log = false; - const _log = this.v7EvalAttr('log', 0), - _symlog = this.v7EvalAttr('symlog', 0); - this.reverse = opts.reverse || false; - - if (this.v7EvalAttr('time')) { - this.kind = kAxisTime; - this.timeoffset = 0; - let toffset = this.v7EvalAttr('timeOffset'); - if (toffset !== undefined) { - toffset = parseFloat(toffset); - if (Number.isFinite(toffset)) - this.timeoffset = toffset * 1000; - } - } else if (this.axis?.fLabelsIndex) { - this.kind = kAxisLabels; - delete this.own_labels; - } else if (opts.labels) - this.kind = kAxisLabels; - else - this.kind = kAxisNormal; - - if (this.kind === kAxisTime) - this.func = d3_scaleTime().domain([this.convertDate(smin), this.convertDate(smax)]); - else if (_symlog && (_symlog > 0)) { - this.symlog = _symlog; - this.func = d3_scaleSymlog().constant(_symlog).domain([smin, smax]); - } else if (_log) { - if (smax <= 0) - smax = 1; - if ((smin <= 0) || (smin >= smax)) - smin = smax * 0.0001; - this.log = true; - this.logbase = 10; - if (Math.abs(_log - Math.exp(1)) < 0.1) - this.logbase = Math.exp(1); - else if (_log > 1.9) - this.logbase = Math.round(_log); - this.func = d3_scaleLog().base(this.logbase).domain([smin, smax]); - } else - this.func = d3_scaleLinear().domain([smin, smax]); - - this.scale_min = smin; - this.scale_max = smax; - - this.gr_range = axis_range || 1000; // when not specified, one can ignore it - - const range = frame_range ?? [0, this.gr_range]; - - this.axis_shift = range[1] - this.gr_range; - - if (this.reverse) - this.func.range([range[1], range[0]]); - else - this.func.range(range); - - if (this.kind === kAxisTime) - this.gr = val => this.func(this.convertDate(val)); - else if (this.log) - this.gr = val => { return (val < this.scale_min) ? (this.vertical ? this.func.range()[0] + 5 : -5) : this.func(val); }; - else - this.gr = this.func; - - delete this.format;// remove formatting func - - const ndiv = this.v7EvalAttr('ndiv', 508); - - this.nticks = ndiv % 100; - this.nticks2 = (ndiv % 10000 - this.nticks) / 100; - this.nticks3 = Math.floor(ndiv / 10000); - - this.nticks = Math.min(this.nticks, 20); - - const gr_range = Math.abs(this.gr_range) || 100; - - if (this.kind === kAxisTime) { - this.nticks = Math.min(this.nticks, 8); - - const scale_range = this.scale_max - this.scale_min, - tf2 = chooseTimeFormat(scale_range / gr_range, false); - let tf1 = this.v7EvalAttr('timeFormat', ''); - - if (!tf1 || (scale_range < 0.1 * (this.full_max - this.full_min))) - tf1 = chooseTimeFormat(scale_range / this.nticks, true); - - this.tfunc1 = this.tfunc2 = d3_timeFormat(tf1); - if (tf2 !== tf1) - this.tfunc2 = d3_timeFormat(tf2); - - this.format = this.formatTime; - } else if (this.log) { - if (this.nticks2 > 1) { - this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally - this.nticks2 = 1; - } - this.noexp = this.v7EvalAttr('noexp', false); - if ((this.scale_max < 300) && (this.scale_min > 0.3) && (this.logbase === 10)) - this.noexp = true; - this.moreloglabels = this.v7EvalAttr('moreloglbls', false); - - this.format = this.formatLog; - } else if (this.kind === kAxisLabels) { - this.nticks = 50; // for text output allow max 50 names - const scale_range = this.scale_max - this.scale_min; - if (this.nticks > scale_range) - this.nticks = Math.round(scale_range); - this.nticks2 = 1; - - this.format = this.formatLabels; - } else { - this.order = 0; - this.ndig = 0; - this.format = this.formatNormal; - } - } - - /** @summary Return scale min */ - getScaleMin() { - return this.func ? this.func.domain()[0] : 0; - } - - /** @summary Return scale max */ - getScaleMax() { - return this.func ? this.func.domain()[1] : 0; - } - - /** @summary Provide label for axis value */ - formatLabels(d) { - const indx = Math.round(d); - if (this.axis?.fLabelsIndex) { - if ((indx < 0) || (indx >= this.axis.fNBinsNoOver)) - return null; - for (let i = 0; i < this.axis.fLabelsIndex.length; ++i) { - const pair = this.axis.fLabelsIndex[i]; - if (pair.second === indx) - return pair.first; - } - } else { - const labels = this.getObject().fLabels; - if (labels && (indx >= 0) && (indx < labels.length)) - return labels[indx]; - } - return null; - } - - /** @summary Creates array with minor/middle/major ticks */ - createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { - if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) - this.noticksopt = true; - - const ticks = this.produceTicks(this.nticks), - handle = { nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: ticks, middle: ticks, major: ticks }; - - if (only_major_as_array) { - const res = handle.major, delta = (this.scale_max - this.scale_min) * 1e-5; - if (res.at(0) > this.scale_min + delta) - res.unshift(this.scale_min); - if (res.at(-1) < this.scale_max - delta) - res.push(this.scale_max); - return res; - } - - if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10))) { - handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); - - const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - - // avoid black filling by middle-size - if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range)) - handle.minor = handle.middle = handle.major; - else if ((this.nticks3 > 1) && !this.log) { - handle.minor = this.produceTicks(handle.middle.length, this.nticks3); - if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range)) - handle.minor = handle.middle; - } - } - - handle.reset = function() { - this.nminor = this.nmiddle = this.nmajor = 0; - }; - - handle.next = function(doround) { - if (this.nminor >= this.minor.length) - return false; - - this.tick = this.minor[this.nminor++]; - this.grpos = this.func(this.tick); - if (doround) - this.grpos = Math.round(this.grpos); - this.kind = 3; - - if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { - this.nmiddle++; - this.kind = 2; - } - - if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1)) { - this.nmajor++; - this.kind = 1; - } - return true; - }; - - handle.last_major = function() { - return (this.kind !== 1) ? false : this.nmajor === this.major.length; - }; - - handle.next_major_grpos = function() { - return this.nmajor >= this.major.length ? null : this.func(this.major[this.nmajor]); - }; - - handle.get_modifier = function() { return null; }; - - this.order = 0; - this.ndig = 0; - - // at the moment when drawing labels, we can try to find most optimal text representation for them - - if ((this.kind === kAxisNormal) && !this.log && handle.major.length) { - let maxorder = 0, minorder = 0, exclorder3 = false; - - if (!optionNoexp) { - const maxtick = Math.max(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), - mintick = Math.min(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), - ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick) / 3) * 3 : 0, - ord2 = (mintick > 0) ? Math.round(Math.log10(mintick) / 3) * 3 : 0; - - exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - - if (maxtick || mintick) { - maxorder = Math.max(ord1, ord2) + 3; - minorder = Math.min(ord1, ord2) - 3; - } - } - - // now try to find best combination of order and ndig for labels - - let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; - - for (let order = minorder; order <= maxorder; order += 3) { - if (exclorder3 && (order === 3)) - continue; - this.order = order; - this.ndig = 0; - let lbls = [], indx = 0, totallen = 0; - while (indx < handle.major.length) { - const v0 = handle.major[indx], - lbl = this.format(v0, true); - - let bad_value = lbls.indexOf(lbl) >= 0; - if (!bad_value) { - try { - const v1 = parseFloat(lbl) * Math.pow(10, order); - bad_value = (Math.abs(v0) > 1e-30) && (Math.abs(v1 - v0) / Math.abs(v0) > 1e-8); - } catch { - console.warn('Failure by parsing of', lbl); - bad_value = true; - } - } - if (bad_value) { - if (++this.ndig > 15) { - totallen += 1e10; - break; // not too many digits, anyway it will be exponential - } - lbls = []; - indx = totallen = 0; - } else { - lbls.push(lbl); - totallen += lbl.length; - indx++; - } - } - - // for order === 0 we should virtually remove '0.' and extra label on top - if (!order && (this.ndig < 4)) - totallen -= (handle.major.length * 2 + 3); - - if (totallen < bestlen) { - bestlen = totallen; - bestorder = this.order; - bestndig = this.ndig; - } - } - - this.order = bestorder; - this.ndig = bestndig; - - if (optionInt) { - if (this.order) - console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); - if (this.ndig) - console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); - this.ndig = 0; - this.order = 0; - } - } - - return handle; - } - - /** @summary Is labels should be centered */ - isCenteredLabels() { - if (this.kind === kAxisLabels) - return true; - if (this.kind === 'log') - return false; - return this.v7EvalAttr('labels_center', false); - } - - /** @summary Is labels should be rotated */ - isRotateLabels() { return false; } - - /** @summary Used to move axis labels instead of zooming - * @private */ - processLabelsMove(arg, pos) { - if (this.optionUnlab || !this.axis_g) - return false; - - const label_g = this.axis_g.select('.axis_labels'); - if (!label_g || (label_g.size() !== 1)) - return false; - - if (arg === 'start') { - // no moving without labels - const box = label_g.node().getBBox(); - - label_g.append('rect') - .classed('drag', true) - .attr('x', box.x) - .attr('y', box.y) - .attr('width', box.width) - .attr('height', box.height) - .style('cursor', 'move') - .call(addHighlightStyle, true); - if (this.vertical) - this.drag_pos0 = pos[0]; - else - this.drag_pos0 = pos[1]; - - - return true; - } - - let offset = label_g.property('fix_offset'); - - if (this.vertical) { - offset += Math.round(pos[0] - this.drag_pos0); - makeTranslate(label_g, offset); - } else { - offset += Math.round(pos[1] - this.drag_pos0); - makeTranslate(label_g, 0, offset); - } - if (!offset) - makeTranslate(label_g); - - if (arg === 'stop') { - label_g.select('rect.drag').remove(); - delete this.drag_pos0; - if (offset !== label_g.property('fix_offset')) { - label_g.property('fix_offset', offset); - const side = label_g.property('side') || 1; - this.labelsOffset = offset / (this.vertical ? -side : side); - this.changeAxisAttr(1, 'labels_offset', this.labelsOffset / this.scalingSize); - } - } - - return true; - } - - /** @summary Add interactive elements to draw axes title */ - addTitleDrag(title_g, side) { - if (!settings.MoveResize || this.isBatchMode()) - return; - - let drag_rect = null, - acc_x, acc_y, new_x, new_y, alt_pos, curr_indx; - - const drag_move = d3_drag().subject(Object); - - drag_move - .on('start', evnt => { - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const box = title_g.node().getBBox(), // check that elements visible, request precise value - title_length = this.vertical ? box.height : box.width; - - new_x = acc_x = title_g.property('shift_x'); - new_y = acc_y = title_g.property('shift_y'); - - if (this.titlePos === 'center') - curr_indx = 1; - else - curr_indx = (this.titlePos === 'left') ? 0 : 2; - - // let d = ((this.gr_range > 0) && this.vertical) ? title_length : 0; - alt_pos = [0, this.gr_range / 2, this.gr_range]; // possible positions - const off = this.vertical ? -title_length : title_length, - swap = this.isReverseAxis() ? 2 : 0; - if (this.title_align === 'middle') { - alt_pos[swap] += off / 2; - alt_pos[2 - swap] -= off / 2; - } else if ((this.title_align === 'begin') ^ this.isTitleRotated()) { - alt_pos[1] -= off / 2; - alt_pos[2 - swap] -= off; - } else { // end - alt_pos[swap] += off; - alt_pos[1] += off / 2; - } - - alt_pos[curr_indx] = this.vertical ? acc_y : acc_x; - - drag_rect = title_g.append('rect') - .attr('x', box.x) - .attr('y', box.y) - .attr('width', box.width) - .attr('height', box.height) - .style('cursor', 'move') - .call(addHighlightStyle, true); - // .style('pointer-events','none'); // let forward double click to underlying elements - }).on('drag', evnt => { - if (!drag_rect) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - acc_x += evnt.dx; - acc_y += evnt.dy; - - const p = this.vertical ? acc_y : acc_x; - let set_x, set_y, besti = 0; - - for (let i = 1; i < 3; ++i) { - if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) - besti = i; - } - - if (this.vertical) { - set_x = acc_x; - set_y = alt_pos[besti]; - } else { - set_x = alt_pos[besti]; - set_y = acc_y; - } - - new_x = set_x; - new_y = set_y; - curr_indx = besti; - makeTranslate(title_g, new_x, new_y); - }).on('end', evnt => { - if (!drag_rect) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const basepos = title_g.property('basepos') || 0; - - title_g.property('shift_x', new_x) - .property('shift_y', new_y); - - this.titleOffset = (this.vertical ? basepos - new_x : new_y - basepos) * side; - - if (curr_indx === 1) - this.titlePos = 'center'; - else if (curr_indx === 0) - this.titlePos = 'left'; - else - this.titlePos = 'right'; - - this.changeAxisAttr(0, 'title_position', this.titlePos, 'title_offset', this.titleOffset / this.scalingSize); - - drag_rect.remove(); - drag_rect = null; - }); - - title_g.style('cursor', 'move').call(drag_move); - } - - /** @summary checks if value inside graphical range, taking into account delta */ - isInsideGrRange(pos, delta1, delta2) { - if (!delta1) - delta1 = 0; - if (delta2 === undefined) - delta2 = delta1; - if (this.gr_range < 0) - return (pos >= this.gr_range - delta2) && (pos <= delta1); - return (pos >= -delta1) && (pos <= this.gr_range + delta2); - } - - /** @summary returns graphical range */ - getGrRange(delta) { - if (!delta) - delta = 0; - if (this.gr_range < 0) - return this.gr_range - delta; - return this.gr_range + delta; - } - - /** @summary If axis direction is negative coordinates direction */ - isReverseAxis() { - return !this.vertical !== (this.getGrRange() > 0); - } - - /** @summary Draw axis ticks - * @private */ - drawMainLine(axis_g) { - let ending = ''; - - if (this.endingSize && this.endingStyle) { - let sz = (this.gr_range > 0) ? -this.endingSize : this.endingSize; - const sz7 = Math.round(sz * 0.7); - sz = Math.round(sz); - if (this.vertical) - ending = `l${sz7},${sz}M0,${this.gr_range}l${-sz7},${sz}`; - else - ending = `l${sz},${sz7}M${this.gr_range},0l${sz},${-sz7}`; - } - - axis_g.append('svg:path') - .attr('d', 'M0,0' + (this.vertical ? 'v' : 'h') + this.gr_range + ending) - .call(this.lineatt.func) - .style('fill', ending ? 'none' : null); - } - - /** @summary Draw axis ticks - * @return {object} with gaps on left and right side - * @private */ - drawTicks(axis_g, side, main_draw) { - if (main_draw) - this.ticks = []; - - this.handle.reset(); - - let res = '', ticks_plusminus = 0; - if (this.ticksSide === 'both') { - side = 1; - ticks_plusminus = 1; - } - - while (this.handle.next(true)) { - let h1 = Math.round(this.ticksSize / 4), h2; - - if (this.handle.kind < 3) - h1 = Math.round(this.ticksSize / 2); - - const grpos = this.handle.grpos - this.axis_shift; - - if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(grpos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) - continue; - - if (this.handle.kind === 1) { - // if not showing labels, not show large tick - if ((this.kind === kAxisLabels) || (this.format(this.handle.tick, true) !== null)) - h1 = this.ticksSize; - - if (main_draw) - this.ticks.push(grpos); // keep graphical positions of major ticks - } - - if (ticks_plusminus > 0) - h2 = -h1; - else if (side < 0) { - h2 = -h1; - h1 = 0; - } else - h2 = 0; - - res += this.vertical ? `M${h1},${grpos}H${h2}` : `M${grpos},${-h1}V${-h2}`; - } - - if (res) { - axis_g.append('svg:path') - .attr('d', res) - .style('stroke', this.ticksColor || this.lineatt.color) - .style('stroke-width', !this.ticksWidth || (this.ticksWidth === 1) ? null : this.ticksWidth); - } - - const gap0 = Math.round(0.25 * this.ticksSize), gap = Math.round(1.25 * this.ticksSize); - return { - '-1': (side > 0) || ticks_plusminus ? gap : gap0, - 1: (side < 0) || ticks_plusminus ? gap : gap0 - }; - } - - /** @summary Performs labels drawing - * @return {Promise} with gaps in both direction */ - async drawLabels(axis_g, side, gaps) { - const center_lbls = this.isCenteredLabels(), - rotate_lbls = Boolean(this.labelsFont.angle), - label_g = axis_g.append('svg:g').attr('class', 'axis_labels').property('side', side), - lbl_pos = this.handle.lbl_pos || this.handle.major; - let textscale = 1, maxtextlen = 0, lbls_tilt = false, - max_lbl_width = 0, max_lbl_height = 0; - - // function called when text is drawn to analyze width, required to correctly scale all labels - function process_drawtext_ready(painter) { - max_lbl_width = Math.max(max_lbl_width, this.result_width); - max_lbl_height = Math.max(max_lbl_height, this.result_height); - - const textwidth = this.result_width; - - if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) { - const maxwidth = !this.gap_before ? 0.9 * this.gap_after : (!this.gap_after ? 0.9 * this.gap_before : this.gap_before * 0.45 + this.gap_after * 0.45); - textscale = Math.min(textscale, maxwidth / textwidth); - } - - if ((textscale > 0.0001) && (textscale < 0.8) && !painter.vertical && !rotate_lbls && (maxtextlen > 5) && (side > 0)) - lbls_tilt = true; - - const scale = textscale * (lbls_tilt ? 3 : 1); - if ((scale > 0.0001) && (scale < 1)) - painter.scaleTextDrawing(1 / scale, label_g); - } - - const fix_offset = Math.round((this.vertical ? -side : side) * this.labelsOffset), - fix_coord = Math.round((this.vertical ? -side : side) * gaps[side]); - let lastpos = 0; - - if (fix_offset) - makeTranslate(label_g, this.vertical ? fix_offset : 0, this.vertical ? 0 : fix_offset); - - label_g.property('fix_offset', fix_offset); - - return this.startTextDrawingAsync(this.labelsFont, 'font', label_g).then(() => { - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - const lbl = this.format(lbl_pos[nmajor], true); - if (lbl === null) - continue; - - const arg = { text: lbl, latex: 1, draw_g: label_g }; - let pos = Math.round(this.func(lbl_pos[nmajor])); - - arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; - arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; - - if (center_lbls) { - const gap = arg.gap_after || arg.gap_before; - pos = Math.round(pos - (this.vertical ? 0.5 * gap : -0.5 * gap)); - if (!this.isInsideGrRange(pos, 5)) - continue; - } - - maxtextlen = Math.max(maxtextlen, lbl.length); - - pos -= this.axis_shift; - - if ((this.startingSize || this.endingSize) && !this.isInsideGrRange(pos, -Math.abs(this.startingSize), -Math.abs(this.endingSize))) - continue; - - if (this.vertical) { - arg.x = fix_coord; - arg.y = pos; - arg.align = rotate_lbls ? ((side < 0) ? 23 : 20) : ((side < 0) ? 12 : 32); - } else { - arg.x = pos; - arg.y = fix_coord; - arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 20 : 23); - if (this.log && !this.noexp && !this.vertical && arg.align === 23) { - arg.align = 21; - arg.y += this.labelsFont.size; - } - } - - arg.post_process = process_drawtext_ready; - - this.drawText(arg); - - if (lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { - const axis_step = Math.abs(pos - lastpos); - textscale = Math.min(textscale, 0.9 * axis_step / this.labelsFont.size); - } - - lastpos = pos; - } - - if (this.order) { - this.drawText({ x: this.vertical ? side * 5 : this.getGrRange(5), - y: this.has_obstacle ? fix_coord : (this.vertical ? this.getGrRange(3) : -3 * side), - align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), - latex: 1, - text: '#times' + this.formatExp(10, this.order), - draw_g: label_g }); - } - - return this.finishTextDrawing(label_g); - }).then(() => { - if (lbls_tilt) { - label_g.selectAll('text').each(function() { - const txt = d3_select(this), tr = txt.attr('transform'); - txt.attr('transform', tr + ' rotate(25)').style('text-anchor', 'start'); - }); - } - - if (this.vertical) - gaps[side] += Math.round(rotate_lbls ? 1.2 * max_lbl_height : max_lbl_width + 0.4 * this.labelsFont.size) - side * fix_offset; - else { - const tilt_height = lbls_tilt ? max_lbl_width * Math.sin(25 / 180 * Math.PI) + max_lbl_height * (Math.cos(25 / 180 * Math.PI) + 0.2) : 0; - - gaps[side] += Math.round(Math.max(rotate_lbls ? max_lbl_width + 0.4 * this.labelsFont.size : 1.2 * max_lbl_height, 1.2 * this.labelsFont.size, tilt_height)) + fix_offset; - } - - return gaps; - }); - } - - /** @summary Add zooming rect to axis drawing */ - addZoomingRect(axis_g, side, lgaps) { - if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { - const sz = Math.max(lgaps[side], 10), - d = this.vertical ? `v${this.gr_range}h${-side * sz}v${-this.gr_range}` : `h${this.gr_range}v${side * sz}h${-this.gr_range}`; - axis_g.append('svg:path') - .attr('d', `M0,0${d}z`) - .attr('class', 'axis_zoom') - .style('opacity', '0') - .style('cursor', 'crosshair'); - } - } - - /** @summary Returns true if axis title is rotated */ - isTitleRotated() { - return this.titleFont && (this.titleFont.angle !== (this.vertical ? 270 : 0)); - } - - /** @summary Draw axis title */ - async drawTitle(axis_g, side, lgaps) { - if (!this.fTitle) - return this; - - const title_g = axis_g.append('svg:g').attr('class', 'axis_title'), - rotated = this.isTitleRotated(); - - return this.startTextDrawingAsync(this.titleFont, 'font', title_g).then(() => { - let title_shift_x, title_shift_y, title_basepos; - - this.title_align = this.titleCenter ? 'middle' : (this.titleOpposite ^ (this.isReverseAxis() || rotated) ? 'begin' : 'end'); - - if (this.vertical) { - title_basepos = Math.round(-side * (lgaps[side])); - title_shift_x = title_basepos + Math.round(-side * this.titleOffset); - title_shift_y = Math.round(this.titleCenter ? this.gr_range / 2 : (this.titleOpposite ? 0 : this.gr_range)); - this.drawText({ - align: [this.title_align, ((side < 0) ^ rotated ? 'top' : 'bottom')], - text: this.fTitle, draw_g: title_g - }); - } else { - title_shift_x = Math.round(this.titleCenter ? this.gr_range / 2 : (this.titleOpposite ? 0 : this.gr_range)); - title_basepos = Math.round(side * lgaps[side]); - title_shift_y = title_basepos + Math.round(side * this.titleOffset); - this.drawText({ - align: [this.title_align, ((side > 0) ^ rotated ? 'top' : 'bottom')], - text: this.fTitle, draw_g: title_g - }); - } - - makeTranslate(title_g, title_shift_x, title_shift_y) - .property('basepos', title_basepos) - .property('shift_x', title_shift_x) - .property('shift_y', title_shift_y); - - this.addTitleDrag(title_g, side); - - return this.finishTextDrawing(title_g); - }); - } - - /** @summary Extract major draw attributes, which are also used in interactive operations - * @private */ - extractDrawAttributes(scalingSize) { - const pp = this.getPadPainter(), - rect = pp?.getPadRect() || { width: 10, height: 10 }; - - this.scalingSize = scalingSize || (this.vertical ? rect.width : rect.height); - - this.createv7AttLine('line_'); - - this.optionUnlab = this.v7EvalAttr('labels_hide', false); - - this.endingStyle = this.v7EvalAttr('ending_style', ''); - this.endingSize = Math.round(this.v7EvalLength('ending_size', this.scalingSize, this.endingStyle ? 0.02 : 0)); - this.startingSize = Math.round(this.v7EvalLength('starting_size', this.scalingSize, 0)); - this.ticksSize = this.v7EvalLength('ticks_size', this.scalingSize, 0.02); - this.ticksSide = this.v7EvalAttr('ticks_side', 'normal'); - this.ticksColor = this.v7EvalColor('ticks_color', ''); - this.ticksWidth = this.v7EvalAttr('ticks_width', 1); - if (scalingSize && (this.ticksSize < 0)) - this.ticksSize = -this.ticksSize; - - this.fTitle = this.v7EvalAttr('title_value', ''); - - if (this.fTitle) { - this.titleFont = this.v7EvalFont('title', { size: 0.03 }, scalingSize || pp?.getPadHeight() || 10); - this.titleFont.roundAngle(180, this.vertical ? 270 : 0); - - this.titleOffset = this.v7EvalLength('title_offset', this.scalingSize, 0); - this.titlePos = this.v7EvalAttr('title_position', 'right'); - this.titleCenter = (this.titlePos === 'center'); - this.titleOpposite = (this.titlePos === 'left'); - } else { - delete this.titleFont; - delete this.titleOffset; - delete this.titlePos; - } - - // TODO: remove old scaling factors for labels and ticks - this.labelsFont = this.v7EvalFont('labels', { size: scalingSize ? 0.05 : 0.03 }); - this.labelsFont.roundAngle(180); - if (this.labelsFont.angle) - this.labelsFont.angle = 270; - this.labelsOffset = this.v7EvalLength('labels_offset', this.scalingSize, 0); - - if (scalingSize) - this.ticksSize = this.labelsFont.size * 0.5; // old lego scaling factor - - if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) - this.ticksSize = this.maxTickSize; - } - - /** @summary Performs axis drawing - * @return {Promise} which resolved when drawing is completed */ - async drawAxis(layer, transform, side) { - let axis_g = layer; - - if (side === undefined) - side = 1; - - if (!this.standalone) { - axis_g = layer.selectChild(`.${this.name}_container`); - if (axis_g.empty()) - axis_g = layer.append('svg:g').attr('class', `${this.name}_container`); - else - axis_g.selectAll('*').remove(); - } - - axis_g.attr('transform', transform); - - this.extractDrawAttributes(); - this.axis_g = axis_g; - this.side = side; - - if (this.ticksSide === 'invert') - side = -side; - - if (this.standalone) - this.drawMainLine(axis_g); - - const optionNoopt = false, // no ticks position optimization - optionInt = false, // integer labels - optionNoexp = false; // do not create exp - - this.handle = this.createTicks(false, optionNoexp, optionNoopt, optionInt); - - // first draw ticks - const tgaps = this.drawTicks(axis_g, side, true), - labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); // draw labels - - return labelsPromise.then(lgaps => { - // when drawing axis on frame, zoom rect should be always outside - this.addZoomingRect(axis_g, this.standalone ? side : this.side, lgaps); - - return this.drawTitle(axis_g, side, lgaps); - }); - } - - /** @summary Assign handler, which is called when axis redraw by interactive changes - * @desc Used by palette painter to reassign interactive handlers - * @private */ - setAfterDrawHandler(handler) { - this._afterDrawAgain = handler; - } - - /** @summary Draw axis with the same settings, used by interactive changes */ - drawAxisAgain() { - if (!this.axis_g || !this.side) - return; - - this.axis_g.selectAll('*').remove(); - - this.extractDrawAttributes(); - - let side = this.side; - if (this.ticksSide === 'invert') - side = -side; - - if (this.standalone) - this.drawMainLine(this.axis_g); - - // first draw ticks - const tgaps = this.drawTicks(this.axis_g, side, false), - labelsPromise = this.optionUnlab ? Promise.resolve(tgaps) : this.drawLabels(this.axis_g, side, tgaps); - - return labelsPromise.then(lgaps => { - // when drawing axis on frame, zoom rect should be always outside - this.addZoomingRect(this.axis_g, this.standalone ? side : this.side, lgaps); - - return this.drawTitle(this.axis_g, side, lgaps); - }).then(() => { - if (isFunc(this._afterDrawAgain)) - this._afterDrawAgain(); - }); - } - - /** @summary Draw axis again on opposite frame size */ - drawAxisOtherPlace(layer, transform, side, only_ticks) { - let axis_g = layer.selectChild(`.${this.name}_container2`); - if (axis_g.empty()) - axis_g = layer.append('svg:g').attr('class', `${this.name}_container2`); - else - axis_g.selectAll('*').remove(); - - axis_g.attr('transform', transform); - - if (this.ticksSide === 'invert') - side = -side; - - // draw ticks and labels again - const tgaps = this.drawTicks(axis_g, side, false), - promise = this.optionUnlab || only_ticks ? Promise.resolve(tgaps) : this.drawLabels(axis_g, side, tgaps); - - return promise.then(lgaps => { - this.addZoomingRect(axis_g, side, lgaps); - return true; - }); - } - - /** @summary Change zooming in standalone mode */ - zoomStandalone(min, max) { - return this.changeAxisAttr(1, 'zoomMin', min, 'zoomMax', max); - } - - /** @summary Redraw axis, used in standalone mode for RAxisDrawable */ - redraw() { - const drawable = this.getObject(), - pp = this.getPadPainter(), - pos = pp.getCoordinate(drawable.fPos), - reverse = this.v7EvalAttr('reverse', false), - labels_len = drawable.fLabels.length, - min = (labels_len > 0) ? 0 : this.v7EvalAttr('min', 0), - max = (labels_len > 0) ? labels_len : this.v7EvalAttr('max', 100); - let len = pp.getPadLength(drawable.fVertical, drawable.fLength), - smin = this.v7EvalAttr('zoomMin'), - smax = this.v7EvalAttr('zoomMax'); - - // in vertical direction axis drawn in negative direction - if (drawable.fVertical) - len -= pp.getPadHeight(); - - if (smin === smax) { - smin = min; - smax = max; - } - - this.configureAxis('axis', min, max, smin, smax, drawable.fVertical, undefined, len, { reverse, labels: labels_len > 0 }); - - const g = this.createG(); - - this.standalone = true; // no need to clean axis container - - const promise = this.drawAxis(g, makeTranslate(pos.x, pos.y)); - - if (this.isBatchMode()) - return promise; - - return promise.then(() => { - if (settings.ContextMenu) { - g.on('contextmenu', evnt => { - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - createMenu(evnt, this).then(menu => { - menu.header('RAxisDrawable', `${urlClassPrefix}ROOT_1_1Experimental_1_1RAxisBase.html`); - menu.add('Unzoom', () => this.zoomStandalone()); - this.fillAxisContextMenu(menu, ''); - menu.show(); - }); - }); - } - - addDragHandler(this, { - x: pos.x, y: pos.y, width: this.vertical ? 10 : len, height: this.vertical ? len : 10, - only_move: true, redraw: d => this.positionChanged(d) - }); - - g.on('dblclick', () => this.zoomStandalone()); - - if (settings.ZoomWheel) { - g.on('wheel', evnt => { - evnt.stopPropagation(); - evnt.preventDefault(); - - const pos2 = d3_pointer(evnt, this.getG().node()), - coord = this.vertical ? (1 - pos2[1] / len) : pos2[0] / len, - item = this.analyzeWheelEvent(evnt, coord); - - if (item.changed) - this.zoomStandalone(item.min, item.max); - }); - } - }); - } - - /** @summary Process interactive moving of the axis drawing */ - positionChanged(drag) { - const drawable = this.getObject(), - rect = this.getPadPainter().getPadRect(), - xn = drag.x / rect.width, - yn = 1 - drag.y / rect.height; - - drawable.fPos.fHoriz.fArr = [xn]; - drawable.fPos.fVert.fArr = [yn]; - - this.submitCanvExec(`SetPos({${xn.toFixed(4)},${yn.toFixed(4)}})`); - } - - /** @summary Change axis attribute, submit changes to server and redraw axis when specified - * @desc Arguments as redraw_mode, name1, value1, name2, value2, ... */ - changeAxisAttr(redraw_mode, ...args) { - const changes = {}; - let indx = 0; - while (indx < args.length) { - this.v7AttrChange(changes, args[indx], args[indx + 1]); - this.v7SetAttr(args[indx], args[indx + 1]); - indx += 2; - } - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - if (redraw_mode === 1) { - if (this.standalone) - this.redraw(); - else - this.drawAxisAgain(); - } else if (redraw_mode) - this.redrawPad(); - } - - /** @summary Change axis log scale kind */ - changeAxisLog(arg) { - if ((this.kind === kAxisLabels) || (this.kind === kAxisTime)) - return; - if (arg === 'toggle') - arg = this.log ? 0 : 10; - - arg = parseFloat(arg); - if (Number.isFinite(arg)) - this.changeAxisAttr(2, 'log', arg, 'symlog', 0); - } - - /** @summary Provide context menu for axis */ - fillAxisContextMenu(menu, kind) { - if (kind) - menu.add('Unzoom', () => this.getFramePainter().unzoom(kind)); - - menu.sub('Log scale', () => this.changeAxisLog('toggle')); - menu.addchk(!this.log && !this.symlog, 'linear', 0, arg => this.changeAxisLog(arg)); - menu.addchk(this.log && !this.symlog && (this.logbase === 10), 'log10', () => this.changeAxisLog(10)); - menu.addchk(this.log && !this.symlog && (this.logbase === 2), 'log2', () => this.changeAxisLog(2)); - menu.addchk(this.log && !this.symlog && Math.abs(this.logbase - Math.exp(1)) < 0.1, 'ln', () => this.changeAxisLog(Math.exp(1))); - menu.addchk(!this.log && this.symlog, 'symlog', 0, () => - menu.input('set symlog constant', this.symlog || 10, 'float').then(v => this.changeAxisAttr(2, 'symlog', v))); - menu.endsub(); - - menu.add('Divisions', () => menu.input('Set axis devisions', this.v7EvalAttr('ndiv', 508), 'int').then(val => this.changeAxisAttr(2, 'ndiv', val))); - - menu.sub('Ticks'); - menu.addRColorMenu('color', this.ticksColor, col => this.changeAxisAttr(1, 'ticks_color', col)); - menu.addSizeMenu('size', 0, 0.05, 0.01, this.ticksSize / this.scalingSize, sz => this.changeAxisAttr(1, 'ticks_size', sz)); - menu.addSelectMenu('side', ['normal', 'invert', 'both'], this.ticksSide, side => this.changeAxisAttr(1, 'ticks_side', side)); - menu.endsub(); - - if (!this.optionUnlab && this.labelsFont) { - menu.sub('Labels'); - menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.labelsOffset / this.scalingSize, - offset => this.changeAxisAttr(1, 'labels_offset', offset)); - menu.addRAttrTextItems(this.labelsFont, { noangle: 1, noalign: 1 }, - change => this.changeAxisAttr(1, 'labels_' + change.name, change.value)); - menu.addchk(this.labelsFont.angle, 'rotate', res => this.changeAxisAttr(1, 'labels_angle', res ? 180 : 0)); - menu.endsub(); - } - - menu.sub('Title', () => menu.input('Enter axis title', this.fTitle).then(t => this.changeAxisAttr(1, 'title_value', t))); - - if (this.fTitle) { - menu.addSizeMenu('offset', -0.05, 0.05, 0.01, this.titleOffset / this.scalingSize, - offset => this.changeAxisAttr(1, 'title_offset', offset)); - - menu.addSelectMenu('position', ['left', 'center', 'right'], this.titlePos, - pos => this.changeAxisAttr(1, 'title_position', pos)); - - menu.addchk(this.isTitleRotated(), 'rotate', flag => this.changeAxisAttr(1, 'title_angle', flag ? 180 : 0)); - - menu.addRAttrTextItems(this.titleFont, { noangle: 1, noalign: 1 }, change => this.changeAxisAttr(1, 'title_' + change.name, change.value)); - } - - menu.endsub(); - return true; - } - -} // class RAxisPainter - -export { RAxisPainter }; diff --git a/js/modules/gpad/RCanvasPainter.mjs b/js/modules/gpad/RCanvasPainter.mjs deleted file mode 100644 index b3c2b42c2a3ba..0000000000000 --- a/js/modules/gpad/RCanvasPainter.mjs +++ /dev/null @@ -1,1022 +0,0 @@ -import { settings, create, parse, toJSON, loadScript, registerMethods, isBatchMode, isFunc, isStr, nsREX } from '../core.mjs'; -import { select as d3_select, rgb as d3_rgb } from '../d3.mjs'; -import { closeCurrentWindow, showProgress, loadOpenui5, ToolbarIcons, getColorExec } from '../gui/utils.mjs'; -import { GridDisplay, getHPainter } from '../gui/display.mjs'; -import { makeTranslate } from '../base/BasePainter.mjs'; -import { convertColor } from '../base/colors.mjs'; -import { selectActivePad, cleanup, resize, EAxisBits } from '../base/ObjectPainter.mjs'; -import { RObjectPainter } from '../base/RObjectPainter.mjs'; -import { RAxisPainter } from './RAxisPainter.mjs'; -import { RFramePainter } from './RFramePainter.mjs'; -import { RPadPainter } from './RPadPainter.mjs'; -import { addDragHandler } from './TFramePainter.mjs'; - - -/** - * @summary Painter class for RCanvas - * - * @private - */ - -class RCanvasPainter extends RPadPainter { - - #websocket; // WebWindow handle used for communication with server - #changed_layout; // modified layout - #submreq; // submitted requests - #nextreqid; // id of next request - - /** @summary constructor */ - constructor(dom, canvas, opt) { - super(dom, canvas, opt, true); - this.#websocket = null; - this.#submreq = {}; - this.tooltip_allowed = settings.Tooltip; - this.v7canvas = true; - } - - /** @summary Cleanup canvas painter */ - cleanup() { - this.#websocket = undefined; - this.#submreq = {}; - - if (this.#changed_layout) - this.setLayoutKind('simple'); - this.#changed_layout = undefined; - - super.cleanup(); - } - - /** @summary Returns readonly flag */ - isReadonly() { return false; } - - /** @summary Returns canvas name */ - getCanvasName() { - const title = this.getRootPad()?.fTitle; - return (!title || !isStr(title)) ? 'rcanvas' : title.replace(/ /g, '_'); - } - - /** @summary Returns layout kind */ - getLayoutKind() { - const origin = this.selectDom('origin'), - layout = origin.empty() ? '' : origin.property('layout'); - return layout || 'simple'; - } - - /** @summary Set canvas layout kind */ - setLayoutKind(kind, main_selector) { - const origin = this.selectDom('origin'); - if (!origin.empty()) { - if (!kind) - kind = 'simple'; - origin.property('layout', kind); - origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); - this.#changed_layout = (kind !== 'simple'); // use in cleanup - } - } - - /** @summary Changes layout - * @return {Promise} indicating when finished */ - async changeLayout(layout_kind, mainid) { - const current = this.getLayoutKind(); - if (current === layout_kind) - return true; - - const origin = this.selectDom('origin'), - sidebar2 = origin.select('.side_panel2'), - lst = []; - let sidebar = origin.select('.side_panel'), - main = this.selectDom(), force; - - while (main.node().firstChild) - lst.push(main.node().removeChild(main.node().firstChild)); - - if (!sidebar.empty()) - cleanup(sidebar.node()); - if (!sidebar2.empty()) - cleanup(sidebar2.node()); - - this.setLayoutKind('simple'); // restore defaults - origin.html(''); // cleanup origin - - if (layout_kind === 'simple') { - main = origin; - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); - this.setLayoutKind(layout_kind); - force = true; - } else { - const grid = new GridDisplay(origin.node(), layout_kind); - - if (mainid === undefined) - mainid = (layout_kind.indexOf('vert') === 0) ? 0 : 1; - - main = d3_select(grid.getGridFrame(mainid)); - main.classed('central_panel', true).style('position', 'relative'); - - if (mainid === 2) { - // left panel for Y - sidebar = d3_select(grid.getGridFrame(0)); - sidebar.classed('side_panel2', true).style('position', 'relative'); - // bottom panel for X - sidebar = d3_select(grid.getGridFrame(3)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } else { - sidebar = d3_select(grid.getGridFrame(1 - mainid)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } - - // now append all childs to the new main - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); - - this.setLayoutKind(layout_kind, '.central_panel'); - - // remove reference to MDIDisplay, solves resize problem - origin.property('mdi', null); - } - - // resize main drawing and let draw extras - resize(main.node(), force); - return true; - } - - /** @summary Toggle projection - * @return {Promise} indicating when ready - * @private */ - async toggleProjection(kind) { - delete this.proj_painter; - - if (kind) - this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed - - if (isFunc(this.showUI5ProjectionArea)) - return this.showUI5ProjectionArea(kind); - - let layout = 'simple', mainid; - - switch (kind) { - case 'XY': - layout = 'projxy'; - mainid = 2; - break; - case 'X': - case 'bottom': - layout = 'vert2_31'; - mainid = 0; - break; - case 'Y': - case 'left': - layout = 'horiz2_13'; - mainid = 1; - break; - case 'top': - layout = 'vert2_13'; - mainid = 1; - break; - case 'right': - layout = 'horiz2_31'; - mainid = 0; - break; - } - - return this.changeLayout(layout, mainid); - } - - /** @summary Draw projection for specified histogram - * @private */ - async drawProjection(/* kind, hist, hopt */) { - // dummy for the moment - return false; - } - - /** @summary Draw in side panel - * @private */ - async drawInSidePanel(canv, opt, kind) { - const sel = ((this.getLayoutKind() === 'projxy') && (kind === 'Y')) ? '.side_panel2' : '.side_panel', - side = this.selectDom('origin').select(sel); - return side.empty() ? null : this.drawObject(side.node(), canv, opt); - } - - /** @summary Checks if canvas shown inside ui5 widget - * @desc Function should be used only from the func which supposed to be replaced by ui5 - * @private */ - testUI5() { - return this.use_openui ?? false; - } - - /** @summary Show message - * @desc Used normally with web-based canvas and handled in ui5 - * @private */ - showMessage(msg) { - if (!this.testUI5()) - showProgress(msg, 7000); - } - - /** @summary Function called when canvas menu item Save is called */ - saveCanvasAsFile(fname) { - const pnt = fname.lastIndexOf('.'); - this.createImage(fname.slice(pnt + 1)) - .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); - } - - /** @summary Send command to server to save canvas with specified name - * @desc Should be only used in web-based canvas - * @private */ - sendSaveCommand(fname) { this.sendWebsocket('PRODUCE:' + fname); } - - /** @summary Return assigned web socket - * @private */ - getWebsocket() { return this.#websocket; } - - /** @summary Return true if message can be send via web socket - * @private */ - canSendWebsocket(noper = 1) { return this.#websocket?.canSend(noper); } - - /** @summary Send message via web socket - * @private */ - sendWebsocket(msg) { - if (this.#websocket?.canSend()) { - this.#websocket.send(msg); - return true; - } - - return false; - } - - /** @summary Close websocket connection to canvas - * @private */ - closeWebsocket(force) { - if (this.#websocket) { - this.#websocket.close(force); - this.#websocket.cleanup(); - this.#websocket = undefined; - } - } - - /** @summary Use provided connection for the web canvas - * @private */ - useWebsocket(handle) { - this.closeWebsocket(); - - this.#websocket = handle; - this.#websocket.setReceiver(this); - this.#websocket.connect(); - } - - /** @summary set, test or reset timeout of specified name - * @desc Used to prevent overloading of websocket for specific function */ - websocketTimeout(name, tm) { - if (!this.#websocket) - return; - if (!this.#websocket._tmouts) - this.#websocket._tmouts = {}; - - const handle = this.#websocket._tmouts[name]; - if (tm === undefined) - return handle !== undefined; - - if (tm === 'reset') { - if (handle) { - clearTimeout(handle); - delete this.#websocket._tmouts[name]; - } - } else if (!handle && Number.isInteger(tm)) - this.#websocket._tmouts[name] = setTimeout(() => { delete this.#websocket._tmouts[name]; }, tm); - } - - /** @summary Handler for websocket open event - * @private */ - onWebsocketOpened(/* handle */) { - } - - /** @summary Handler for websocket close event - * @private */ - onWebsocketClosed(/* handle */) { - if (!this.embed_canvas) - closeCurrentWindow(); - } - - /** @summary Handler for websocket message - * @private */ - onWebsocketMsg(handle, msg) { - // console.log('GET_MSG ' + msg.slice(0,30)); - - if (msg === 'CLOSE') { - this.onWebsocketClosed(); - this.closeWebsocket(true); - } else if (msg.slice(0, 5) === 'SNAP:') { - msg = msg.slice(5); - const p1 = msg.indexOf(':'), - snapid = msg.slice(0, p1), - snap = parse(msg.slice(p1 + 1)); - this.syncDraw(true) - .then(() => { - if (!this.getSnapId() && snap?.fWinSize) - this.resizeBrowser(snap.fWinSize[0], snap.fWinSize[1]); - }).then(() => this.redrawPadSnap(snap)) - .then(() => { - this.addPadInteractive(); - handle.send(`SNAPDONE:${snapid}`); // send ready message back when drawing completed - this.confirmDraw(); - }).catch(err => { - if (isFunc(this.showConsoleError)) - this.showConsoleError(err); - else - console.log(err); - }); - } else if (msg.slice(0, 4) === 'JSON') { - const obj = parse(msg.slice(4)); - this.redrawObject(obj); - } else if (msg.slice(0, 9) === 'REPL_REQ:') - this.processDrawableReply(msg.slice(9)); - else if (msg.slice(0, 4) === 'CMD:') { - msg = msg.slice(4); - const p1 = msg.indexOf(':'), - cmdid = msg.slice(0, p1), - cmd = msg.slice(p1 + 1), - reply = `REPLY:${cmdid}:`; - if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG') || (cmd === 'WEBP') || (cmd === 'PDF')) { - this.createImage(cmd.toLowerCase()) - .then(res => handle.send(reply + res)); - } else if (cmd.indexOf('ADDPANEL:') === 0) { - if (!isFunc(this.showUI5Panel)) - handle.send(reply + 'false'); - else { - const window_path = cmd.slice(9), - conn = handle.createNewInstance(window_path); - - // set interim receiver until first message arrives - conn.setReceiver({ - cpainter: this, - - onWebsocketOpened() { - }, - - onWebsocketMsg(panel_handle, msg2) { - const panel_name = (msg2.indexOf('SHOWPANEL:') === 0) ? msg2.slice(10) : ''; - this.cpainter.showUI5Panel(panel_name, panel_handle) - .then(res => handle.send(reply + (res ? 'true' : 'false'))); - }, - - onWebsocketClosed() { - // if connection failed, - handle.send(reply + 'false'); - }, - - onWebsocketError() { - // if connection failed, - handle.send(reply + 'false'); - } - - }); - - // only when connection established, panel will be activated - conn.connect(); - } - } else { - console.log('Unrecognized command ' + cmd); - handle.send(reply); - } - } else if ((msg.slice(0, 7) === 'DXPROJ:') || (msg.slice(0, 7) === 'DYPROJ:')) { - const kind = msg[1], - hist = parse(msg.slice(7)); - this.drawProjection(kind, hist); - } else if (msg.slice(0, 5) === 'SHOW:') { - const that = msg.slice(5), - on = that.at(-1) === '1'; - this.showSection(that.slice(0, that.length - 2), on); - } else - console.log(`unrecognized msg len: ${msg.length} msg: ${msg.slice(0, 30)}`); - } - - /** @summary Submit request to RDrawable object on server side */ - submitDrawableRequest(kind, req, painter, method) { - if (!this.getWebsocket() || !req?._typename || !painter.getSnapId()) - return null; - - if (kind && method) { - // if kind specified - check if such request already was submitted - if (!painter._requests) - painter._requests = {}; - - const prevreq = painter._requests[kind]; - - if (prevreq) { - const tm = new Date().getTime(); - if (!prevreq._tm || (tm - prevreq._tm < 5000)) { - prevreq._nextreq = req; // submit when got reply - return false; - } - delete painter._requests[kind]; // let submit new request after timeout - } - - painter._requests[kind] = req; // keep reference on the request - } - - req.id = painter.getSnapId(); - - if (method) { - if (!this.#nextreqid) - this.#nextreqid = 1; - req.reqid = this.#nextreqid++; - } else - req.reqid = 0; // request will not be replied - - - const msg = JSON.stringify(req); - - if (req.reqid) { - req._kind = kind; - req._painter = painter; - req._method = method; - req._tm = new Date().getTime(); - - this.#submreq[req.reqid] = req; // fast access to submitted requests - } - - this.sendWebsocket('REQ:' + msg); - return req; - } - - /** @summary Submit menu request - * @private */ - async submitMenuRequest(painter, menukind, reqid) { - return new Promise(resolveFunc => { - this.submitDrawableRequest('', { - _typename: `${nsREX}RDrawableMenuRequest`, - menukind: menukind || '', - menureqid: reqid // used to identify menu request - }, painter, resolveFunc); - }); - } - - /** @summary Submit executable command for given painter */ - submitExec(painter, exec, subelem) { - if (subelem && isStr(subelem)) { - const len = subelem.length; - if ((len > 2) && (subelem.indexOf('#x') === len - 2)) - subelem = 'x'; - else if ((len > 2) && (subelem.indexOf('#y') === len - 2)) - subelem = 'y'; - else if ((len > 2) && (subelem.indexOf('#z') === len - 2)) - subelem = 'z'; - - if ((subelem === 'x') || (subelem === 'y') || (subelem === 'z')) - exec = subelem + 'axis#' + exec; - else - return console.log(`not recoginzed subelem ${subelem} in submitExec`); - } - - this.submitDrawableRequest('', { _typename: `${nsREX}RDrawableExecRequest`, exec }, painter); - } - - /** @summary Process reply from request to RDrawable */ - processDrawableReply(msg) { - const reply = parse(msg); - if (!reply?.reqid) - return false; - - const req = this.#submreq[reply.reqid]; - if (!req) - return false; - - // remove reference first - this.#submreq[reply.reqid] = undefined; - - // remove blocking reference for that kind - if (req._kind && req._painter?._requests) { - if (req._painter._requests[req._kind] === req) - delete req._painter._requests[req._kind]; - } - - if (req._method) - req._method(reply, req); - - // resubmit last request of that kind - if (req._nextreq && !req._painter._requests[req._kind]) - this.submitDrawableRequest(req._kind, req._nextreq, req._painter, req._method); - } - - /** @summary Show specified section in canvas */ - async showSection(that, on) { - switch (that) { - case 'Menu': break; - case 'StatusBar': break; - case 'Editor': break; - case 'ToolBar': break; - case 'ToolTips': this.setTooltipAllowed(on); break; - } - return true; - } - - /** @summary Method informs that something was changed in the canvas - * @desc used to update information on the server (when used with web6gui) - * @private */ - processChanges(kind, painter, subelem) { - // check if we could send at least one message more - for some meaningful actions - if (!this.canSendWebsocket(2) || !isStr(kind)) - return; - - const msg = ''; - if (!painter) - painter = this; - switch (kind) { - case 'sbits': - console.log('Status bits in RCanvas are changed - that to do?'); - break; - case 'frame': // when moving frame - case 'zoom': // when changing zoom inside frame - console.log('Frame moved or zoom is changed - that to do?'); - break; - case 'pave_moved': - console.log('TPave is moved inside RCanvas - that to do?'); - break; - default: - if ((kind.slice(0, 5) === 'exec:') && painter?.getSnapId()) - this.submitExec(painter, kind.slice(5), subelem); - else - console.log('UNPROCESSED CHANGES', kind); - } - - if (msg) - console.log('RCanvas::processChanges want to send ' + msg.length + ' ' + msg.slice(0, 40)); - } - - /** @summary Handle pad button click event - * @private */ - clickPadButton(funcname, evnt) { - if (funcname === 'ToggleGed') - return this.activateGed(this, null, 'toggle'); - if (funcname === 'ToggleStatus') - return this.activateStatusBar('toggle'); - return super.clickPadButton(funcname, evnt); - } - - /** @summary returns true when event status area exist for the canvas */ - hasEventStatus() { - if (this.testUI5()) - return false; - if (this.brlayout) - return this.brlayout.hasStatus(); - const hp = getHPainter(); - return hp ? hp.hasStatusLine() : false; - } - - /** @summary Check if status bar can be toggled - * @private */ - canStatusBar() { - return this.testUI5() || this.brlayout || getHPainter(); - } - - /** @summary Show/toggle event status bar - * @private */ - activateStatusBar(state) { - if (this.testUI5()) - return; - if (this.brlayout) - this.brlayout.createStatusLine(23, state); - else - getHPainter()?.createStatusLine(23, state); - - this.processChanges('sbits', this); - } - - /** @summary Show online canvas status - * @private */ - showCanvasStatus(...msgs) { - if (this.testUI5()) - return; - - const br = this.brlayout || getHPainter()?.brlayout; - br?.showStatus(...msgs); - } - - /** @summary Returns true if GED is present on the canvas */ - hasGed() { - if (this.testUI5()) - return false; - return this.brlayout?.hasContent() ?? false; - } - - /** @summary Function used to de-activate GED - * @private */ - removeGed() { - if (this.testUI5()) - return; - - this.registerForPadEvents(null); - - if (this.ged_view) { - this.ged_view.getController().cleanupGed(); - this.ged_view.destroy(); - delete this.ged_view; - } - this.brlayout?.deleteContent(true); - this.processChanges('sbits', this); - } - - /** @summary Get view data for ui5 panel - * @private */ - getUi5PanelData(/* panel_name */) { - return { jsroot: { settings, create, parse, toJSON, loadScript, EAxisBits, getColorExec } }; - } - - /** @summary Function used to activate GED - * @return {Promise} when GED is there - * @private */ - async activateGed(objpainter, kind, mode) { - if (this.testUI5() || !this.brlayout) - return false; - - if (this.brlayout.hasContent()) { - if ((mode === 'toggle') || (mode === false)) - this.removeGed(); - else - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - - return true; - } - - if (mode === false) - return false; - - const btns = this.brlayout.createBrowserBtns(); - - ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('fix')); - - ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('float')); - - ToolbarIcons.createSVG(btns, ToolbarIcons.cross, 15, 'delete GED', 'browser') - .style('margin', '3px').on('click', () => this.removeGed()); - - // be aware, that jsroot_browser_hierarchy required for flexible layout that element use full browser area - this.brlayout.setBrowserContent('<div class=\'jsroot_browser_hierarchy\' id=\'ged_placeholder\'>Loading GED ...</div>'); - this.brlayout.setBrowserTitle('GED'); - this.brlayout.toggleBrowserKind(kind || 'float'); - - return new Promise(resolveFunc => { - loadOpenui5.then(sap => { - d3_select('#ged_placeholder').text(''); - - sap.ui.require(['sap/ui/model/json/JSONModel', 'sap/ui/core/mvc/XMLView'], (JSONModel, XMLView) => { - const oModel = new JSONModel({ handle: null }); - - XMLView.create({ - viewName: 'rootui5.canv.view.Ged', - viewData: this.getUi5PanelData('Ged') - }).then(oGed => { - oGed.setModel(oModel); - - oGed.placeAt('ged_placeholder'); - - this.ged_view = oGed; - - // TODO: should be moved into Ged controller - it must be able to detect canvas painter itself - this.registerForPadEvents(oGed.getController().padEventsReceiver.bind(oGed.getController())); - - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - - this.processChanges('sbits', this); - - resolveFunc(true); - }); - }); - }); - }); - } - - /** @summary produce JSON for RCanvas, which can be used to display canvas once again - * @private */ - produceJSON() { - console.error('RCanvasPainter.produceJSON not yet implemented'); - return ''; - } - - /** @summary resize browser window to get requested canvas sizes */ - resizeBrowser(fullW, fullH) { - if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) - return; - this.getWebsocket()?.resizeWindow(fullW, fullH); - } - - /** @summary draw RCanvas object */ - static async draw(dom, can, opt) { - const nocanvas = !can; - if (nocanvas) - can = create(`${nsREX}RCanvas`); - - const painter = new RCanvasPainter(dom, can, opt); - painter.createCanvasSvg(0); - - selectActivePad({ pp: painter, active: false }); - - return painter.drawPrimitives().then(() => { - painter.addPadInteractive(); - painter.addPadButtons(); - painter.showPadButtons(); - return painter; - }); - } - -} // class RCanvasPainter - - -/** @summary draw RPadSnapshot object - * @private */ -function drawRPadSnapshot(dom, snap, opt) { - const painter = new RCanvasPainter(dom, null, opt); - painter.batch_mode = isBatchMode(); - return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { - painter.confirmDraw(); - painter.showPadButtons(); - return painter; - }); -} - -/** @summary Ensure RCanvas and RFrame for the painter object - * @param {object} painter - painter object to process - * @desc Assigns DOM, creates and draw RCanvas and RFrame if necessary, add painter to pad list of painters - * @return {Promise} for ready - * @private */ -async function ensureRCanvas(painter) { - if (!painter) - return Promise.reject(Error('Painter not provided in ensureRCanvas')); - - // simple check - if canvas there, can use painter - const pad_painter = painter.getPadPainter(), - pr = pad_painter ? Promise.resolve(pad_painter) - : RCanvasPainter.draw(painter.getDom(), null); - - return pr.then(pp => { - painter.addToPadPrimitives(pp); - return painter; - }); -} - - -/** @summary Function used for direct draw of RFrameTitle - * @private */ -function drawRFrameTitle(reason, drag) { - const fp = this.getFramePainter(); - if (!fp) - return console.log('no frame painter - no title'); - - const rect = fp.getFrameRect(), - fx = rect.x, - fy = rect.y, - fw = rect.width, - // fh = rect.height, - ph = this.getPadPainter().getPadHeight(), - title = this.getObject(), - title_width = fw, - textFont = this.v7EvalFont('text', { size: 0.07, color: 'black', align: 22 }); - let title_margin = this.v7EvalLength('margin', ph, 0.02), - title_height = this.v7EvalLength('height', ph, 0.05); - - if (reason === 'drag') { - title_height = drag.height; - title_margin = fy - drag.y - drag.height; - const changes = {}; - this.v7AttrChange(changes, 'margin', title_margin / ph); - this.v7AttrChange(changes, 'height', title_height / ph); - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - } - - const g = this.createG(); - - makeTranslate(g, fx, Math.round(fy - title_margin - title_height)); - - return this.startTextDrawingAsync(textFont, 'font').then(() => { - this.drawText({ x: title_width / 2, y: title_height / 2, text: title.fText, latex: 1 }); - return this.finishTextDrawing(); - }).then(() => { - addDragHandler(this, { - x: fx, y: Math.round(fy - title_margin - title_height), width: title_width, height: title_height, - minwidth: 20, minheight: 20, no_change_x: true, redraw: d => this.redraw('drag', d) - }); - }); -} - -// ========================================================== - -registerMethods(`${nsREX}RPalette`, { - - extractRColor(rcolor) { - const col = rcolor.fColor || 'black'; - return convertColor(col); - }, - - getColor(indx) { - return this.palette[indx]; - }, - - getContourIndex(zc) { - const cntr = this.fContour; - let l = 0, r = cntr.length - 1; - - if (zc < cntr[0]) - return -1; - if (zc >= cntr[r]) - return r - 1; - - if (this.fCustomContour) { - while (l < r - 1) { - const mid = Math.round((l + r) / 2); - if (cntr[mid] > zc) - r = mid; - else - l = mid; - } - return l; - } - - // last color in palette starts from level cntr[r-1] - return Math.floor((zc - cntr[0]) / (cntr[r - 1] - cntr[0]) * (r - 1)); - }, - - getContourColor(zc) { - const zindx = this.getContourIndex(zc); - return (zindx < 0) ? '' : this.getColor(zindx); - }, - - getContour() { - return this.fContour && (this.fContour.length > 1) ? this.fContour : null; - }, - - deleteContour() { - this.fContour = undefined; - }, - - calcColor(value, entry1, entry2) { - const dist = entry2.fOrdinal - entry1.fOrdinal, - r1 = entry2.fOrdinal - value, - r2 = value - entry1.fOrdinal; - - if (!this.fInterpolate || (dist <= 0)) - return convertColor((r1 < r2) ? entry2.fColor : entry1.fColor); - - // interpolate - const col1 = d3_rgb(this.extractRColor(entry1.fColor)), - col2 = d3_rgb(this.extractRColor(entry2.fColor)), - color = d3_rgb(Math.round((col1.r * r1 + col2.r * r2) / dist), - Math.round((col1.g * r1 + col2.g * r2) / dist), - Math.round((col1.b * r1 + col2.b * r2) / dist)); - - return color.formatRgb(); - }, - - createPaletteColors(len) { - const arr = []; - let indx = 0; - - while (arr.length < len) { - const value = arr.length / (len - 1), - entry = this.fColors[indx]; - - if ((Math.abs(entry.fOrdinal - value) < 0.0001) || (indx === this.fColors.length - 1)) { - arr.push(this.extractRColor(entry.fColor)); - continue; - } - - const next = this.fColors[indx + 1]; - if (next.fOrdinal <= value) - indx++; - else - arr.push(this.calcColor(value, entry, next)); - } - - return arr; - }, - - getColorOrdinal(value) { - // extract color with ordinal value between 0 and 1 - if (!this.fColors) - return 'black'; - if ((typeof value !== 'number') || (value < 0)) - value = 0; - else if (value > 1) - value = 1; - - // TODO: implement better way to find index - - let entry, next = this.fColors[0]; - for (let indx = 0; indx < this.fColors.length - 1; ++indx) { - entry = next; - - if (Math.abs(entry.fOrdinal - value) < 0.0001) - return this.extractRColor(entry.fColor); - - next = this.fColors[indx + 1]; - if (next.fOrdinal > value) - return this.calcColor(value, entry, next); - } - - return this.extractRColor(next.fColor); - }, - - setFullRange(min, max) { - // set full z scale range, used in zooming - this.full_min = min; - this.full_max = max; - }, - - createContour(logz, nlevels, zmin, zmax, zminpositive) { - this.fContour = []; - delete this.fCustomContour; - this.colzmin = zmin; - this.colzmax = zmax; - - if (logz) { - if (this.colzmax <= 0) - this.colzmax = 1.0; - if (this.colzmin <= 0) { - if ((zminpositive === undefined) || (zminpositive <= 0)) - this.colzmin = 0.0001 * this.colzmax; - else - this.colzmin = ((zminpositive < 3) || (zminpositive > 100)) ? 0.3 * zminpositive : 1; - } - if (this.colzmin >= this.colzmax) - this.colzmin = 0.0001 * this.colzmax; - - const logmin = Math.log(this.colzmin) / Math.log(10), - logmax = Math.log(this.colzmax) / Math.log(10), - dz = (logmax - logmin) / nlevels; - this.fContour.push(this.colzmin); - for (let level = 1; level < nlevels; level++) - this.fContour.push(Math.exp((logmin + dz * level) * Math.log(10))); - this.fContour.push(this.colzmax); - this.fCustomContour = true; - } else { - if ((this.colzmin === this.colzmax) && this.colzmin) { - this.colzmax += 0.01 * Math.abs(this.colzmax); - this.colzmin -= 0.01 * Math.abs(this.colzmin); - } - const dz = (this.colzmax - this.colzmin) / nlevels; - for (let level = 0; level <= nlevels; level++) - this.fContour.push(this.colzmin + dz * level); - } - - if (!this.palette || (this.palette.length !== nlevels)) - this.palette = this.createPaletteColors(nlevels); - } - -}); - -/** @summary draw RFont object - * @private */ -function drawRFont() { - const font = this.getObject(), - svg = this.getCanvSvg(), - clname = 'custom_font_' + font.fFamily + font.fWeight + font.fStyle; - let defs = svg.selectChild('.canvas_defs'); - - if (defs.empty()) - defs = svg.insert('svg:defs', ':first-child').attr('class', 'canvas_defs'); - - let entry = defs.selectChild('.' + clname); - if (entry.empty()) { - entry = defs.append('style') - .attr('type', 'text/css') - .attr('class', clname) - .text(`@font-face { font-family: "${font.fFamily}"; font-weight: ${font.fWeight ? font.fWeight : 'normal'}; font-style: ${font.fStyle ? font.fStyle : 'normal'}; src: ${font.fSrc}; }`); - const p1 = font.fSrc.indexOf('base64,'), - p2 = font.fSrc.lastIndexOf(' format('); - if (p1 > 0 && p2 > p1) { - const base64 = font.fSrc.slice(p1 + 7, p2 - 2), - is_ttf = font.fSrc.indexOf('data:application/font-ttf') > 0; - // TODO: for the moment only ttf format supported by jsPDF - if (is_ttf) - entry.property('$fontcfg', { n: font.fFamily, base64 }); - } - } - - if (font.fDefault) - this.getPadPainter()._dfltRFont = font; - - return true; -} - -/** @summary draw RAxis object - * @private */ -function drawRAxis(dom, obj, opt) { - const painter = new RAxisPainter(dom, obj, opt); - painter.disable_zooming = true; - return ensureRCanvas(painter) - .then(() => painter.redraw()) - .then(() => painter); -} - -/** @summary draw RFrame object - * @private */ -function drawRFrame(dom, obj, opt) { - const p = new RFramePainter(dom, obj); - if (opt === '3d') - p.mode3d = true; - return ensureRCanvas(p).then(() => p.redraw()); -} - -export { ensureRCanvas, drawRPadSnapshot, - drawRFrameTitle, drawRFont, drawRAxis, drawRFrame, - RObjectPainter, RPadPainter, RCanvasPainter }; diff --git a/js/modules/gpad/RFramePainter.mjs b/js/modules/gpad/RFramePainter.mjs deleted file mode 100644 index b7193222e3028..0000000000000 --- a/js/modules/gpad/RFramePainter.mjs +++ /dev/null @@ -1,1302 +0,0 @@ -import { gStyle, settings, internals, create, isFunc, isStr, clTAxis, nsREX, urlClassPrefix } from '../core.mjs'; -import { pointer as d3_pointer } from '../d3.mjs'; -import { getSvgLineStyle } from '../base/TAttLineHandler.mjs'; -import { makeTranslate } from '../base/BasePainter.mjs'; -import { EAxisBits, TAxisPainter } from './TAxisPainter.mjs'; -import { RAxisPainter } from './RAxisPainter.mjs'; -import { FrameInteractive, getEarthProjectionFunc } from './TFramePainter.mjs'; -import { RObjectPainter } from '../base/RObjectPainter.mjs'; - - -/** - * @summary Painter class for RFrame, main handler for interactivity - * - * @private - */ - -class RFramePainter extends RObjectPainter { - - #frame_x; // frame X coordinate - #frame_y; // frame Y coordinate - #frame_width; // frame width - #frame_height; // frame height - #frame_trans; // transform of frame element - #swap_xy; // swap X/Y axis on the frame - #reverse_x; // reverse X axis - #reverse_y; // reverse Y axis - #axes_drawn; // when axes are drawn - #projection; // id of projection function - #click_handler; // handle for click events - #dblclick_handler; // handle for double click events - #keys_handler; // assigned handler for keyboard events - #enabled_keys; // when keyboard press handling enabled - #last_event_pos; // position of last event - - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} frame - RFrame object */ - constructor(dom, frame) { - super(dom, frame, '', 'frame'); - this.mode3d = false; - this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing - this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing - this.#axes_drawn = false; - this.#projection = 0; // different projections - this.v7_frame = true; // indicator of v7, used in interactive part - } - - /** @summary Returns frame painter - object itself */ - getFramePainter() { return this; } - - /** @summary Returns true if it is ROOT6 frame - * @private */ - is_root6() { return false; } - - /** @summary Set active flag for frame - can block some events - * @private */ - setFrameActive(on) { - this.#enabled_keys = on && settings.HandleKeys; - // used only in 3D mode - if (this.control) - this.control.enableKeys = this.#enabled_keys; - } - - /** @summary Returns true if keys handling enabled - * @private */ - isEnabledKeys() { return this.#enabled_keys; } - - /** @summary Returns true if X/Y axis swapped */ - swap_xy() { return this.#swap_xy; } - - /** @summary Is reverse x */ - reverse_x() { return this.#reverse_x; } - - /** @summary Is reverse x */ - reverse_y() { return this.#reverse_y; } - - /** @summary Set position of last context menu event - * @private */ - setLastEventPos(pnt) { this.#last_event_pos = pnt; } - - /** @summary Return position of last event - * @private */ - getLastEventPos() { return this.#last_event_pos; } - - /** @summary Update graphical attributes */ - updateAttributes(force) { - if ((this.fX1NDC === undefined) || (force && !this.$modifiedNDC)) { - const rect = this.getPadPainter().getPadRect(); - this.fX1NDC = this.v7EvalLength('margins_left', rect.width, gStyle.fPadLeftMargin) / rect.width; - this.fY1NDC = this.v7EvalLength('margins_bottom', rect.height, gStyle.fPadBottomMargin) / rect.height; - this.fX2NDC = 1 - this.v7EvalLength('margins_right', rect.width, gStyle.fPadRightMargin) / rect.width; - this.fY2NDC = 1 - this.v7EvalLength('margins_top', rect.height, gStyle.fPadTopMargin) / rect.height; - } - - if (!this.fillatt) - this.createv7AttFill(); - - this.createv7AttLine('border_'); - } - - /** @summary Returns coordinates transformation func */ - getProjectionFunc() { return getEarthProjectionFunc(this.#projection); } - - /** @summary Recalculate frame ranges using specified projection functions - * @desc Not yet used in v7 */ - recalculateRange(Proj) { - this.#projection = Proj || 0; - - if ((this.#projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { - console.warn(`Mercator Projection: latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); - this.#projection = 0; - } - - const func = this.getProjectionFunc(); - if (!func) - return; - - const pnts = [func(this.scale_xmin, this.scale_ymin), - func(this.scale_xmin, this.scale_ymax), - func(this.scale_xmax, this.scale_ymax), - func(this.scale_xmax, this.scale_ymin)]; - if (this.scale_xmin < 0 && this.scale_xmax > 0) { - pnts.push(func(0, this.scale_ymin)); - pnts.push(func(0, this.scale_ymax)); - } - if (this.scale_ymin < 0 && this.scale_ymax > 0) { - pnts.push(func(this.scale_xmin, 0)); - pnts.push(func(this.scale_xmax, 0)); - } - - this.original_xmin = this.scale_xmin; - this.original_xmax = this.scale_xmax; - this.original_ymin = this.scale_ymin; - this.original_ymax = this.scale_ymax; - - this.scale_xmin = this.scale_xmax = pnts[0].x; - this.scale_ymin = this.scale_ymax = pnts[0].y; - - for (let n = 1; n < pnts.length; ++n) { - this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x); - this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x); - this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y); - this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y); - } - } - - getFrameSvg() { return this.getPadPainter().getFrameSvg(); } - - - /** @summary Draw axes grids - * @desc Called immediately after axes drawing */ - drawGrids() { - const layer = this.getFrameSvg().selectChild('.axis_layer'); - - layer.selectAll('.xgrid').remove(); - layer.selectAll('.ygrid').remove(); - - const h = this.getFrameHeight(), - w = this.getFrameWidth(), - gridx = this.v7EvalAttr('gridX', false), - gridy = this.v7EvalAttr('gridY', false), - grid_style = getSvgLineStyle(gStyle.fGridStyle), - grid_color = (gStyle.fGridColor > 0) ? this.getColor(gStyle.fGridColor) : 'black'; - - if (this.x_handle) - this.x_handle.draw_grid = gridx; - - // add a grid on x axis, if the option is set - if (this.x_handle?.draw_grid) { - let grid = ''; - for (let n = 0; n < this.x_handle.ticks.length; ++n) { - grid += this.#swap_xy - ? `M0,${h + this.x_handle.ticks[n]}h${w}` - : `M${this.x_handle.ticks[n]},0v${h}`; - } - - if (grid) { - layer.append('svg:path') - .attr('class', 'xgrid') - .attr('d', grid) - .style('stroke', grid_color) - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', grid_style); - } - } - - if (this.y_handle) - this.y_handle.draw_grid = gridy; - - // add a grid on y axis, if the option is set - if (this.y_handle?.draw_grid) { - let grid = ''; - for (let n = 0; n < this.y_handle.ticks.length; ++n) { - grid += this.#swap_xy - ? `M${this.y_handle.ticks[n]},0v${h}` - : `M0,${h + this.y_handle.ticks[n]}h${w}`; - } - - if (grid) { - layer.append('svg:path') - .attr('class', 'ygrid') - .attr('d', grid) - .style('stroke', grid_color) - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', grid_style); - } - } - } - - /** @summary Converts 'raw' axis value into text */ - axisAsText(axis, value) { - const handle = this[`${axis}_handle`]; - - return handle ? handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']) : value.toPrecision(4); - } - - /** @summary Set axis range */ - _setAxisRange(prefix, vmin, vmax) { - const nmin = `${prefix}min`, nmax = `${prefix}max`; - if (this[nmin] !== this[nmax]) - return; - let min = this.v7EvalAttr(`${prefix}_min`), - max = this.v7EvalAttr(`${prefix}_max`); - - if (min !== undefined) - vmin = min; - if (max !== undefined) - vmax = max; - - if (vmin < vmax) { - this[nmin] = vmin; - this[nmax] = vmax; - } - - const nzmin = `zoom_${prefix}min`, nzmax = `zoom_${prefix}max`; - - if ((this[nzmin] === this[nzmax]) && !this.zoomChangedInteractive(prefix)) { - min = this.v7EvalAttr(`${prefix}_zoomMin`); - max = this.v7EvalAttr(`${prefix}_zoomMax`); - - if ((min !== undefined) || (max !== undefined)) { - this[nzmin] = (min === undefined) ? this[nmin] : min; - this[nzmax] = (max === undefined) ? this[nmax] : max; - } - } - } - - /** @summary Set axes ranges for drawing, check configured attributes if range already specified */ - setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax) { - if (this.#axes_drawn) - return; - this.xaxis = xaxis; - this._setAxisRange('x', xmin, xmax); - this.yaxis = yaxis; - this._setAxisRange('y', ymin, ymax); - this.zaxis = zaxis; - this._setAxisRange('z', zmin, zmax); - } - - /** @summary Set secondary axes ranges */ - setAxes2Ranges(second_x, xaxis, xmin, xmax, second_y, yaxis, ymin, ymax) { - if (second_x) { - this.x2axis = xaxis; - this._setAxisRange('x2', xmin, xmax); - } - if (second_y) { - this.y2axis = yaxis; - this._setAxisRange('y2', ymin, ymax); - } - } - - /** @summary Create x,y objects which maps user coordinates into pixels - * @desc Must be used only for v6 objects, see TFramePainter for more details - * @private */ - createXY(opts) { - if (this.self_drawaxes) - return; - - this.cleanXY(); // remove all previous configurations - - if (!opts) - opts = { ndim: 1 }; - - this.v6axes = true; - this.#swap_xy = opts.swap_xy || false; - this.#reverse_x = opts.reverse_x || false; - this.#reverse_y = opts.reverse_y || false; - - this.logx = this.v7EvalAttr('x_log', 0); - this.logy = this.v7EvalAttr('y_log', 0); - - const w = this.getFrameWidth(), h = this.getFrameHeight(), pp = this.getPadPainter(); - - this.scales_ndim = opts.ndim; - - this.scale_xmin = this.xmin; - this.scale_xmax = this.xmax; - - this.scale_ymin = this.ymin; - this.scale_ymax = this.ymax; - - if (opts.extra_y_space) { - const log_scale = this.#swap_xy ? this.logx : this.logy; - if (log_scale && (this.scale_ymax > 0)) - this.scale_ymax = Math.exp(Math.log(this.scale_ymax) * 1.1); - else - this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1; - } - - if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { - this.zoom_xmin = opts.zoom_xmin; - this.zoom_xmax = opts.zoom_xmax; - } - - if ((opts.zoom_ymin !== opts.zoom_ymax) && ((this.zoom_ymin === this.zoom_ymax) || !this.zoomChangedInteractive('y'))) { - this.zoom_ymin = opts.zoom_ymin; - this.zoom_ymax = opts.zoom_ymax; - } - - if (this.zoom_xmin !== this.zoom_xmax) { - this.scale_xmin = this.zoom_xmin; - this.scale_xmax = this.zoom_xmax; - } - - if (this.zoom_ymin !== this.zoom_ymax) { - this.scale_ymin = this.zoom_ymin; - this.scale_ymax = this.zoom_ymax; - } - - let xaxis = this.xaxis, yaxis = this.yaxis; - if (xaxis?._typename !== clTAxis) - xaxis = create(clTAxis); - if (yaxis?._typename !== clTAxis) - yaxis = create(clTAxis); - - this.x_handle = new TAxisPainter(pp, xaxis, true); - this.x_handle.optionUnlab = this.v7EvalAttr('x_labels_hide', false); - - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { - reverse: this.#reverse_x, - log: this.#swap_xy ? this.logy : this.logx, - symlog: this.#swap_xy ? opts.symlog_y : opts.symlog_x, - logcheckmin: (opts.ndim > 1) || !this.#swap_xy, - logminfactor: 0.0001 - }); - - this.x_handle.assignFrameMembers(this, 'x'); - - this.y_handle = new TAxisPainter(pp, yaxis, true); - this.y_handle.optionUnlab = this.v7EvalAttr('y_labels_hide', false); - - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { - reverse: this.#reverse_y, - log: this.#swap_xy ? this.logx : this.logy, - symlog: this.#swap_xy ? opts.symlog_x : opts.symlog_y, - logcheckmin: (opts.ndim > 1) || this.#swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.ymax) ? 0.5 * opts.ymin_nz : 0, - logminfactor: 3e-4 - }); - - this.y_handle.assignFrameMembers(this, 'y'); - } - - /** @summary Identify if requested axes are drawn - * @desc Checks if x/y axes are drawn. Also if second side is already there */ - hasDrawnAxes(second_x, second_y) { return !second_x && !second_y ? this.#axes_drawn : false; } - - /** @summary Draw configured axes on the frame - * @desc axes can be drawn only for main histogram */ - async drawAxes() { - if (this.#axes_drawn || (this.xmin === this.xmax) || (this.ymin === this.ymax)) - return this.#axes_drawn; - - const ticksx = this.v7EvalAttr('ticksX', 1), - ticksy = this.v7EvalAttr('ticksY', 1), - sidex = this.v7EvalAttr('swapX', false) ? -1 : 1, - sidey = this.v7EvalAttr('swapY', false) ? -1 : 1, - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(); - - if (!this.v6axes) { - // this is partially same as v6 createXY method - - this.cleanupAxes(); - - this.#swap_xy = false; - - if (this.zoom_xmin !== this.zoom_xmax) { - this.scale_xmin = this.zoom_xmin; - this.scale_xmax = this.zoom_xmax; - } else { - this.scale_xmin = this.xmin; - this.scale_xmax = this.xmax; - } - - if (this.zoom_ymin !== this.zoom_ymax) { - this.scale_ymin = this.zoom_ymin; - this.scale_ymax = this.zoom_ymax; - } else { - this.scale_ymin = this.ymin; - this.scale_ymax = this.ymax; - } - - this.recalculateRange(0); - - this.x_handle = new RAxisPainter(pp, this, this.xaxis, 'x_'); - this.x_handle.assignSnapId(this.getSnapId()); - this.x_handle.draw_swapside = (sidex < 0); - this.x_handle.draw_ticks = ticksx; - - this.y_handle = new RAxisPainter(pp, this, this.yaxis, 'y_'); - this.y_handle.assignSnapId(this.getSnapId()); - this.y_handle.draw_swapside = (sidey < 0); - this.y_handle.draw_ticks = ticksy; - - this.z_handle = new RAxisPainter(pp, this, this.zaxis, 'z_'); - this.z_handle.assignSnapId(this.getSnapId()); - - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, false, [0, w], w, { reverse: false }); - this.x_handle.assignFrameMembers(this, 'x'); - - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, true, [h, 0], -h, { reverse: false }); - this.y_handle.assignFrameMembers(this, 'y'); - - // only get basic properties like log scale - this.z_handle.configureZAxis('zaxis', this); - } - - const layer = this.getFrameSvg().selectChild('.axis_layer'); - - this.x_handle.has_obstacle = false; - - const draw_horiz = this.#swap_xy ? this.y_handle : this.x_handle, - draw_vertical = this.#swap_xy ? this.x_handle : this.y_handle; - let pr; - - if (this.getPadPainter()?.isFastDrawing()) - pr = Promise.resolve(true); // do nothing - else if (this.v6axes) { - // in v7 ticksx/y values shifted by 1 relative to v6 - // In v7 ticksx === 0 means no ticks, ticksx === 1 equivalent to === 0 in v6 - - const can_adjust_frame = false, disable_x_draw = false, disable_y_draw = false; - - draw_horiz.disable_ticks = (ticksx <= 0); - draw_vertical.disable_ticks = (ticksy <= 0); - - const pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - (ticksx > 1) ? -h : 0, disable_x_draw, - undefined, false, this.getPadPainter().getPadHeight() - h - this.getFrameY()), - - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - (ticksy > 1) ? w : 0, disable_y_draw, - draw_vertical.invert_side ? 0 : this.#frame_x, can_adjust_frame); - - pr = Promise.all([pr1, pr2]).then(() => this.drawGrids()); - } else { - let arr = []; - - if (ticksx > 0) - arr.push(draw_horiz.drawAxis(layer, makeTranslate(0, sidex > 0 ? h : 0), sidex)); - - if (ticksy > 0) - arr.push(draw_vertical.drawAxis(layer, makeTranslate(sidey > 0 ? 0 : w, h), sidey)); - - pr = Promise.all(arr).then(() => { - arr = []; - if (ticksx > 1) - arr.push(draw_horiz.drawAxisOtherPlace(layer, makeTranslate(0, sidex < 0 ? h : 0), -sidex, ticksx === 2)); - - if (ticksy > 1) - arr.push(draw_vertical.drawAxisOtherPlace(layer, makeTranslate(sidey < 0 ? 0 : w, h), -sidey, ticksy === 2)); - return Promise.all(arr); - }).then(() => this.drawGrids()); - } - - return pr.then(() => { - this.#axes_drawn = true; - return true; - }); - } - - /** @summary Draw secondary configured axes */ - drawAxes2(second_x, second_y) { - const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), - layer = this.getFrameSvg().selectChild('.axis_layer'); - let pr1, pr2; - - if (second_x) { - if (this.zoom_x2min !== this.zoom_x2max) { - this.scale_x2min = this.zoom_x2min; - this.scale_x2max = this.zoom_x2max; - } else { - this.scale_x2min = this.x2min; - this.scale_x2max = this.x2max; - } - this.x2_handle = new RAxisPainter(pp, this, this.x2axis, 'x2_'); - this.x2_handle.assignSnapId(this.getSnapId()); - - this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, false, [0, w], w, { reverse: false }); - this.x2_handle.assignFrameMembers(this, 'x2'); - - pr1 = this.x2_handle.drawAxis(layer, null, -1); - } - - if (second_y) { - if (this.zoom_y2min !== this.zoom_y2max) { - this.scale_y2min = this.zoom_y2min; - this.scale_y2max = this.zoom_y2max; - } else { - this.scale_y2min = this.y2min; - this.scale_y2max = this.y2max; - } - - this.y2_handle = new RAxisPainter(pp, this, this.y2axis, 'y2_'); - this.y2_handle.assignSnapId(this.getSnapId()); - - this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, true, [h, 0], -h, { reverse: false }); - this.y2_handle.assignFrameMembers(this, 'y2'); - - pr2 = this.y2_handle.drawAxis(layer, makeTranslate(w, h), -1); - } - - return Promise.all([pr1, pr2]); - } - - /** @summary Return functions to create x/y points based on coordinates - * @desc In default case returns frame painter itself - * @private */ - getGrFuncs(second_x, second_y) { - const use_x2 = second_x && this.grx2, - use_y2 = second_y && this.gry2; - if (!use_x2 && !use_y2) - return this; - - return { - use_x2, - grx: use_x2 ? this.grx2 : this.grx, - x_handle: use_x2 ? this.x2_handle : this.x_handle, - logx: use_x2 ? this.x2_handle.log : this.x_handle.log, - scale_xmin: use_x2 ? this.scale_x2min : this.scale_xmin, - scale_xmax: use_x2 ? this.scale_x2max : this.scale_xmax, - use_y2, - gry: use_y2 ? this.gry2 : this.gry, - y_handle: use_y2 ? this.y2_handle : this.y_handle, - logy: use_y2 ? this.y2_handle.log : this.y_handle.log, - scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, - scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, - fp: this, - swap_xy() { return this.fp.swap_xy(); }, - revertAxis(name, v) { - if ((name === 'x') && this.use_x2) - name = 'x2'; - if ((name === 'y') && this.use_y2) - name = 'y2'; - return this.fp.revertAxis(name, v); - }, - axisAsText(name, v) { - if ((name === 'x') && this.use_x2) - name = 'x2'; - if ((name === 'y') && this.use_y2) - name = 'y2'; - return this.fp.axisAsText(name, v); - } - }; - } - - /** @summary function called at the end of resize of frame - * @desc Used to update attributes on the server - * @private */ - sizeChanged() { - const changes = {}; - this.v7AttrChange(changes, 'margins_left', this.fX1NDC); - this.v7AttrChange(changes, 'margins_bottom', this.fY1NDC); - this.v7AttrChange(changes, 'margins_right', 1 - this.fX2NDC); - this.v7AttrChange(changes, 'margins_top', 1 - this.fY2NDC); - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - - this.redrawPad(); - } - - /** @summary Remove all x/y functions - * @private */ - cleanXY() { - // remove all axes drawings - const clean = (name, grname) => { - this[name]?.cleanup(); - delete this[name]; - delete this[grname]; - }; - - clean('x_handle', 'grx'); - clean('y_handle', 'gry'); - clean('z_handle', 'grz'); - clean('x2_handle', 'grx2'); - clean('y2_handle', 'gry2'); - - delete this.v6axes; // marker that v6 axes are used - } - - /** @summary Remove all axes drawings - * @private */ - cleanupAxes() { - this.cleanXY(); - - this.getG()?.selectChild('.axis_layer').selectAll('*').remove(); - this.#axes_drawn = false; - } - - /** @summary Removes all drawn elements of the frame - * @private */ - cleanFrameDrawings() { - // cleanup all 3D drawings if any - if (isFunc(this.create3DScene)) - this.create3DScene(-1); - - this.cleanupAxes(); - - const clean = (name) => { - this[name + 'min'] = this[name + 'max'] = 0; - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - this[`scale_${name}min`] = this[`scale_${name}max`] = 0; - }; - - clean('x'); - clean('y'); - clean('z'); - clean('x2'); - clean('y2'); - - this.getG()?.selectChild('.main_layer').selectAll('*').remove(); - this.getG()?.selectChild('.upper_layer').selectAll('*').remove(); - } - - /** @summary Fully cleanup frame - * @private */ - cleanup() { - this.cleanFrameDrawings(); - - this.getG()?.selectAll('*').remove(); - this.getG()?.on('mousedown', null) - .on('dblclick', null) - .on('wheel', null) - .on('contextmenu', null) - .property('interactive_set', null); - - if (this.#keys_handler) { - window.removeEventListener('keydown', this.#keys_handler, false); - this.#keys_handler = undefined; - } - this.#enabled_keys = undefined; - delete this.self_drawaxes; - - delete this.xaxis; - delete this.yaxis; - delete this.zaxis; - delete this.x2axis; - delete this.y2axis; - - this.setG(undefined); // frame <g> element managed by the pad - - this.#click_handler = undefined; - this.#dblclick_handler = undefined; - - this.getPadPainter()?.setFramePainter(this, false); - - super.cleanup(); - } - - /** @summary Redraw frame - * @private */ - redraw() { - const pp = this.getPadPainter(); - pp?.setFramePainter(this, true); - - // first update all attributes from objects - this.updateAttributes(); - - const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, - lm = Math.round(rect.width * this.fX1NDC), - tm = Math.round(rect.height * (1 - this.fY2NDC)), - rotate = pp?.options?.RotateFrame, - w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), - h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)); - - // update values here to let access even when frame is not really updated - this.#frame_x = lm; - this.#frame_y = tm; - this.#frame_width = rotate ? h : w; - this.#frame_height = rotate ? w : h; - this.#frame_trans = rotate ? `rotate(-90,${lm},${tm}) translate(${lm - h},${tm})` : makeTranslate(lm, tm); - this.$can_drag = !rotate && !pp?.options?.FixFrame; - - return this.mode3d ? this : this.createFrameG(); - } - - /** @summary Create frame element and update all attributes - * @private */ - createFrameG() { - // this is svg:g object - container for every other items belonging to frame - let g = this.setG(this.getFrameSvg()), - top_rect, main_svg; - - if (g.empty()) { - g = this.setG(this.getPadPainter().getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame')); - - if (!this.isBatchMode()) - g.append('svg:title').text(''); - - top_rect = g.append('svg:rect'); - - main_svg = g.append('svg:svg') - .attr('class', 'main_layer') - .attr('x', 0) - .attr('y', 0) - .attr('overflow', 'hidden'); - - g.append('svg:g').attr('class', 'axis_layer'); - g.append('svg:g').attr('class', 'upper_layer'); - } else { - top_rect = g.selectChild('rect'); - main_svg = g.selectChild('.main_layer'); - } - - this.#axes_drawn = false; - - g.attr('transform', this.#frame_trans); - - top_rect.attr('x', 0) - .attr('y', 0) - .attr('width', this.#frame_width) - .attr('height', this.#frame_height) - .attr('rx', this.lineatt.rx || null) - .attr('ry', this.lineatt.ry || null) - .call(this.fillatt.func) - .call(this.lineatt.func); - - main_svg.attr('width', this.#frame_width) - .attr('height', this.#frame_height) - .attr('viewBox', `0 0 ${this.#frame_width} ${this.#frame_height}`); - - let pr = Promise.resolve(true); - - if (this.v7EvalAttr('drawAxes')) { - this.self_drawaxes = true; - this.setAxesRanges(); - pr = this.drawAxes().then(() => this.addInteractivity()); - } - - return pr.then(() => { return this; }); - } - - /** @summary Returns frame X position */ - getFrameX() { return this.#frame_x || 0; } - - /** @summary Returns frame Y position */ - getFrameY() { return this.#frame_y || 0; } - - /** @summary Returns frame width */ - getFrameWidth() { return this.#frame_width || 0; } - - /** @summary Returns frame height */ - getFrameHeight() { return this.#frame_height || 0; } - - /** @summary Returns frame rectangle plus extra info for hint display */ - getFrameRect() { - return { - x: this.#frame_x || 0, - y: this.#frame_y || 0, - width: this.getFrameWidth(), - height: this.getFrameHeight(), - transform: this.getG()?.attr('transform') || '', - hint_delta_x: 0, - hint_delta_y: 0 - }; - } - - /** @summary Returns palette associated with frame */ - getHistPalette() { - return this.getPadPainter().getHistPalette(); - } - - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was performed - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of click will be disabled */ - configureUserClickHandler(handler) { - this.#click_handler = isFunc(handler) ? handler : null; - } - - /** @summary Returns actual click handler */ - getClickHandler() { return this.#click_handler; } - - /** @summary Configure user-defined dblclick handler - * @desc Function will be called every time when double click was called - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ - configureUserDblclickHandler(handler) { - this.#dblclick_handler = isFunc(handler) ? handler : null; - } - - /** @summary Returns actual double-click handler */ - getDblclickHandler() { return this.#dblclick_handler; } - - /** @summary function can be used for zooming into specified range - * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be unzoomed - * @return {Promise} with boolean flag if zoom operation was performed */ - async zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { - // disable zooming when axis conversion is enabled - if (this.#projection) - return false; - - if (xmin === 'x') { - xmin = xmax; - xmax = ymin; - interactive = ymax; - ymin = ymax = undefined; - } else if (xmin === 'y') { - interactive = ymax; - ymax = ymin; - ymin = xmax; - xmin = xmax = undefined; - } else if (xmin === 'z') { - interactive = ymax; - zmin = xmax; - zmax = ymin; - xmin = xmax = ymin = ymax = undefined; - } - - let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), - unzoom_x = false, unzoom_y = false, unzoom_z = false; - - if (zoom_x) { - let cnt = 0; - if (xmin <= this.xmin) { - xmin = this.xmin; - cnt++; - } - if (xmax >= this.xmax) { - xmax = this.xmax; - cnt++; - } - if (cnt === 2) { - zoom_x = false; - unzoom_x = true; - } - } else - unzoom_x = (xmin === xmax) && (xmin === 0); - - if (zoom_y) { - let cnt = 0; - if (ymin <= this.ymin) { - ymin = this.ymin; - cnt++; - } - if (ymax >= this.ymax) { - ymax = this.ymax; - cnt++; - } - if (cnt === 2) { - zoom_y = false; - unzoom_y = true; - } - } else - unzoom_y = (ymin === ymax) && (ymin === 0); - - if (zoom_z) { - let cnt = 0; - if (zmin <= this.zmin) { - zmin = this.zmin; - cnt++; - } - if (zmax >= this.zmax) { - zmax = this.zmax; - cnt++; - } - if (cnt === 2) { - zoom_z = false; - unzoom_z = true; - } - } else - unzoom_z = (zmin === zmax) && (zmin === 0); - - let changed = false, r_x = '', r_y = '', r_z = '', is_any_check = false; - const req = { - _typename: `${nsREX}RFrame::RUserRanges`, - values: [0, 0, 0, 0, 0, 0], - flags: [false, false, false, false, false, false] - }, checkZooming = (painter, force) => { - if (!force && !isFunc(painter.canZoomInside)) - return; - - is_any_check = true; - - if (zoom_x && (force || painter.canZoomInside('x', xmin, xmax))) { - this.zoom_xmin = xmin; - this.zoom_xmax = xmax; - changed = true; - r_x = '0'; - zoom_x = false; - req.values[0] = xmin; - req.values[1] = xmax; - req.flags[0] = req.flags[1] = true; - if (interactive) - this.zoomChangedInteractive('x', interactive); - } - if (zoom_y && (force || painter.canZoomInside('y', ymin, ymax))) { - this.zoom_ymin = ymin; - this.zoom_ymax = ymax; - changed = true; - r_y = '1'; - zoom_y = false; - req.values[2] = ymin; - req.values[3] = ymax; - req.flags[2] = req.flags[3] = true; - if (interactive) - this.zoomChangedInteractive('y', interactive); - } - if (zoom_z && (force || painter.canZoomInside('z', zmin, zmax))) { - this.zoom_zmin = zmin; - this.zoom_zmax = zmax; - changed = true; - r_z = '2'; - zoom_z = false; - req.values[4] = zmin; - req.values[5] = zmax; - req.flags[4] = req.flags[5] = true; - if (interactive) - this.zoomChangedInteractive('z', interactive); - } - }; - - // first process zooming (if any) - if (zoom_x || zoom_y || zoom_z) - this.forEachPainter(painter => checkZooming(painter)); - - // force zooming when no any other painter can verify zoom range - if (!is_any_check && this.self_drawaxes) - checkZooming(null, true); - - // and process unzoom, if any - if (unzoom_x || unzoom_y || unzoom_z) { - if (unzoom_x) { - if (this.zoom_xmin !== this.zoom_xmax) { - changed = true; - r_x = '0'; - } - this.zoom_xmin = this.zoom_xmax = 0; - req.values[0] = req.values[1] = -1; - if (interactive) - this.zoomChangedInteractive('x', interactive); - } - if (unzoom_y) { - if (this.zoom_ymin !== this.zoom_ymax) { - changed = true; - r_y = '1'; - } - this.zoom_ymin = this.zoom_ymax = 0; - req.values[2] = req.values[3] = -1; - if (interactive) - this.zoomChangedInteractive('y', interactive); - } - if (unzoom_z) { - if (this.zoom_zmin !== this.zoom_zmax) { - changed = true; - r_z = '2'; - } - this.zoom_zmin = this.zoom_zmax = 0; - req.values[4] = req.values[5] = -1; - if (interactive) - this.zoomChangedInteractive('z', interactive); - } - } - - if (!changed) - return false; - - if (this.v7NormalMode()) - this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); - - return this.interactiveRedraw('pad', 'zoom' + r_x + r_y + r_z).then(() => true); - } - - /** @summary Zooming of single axis - * @param {string} name - axis name like x/y/z but also second axis x2 or y2 - * @param {number} vmin - axis minimal value, 0 for unzoom - * @param {number} vmax - axis maximal value, 0 for unzoom - * @param {boolean} [interactive] - if change was performed interactively - * @protected */ - async zoomSingle(name, vmin, vmax, interactive) { - const names = ['x', 'y', 'z', 'x2', 'y2'], indx = names.indexOf(name); - - // disable zooming when axis conversion is enabled - if (this.#projection || (!this[`${name}_handle`] && (name !== 'z')) || (indx < 0)) - return false; - - let zoom_v = (vmin !== vmax), unzoom_v = false; - - if (zoom_v) { - let cnt = 0; - if (vmin <= this[name + 'min']) { - vmin = this[name + 'min']; - cnt++; - } - if (vmax >= this[name + 'max']) { - vmax = this[name + 'max']; - cnt++; - } - if (cnt === 2) { - zoom_v = false; - unzoom_v = true; - } - } else - unzoom_v = (vmin === vmax) && (vmin === 0); - - - let changed = false, is_any_check = false; - const req = { - _typename: `${nsREX}RFrame::RUserRanges`, - values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - flags: [false, false, false, false, false, false, false, false, false, false] - }; - - // eslint-disable-next-line one-var - const checkZooming = (painter, force) => { - if (!force && !isFunc(painter?.canZoomInside)) - return; - - is_any_check = true; - - if (zoom_v && (force || painter.canZoomInside(name[0], vmin, vmax))) { - this[`zoom_${name}min`] = vmin; - this[`zoom_${name}max`] = vmax; - changed = true; - zoom_v = false; - req.values[indx * 2] = vmin; - req.values[indx * 2 + 1] = vmax; - req.flags[indx * 2] = req.flags[indx * 2 + 1] = true; - } - }; - - // first process zooming (if any) - if (zoom_v) - this.forEachPainter(painter => checkZooming(painter)); - - // force zooming when no any other painter can verify zoom range - if (!is_any_check && this.self_drawaxes) - checkZooming(null, true); - - if (unzoom_v) { - if (this[`zoom_${name}min`] !== this[`zoom_${name}max`]) - changed = true; - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - req.values[indx * 2] = req.values[indx * 2 + 1] = -1; - } - - if (!changed) - return false; - - if (interactive) - this.zoomChangedInteractive(name, interactive); - - if (this.v7NormalMode()) - this.v7SubmitRequest('zoom', { _typename: `${nsREX}RFrame::RZoomRequest`, ranges: req }); - - return this.interactiveRedraw('pad', `zoom${indx}`).then(() => true); - } - - /** @summary Unzoom single axis */ - async unzoomSingle(name, interactive) { - return this.zoomSingle(name, 0, 0, typeof interactive === 'undefined' ? 'unzoom' : interactive); - } - - /** @summary Checks if specified axis zoomed */ - isAxisZoomed(axis) { - return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; - } - - /** @summary Unzoom specified axes - * @return {Promise} with boolean flag if zoom is changed */ - async unzoom(dox, doy, doz) { - if (dox === 'all') - return this.unzoom('x2').then(() => this.unzoom('y2')).then(() => this.unzoom('xyz')); - - if ((dox === 'x2') || (dox === 'y2')) - return this.unzoomSingle(dox); - - if (typeof dox === 'undefined') - dox = doy = doz = true; - else if (isStr(dox)) { - doz = dox.indexOf('z') >= 0; - doy = dox.indexOf('y') >= 0; - dox = dox.indexOf('x') >= 0; - } - - return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, - doy ? 0 : undefined, doy ? 0 : undefined, - doz ? 0 : undefined, doz ? 0 : undefined, - 'unzoom'); - } - - /** @summary Reset all zoom attributes - * @private */ - resetZoom() { - ['x', 'y', 'z', 'x2', 'y2'].forEach(n => { - this[`zoom_${n}min`] = undefined; - this[`zoom_${n}max`] = undefined; - this[`zoom_changed_${n}`] = undefined; - }); - } - - /** @summary Mark/check if zoom for specific axis was changed interactively - * @private */ - zoomChangedInteractive(axis, value) { - if (axis === 'reset') { - this.zoom_changed_x = this.zoom_changed_y = this.zoom_changed_z = undefined; - return; - } - if (!axis || axis === 'any') - return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - - if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) - return; - - const fld = 'zoom_changed_' + axis; - if (value === undefined) - return this[fld]; - - if (value === 'unzoom') { - // special handling of unzoom, only if was never changed before flag set to true - this[fld] = (this[fld] === undefined); - return; - } - - if (value) - this[fld] = true; - } - - /** @summary Fill menu for frame when server is not there */ - fillObjectOfflineMenu(menu, kind) { - if ((kind !== 'x') && (kind !== 'y')) - return; - - menu.add('Unzoom', () => this.unzoom(kind)); - - // here should be all axes attributes in offline - } - - /** @summary Set grid drawing for specified axis */ - changeFrameAttr(attr, value) { - const changes = {}; - this.v7AttrChange(changes, attr, value); - this.v7SetAttr(attr, value); - this.v7SendAttrChanges(changes, false); // do not invoke canvas update on the server - this.redrawPad(); - } - - /** @summary Fill context menu */ - fillContextMenu(menu, kind, obj) { - if (kind === 'pal') - kind = 'z'; - - if ((kind === 'x') || (kind === 'y') || (kind === 'x2') || (kind === 'y2')) { - const handle = this[kind + '_handle'], - faxis = obj || this[kind + 'axis']; - if (!handle) - return false; - menu.header(`${kind.toUpperCase()} axis`, `${urlClassPrefix}ROOT_1_1Experimental_1_1RAxisBase.html`); - - if (isFunc(faxis?.TestBit)) { - const main = this.getMainPainter(true); - menu.addTAxisMenu(EAxisBits, main || this, faxis, kind); - return true; - } - - return handle.fillAxisContextMenu(menu, kind); - } - - const alone = menu.size() === 0; - - if (alone) - menu.header('Frame', `${urlClassPrefix}ROOT_1_1Experimental_1_1RFrame.html`); - else - menu.separator(); - - if (this.zoom_xmin !== this.zoom_xmax) - menu.add('Unzoom X', () => this.unzoom('x')); - if (this.zoom_ymin !== this.zoom_ymax) - menu.add('Unzoom Y', () => this.unzoom('y')); - if (this.zoom_zmin !== this.zoom_zmax) - menu.add('Unzoom Z', () => this.unzoom('z')); - if (this.zoom_x2min !== this.zoom_x2max) - menu.add('Unzoom X2', () => this.unzoom('x2')); - if (this.zoom_y2min !== this.zoom_y2max) - menu.add('Unzoom Y2', () => this.unzoom('y2')); - menu.add('Unzoom all', () => this.unzoom('all')); - - menu.separator(); - - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - - if (this.x_handle) - menu.addchk(this.x_handle.draw_grid, 'Grid x', flag => this.changeFrameAttr('gridX', flag)); - if (this.y_handle) - menu.addchk(this.y_handle.draw_grid, 'Grid y', flag => this.changeFrameAttr('gridY', flag)); - if (this.x_handle && !this.x2_handle) - menu.addchk(this.x_handle.draw_swapside, 'Swap x', flag => this.changeFrameAttr('swapX', flag)); - if (this.y_handle && !this.y2_handle) - menu.addchk(this.y_handle.draw_swapside, 'Swap y', flag => this.changeFrameAttr('swapY', flag)); - if (this.x_handle && !this.x2_handle) { - menu.sub('Ticks x'); - menu.addchk(this.x_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksX', 0)); - menu.addchk(this.x_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksX', 1)); - menu.addchk(this.x_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksX', 2)); - menu.addchk(this.x_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksX', 3)); - menu.endsub(); - } - if (this.y_handle && !this.y2_handle) { - menu.sub('Ticks y'); - menu.addchk(this.y_handle.draw_ticks === 0, 'off', () => this.changeFrameAttr('ticksY', 0)); - menu.addchk(this.y_handle.draw_ticks === 1, 'normal', () => this.changeFrameAttr('ticksY', 1)); - menu.addchk(this.y_handle.draw_ticks === 2, 'ticks on both sides', () => this.changeFrameAttr('ticksY', 2)); - menu.addchk(this.y_handle.draw_ticks === 3, 'labels on both sides', () => this.changeFrameAttr('ticksY', 3)); - menu.endsub(); - } - - menu.addAttributesMenu(this, alone ? '' : 'Frame '); - menu.separator(); - - menu.sub('Save as'); - const fmts = ['svg', 'png', 'jpeg', 'webp']; - if (internals.makePDF) - fmts.push('pdf'); - fmts.forEach(fmt => menu.add(`frame.${fmt}`, () => this.getPadPainter().saveAs(fmt, 'frame', `frame.${fmt}`))); - menu.endsub(); - - return true; - } - - /** @summary Convert graphical coordinate into axis value */ - revertAxis(axis, pnt) { return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; } - - /** @summary Show axis status message - * @desc method called normally when mouse enter main object element - * @private */ - showAxisStatus(axis_name, evnt) { - const hint_name = axis_name, hint_title = 'axis', - m = d3_pointer(evnt, this.getFrameSvg().node()); - let id = (axis_name === 'x') ? 0 : 1; - - if (this.#swap_xy) - id = 1 - id; - - const axis_value = this.revertAxis(axis_name, m[id]); - - this.showObjectStatus(hint_name, hint_title, `${axis_name} : ${this.axisAsText(axis_name, axis_value)}`, `${Math.round(m[0])},${Math.round(m[1])}`); - } - - /** @summary Add interactive keys handlers - * @private */ - addKeysHandler() { - if (this.isBatchMode() || this.#keys_handler || (typeof window === 'undefined')) - return; - - FrameInteractive.assign(this); - - this.#keys_handler = evnt => this.processKeyPress(evnt); - - window.addEventListener('keydown', this.#keys_handler, false); - } - - /** @summary Add interactive functionality to the frame - * @private */ - addInteractivity(for_second_axes) { - if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) - return true; - - FrameInteractive.assign(this); - if (!for_second_axes) - this.addBasicInteractivity(); - return this.addFrameInteractivity(for_second_axes); - } - - /** @summary Set selected range back to pad object - to be implemented - * @private */ - setRootPadRange(/* pad, is3d */) { - // TODO: change of pad range and send back to root application - } - - /** @summary Toggle log scale on the specified axes */ - toggleAxisLog(axis) { - const handle = this[axis + '_handle']; - return handle?.changeAxisLog('toggle'); - } - -} // class RFramePainter - -export { RFramePainter }; diff --git a/js/modules/gpad/RPadPainter.mjs b/js/modules/gpad/RPadPainter.mjs deleted file mode 100644 index efc81bba478bb..0000000000000 --- a/js/modules/gpad/RPadPainter.mjs +++ /dev/null @@ -1,1890 +0,0 @@ -import { gStyle, settings, browser, constants, internals, addMethods, isPromise, getPromise, postponePromise, - isBatchMode, isObject, isFunc, isStr, clTFrame, nsREX, nsSVG, urlClassPrefix } from '../core.mjs'; -import { select as d3_select } from '../d3.mjs'; -import { ColorPalette, addColor, getRootColors, convertColor } from '../base/colors.mjs'; -import { RObjectPainter } from '../base/RObjectPainter.mjs'; -import { prSVG, getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, svgToImage } from '../base/BasePainter.mjs'; -import { selectActivePad, getActivePad, isPadPainter } from '../base/ObjectPainter.mjs'; -import { registerForResize, saveFile } from '../gui/utils.mjs'; -import { BrowserLayout, getHPainter } from '../gui/display.mjs'; -import { createMenu, closeMenu } from '../gui/menu.mjs'; -import { PadButtonsHandler, webSnapIds } from './TPadPainter.mjs'; - - -/** - * @summary Painter class for RPad - * - * @private - */ - -class RPadPainter extends RObjectPainter { - - #iscan; // is canvas flag - #pad_name; // name of the pad - #pad; // RPad object - #painters; // painters in the pad - #pad_scale; // scaling factor of the pad - #pad_x; // pad x coordinate - #pad_y; // pad y coordinate - #pad_width; // pad width - #pad_height; // pad height - #doing_draw; // drawing handles - #custom_palette; // custom palette - #frame_painter_ref; // frame painter - #main_painter_ref; // main painter on the pad - #num_primitives; // number of primitives - #auto_color_cnt; // counter for auto colors - #fixed_size; // fixed size flag - #has_canvas; // when top-level canvas exists - #fast_drawing; // fast drawing mode - #resize_tmout; // timeout handle for resize - #start_draw_tm; // time when start drawing primitives - - /** @summary constructor */ - constructor(dom, pad, opt, iscan, add_to_primitives) { - super(dom, pad, '', 'pad'); - this.#pad = pad; - this.#iscan = iscan; // indicate if working with canvas - this.#pad_name = ''; - if (!iscan && pad) { - if (pad.fObjectID) - this.#pad_name = 'pad' + pad.fObjectID; // use objectid as pad name - else - this.#pad_name = 'ppp' + internals.id_counter++; // artificial name - } - this.#painters = []; // complete list of all painters in the pad - this.#has_canvas = true; - this.forEachPainter = this.forEachPainterInPad; - - const d = this.selectDom(); - if (!d.empty() && d.property('_batch_mode')) - this.batch_mode = true; - - if (opt !== undefined) - this.decodeOptions(opt); - - if (add_to_primitives) { - if ((add_to_primitives !== 'webpad') && this.getCanvSvg().empty()) { - this.#has_canvas = false; - this.#pad_name = ''; - this.setTopPainter(); - } else - this.addToPadPrimitives(); // must be here due to pad painter - } - } - - /** @summary Returns pad name - * @protected */ - getPadName() { return this.#pad_name; } - - /** @summary Indicates that drawing runs in batch mode - * @private */ - isBatchMode() { - if (this.batch_mode !== undefined) - return this.batch_mode; - - if (isBatchMode()) - return true; - - return this.isTopPad() ? false : this.getCanvPainter()?.isBatchMode(); - } - - /** @summary Indicates that is not Root6 pad painter - * @private */ - isRoot6() { return false; } - - /** @summary Returns true if pad is editable */ - isEditable() { return true; } - - /** @summary Returns true if button */ - isButton() { return false; } - - /** @summary Returns true if it is canvas - * @param {boolean} [is_online = false] - if specified, checked if it is canvas with configured connection to server */ - isCanvas(is_online = false) { - if (!this.#iscan) - return false; - if (is_online === true) - return isFunc(this.getWebsocket) && this.getWebsocket(); - return isStr(is_online) ? this.#iscan === is_online : true; - } - - /** @summary Returns true if it is canvas or top pad without canvas */ - isTopPad() { return this.isCanvas() || !this.#has_canvas; } - - /** @summary returns pad painter - * @protected */ - getPadPainter() { return this.isTopPad() ? null : super.getPadPainter(); } - - /** @summary returns canvas painter - * @protected */ - getCanvPainter(try_select) { return this.isTopPad() ? this : super.getCanvPainter(try_select); } - - /** @summary Canvas main svg element - * @return {object} d3 selection with canvas svg - * @protected */ - getCanvSvg() { return this.selectDom().select('.root_canvas'); } - - /** @summary Pad svg element - * @return {object} d3 selection with pad svg - * @protected */ - getPadSvg() { - const c = this.getCanvSvg(); - if (!this.#pad_name || c.empty()) - return c; - - return c.select('.primitives_layer .__root_pad_' + this.#pad_name); - } - - /** @summary Method selects immediate layer under canvas/pad main element - * @param {string} name - layer name like 'primitives_layer', 'btns_layer', 'info_layer' - * @protected */ - getLayerSvg(name) { return this.getPadSvg().selectChild('.' + name); } - - /** @summary Returns svg element for the frame in current pad - * @protected */ - getFrameSvg() { - const layer = this.getLayerSvg('primitives_layer'); - if (layer.empty()) - return layer; - let node = layer.node().firstChild; - while (node) { - const elem = d3_select(node); - if (elem.classed('root_frame')) - return elem; - node = node.nextSibling; - } - return d3_select(null); - } - - /** @summary Returns main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - getMainPainter() { return this.#main_painter_ref || null; } - - /** @summary Assign main painter on the pad - * @private */ - setMainPainter(painter, force) { - if (!this.#main_painter_ref || force) - this.#main_painter_ref = painter; - } - - /** @summary cleanup pad and all primitives inside */ - cleanup() { - if (this.#doing_draw) - console.error('pad drawing is not completed when cleanup is called'); - - this.#painters.forEach(p => p.cleanup()); - - const svg_p = this.getPadSvg(); - if (!svg_p.empty()) { - svg_p.property('pad_painter', null); - if (!this.isCanvas()) - svg_p.remove(); - } - - this.#main_painter_ref = undefined; - this.#frame_painter_ref = undefined; - this.#pad_x = this.#pad_y = this.#pad_width = this.#pad_height = undefined; - this.#doing_draw = undefined; - delete this._dfltRFont; - - this.#painters = []; - this.#pad = undefined; - this.assignObject(null); - this.#pad_name = undefined; - this.#has_canvas = false; - - selectActivePad({ pp: this, active: false }); - - super.cleanup(); - } - - /** @summary Returns frame painter inside the pad - * @private */ - getFramePainter() { return this.#frame_painter_ref; } - - /** @summary Assign actual frame painter - * @private */ - setFramePainter(fp, on) { - if (on) - this.#frame_painter_ref = fp; - else if (this.#frame_painter_ref === fp) - this.#frame_painter_ref = undefined; - } - - /** @summary get pad width */ - getPadWidth() { return this.#pad_width || 0; } - - /** @summary get pad height */ - getPadHeight() { return this.#pad_height || 0; } - - /** @summary get pad height */ - getPadScale() { return this.#pad_scale || 1; } - - /** @summary return pad log state x or y are allowed */ - getPadLog(/* name */) { return false; } - - /** @summary get pad rect */ - getPadRect() { - return { - x: this.#pad_x || 0, - y: this.#pad_y || 0, - width: this.getPadWidth(), - height: this.getPadHeight() - }; - } - - /** @summary Returns frame coordinates - also when frame is not drawn */ - getFrameRect() { - const fp = this.getFramePainter(); - if (fp) - return fp.getFrameRect(); - - const w = this.getPadWidth(), - h = this.getPadHeight(), - rect = {}; - - rect.szx = Math.round(0.5 * w); - rect.szy = Math.round(0.5 * h); - rect.width = 2 * rect.szx; - rect.height = 2 * rect.szy; - rect.x = Math.round(w / 2 - rect.szx); - rect.y = Math.round(h / 2 - rect.szy); - rect.hint_delta_x = rect.szx; - rect.hint_delta_y = rect.szy; - rect.transform = makeTranslate(rect.x, rect.y) || ''; - return rect; - } - - /** @summary return RPad object */ - getRootPad(is_root6) { - return (is_root6 === undefined) || !is_root6 ? this.#pad : null; - } - - /** @summary Cleanup primitives from pad - selector lets define which painters to remove - * @private */ - cleanPrimitives(selector) { - // remove all primitives - if (selector === true) - selector = () => true; - - if (!isFunc(selector)) - return false; - - let is_any = false; - - for (let k = this.#painters.length - 1; k >= 0; --k) { - const subp = this.#painters[k]; - if (selector(subp)) { - subp.cleanup(); - this.#painters.splice(k, 1); - is_any = true; - } - } - - return is_any; - } - - /** @summary Divide pad on sub-pads */ - async divide(/* nx, ny, use_existing */) { - if (settings.Debug) - console.warn('RPadPainter.divide not implemented'); - return this; - } - - /** @summary Removes and cleanup specified primitive - * @desc also secondary primitives will be removed - * @return new index to continue loop or -111 if main painter removed - * @private */ - removePrimitive(arg, clean_only_secondary) { - let indx, prim = null; - if (Number.isInteger(arg)) { - indx = arg; - prim = this.#painters[indx]; - } else { - indx = this.#painters.indexOf(arg); - prim = arg; - } - if (indx < 0) - return indx; - - const arr = []; - let resindx = indx - 1; // object removed itself - arr.push(prim); - this.#painters.splice(indx, 1); - - let len0 = 0; - while (len0 < arr.length) { - for (let k = this.#painters.length - 1; k >= 0; --k) { - if (this.#painters[k].isSecondary(arr[len0])) { - arr.push(this.#painters[k]); - this.#painters.splice(k, 1); - if (k <= indx) - resindx--; - } - } - len0++; - } - - arr.forEach(painter => { - if ((painter !== prim) || !clean_only_secondary) - painter.cleanup(); - if (this.getMainPainter() === painter) { - this.setMainPainter(undefined, true); - resindx = -111; - } - }); - - return resindx; - } - - /** @summary try to find object by name in list of pad primitives - * @desc used to find title drawing - * @private */ - findInPrimitives(/* objname, objtype */) { - if (settings.Debug) - console.warn('findInPrimitives not implemented for RPad'); - return null; - } - - /** @summary Try to find painter for specified object - * @desc can be used to find painter for some special objects, registered as - * histogram functions - * @private */ - findPainterFor(selobj, selname, seltype) { - return this.#painters.find(p => { - const pobj = p.getObject(); - if (!pobj) - return false; - - if (selobj && (pobj === selobj)) - return true; - if (!selname && !seltype) - return false; - if (selname && (pobj.fName !== selname)) - return false; - if (seltype && (pobj._typename !== seltype)) - return false; - return true; - }); - } - - /** @summary Returns palette associated with pad. - * @desc Either from existing palette painter or just default palette */ - getHistPalette() { - const pp = this.findPainterFor(undefined, undefined, `${nsREX}RPaletteDrawable`); - - if (pp) - return pp.getHistPalette(); - - if (!this.fDfltPalette) { - this.fDfltPalette = { - _typename: `${nsREX}RPalette`, - fColors: [{ fOrdinal: 0, fColor: { fColor: 'rgb(53, 42, 135)' } }, - { fOrdinal: 0.125, fColor: { fColor: 'rgb(15, 92, 221)' } }, - { fOrdinal: 0.25, fColor: { fColor: 'rgb(20, 129, 214)' } }, - { fOrdinal: 0.375, fColor: { fColor: 'rgb(6, 164, 202)' } }, - { fOrdinal: 0.5, fColor: { fColor: 'rgb(46, 183, 164)' } }, - { fOrdinal: 0.625, fColor: { fColor: 'rgb(135, 191, 119)' } }, - { fOrdinal: 0.75, fColor: { fColor: 'rgb(209, 187, 89)' } }, - { fOrdinal: 0.875, fColor: { fColor: 'rgb(254, 200, 50)' } }, - { fOrdinal: 1, fColor: { fColor: 'rgb(249, 251, 14)' } }], - fInterpolate: true, - fNormalized: true - }; - addMethods(this.fDfltPalette, `${nsREX}RPalette`); - } - - return this.fDfltPalette; - } - - /** @summary Returns custom palette - * @private */ - getCustomPalette(no_recursion) { - return this.#custom_palette || (no_recursion ? null : this.getCanvPainter()?.getCustomPalette(true)); - } - - /** @summary Returns number of painters - * @protected */ - getNumPainters() { return this.#painters.length; } - - /** @summary Add painter to pad list of painters - * @protected */ - addToPrimitives(painter) { - if (this.#painters.indexOf(painter) < 0) - this.#painters.push(painter); - return this; - } - - /** @summary Call function for each painter in pad - * @param {Function} userfunc - function to call - * @param {string} kind - 'all' for all objects (default), 'pads' only pads and sub-pads, 'objects' only for object in current pad - * @private */ - forEachPainterInPad(userfunc, kind) { - if (!kind) - kind = 'all'; - if (kind !== 'objects') - userfunc(this); - for (let k = 0; k < this.#painters.length; ++k) { - const sub = this.#painters[k]; - if (isFunc(sub.forEachPainterInPad)) { - if (kind !== 'objects') - sub.forEachPainterInPad(userfunc, kind); - } else if (kind !== 'pads') - userfunc(sub); - } - } - - /** @summary register for pad events receiver - * @desc in pad painter, while pad may be drawn without canvas - * @private */ - registerForPadEvents(receiver) { - this.pad_events_receiver = receiver; - } - - /** @summary Generate pad events, normally handled by GED - * @desc in pad painter, while pad may be drawn without canvas - * @private */ - producePadEvent(what, padpainter, painter, position) { - if ((what === 'select') && isFunc(this.selectActivePad)) - this.selectActivePad(padpainter, painter, position); - - if (isFunc(this.pad_events_receiver)) - this.pad_events_receiver({ what, padpainter, painter, position }); - } - - /** @summary method redirect call to pad events receiver */ - selectObjectPainter(painter, pos) { - const canp = this.isTopPad() ? this : this.getCanvPainter(); - - if (painter === undefined) - painter = this; - - if (pos && !this.isTopPad()) - pos = getAbsPosInCanvas(this.getPadSvg(), pos); - - selectActivePad({ pp: this, active: true }); - - canp.producePadEvent('select', this, painter, pos); - } - - /** @summary Set fast drawing property depending on the size - * @private */ - setFastDrawing(w, h) { - const was_fast = this.#fast_drawing; - this.#fast_drawing = !this.hasSnapId() && settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); - if (was_fast !== this.#fast_drawing) - this.showPadButtons(); - } - - /** @summary Return fast drawing flag - * @private */ - isFastDrawing() { return this.#fast_drawing; } - - /** @summary Returns true if canvas configured with grayscale - * @private */ - isGrayscale() { return false; } - - /** @summary Set grayscale mode for the canvas - * @private */ - setGrayscale(/* flag */) { - console.error('grayscale mode not implemented for RCanvas'); - } - - /** @summary Returns true if default pad range is configured - * @private */ - isDefaultPadRange() { - return true; - } - - /** @summary Create SVG element for the canvas */ - createCanvasSvg(check_resize, new_size) { - const lmt = 5; - let factor, svg, rect, btns, frect; - - if (check_resize > 0) { - if (this.#fixed_size) - return check_resize > 1; // flag used to force re-drawing of all sub-pads - - svg = this.getCanvSvg(); - if (svg.empty()) - return false; - - factor = svg.property('height_factor'); - - rect = this.testMainResize(check_resize, null, factor); - - if (!rect.changed && (check_resize === 1)) - return false; - - if (!this.isBatchMode()) - btns = this.getLayerSvg('btns_layer'); - - frect = svg.selectChild('.canvas_fillrect'); - } else { - const render_to = this.selectDom(); - - if (render_to.style('position') === 'static') - render_to.style('position', 'relative'); - - svg = render_to.append('svg') - .attr('class', 'jsroot root_canvas') - .property('pad_painter', this) // this is custom property - .property('redraw_by_resize', false); // could be enabled to force redraw by each resize - - this.setTopPainter(); // assign canvas as top painter of that element - - if (!this.isBatchMode() && !this.online_canvas) - svg.append('svg:title').text('ROOT canvas'); - - if (!this.isBatchMode()) - svg.style('user-select', settings.UserSelect || null); - - frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); - if (!this.isBatchMode()) { - frect.style('pointer-events', 'visibleFill') - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter(this, null)) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } - - svg.append('svg:g').attr('class', 'primitives_layer'); - svg.append('svg:g').attr('class', 'info_layer'); - if (!this.isBatchMode()) { - btns = svg.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide === 'left') - .property('vertical', settings.ToolBarVert); - } - - factor = 0.66; - if (this.#pad?.fWinSize[0] && this.#pad.fWinSize[1]) { - factor = this.#pad.fWinSize[1] / this.#pad.fWinSize[0]; - if ((factor < 0.1) || (factor > 10)) - factor = 0.66; - } - - if (this.#fixed_size) { - render_to.style('overflow', 'auto'); - rect = { width: this.#pad.fWinSize[0], height: this.#pad.fWinSize[1] }; - if (!rect.width || !rect.height) - rect = getElementRect(render_to); - } else - rect = this.testMainResize(2, new_size, factor); - } - - this.createAttFill({ pattern: 1001, color: 0 }); - - if ((rect.width <= lmt) || (rect.height <= lmt)) { - if (!this.hasSnapId()) { - svg.style('display', 'none'); - console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); - } - if (this.#pad_width && this.#pad_height) { - // use last valid dimensions - rect.width = this.#pad_width; - rect.height = this.#pad_height; - } else { - // just to complete drawing. - rect.width = 800; - rect.height = 600; - } - } else - svg.style('display', null); - - if (this.#fixed_size) { - svg.attr('x', 0) - .attr('y', 0) - .attr('width', rect.width) - .attr('height', rect.height) - .style('position', 'absolute'); - } else { - svg.attr('x', 0) - .attr('y', 0) - .style('width', '100%') - .style('height', '100%') - .style('position', 'absolute') - .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - } - - svg.style('filter', settings.DarkMode ? 'invert(100%)' : null); - - svg.attr('viewBox', `0 0 ${rect.width} ${rect.height}`) - .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio - .property('height_factor', factor) - .property('draw_x', 0) - .property('draw_y', 0) - .property('draw_width', rect.width) - .property('draw_height', rect.height); - - this.#pad_x = 0; - this.#pad_y = 0; - this.#pad_width = rect.width; - this.#pad_height = rect.height; - - frect.attr('d', `M0,0H${rect.width}V${rect.height}H0Z`) - .call(this.fillatt.func); - - this.setFastDrawing(rect.width, rect.height); - - if (isFunc(this.alignButtons) && btns) - this.alignButtons(btns, rect.width, rect.height); - - return true; - } - - /** @summary Draw item name on canvas, dummy for RPad - * @private */ - drawItemNameOnCanvas() { - } - - /** @summary Enlarge pad draw element when possible */ - enlargePad(evnt, is_dblclick, is_escape) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - // ignore double click on online canvas itself for enlarge - if (is_dblclick && this.isCanvas(true) && (this.enlargeMain('state') === 'off')) - return; - - const svg_can = this.getCanvSvg(), - pad_enlarged = svg_can.property('pad_enlarged'); - - if (this.isTopPad() || (!pad_enlarged && !this.hasObjectsToDraw() && !this.#painters)) { - if (this.#fixed_size) - return; // canvas cannot be enlarged in such mode - if (!this.enlargeMain(is_escape ? false : 'toggle')) - return; - if (this.enlargeMain('state') === 'off') - svg_can.property('pad_enlarged', null); - else - selectActivePad({ pp: this, active: true }); - } else if (!pad_enlarged && !is_escape) { - this.enlargeMain(true, true); - svg_can.property('pad_enlarged', this.#pad); - selectActivePad({ pp: this, active: true }); - } else if (pad_enlarged === this.#pad) { - this.enlargeMain(false); - svg_can.property('pad_enlarged', null); - } else if (!is_escape && is_dblclick) - console.error('missmatch with pad double click events'); - - return this.checkResize(true); - } - - /** @summary Create SVG element for the pad - * @return true when pad is displayed and all its items should be redrawn */ - createPadSvg(only_resize) { - if (this.isTopPad()) { - this.createCanvasSvg(only_resize ? 2 : 0); - return true; - } - - const svg_parent = this.getPadPainter()?.getPadSvg(), - svg_can = this.getCanvSvg(), - width = svg_parent.property('draw_width'), - height = svg_parent.property('draw_height'), - pad_enlarged = svg_can.property('pad_enlarged'); - let pad_visible = true, - w = width, h = height, x = 0, y = 0, - svg_pad, svg_rect, btns = null; - - if (this.#pad?.fPos && this.#pad?.fSize) { - x = Math.round(width * this.#pad.fPos.fHoriz.fArr[0]); - y = Math.round(height * this.#pad.fPos.fVert.fArr[0]); - w = Math.round(width * this.#pad.fSize.fHoriz.fArr[0]); - h = Math.round(height * this.#pad.fSize.fVert.fArr[0]); - } - - if (pad_enlarged) { - pad_visible = false; - if (pad_enlarged === this.#pad) - pad_visible = true; - else { - this.forEachPainterInPad(pp => { - if (pp.getObject() === pad_enlarged) - pad_visible = true; - }, 'pads'); - } - - if (pad_visible) { - w = width; - h = height; - x = y = 0; - } - } - - if (only_resize) { - svg_pad = this.getPadSvg(); - svg_rect = svg_pad.selectChild('.root_pad_border'); - if (!this.isBatchMode()) - btns = this.getLayerSvg('btns_layer'); - this.addPadInteractive(true); - } else { - svg_pad = svg_parent.selectChild('.primitives_layer') - .append('svg:svg') // here was g before, svg used to blend all drawings outside - .classed('__root_pad_' + this.#pad_name, true) - .attr('pad', this.#pad_name) // set extra attribute to mark pad name - .property('pad_painter', this); // this is custom property - - if (!this.isBatchMode()) - svg_pad.append('svg:title').text('ROOT subpad'); - - svg_rect = svg_pad.append('svg:path').attr('class', 'root_pad_border'); - - svg_pad.append('svg:g').attr('class', 'primitives_layer'); - if (!this.isBatchMode()) { - btns = svg_pad.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide !== 'left') - .property('vertical', settings.ToolBarVert); - } - - if (settings.ContextMenu) - svg_rect.on('contextmenu', evnt => this.padContextMenu(evnt)); - - if (!this.isBatchMode()) { - svg_rect.style('pointer-events', 'visibleFill') // get events also for not visible rect - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter(this, null)) - .on('mouseenter', () => this.showObjectStatus()); - } - } - - this.createAttFill({ attr: this.#pad }); - - this.createAttLine({ attr: this.#pad, color0: this.#pad.fBorderMode === 0 ? 'none' : '' }); - - svg_pad.style('display', pad_visible ? null : 'none') - .attr('viewBox', `0 0 ${w} ${h}`) // due to svg - .attr('preserveAspectRatio', 'none') // due to svg, we do not preserve relative ratio - .attr('x', x) // due to svg - .attr('y', y) // due to svg - .attr('width', w) // due to svg - .attr('height', h) // due to svg - .property('draw_x', x) // this is to make similar with canvas - .property('draw_y', y) - .property('draw_width', w) - .property('draw_height', h); - - this.#pad_x = x; - this.#pad_y = y; - this.#pad_width = w; - this.#pad_height = h; - - svg_rect.attr('d', `M0,0H${w}V${h}H0Z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - - this.setFastDrawing(w, h); - - // special case of 3D canvas overlay - if (svg_pad.property('can3d') === constants.Embed3D.Overlay) { - this.selectDom().select('.draw3d_' + this.#pad_name) - .style('display', pad_visible ? '' : 'none'); - } - - if (this.alignButtons && btns) - this.alignButtons(btns, w, h); - - return pad_visible; - } - - /** @summary Add pad interactive features like dragging and resize - * @private */ - addPadInteractive(/* cleanup = false */) { - if (isFunc(this.$userInteractive)) { - this.$userInteractive(); - delete this.$userInteractive; - } - // if (this.isBatchMode()) - // return; - } - - /** @summary returns true if any objects beside sub-pads exists in the pad */ - hasObjectsToDraw() { - return this.#pad?.fPrimitives?.find(obj => obj._typename !== `${nsREX}RPadDisplayItem`); - } - - /** @summary sync drawing/redrawing/resize of the pad - * @param {string} kind - kind of draw operation, if true - always queued - * @return {Promise} when pad is ready for draw operation or false if operation already queued - * @private */ - syncDraw(kind) { - const entry = { kind: kind || 'redraw' }; - if (this.#doing_draw === undefined) { - this.#doing_draw = [entry]; - return Promise.resolve(true); - } - // if queued operation registered, ignore next calls, indx === 0 is running operation - if ((entry.kind !== true) && (this.#doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) - return false; - this.#doing_draw.push(entry); - return new Promise(resolveFunc => { - entry.func = resolveFunc; - }); - } - - /** @summary confirms that drawing is completed, may trigger next drawing immediately - * @private */ - confirmDraw() { - if (this.#doing_draw === undefined) - return console.warn('failure, should not happen'); - this.#doing_draw.shift(); - if (!this.#doing_draw.length) - this.#doing_draw = undefined; - else { - const entry = this.#doing_draw[0]; - if (entry.func) { - entry.func(); - delete entry.func; - } - } - } - - /** @summary Draw single primitive */ - async drawObject(/* dom, obj, opt */) { - console.log('Not possible to draw object without loading of draw.mjs'); - return null; - } - - /** @summary Draw pad primitives - * @private */ - async drawPrimitives(indx) { - if (indx === undefined) { - if (this.isCanvas()) - this.#start_draw_tm = new Date().getTime(); - - // set number of primitives - this.#num_primitives = this.#pad?.fPrimitives?.length ?? 0; - - return this.syncDraw(true).then(() => this.drawPrimitives(0)); - } - - if (!this.#pad || (indx >= this.#num_primitives)) { - this.confirmDraw(); - - if (this.#start_draw_tm) { - const spenttm = new Date().getTime() - this.#start_draw_tm; - if (spenttm > 3000) - console.log(`Canvas drawing took ${(spenttm * 1e-3).toFixed(2)}s`); - this.#start_draw_tm = undefined; - } - - return; - } - - // handle used to invoke callback only when necessary - return this.drawObject(this, this.#pad.fPrimitives[indx], '').then(op => { - // mark painter as belonging to primitives - if (isObject(op)) - op._primitive = true; - - return this.drawPrimitives(indx + 1); - }); - } - - /** @summary Provide autocolor - * @private */ - getAutoColor() { - const pal = this.getHistPalette(), - cnt = this.#auto_color_cnt++, - num = Math.max(this.#num_primitives - 1, 2); - return pal?.getColorOrdinal((cnt % num) / num) ?? 'blue'; - } - - /** @summary Process tooltip event in the pad - * @private */ - processPadTooltipEvent(pnt) { - const painters = [], hints = []; - - // first count - how many processors are there - this.#painters?.forEach(obj => { - if (isFunc(obj.processTooltipEvent)) - painters.push(obj); - }); - - if (pnt) - pnt.nproc = painters.length; - - painters.forEach(obj => { - const hint = obj.processTooltipEvent(pnt) || { user_info: null }; - hints.push(hint); - if (pnt?.painters) - hint.painter = obj; - }); - - return hints; - } - - /** @summary Changes canvas dark mode - * @private */ - changeDarkMode(mode) { - this.getCanvSvg().style('filter', (mode ?? settings.DarkMode) ? 'invert(100%)' : null); - } - - /** @summary Fill pad context menu - * @private */ - fillContextMenu(menu) { - const clname = this.isCanvas() ? 'RCanvas' : 'RPad'; - - menu.header(clname, `${urlClassPrefix}ROOT_1_1Experimental_1_1${clname}.html`); - - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - - if (!this.isCanvas(true)) { - // if not online canvas - menu.addAttributesMenu(this); - if (this.isCanvas()) { - menu.addSettingsMenu(false, false, arg => { - if (arg === 'dark') - this.changeDarkMode(); - }); - } - } - - menu.separator(); - - if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) - menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); - - if (isFunc(this.hasEventStatus) && isFunc(this.activateStatusBar) && isFunc(this.canStatusBar)) { - if (this.canStatusBar()) - menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); - } - - if (this.enlargeMain() || (!this.isTopPad() && this.hasObjectsToDraw())) - menu.addchk((this.enlargeMain('state') === 'on'), 'Enlarge ' + (this.isCanvas() ? 'canvas' : 'pad'), () => this.enlargePad()); - - const fname = this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad'); - menu.sub('Save as'); - ['svg', 'png', 'jpeg', 'pdf', 'webp'].forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.isCanvas(), `${fname}.${fmt}`))); - menu.endsub(); - - return true; - } - - /** @summary Show pad context menu - * @private */ - padContextMenu(evnt) { - if (evnt.stopPropagation) { - // this is normal event processing and not emulated jsroot event - - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - - this.getFramePainter()?.setLastEventPos(); - } - - createMenu(evnt, this).then(menu => { - this.fillContextMenu(menu); - return this.fillObjectExecMenu(menu); - }).then(menu => menu.show()); - } - - /** @summary Redraw legend object - * @desc Used when object attributes are changed to ensure that legend is up to date - * @private */ - async redrawLegend() {} - - /** @summary Deliver mouse move or click event to the web canvas - * @private */ - deliverWebCanvasEvent() {} - - /** @summary Redraw pad means redraw ourself - * @return {Promise} when redrawing ready */ - async redrawPad(reason) { - const sync_promise = this.syncDraw(reason); - if (sync_promise === false) { - console.log('Prevent RPad redrawing'); - return false; - } - - let showsubitems = true; - const redrawNext = indx => { - while (indx < this.#painters.length) { - const sub = this.#painters[indx++]; - let res = 0; - if (showsubitems || isPadPainter(sub)) - res = sub.redraw(reason); - - if (isPromise(res)) - return res.then(() => redrawNext(indx)); - } - return true; - }; - - return sync_promise.then(() => { - if (this.isCanvas()) - this.createCanvasSvg(2); - else - showsubitems = this.createPadSvg(true); - - return redrawNext(0); - }).then(() => { - this.addPadInteractive(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - this.confirmDraw(); - return true; - }); - } - - /** @summary redraw pad */ - redraw(reason) { - return this.redrawPad(reason); - } - - - /** @summary Checks if pad should be redrawn by resize - * @private */ - needRedrawByResize() { - const elem = this.getPadSvg(); - if (!elem.empty() && elem.property('can3d') === constants.Embed3D.Overlay) - return true; - - for (let i = 0; i < this.#painters.length; ++i) { - if (isFunc(this.#painters[i].needRedrawByResize)) { - if (this.#painters[i].needRedrawByResize()) - return true; - } - } - - return false; - } - - /** @summary Check resize of canvas */ - checkCanvasResize(size, force) { - if (this._ignore_resize || !this.isTopPad()) - return false; - - const sync_promise = this.syncDraw('canvas_resize'); - if (sync_promise === false) - return false; - - if ((size === true) || (size === false)) { - force = size; - size = null; - } - - if (isObject(size) && size.force) - force = true; - - if (!force) - force = this.needRedrawByResize(); - - let changed = false; - const redrawNext = indx => { - if (!changed || (indx >= this.#painters.length)) { - this.confirmDraw(); - return changed; - } - - return getPromise(this.#painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx + 1)); - }; - - - return sync_promise.then(() => { - changed = this.createCanvasSvg(force ? 2 : 1, size); - - if (changed && this.isCanvas() && this.#pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { - if (this.#resize_tmout) - clearTimeout(this.#resize_tmout); - this.#resize_tmout = setTimeout(() => { - this.#resize_tmout = undefined; - if (!this.#pad?.fWinSize) - return; - const cw = this.getPadWidth(), ch = this.getPadHeight(); - if ((cw > 0) && (ch > 0) && ((this.#pad.fWinSize[0] !== cw) || (this.#pad.fWinSize[1] !== ch))) { - this.#pad.fWinSize[0] = cw; - this.#pad.fWinSize[1] = ch; - this.sendWebsocket(`RESIZED:[${cw},${ch}]`); - } - }, 1000); // long enough delay to prevent multiple occurrence - } - - // if canvas changed, redraw all its subitems. - // If redrawing was forced for canvas, same applied for sub-elements - return redrawNext(0); - }); - } - - /** @summary update RPad object - * @private */ - updateObject(obj) { - if (!obj) - return false; - - this.#pad.fStyle = obj.fStyle; - this.#pad.fAttr = obj.fAttr; - - if (this.isCanvas()) { - this.#pad.fTitle = obj.fTitle; - this.#pad.fWinSize = obj.fWinSize; - } else { - this.#pad.fPos = obj.fPos; - this.#pad.fSize = obj.fSize; - } - - return true; - } - - /** @summary Add object painter to list of primitives - * @private */ - addObjectPainter(objpainter, lst, indx) { - if (objpainter && lst && lst[indx] && !objpainter.hasSnapId()) { - // keep snap id in painter, will be used for the - if (this.#painters.indexOf(objpainter) < 0) - this.#painters.push(objpainter); - objpainter.assignSnapId(lst[indx].fObjectID); - if (!objpainter.rstyle) - objpainter.rstyle = lst[indx].fStyle || this.rstyle; - } - } - - /** @summary Extract properties from TObjectDisplayItem */ - extractTObjectProp(snap) { - if (snap.fColIndex && snap.fColValue) { - const colors = this.getColors() || getRootColors(); - for (let k = 0; k < snap.fColIndex.length; ++k) - colors[snap.fColIndex[k]] = convertColor(snap.fColValue[k]); - } - - // painter used only for evaluation of attributes - const pattr = new RObjectPainter(), obj = snap.fObject; - pattr.assignObject(snap); - pattr.csstype = snap.fCssType; - pattr.rstyle = snap.fStyle; - - snap.fOption = pattr.v7EvalAttr('options', ''); - - const extract_color = (member_name, attr_name) => { - const col = pattr.v7EvalColor(attr_name, ''); - if (col) - obj[member_name] = addColor(col, this.getColors()); - }; - - // handle TAttLine - if ((obj.fLineColor !== undefined) && (obj.fLineWidth !== undefined) && (obj.fLineStyle !== undefined)) { - extract_color('fLineColor', 'line_color'); - obj.fLineWidth = pattr.v7EvalAttr('line_width', obj.fLineWidth); - obj.fLineStyle = pattr.v7EvalAttr('line_style', obj.fLineStyle); - } - - // handle TAttFill - if ((obj.fFillColor !== undefined) && (obj.fFillStyle !== undefined)) { - extract_color('fFillColor', 'fill_color'); - obj.fFillStyle = pattr.v7EvalAttr('fill_style', obj.fFillStyle); - } - - // handle TAttMarker - if ((obj.fMarkerColor !== undefined) && (obj.fMarkerStyle !== undefined) && (obj.fMarkerSize !== undefined)) { - extract_color('fMarkerColor', 'marker_color'); - obj.fMarkerStyle = pattr.v7EvalAttr('marker_style', obj.fMarkerStyle); - obj.fMarkerSize = pattr.v7EvalAttr('marker_size', obj.fMarkerSize); - } - - // handle TAttText - if ((obj.fTextColor !== undefined) && (obj.fTextAlign !== undefined) && (obj.fTextAngle !== undefined) && (obj.fTextSize !== undefined)) { - extract_color('fTextColor', 'text_color'); - obj.fTextAlign = pattr.v7EvalAttr('text_align', obj.fTextAlign); - obj.fTextAngle = pattr.v7EvalAttr('text_angle', obj.fTextAngle); - obj.fTextSize = pattr.v7EvalAttr('text_size', obj.fTextSize); - // TODO: v7 font handling differs much from v6, ignore for the moment - } - } - - /** @summary Function called when drawing next snapshot from the list - * @return {Promise} with pad painter when ready - * @private */ - async drawNextSnap(lst, pindx, indx) { - if (indx === undefined) { - indx = -1; - // flag used to prevent immediate pad redraw during first draw - this.#num_primitives = lst?.length ?? 0; - this.#auto_color_cnt = 0; - } - - delete this.next_rstyle; - - ++indx; // change to the next snap - - if (!lst || indx >= lst.length) { - this.#auto_color_cnt = undefined; - return this; - } - - const snap = lst[indx], is_subpad = snap._typename === `${nsREX}RPadDisplayItem`; - - // empty object, no need to do something, take next - if (snap.fDummy) - return this.drawNextSnap(lst, pindx + 1, indx); - - if (snap._typename === `${nsREX}TObjectDisplayItem`) { - // identifier used in TObjectDrawable - - if (snap.fKind === webSnapIds.kStyle) { - Object.assign(gStyle, snap.fObject); - return this.drawNextSnap(lst, pindx, indx); - } - - if (snap.fKind === webSnapIds.kColors) { - const colors = [], arr = snap.fObject.arr; - for (let n = 0; n < arr.length; ++n) { - const name = arr[n].fString, p = name.indexOf('='); - if (p > 0) - colors[parseInt(name.slice(0, p))] = convertColor(name.slice(p + 1)); - } - - this.setColors(colors); - // set global list of colors - // adoptRootColors(ListOfColors); - return this.drawNextSnap(lst, pindx, indx); - } - - if (snap.fKind === webSnapIds.kPalette) { - const arr = snap.fObject.arr, palette = []; - for (let n = 0; n < arr.length; ++n) - palette[n] = arr[n].fString; - this.#custom_palette = new ColorPalette(palette); - return this.drawNextSnap(lst, pindx, indx); - } - - if (snap.fKind === webSnapIds.kFont) - return this.drawNextSnap(lst, pindx, indx); - - if (!this.getFramePainter()) { - // draw dummy frame which is not provided by RCanvas - return this.drawObject(this, { _typename: clTFrame, $dummy: true }, '') - .then(() => this.drawNextSnap(lst, pindx, indx - 1)); - } - - this.extractTObjectProp(snap); - } - - // try to locate existing object painter, only allowed when redrawing pad snap - let objpainter, promise; - - while ((pindx !== undefined) && (pindx < this.#painters.length)) { - const subp = this.#painters[pindx++]; - - if (subp.getSnapId() === snap.fObjectID) { - objpainter = subp; - break; - } else if (subp.getSnapId() && !subp.isSecondary() && !is_subpad) { - console.warn(`Mismatch in snapid between painter ${subp.getSnapId()} secondary: ${subp.isSecondary()} type: ${subp.getClassName()} and primitive ${snap.fObjectID} kind ${snap.fKind} type ${snap.fDrawable?._typename}`); - break; - } - } - - if (objpainter) { - if (is_subpad) - promise = objpainter.redrawPadSnap(snap); - else if (objpainter.updateObject(snap.fDrawable || snap.fObject || snap, snap.fOption || '', true)) - promise = objpainter.redraw(); - } else if (is_subpad) { - const padpainter = new RPadPainter(this, snap, '', false, 'webpad'); - padpainter.assignSnapId(snap.fObjectID); - padpainter.rstyle = snap.fStyle; - - padpainter.createPadSvg(); - - if (snap.fPrimitives?.length) - padpainter.addPadButtons(); - - pindx++; // new painter will be add - promise = padpainter.drawNextSnap(snap.fPrimitives).then(() => padpainter.addPadInteractive()); - } else { - // will be used in addToPadPrimitives to assign style to sub-painters - this.next_rstyle = snap.fStyle || this.rstyle; - pindx++; // new painter will be add - - // TODO - fDrawable is v7, fObject from v6, maybe use same data member? - promise = this.drawObject(this, snap.fDrawable || snap.fObject || snap, snap.fOption || '') - .then(objp => this.addObjectPainter(objp, lst, indx)); - } - - return getPromise(promise).then(() => this.drawNextSnap(lst, pindx, indx)); // call next - } - - /** @summary Search painter with specified snapid, also sub-pads are checked - * @private */ - findSnap(snapid, onlyid) { - function check(checkid) { - if (!checkid || !isStr(checkid)) - return false; - if (checkid === snapid) - return true; - return onlyid && (checkid.length > snapid.length) && - (checkid.indexOf(snapid) === (checkid.length - snapid.length)); - } - - if (check(this.getSnapId())) - return this; - - if (!this.#painters) - return null; - - for (let k = 0; k < this.#painters.length; ++k) { - let sub = this.#painters[k]; - - if (!onlyid && isFunc(sub.findSnap)) - sub = sub.findSnap(snapid); - else if (!check(sub.getSnapId())) - sub = null; - - if (sub) - return sub; - } - - return null; - } - - /** @summary Redraw pad snap - * @desc Online version of drawing pad primitives - * @return {Promise} with pad painter */ - async redrawPadSnap(snap) { - // for the pad/canvas display item contains list of primitives plus pad attributes - - if (!snap || !snap.fPrimitives) - return this; - - if (this.isCanvas(true) && snap.fTitle && !this.embed_canvas && (typeof document !== 'undefined')) - document.title = snap.fTitle; - - if (!this.hasSnapId()) { - // first time getting snap, create all gui elements first - - this.assignSnapId(snap.fObjectID); - - this.assignObject(snap); - this.#pad = snap; - - if (this.isBatchMode() && this.isCanvas()) - this.#fixed_size = true; - - const mainid = this.selectDom().attr('id'); - - if (!this.isBatchMode() && this.online_canvas && !this.use_openui && !this.brlayout && mainid && isStr(mainid) && !getHPainter()) { - this.brlayout = new BrowserLayout(mainid, null, this); - this.brlayout.create(mainid, true); - this.setDom(this.brlayout.drawing_divid()); // need to create canvas - registerForResize(this.brlayout); - } - - this.createCanvasSvg(0); - this.addPadButtons(true); - - return this.drawNextSnap(snap.fPrimitives).then(() => { - if (isFunc(this.onCanvasUpdated)) - this.onCanvasUpdated(this); - return this; - }); - } - - // update only pad/canvas attributes - this.updateObject(snap); - - // apply all changes in the object (pad or canvas) - if (this.isCanvas()) - this.createCanvasSvg(2); - else - this.createPadSvg(true); - - let missmatch = false, i = 0, k = 0; - - // match painters with new list of primitives - while (k < this.#painters.length) { - const sub = this.#painters[k]; - - // skip check secondary painters or painters without snapid - // also frame painter will be excluded here - if (!sub.hasSnapId() || sub.isSecondary()) { - k++; - continue; // look only for painters with snapid - } - - if (i >= snap.fPrimitives.length) - break; - - - const prim = snap.fPrimitives[i]; - - if (prim.fObjectID === sub.getSnapId()) { - i++; - k++; - } else if (prim.fDummy || !prim.fObjectID || ((prim._typename === `${nsREX}TObjectDisplayItem`) && ((prim.fKind === webSnapIds.kStyle) || (prim.fKind === webSnapIds.kColors) || (prim.fKind === webSnapIds.kPalette) || (prim.fKind === webSnapIds.kFont)))) { - // ignore primitives without snapid or which are not produce drawings - i++; - } else { - missmatch = true; - break; - } - } - - let cnt = 1000; - // remove painters without primitives, limit number of checks - while (!missmatch && (k < this.#painters.length) && (--cnt >= 0)) { - if (this.removePrimitive(k) === -111) - missmatch = true; - } - if (cnt < 0) - missmatch = true; - - if (missmatch) { - const old_painters = this.#painters; - this.#painters = []; - old_painters.forEach(objp => objp.cleanup()); - this.setMainPainter(undefined, true); - if (isFunc(this.removePadButtons)) - this.removePadButtons(); - this.addPadButtons(true); - } - - return this.drawNextSnap(snap.fPrimitives, missmatch ? undefined : 0).then(() => { - this.addPadInteractive(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - if (isFunc(this.onCanvasUpdated)) - this.onCanvasUpdated(this); - return this; - }); - } - - /** @summary Create image for the pad - * @desc Used with web-based canvas to create images for server side - * @return {Promise} with image data, coded with btoa() function - * @private */ - async createImage(format) { - if ((format === 'png') || (format === 'jpeg') || (format === 'svg') || (format === 'webp') || (format === 'pdf')) { - return this.produceImage(true, format).then(res => { - if (!res || (format === 'svg')) - return res; - const separ = res.indexOf('base64,'); - return (separ > 0) ? res.slice(separ + 7) : ''; - }); - } - return ''; - } - - /** @summary Show context menu for specified item - * @private */ - itemContextMenu(name) { - const rrr = this.getPadSvg().node().getBoundingClientRect(), - evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; - - // use timeout to avoid conflict with mouse click and automatic menu close - if (name === 'pad') - return postponePromise(() => this.padContextMenu(evnt), 50); - - let selp = null, selkind; - - switch (name) { - case 'xaxis': - case 'yaxis': - case 'zaxis': - selp = this.getMainPainter(); - selkind = name[0]; - break; - case 'frame': - selp = this.getFramePainter(); - break; - default: { - const indx = parseInt(name); - if (Number.isInteger(indx)) - selp = this.#painters[indx]; - } - } - - if (!isFunc(selp?.fillContextMenu)) - return; - - return createMenu(evnt, selp).then(menu => { - const offline_menu = selp.fillContextMenu(menu, selkind); - if (offline_menu || selp.getSnapId()) - selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); - }); - } - - /** @summary Save pad in specified format - * @desc Used from context menu */ - saveAs(kind, full_canvas, filename) { - if (!filename) - filename = (this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad')) + '.' + kind; - - this.produceImage(full_canvas, kind).then(imgdata => { - if (!imgdata) - return console.error(`Fail to produce image ${filename}`); - - if ((browser.qt6 || browser.cef3) && this.getSnapId()) { - console.warn(`sending file ${filename} to server`); - let res = imgdata; - if (kind !== 'svg') { - const separ = res.indexOf('base64,'); - res = (separ > 0) ? res.slice(separ + 7) : ''; - } - if (res) - this.getCanvPainter()?.sendWebsocket(`SAVE:${filename}:${res}`); - } else - saveFile(filename, (kind !== 'svg') ? imgdata : prSVG + encodeURIComponent(imgdata)); - }); - } - - /** @summary Search active pad - * @return {object} pad painter for active pad */ - findActivePad() { return null; } - - /** @summary Produce image for the pad - * @return {Promise} with created image */ - async produceImage(full_canvas, file_format, args) { - const use_frame = (full_canvas === 'frame'), - elem = use_frame ? this.getFrameSvg() : (full_canvas ? this.getCanvSvg() : this.getPadSvg()), - painter = (full_canvas && !use_frame) ? this.getCanvPainter() : this, - items = []; // keep list of replaced elements, which should be moved back at the end - - if (elem.empty()) - return ''; - - if (use_frame || !full_canvas) { - const defs = this.getCanvSvg().selectChild('.canvas_defs'); - if (!defs.empty()) { - items.push({ prnt: this.getCanvSvg(), defs }); - elem.node().insertBefore(defs.node(), elem.node().firstChild); - } - } - - if (!use_frame) { - // do not make transformations for the frame - painter.forEachPainterInPad(pp => { - const item = { prnt: pp.getPadSvg() }; - items.push(item); - - // remove buttons from each sub-pad - const btns = pp.getLayerSvg('btns_layer'); - item.btns_node = btns.node(); - if (item.btns_node) { - item.btns_prnt = item.btns_node.parentNode; - item.btns_next = item.btns_node.nextSibling; - btns.remove(); - } - - const fp = pp.getFramePainter(); - if (!isFunc(fp?.access3dKind)) - return; - - const can3d = fp.access3dKind(); - if ((can3d !== constants.Embed3D.Overlay) && (can3d !== constants.Embed3D.Embed)) - return; - - const main = isFunc(fp.getRenderer) ? fp : fp.getMainPainter(), - canvas = isFunc(main.getRenderer) ? main.getRenderer()?.domElement : null; - if (!isFunc(main?.render3D) || !isObject(canvas)) - return; - - const sz2 = fp.getSizeFor3d(constants.Embed3D.Embed); // get size and position of DOM element as it will be embed - main.render3D(0); // WebGL clears buffers, therefore we should render scene and convert immediately - const dataUrl = canvas.toDataURL('image/png'); - - // remove 3D drawings - if (can3d === constants.Embed3D.Embed) { - item.foreign = item.prnt.select('.' + sz2.clname); - item.foreign.remove(); - } - - const svg_frame = fp.getFrameSvg(); - item.frame_node = svg_frame.node(); - if (item.frame_node) { - item.frame_next = item.frame_node.nextSibling; - svg_frame.remove(); - } - - // add svg image - item.img = item.prnt.insert('image', '.primitives_layer') // create image object - .attr('x', sz2.x) - .attr('y', sz2.y) - .attr('width', canvas.width) - .attr('height', canvas.height) - .attr('href', dataUrl); - }, 'pads'); - } - - let width = elem.property('draw_width'), height = elem.property('draw_height'); - if (use_frame) { - const fp = this.getFramePainter(); - width = fp.getFrameWidth(); - height = fp.getFrameHeight(); - } - - const arg = (file_format === 'pdf') - ? { node: elem.node(), width, height, reset_tranform: use_frame } - : compressSVG(`<svg width="${width}" height="${height}" xmlns="${nsSVG}">${elem.node().innerHTML}</svg>`); - - return svgToImage(arg, file_format, args).then(res => { - for (let k = 0; k < items.length; ++k) { - const item = items[k]; - - item.img?.remove(); // delete embed image - - const prim = item.prnt.selectChild('.primitives_layer'); - - if (item.foreign) // reinsert foreign object - item.prnt.node().insertBefore(item.foreign.node(), prim.node()); - - if (item.frame_node) // reinsert frame as first in list of primitives - prim.node().insertBefore(item.frame_node, item.frame_next); - - if (item.btns_node) // reinsert buttons - item.btns_prnt.insertBefore(item.btns_node, item.btns_next); - - if (item.defs) // reinsert defs - item.prnt.node().insertBefore(item.defs.node(), item.prnt.node().firstChild); - } - return res; - }); - } - - /** @summary Process pad button click */ - clickPadButton(funcname, evnt) { - if (funcname === 'CanvasSnapShot') - return this.saveAs('png', true); - - if (funcname === 'enlargePad') - return this.enlargePad(); - - if (funcname === 'PadSnapShot') - return this.saveAs('png', false); - - if (funcname === 'PadContextMenus') { - evnt?.preventDefault(); - evnt?.stopPropagation(); - if (closeMenu()) - return; - - return createMenu(evnt, this).then(menu => { - menu.header('Menus'); - - menu.add(this.isCanvas() ? 'Canvas' : 'Pad', 'pad', this.itemContextMenu); - - if (this.getFramePainter()) - menu.add('Frame', 'frame', this.itemContextMenu); - - const main = this.getMainPainter(); // here hist painter methods - - if (main) { - menu.add('X axis', 'xaxis', this.itemContextMenu); - menu.add('Y axis', 'yaxis', this.itemContextMenu); - if (isFunc(main.getDimension) && (main.getDimension() > 1)) - menu.add('Z axis', 'zaxis', this.itemContextMenu); - } - - if (this.#painters?.length) { - menu.separator(); - const shown = []; - this.#painters.forEach((pp, indx) => { - const obj = pp?.getObject(); - if (!obj || (shown.indexOf(obj) >= 0) || pp.isSecondary()) - return; - let name = isFunc(pp.getClassName) ? pp.getClassName() : (obj._typename || ''); - if (name) - name += '::'; - name += isFunc(pp.getObjectName) ? pp.getObjectName() : (obj.fName || `item${indx}`); - menu.add(name, indx, this.itemContextMenu); - shown.push(obj); - }); - } - - menu.show(); - }); - } - - // click automatically goes to all sub-pads - // if any painter indicates that processing completed, it returns true - let done = false; - const prs = []; - - for (let i = 0; i < this.#painters.length; ++i) { - const pp = this.#painters[i]; - - if (isFunc(pp.clickPadButton)) - prs.push(pp.clickPadButton(funcname, evnt)); - - if (!done && isFunc(pp.clickButton)) { - done = pp.clickButton(funcname); - if (isPromise(done)) - prs.push(done); - } - } - - return Promise.all(prs); - } - - /** @summary Add button to the pad - * @private */ - addPadButton(btn, tooltip, funcname, keyname) { - if (!settings.ToolBar || this.isBatchMode()) - return; - - if (!this._buttons) - this._buttons = []; - // check if there are duplications - - for (let k = 0; k < this._buttons.length; ++k) { - if (this._buttons[k].funcname === funcname) - return; - } - - this._buttons.push({ btn, tooltip, funcname, keyname }); - - if (!this.isTopPad() && funcname.indexOf('Pad') && (funcname !== 'enlargePad')) { - const cp = this.getCanvPainter(); - if (cp && (cp !== this)) - cp.addPadButton(btn, tooltip, funcname); - } - } - - /** @summary Add buttons for pad or canvas - * @private */ - addPadButtons(is_online) { - this.addPadButton('camera', 'Create PNG', this.isCanvas() ? 'CanvasSnapShot' : 'PadSnapShot', 'Ctrl PrintScreen'); - - if (settings.ContextMenu) - this.addPadButton('question', 'Access context menus', 'PadContextMenus'); - - const add_enlarge = !this.isTopPad() && this.hasObjectsToDraw(); - - if (add_enlarge || this.enlargeMain('verify')) - this.addPadButton('circle', 'Enlarge canvas', 'enlargePad'); - - if (is_online && this.brlayout) { - this.addPadButton('diamand', 'Toggle Ged', 'ToggleGed'); - this.addPadButton('three_circles', 'Toggle Status', 'ToggleStatus'); - } - } - - /** @summary Show pad buttons - * @private */ - showPadButtons() { - if (!this._buttons) - return; - - PadButtonsHandler.assign(this); - this.showPadButtons(); - } - - /** @summary Calculates RPadLength value */ - getPadLength(vertical, len, frame_painter) { - let rect, res; - const sign = vertical ? -1 : 1, - getV = (indx, dflt) => { return (indx < len.fArr.length) ? len.fArr[indx] : dflt; }, - getRect = () => { - if (!rect) - rect = frame_painter ? frame_painter.getFrameRect() : this.getPadRect(); - return rect; - }; - - if (frame_painter) { - const user = getV(2), func = vertical ? 'gry' : 'grx'; - if ((user !== undefined) && frame_painter[func]) - res = frame_painter[func](user); - } - - if (res === undefined) - res = vertical ? getRect().height : 0; - - const norm = getV(0, 0), pixel = getV(1, 0); - - res += sign * pixel; - - if (norm) - res += sign * (vertical ? getRect().height : getRect().width) * norm; - - return Math.round(res); - } - - - /** @summary Calculates pad position for RPadPos values - * @param {object} pos - instance of RPadPos - * @param {object} frame_painter - if drawing will be performed inside frame, frame painter */ - getCoordinate(pos, frame_painter) { - return { - x: this.getPadLength(false, pos.fHoriz, frame_painter), - y: this.getPadLength(true, pos.fVert, frame_painter) - }; - } - - /** @summary Decode pad draw options */ - decodeOptions(opt) { - const pad = this.getObject(); - if (!pad) - return; - - const d = new DrawOptions(opt), - o = this.setOptions({ GlobalColors: true, LocalColors: false, IgnorePalette: false, RotateFrame: false, FixFrame: false }); - - if (d.check('NOCOLORS') || d.check('NOCOL')) - o.GlobalColors = o.LocalColors = false; - if (d.check('LCOLORS') || d.check('LCOL')) { - o.GlobalColors = false; - o.LocalColors = true; - } - if (d.check('NOPALETTE') || d.check('NOPAL')) - o.IgnorePalette = true; - if (d.check('ROTATE')) - o.RotateFrame = true; - if (d.check('FIXFRAME')) - o.FixFrame = true; - - if (d.check('WHITE')) - pad.fFillColor = 0; - if (d.check('LOGX')) - pad.fLogx = 1; - if (d.check('LOGY')) - pad.fLogy = 1; - if (d.check('LOGZ')) - pad.fLogz = 1; - if (d.check('LOG')) - pad.fLogx = pad.fLogy = pad.fLogz = 1; - if (d.check('GRIDX')) - pad.fGridx = 1; - if (d.check('GRIDY')) - pad.fGridy = 1; - if (d.check('GRID')) - pad.fGridx = pad.fGridy = 1; - if (d.check('TICKX2')) - pad.fTickx = 2; - if (d.check('TICKY2')) - pad.fTicky = 2; - if (d.check('TICK2')) - pad.fTickx = pad.fTicky = 2; - if (d.check('TICKX')) - pad.fTickx = 1; - if (d.check('TICKY')) - pad.fTicky = 1; - if (d.check('TICK')) - pad.fTickx = pad.fTicky = 1; - } - - /** @summary draw RPad object */ - static async draw(dom, pad, opt) { - const painter = new RPadPainter(dom, pad, opt, false, true); - - painter.createPadSvg(); - - if (painter.matchObjectType(nsREX + 'RPad') && (painter.isTopPad() || painter.hasObjectsToDraw())) - painter.addPadButtons(); - - selectActivePad({ pp: painter, active: false }); - - // flag used to prevent immediate pad redraw during first draw - return painter.drawPrimitives().then(() => { - painter.addPadInteractive(); - painter.showPadButtons(); - return painter; - }); - } - -} // class RPadPainter - -export { RPadPainter }; diff --git a/js/modules/gpad/TAxisPainter.mjs b/js/modules/gpad/TAxisPainter.mjs deleted file mode 100644 index d789ab528ca06..0000000000000 --- a/js/modules/gpad/TAxisPainter.mjs +++ /dev/null @@ -1,1578 +0,0 @@ -import { gStyle, settings, constants, clTAxis, clTGaxis, isFunc, isStr } from '../core.mjs'; -import { select as d3_select, drag as d3_drag, timeFormat as d3_timeFormat, utcFormat as d3_utcFormat, - scaleTime as d3_scaleTime, scaleSymlog as d3_scaleSymlog, - scaleLog as d3_scaleLog, scaleLinear as d3_scaleLinear } from '../d3.mjs'; -import { floatToString, makeTranslate, addHighlightStyle } from '../base/BasePainter.mjs'; -import { ObjectPainter, EAxisBits, kAxisLabels, kAxisNormal, kAxisFunc, kAxisTime } from '../base/ObjectPainter.mjs'; -import { FontHandler } from '../base/FontHandler.mjs'; - - -/** @summary Return time offset value for given TAxis object - * @private */ -function getTimeOffset(axis) { - const dflt_time_offset = 788918400000; - - if (!axis) - return dflt_time_offset; - const idF = axis.fTimeFormat.indexOf('%F'); - if (idF < 0) - return gStyle.fTimeOffset * 1000; - let sof = axis.fTimeFormat.slice(idF + 2); - // default string in axis offset - if (sof.indexOf('1995-01-01 00:00:00s0') === 0) - return dflt_time_offset; - // another default string with unix time - if (sof.indexOf('1970-01-01 00:00:00s0') === 0) - return 0; - // special case, used from DABC painters - if ((sof === '0') || (sof === '')) - return 0; - - // decode time from ROOT string - const next = (separ, min, max) => { - const pos = sof.indexOf(separ); - if (pos < 0) - return min; - const val = parseInt(sof.slice(0, pos)); - sof = sof.slice(pos + 1); - if (!Number.isInteger(val) || (val < min) || (val > max)) - return min; - return val; - }; - // eslint-disable-next-line one-var - const year = next('-', 1900, 2900), - month = next('-', 1, 12) - 1, - day = next(' ', 1, 31), - hour = next(':', 0, 23), - min = next(':', 0, 59), - sec = next('s', 0, 59), - msec = next(' ', 0, 999); - - let offset = Date.UTC(year, month, day, hour, min, sec, msec); - - // now also handle suffix like GMT or GMT -0600 - sof = sof.toUpperCase(); - - if (sof.indexOf('GMT') === 0) { - sof = sof.slice(4).trim(); - if (sof.length > 3) { - let p = 0, sign = 1000; - if (sof[0] === '-') { - p = 1; - sign = -1000; - } - offset -= sign * (parseInt(sof.slice(p, p + 2)) * 3600 + parseInt(sof.slice(p + 2, p + 4)) * 60); - } - } - - return offset; -} - -/** @summary Return true when GMT option configured in time format - * @private */ -function getTimeGMT(axis) { - const fmt = axis?.fTimeFormat ?? ''; - return (fmt.indexOf('gmt') > 0) || (fmt.indexOf('GMT') > 0); -} - -/** @summary Tries to choose time format for provided time interval - * @private */ -function chooseTimeFormat(awidth, ticks) { - if (awidth < 0.5) - return ticks ? '%S.%L' : '%H:%M:%S.%L'; - if (awidth < 30) - return ticks ? '%Mm%S' : '%H:%M:%S'; - awidth /= 60; - if (awidth < 30) - return ticks ? '%Hh%M' : '%d/%m %H:%M'; - awidth /= 60; - if (awidth < 12) - return ticks ? '%d-%Hh' : '%d/%m/%y %Hh'; - awidth /= 24; - if (awidth < 15.218425) - return ticks ? '%d/%m' : '%d/%m/%y'; - awidth /= 30.43685; - if (awidth < 6) - return '%d/%m/%y'; - awidth /= 12; - if (awidth < 2) - return ticks ? '%m/%y' : '%d/%m/%y'; - return '%Y'; -} - -/** - * @summary Base axis painter methods - * - * @private - */ - -const AxisPainterMethods = { - - initAxisPainter() { - this.name = 'yaxis'; - this.kind = kAxisNormal; - this.func = null; - this.order = 0; // scaling order for axis labels - - this.full_min = 0; - this.full_max = 1; - this.scale_min = 0; - this.scale_max = 1; - this.ticks = []; // list of major ticks - }, - - /** @summary Cleanup axis painter */ - cleanupAxisPainter() { - this.ticks = []; - delete this.format; - delete this.func; - delete this.tfunc1; - delete this.tfunc2; - delete this.gr; - }, - - /** @summary Assign often used members of frame painter */ - assignFrameMembers(fp, axis) { - fp[`gr${axis}`] = this.gr; // fp.grx - fp[`log${axis}`] = this.log; // fp.logx - fp[`scale_${axis}min`] = this.scale_min; // fp.scale_xmin - fp[`scale_${axis}max`] = this.scale_max; // fp.scale_xmax - }, - - /** @summary Convert axis value into the Date object */ - convertDate(v) { - const dt = new Date(this.timeoffset + v * 1000); - let res = dt; - if (!this.timegmt && settings.TimeZone) { - try { - const ms = dt.getMilliseconds(); - res = new Date(dt.toLocaleString('en-US', { timeZone: settings.TimeZone })); - res.setMilliseconds(ms); - } catch { - res = dt; - } - } - return res; - }, - - /** @summary Convert graphical point back into axis value */ - revertPoint(pnt) { - const value = this.func.invert(pnt); - return this.kind === kAxisTime ? (value - this.timeoffset) / 1000 : value; - }, - - /** @summary Provide label for time axis */ - formatTime(dt, asticks) { - return asticks ? this.tfunc1(dt) : this.tfunc2(dt); - }, - - /** @summary Provide label for log axis */ - formatLog(d, asticks, fmt) { - const val = parseFloat(d), rnd = Math.round(val); - if (!asticks) - return ((rnd === val) && (Math.abs(rnd) < 1e9)) ? rnd.toString() : floatToString(val, fmt || gStyle.fStatFormat); - if (val <= 0) - return null; - let vlog = Math.log10(val); - const base = this.logbase; - if (base !== 10) - vlog /= Math.log10(base); - if (this.moreloglabels || (Math.abs(vlog - Math.round(vlog)) < 0.001)) { - if (!this.noexp && (asticks !== 2)) - return this.formatExp(base, Math.floor(vlog + 0.01), val); - if (Math.abs(base - Math.E) < 0.001) - return floatToString(val, fmt || gStyle.fStatFormat); - return (vlog < 0) ? val.toFixed(Math.round(-vlog + 0.5)) : val.toFixed(0); - } - return null; - }, - - /** @summary Detect if tick is extra-ticks */ - isExtraLogTick(val) { - if (!this.log) - return false; - let vlog = Math.log10(val); - if (this.logbase !== 10) - vlog /= Math.log10(this.logbase); - - return Math.abs(vlog - Math.round(vlog)) >= 0.001; - }, - - /** @summary Provide label for normal axis */ - formatNormal(d, asticks, fmt) { - let val = parseFloat(d); - if (asticks && this.order) - val /= Math.pow(10, this.order); - - if (gStyle.fStripDecimals && (val === Math.round(val))) - return Math.abs(val) < 1e9 ? val.toFixed(0) : val.toExponential(4); - - if (asticks) { - if (this.ndig > 10) - return val.toExponential(this.ndig - 11); - let res = val.toFixed(this.ndig); - const p = res.indexOf('.'); - if ((p > 0) && settings.StripAxisLabels) { - while ((res.length >= p) && ((res.at(-1) === '0') || (res.at(-1) === '.'))) - res = res.slice(0, res.length - 1); - } - return res; - } - - return floatToString(val, fmt || '8.6g'); - }, - - /** @summary Provide label for exponential form */ - formatExp(base, order, value) { - let res = ''; - const sbase = Math.abs(base - Math.E) < 0.001 ? 'e' : base.toString(); - if (value) { - value = Math.round(value / Math.pow(base, order)); - if (settings.StripAxisLabels) { - if (order === 0) - return value.toString(); - else if ((order === 1) && (value === 1)) - return sbase; - } - if (value !== 1) - res = value.toString() + (settings.Latex ? '#times' : 'x'); - } - res += sbase; - if (settings.Latex > constants.Latex.Symbols) - return res + `^{${order}}`; - const superscript_symbols = { - 0: '\u2070', 1: '\xB9', 2: '\xB2', 3: '\xB3', 4: '\u2074', 5: '\u2075', - 6: '\u2076', 7: '\u2077', 8: '\u2078', 9: '\u2079', '-': '\u207B' - }, str = order.toString(); - for (let n = 0; n < str.length; ++n) - res += superscript_symbols[str[n]]; - return res; - }, - - /** @summary Convert 'raw' axis value into text */ - axisAsText(value, fmt) { - if (this.kind === kAxisTime) - value = this.convertDate(value); - if (this.format) - return this.format(value, false, fmt); - return value.toPrecision(4); - }, - - /** @summary Produce ticks for d3.scaleLog - * @desc Fixing following problem, described [here]{@link https://stackoverflow.com/questions/64649793} */ - poduceLogTicks(func, number) { - const linearArray = arr => { - if (arr.length < 2) - return false; - let sum1 = 0, sum2 = 0; - for (let k = 1; k < arr.length; ++k) { - const diff = (arr[k] - arr[k - 1]); - sum1 += diff; - sum2 += diff ** 2; - } - const mean = sum1 / (arr.length - 1), - dev = sum2 / (arr.length - 1) - mean ** 2; - - if (dev <= 0) - return true; - if (Math.abs(mean) < 1e-100) - return false; - return Math.sqrt(dev) / mean < 1e-6; - }; - - let arr = func.ticks(number); - - while ((number > 4) && linearArray(arr)) { - number = Math.round(number * 0.8); - arr = func.ticks(number); - } - - // if still linear array, try to sort out 'bad' ticks - if ((number < 5) && linearArray(arr) && this.logbase && (this.logbase !== 10)) { - const arr2 = []; - arr.forEach(val => { - const pow = Math.log10(val) / Math.log10(this.logbase); - if (Math.abs(Math.round(pow) - pow) < 0.01) - arr2.push(val); - }); - if (arr2.length) - arr = arr2; - } - - return arr; - }, - - /** @summary Produce axis ticks */ - produceTicks(ndiv, ndiv2) { - if (!this.noticksopt && !this.exact_ticks) { - const total = ndiv * (ndiv2 || 1); - - if (this.log) - return this.poduceLogTicks(this.func, total); - - const dom = this.func.domain(), - check = ticks => { - if (ticks.length <= total) - return true; - if (ticks.length > total + 1) - return false; - return (ticks[0] === dom[0]) || (ticks[total] === dom[1]); // special case of N+1 ticks, but match any range - }, res1 = this.func.ticks(total); - if (ndiv2 || check(res1)) - return res1; - - const res2 = this.func.ticks(Math.round(total * 0.7)); - return (res2.length > 2) && check(res2) ? res2 : res1; - } - - const dom = this.func.domain(), ticks = []; - if (ndiv2) - ndiv = (ndiv - 1) * ndiv2; - for (let n = 0; n <= ndiv; ++n) - ticks.push((dom[0] * (ndiv - n) + dom[1] * n) / ndiv); - return ticks; - }, - - /** @summary Method analyze mouse wheel event and returns item with suggested zooming range */ - analyzeWheelEvent(evnt, dmin, item, test_ignore) { - if (!item) - item = {}; - - let delta = 0, delta_left = 1, delta_right = 1; - - if ('dleft' in item) { - delta_left = item.dleft; - delta = 1; - } - if ('dright' in item) { - delta_right = item.dright; - delta = 1; - } - - if (item.delta) - delta = item.delta; - else if (evnt) - delta = evnt.wheelDelta ? -evnt.wheelDelta : (evnt.deltaY || evnt.detail); - - if (!delta || (test_ignore && item.ignore)) - return; - - delta = (delta < 0) ? -0.2 : 0.2; - delta_left *= delta; - delta_right *= delta; - - const lmin = item.min = this.scale_min, - lmax = item.max = this.scale_max, - gmin = this.full_min, - gmax = this.full_max; - - if ((item.min === item.max) && (delta < 0)) { - item.min = gmin; - item.max = gmax; - } - - if (item.min >= item.max) - return; - - if (item.reverse) - dmin = 1 - dmin; - - if ((dmin > 0) && (dmin < 1)) { - if (this.log) { - let factor = (item.min > 0) ? Math.log10(item.max / item.min) : 2; - if (factor > 10) - factor = 10; - else if (factor < 0.01) - factor = 0.01; - item.min /= Math.pow(10, factor * delta_left * dmin); - item.max *= Math.pow(10, factor * delta_right * (1 - dmin)); - // special handling for Z scale - limit zooming of color scale - if (this.minposbin && this.name === 'zaxis') - item.min = Math.max(item.min, 0.3 * this.minposbin); - } else if ((delta_left === -delta_right) && !item.reverse) { - // shift left/right, try to keep range constant - let delta_shift = (item.max - item.min) * delta_right * dmin; - - if ((Math.round(item.max) === item.max) && (Math.round(item.min) === item.min) && (Math.abs(delta_shift) > 1)) - delta_shift = Math.round(delta_shift); - - if (item.min + delta_shift < gmin) - delta_shift = gmin - item.min; - else if (item.max + delta_shift > gmax) - delta_shift = gmax - item.max; - - if (delta_shift) { - item.min += delta_shift; - item.max += delta_shift; - } else { - delete item.min; - delete item.max; - } - } else { - let rx_left = (item.max - item.min), rx_right = rx_left; - if (delta_left > 0) - rx_left = 1.001 * rx_left / (1 - delta_left); - item.min += -delta_left * dmin * rx_left; - if (delta_right > 0) - rx_right = 1.001 * rx_right / (1 - delta_right); - item.max -= -delta_right * (1 - dmin) * rx_right; - } - if (item.min >= item.max) - item.min = item.max = undefined; - else if (delta_left !== delta_right) { - // extra check case when moving left or right - if (((item.min < gmin) && (lmin === gmin)) || - ((item.max > gmax) && (lmax === gmax))) - item.min = item.max = undefined; - } else { - item.min = Math.max(item.min, gmin); - item.max = Math.min(item.max, gmax); - } - } else - item.min = item.max = undefined; - - item.changed = (item.min !== undefined) && (item.max !== undefined); - - return item; - } - -}; // AxisPainterMethods - - -/** - * @summary Painter for TAxis object - * - * @private - */ - -class TAxisPainter extends ObjectPainter { - - /** @summary constructor - * @param {object|string} dom - identifier or dom element - * @param {object} axis - object to draw - * @param {boolean} embedded - if true, painter used in other objects painters */ - constructor(dom, axis, embedded) { - super(dom, axis); - - this.is_gaxis = axis?._typename === clTGaxis; - - Object.assign(this, AxisPainterMethods); - this.initAxisPainter(); - - this.embedded = embedded; // indicate that painter embedded into the histogram painter - this.invert_side = false; - this.lbls_both_sides = false; // draw labels on both sides - } - - /** @summary cleanup painter */ - cleanup() { - this.cleanupAxisPainter(); - delete this.hist_painter; - delete this.hist_axis; - delete this.is_gaxis; - super.cleanup(); - } - - /** @summary Use in GED to identify kind of axis */ - getAxisType() { return clTAxis; } - - /** @summary Configure axis painter - * @desc Axis can be drawn inside frame <g> group with offset to 0 point for the frame - * Therefore one should distinguish when calculated coordinates used for axis drawing itself or for calculation of frame coordinates - * @private */ - configureAxis(name, min, max, smin, smax, vertical, range, opts) { - const axis = this.getObject(); - - this.name = name; - this.full_min = min; - this.full_max = max; - this.kind = kAxisNormal; - this.vertical = vertical; - this.log = opts.log || 0; - this.minposbin = opts.minposbin; - this.ignore_labels = opts.ignore_labels; - this.noexp_changed = opts.noexp_changed; - this.symlog = opts.symlog || false; - this.reverse = opts.reverse || false; - // special flag to change align of labels on vertical axis - // it is workaround shown in TGaxis docu - this.reverseAlign = this.vertical && this.reverse && this.is_gaxis && (axis.fX1 !== axis.fX2); - this.swap_side = opts.swap_side || false; - this.fixed_ticks = opts.fixed_ticks || null; - this.maxTickSize = opts.maxTickSize || 0; - this.value_axis = opts.value_axis ?? false; // use fMinimum/fMaximum from source object - - if (opts.time_scale || axis.fTimeDisplay) { - this.kind = kAxisTime; - this.timeoffset = getTimeOffset(axis); - this.timegmt = getTimeGMT(axis); - } else if (opts.axis_func) - this.kind = kAxisFunc; - else - this.kind = !axis.fLabels || this.ignore_labels ? kAxisNormal : kAxisLabels; - - if (this.kind === kAxisTime) - this.func = d3_scaleTime().domain([this.convertDate(smin), this.convertDate(smax)]); - else if (this.log) { - if ((this.log === 1) || (this.log === 10)) - this.logbase = 10; - else if (this.log === 3) - this.logbase = Math.E; - else - this.logbase = Math.round(this.log); - - if (smax <= 0) - smax = 1; - - if (opts.log_min_nz) - this.log_min_nz = opts.log_min_nz; - else if (axis && opts.logcheckmin) { - let v = 0; - for (let i = 0; i < axis.fNbins; ++i) { - v = axis.GetBinLowEdge(i + 1); - if (v > 0) - break; - v = axis.GetBinCenter(i + 1); - if (v > 0) - break; - } - if (v > 0) - this.log_min_nz = v; - } - - if ((smin <= 0) && this.log_min_nz) - smin = this.log_min_nz; - - if ((smin <= 0) || (smin >= smax)) - smin = smax * (opts.logminfactor || 1e-4); - - if (this.kind === kAxisFunc) - this.func = this.createFuncHandle(opts.axis_func, this.logbase, smin, smax); - else - this.func = d3_scaleLog().base(this.logbase).domain([smin, smax]); - } else if (this.symlog) { - let v = Math.max(Math.abs(smin), Math.abs(smax)); - if (Number.isInteger(this.symlog) && (this.symlog > 0)) - v *= Math.pow(10, -1 * this.symlog); - else - v *= 0.01; - this.func = d3_scaleSymlog().constant(v).domain([smin, smax]); - } else if (this.kind === kAxisFunc) - this.func = this.createFuncHandle(opts.axis_func, 0, smin, smax); - else - this.func = d3_scaleLinear().domain([smin, smax]); - - if (this.vertical ^ this.reverse) - [range[0], range[1]] = [range[1], range[0]]; - - this.func.range(range); - - this.scale_min = smin; - this.scale_max = smax; - - if (this.kind === kAxisTime) - this.gr = val => this.func(this.convertDate(val)); - else if (this.log) - this.gr = val => { return (val < this.scale_min) ? (this.vertical ? this.func.range()[0] + 5 : -5) : this.func(val); }; - else - this.gr = this.func; - - delete this.format; // remove formatting func - - let ndiv = 508; - if (this.is_gaxis) - ndiv = axis.fNdiv; - else if (axis) - ndiv = axis.fNdivisions; - - this.exact_ticks = ndiv < 0; - if (this.exact_ticks) - ndiv = Math.abs(ndiv); - ndiv = Math.max(ndiv, 4); - this.nticks = ndiv % 100; - this.nticks2 = (ndiv % 10000 - this.nticks) / 100; - this.nticks3 = Math.floor(ndiv / 10000); - - if (axis && !this.is_gaxis) - this.nticks = Math.min(this.nticks, 20); - - let gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - if (gr_range <= 0) - gr_range = 100; - - if (this.kind === kAxisTime) { - this.nticks = Math.min(this.nticks, 8); - - const scale_range = this.scale_max - this.scale_min, - idF = axis.fTimeFormat.indexOf('%F'), - tf2 = chooseTimeFormat(scale_range / gr_range, false); - let tf1 = (idF >= 0) ? axis.fTimeFormat.slice(0, idF) : axis.fTimeFormat; - - if (!tf1 || (scale_range < 0.1 * (this.full_max - this.full_min))) - tf1 = chooseTimeFormat(scale_range / this.nticks, true); - - this.tfunc1 = this.tfunc2 = this.timegmt ? d3_utcFormat(tf1) : d3_timeFormat(tf1); - if (tf2 !== tf1) - this.tfunc2 = this.timegmt ? d3_utcFormat(tf2) : d3_timeFormat(tf2); - - this.format = this.formatTime; - } else if (this.log) { - if (this.nticks2 > 1) { - this.nticks *= this.nticks2; // all log ticks (major or minor) created centrally - this.nticks2 = 1; - } - this.noexp = axis?.TestBit(EAxisBits.kNoExponent); - if ((this.scale_max < 300) && (this.scale_min > 0.3) && !this.noexp_changed && (this.log === 1)) - this.noexp = true; - this.moreloglabels = axis?.TestBit(EAxisBits.kMoreLogLabels); - this.format = this.formatLog; - } else if (this.kind === kAxisLabels) { - this.nticks = 50; // for text output allow max 50 names - const scale_range = this.scale_max - this.scale_min; - if (this.nticks > scale_range) - this.nticks = Math.round(scale_range); - - this.regular_labels = true; - - if (axis?.fNbins && axis?.fLabels) { - if ((axis.fNbins !== Math.round(axis.fXmax - axis.fXmin)) || - axis.fXmin || (axis.fXmax !== axis.fNbins)) - this.regular_labels = false; - } - - this.nticks2 = 1; - - this.format = this.formatLabels; - } else { - this.order = 0; - this.ndig = 0; - this.format = this.formatNormal; - } - } - - /** @summary Check zooming value for log scale - * @private */ - checkZoomMin(value) { - return this.log && this.log_min_nz ? Math.max(value, this.log_min_nz) : value; - } - - /** @summary Return scale min */ - getScaleMin() { - return this.func?.domain()[0] ?? 0; - } - - /** @summary Return scale max */ - getScaleMax() { - return this.func?.domain()[1] ?? 0; - } - - /** @summary Return true if labels may be removed while they are not fit to graphical range */ - cutLabels() { - if (!settings.CutAxisLabels) - return false; - if (isStr(settings.CutAxisLabels)) - return settings.CutAxisLabels.indexOf(this.name) >= 0; - return this.vertical; // cut vertical axis by default - } - - /** @summary Provide label for axis value */ - formatLabels(d) { - const a = this.getObject(); - let indx = parseFloat(d); - if (!this.regular_labels) - indx = Math.round((indx - a.fXmin) / (a.fXmax - a.fXmin) * a.fNbins); - else - indx = Math.floor(indx); - if ((indx < 0) || (indx >= a.fNbins)) - return null; - const arr = a.fLabels.arr; - for (let i = 0; i < arr.length; ++i) { - if (arr[i].fUniqueID === indx + 1) - return arr[i].fString; - } - return null; - } - - /** @summary Creates array with minor/middle/major ticks */ - createTicks(only_major_as_array, optionNoexp, optionNoopt, optionInt) { - if (optionNoopt && this.nticks && (this.kind === kAxisNormal)) - this.noticksopt = true; - - const handle = { painter: this, nminor: 0, nmiddle: 0, nmajor: 0, func: this.func, minor: [], middle: [], major: [] }; - let ticks = []; - - if (this.fixed_ticks) { - this.fixed_ticks.forEach(v => { - if ((v >= this.scale_min) && (v <= this.scale_max)) - ticks.push(v); - }); - } else if (this.kind === kAxisLabels) { - handle.lbl_pos = []; - const axis = this.getObject(); - for (let n = 0; n <= axis.fNbins; ++n) { - const x = this.regular_labels ? n : axis.fXmin + n / axis.fNbins * (axis.fXmax - axis.fXmin); - if ((x >= this.scale_min) && (x <= this.scale_max)) { - handle.lbl_pos.push(x); - ticks.push(x); - } - } - } else - ticks = this.produceTicks(this.nticks); - - handle.minor = handle.middle = handle.major = ticks; - - if (only_major_as_array) { - const res = handle.major, delta = (this.scale_max - this.scale_min) * 1e-5; - if (res.at(0) > this.scale_min + delta) - res.unshift(this.scale_min); - if (res.at(-1) < this.scale_max - delta) - res.push(this.scale_max); - return res; - } - - if ((this.nticks2 > 1) && (!this.log || (this.logbase === 10)) && !this.fixed_ticks) { - handle.minor = handle.middle = this.produceTicks(handle.major.length, this.nticks2); - const gr_range = Math.abs(this.func.range()[1] - this.func.range()[0]); - // avoid black filling by middle-size - if ((handle.middle.length <= handle.major.length) || (handle.middle.length > gr_range)) - handle.minor = handle.middle = handle.major; - else if ((this.nticks3 > 1) && !this.log) { - handle.minor = this.produceTicks(handle.middle.length, this.nticks3); - if ((handle.minor.length <= handle.middle.length) || (handle.minor.length > gr_range)) - handle.minor = handle.middle; - } - } - - handle.reset = function() { - this.nminor = this.nmiddle = this.nmajor = 0; - }; - - handle.next = function(doround) { - if (this.nminor >= this.minor.length) - return false; - - this.tick = this.minor[this.nminor++]; - this.grpos = this.func(this.tick); - if (doround) - this.grpos = Math.round(this.grpos); - this.kind = 3; - - if ((this.nmiddle < this.middle.length) && (Math.abs(this.grpos - this.func(this.middle[this.nmiddle])) < 1)) { - this.nmiddle++; - this.kind = 2; - } - - if ((this.nmajor < this.major.length) && (Math.abs(this.grpos - this.func(this.major[this.nmajor])) < 1)) { - this.nmajor++; - this.kind = 1; - } - return true; - }; - - handle.last_major = function() { - return (this.kind !== 1) ? false : this.nmajor === this.major.length; - }; - - handle.next_major_grpos = function() { - return this.nmajor >= this.major.length ? null : this.func(this.major[this.nmajor]); - }; - - handle.get_modifier = function() { - return this.painter.findLabelModifier(this.painter.getObject(), this.nmajor - 1, this.major); - }; - - this.order = 0; - this.ndig = 0; - - // at the moment when drawing labels, we can try to find most optimal text representation for them - if (((this.kind === kAxisNormal) || (this.kind === kAxisFunc)) && !this.log && handle.major.length) { - let maxorder = 0, minorder = 0, exclorder3 = false; - - if (!optionNoexp && !this.cutLabels()) { - const maxtick = Math.max(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), - mintick = Math.min(Math.abs(handle.major.at(0)), Math.abs(handle.major.at(-1))), - ord1 = (maxtick > 0) ? Math.round(Math.log10(maxtick) / 3) * 3 : 0, - ord2 = (mintick > 0) ? Math.round(Math.log10(mintick) / 3) * 3 : 0; - - exclorder3 = (maxtick < 2e4); // do not show 10^3 for values below 20000 - - if (maxtick || mintick) { - maxorder = Math.max(ord1, ord2) + 3; - minorder = Math.min(ord1, ord2) - 3; - } - } - - // now try to find best combination of order and ndig for labels - let bestorder = 0, bestndig = this.ndig, bestlen = 1e10; - - for (let order = minorder; order <= maxorder; order += 3) { - if (exclorder3 && (order === 3)) - continue; - this.order = order; - this.ndig = 0; - let lbls = [], indx = 0, totallen = 0; - while (indx < handle.major.length) { - const v0 = handle.major[indx], - lbl = this.format(v0, true); - - let bad_value = lbls.indexOf(lbl) >= 0; - if (!bad_value) { - try { - const v1 = parseFloat(lbl) * Math.pow(10, order); - bad_value = (Math.abs(v0) > 1e-30) && (Math.abs(v1 - v0) / Math.abs(v0) > 1e-8); - } catch { - console.warn('Failure by parsing of', lbl); - bad_value = true; - } - } - if (bad_value) { - if (++this.ndig > 15) { - totallen += 1e10; - break; // not too many digits, anyway it will be exponential - } - lbls = []; - indx = totallen = 0; - } else { - lbls.push(lbl); - const p = lbl.indexOf('.'); - if (!order && !optionNoexp && ((p > gStyle.fAxisMaxDigits) || ((p < 0) && (lbl.length > gStyle.fAxisMaxDigits)))) { - totallen += 1e10; // do not use order = 0 when too many digits are there - exclorder3 = false; - } - totallen += lbl.length; - indx++; - } - } - - // for order === 0 we should virtually remove '0.' and extra label on top - if (!order && (this.ndig < 4)) - totallen -= handle.major.length * 2 + 3; - - if (totallen < bestlen) { - bestlen = totallen; - bestorder = this.order; - bestndig = this.ndig; - } - } - - this.order = bestorder; - this.ndig = bestndig; - - if (optionInt) { - if (this.order) - console.warn(`Axis painter - integer labels are configured, but axis order ${this.order} is preferable`); - if (this.ndig) - console.warn(`Axis painter - integer labels are configured, but ${this.ndig} decimal digits are required`); - this.ndig = 0; - this.order = 0; - } - } - - return handle; - } - - /** @summary Is labels should be centered */ - isCenteredLabels() { - if (this.kind === kAxisLabels) - return true; - if (this.log) - return false; - return this.getObject()?.TestBit(EAxisBits.kCenterLabels); - } - - /** @summary Is labels should be rotated */ - isRotateLabels() { - return this.getObject()?.TestBit(EAxisBits.kLabelsVert); - } - - /** @summary Is title should be rotated */ - isRotateTitle() { - return this.getObject()?.TestBit(EAxisBits.kRotateTitle); - } - - /** @summary Add interactive elements to draw axes title */ - addTitleDrag(title_g, vertical, offset_k, reverse, axis_length) { - if (!settings.MoveResize || this.isBatchMode()) - return; - - let drag_rect = null, x_0, y_0, i_0, - acc_x, acc_y, new_x, new_y, sign_0, alt_pos, curr_indx, can_indx0 = true; - const drag_move = d3_drag().subject(Object); - - drag_move.on('start', evnt => { - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const box = title_g.node().getBBox(), // check that elements visible, request precise value - title_length = vertical ? box.height : box.width; - - x_0 = new_x = acc_x = title_g.property('shift_x'); - y_0 = new_y = acc_y = title_g.property('shift_y'); - - sign_0 = vertical ? (acc_x > 0) : (acc_y > 0); // sign should remain - can_indx0 = !this.hist_painter?.getSnapId(); // online canvas does not allow alternate position - - alt_pos = vertical ? [axis_length, axis_length / 2, 0] : [0, axis_length / 2, axis_length]; // possible positions - const off = vertical ? -title_length / 2 : title_length / 2; - if (this.title_align === 'middle') { - alt_pos[0] += off; - alt_pos[2] -= off; - } else if (this.title_align === 'begin') { - alt_pos[1] -= off; - alt_pos[2] -= 2 * off; - } else { // end - alt_pos[0] += 2 * off; - alt_pos[1] += off; - } - - if (this.titleCenter) - curr_indx = 1; - else if ((reverse ^ this.titleOpposite) && can_indx0) - curr_indx = 0; - else - curr_indx = 2; - - alt_pos[curr_indx] = vertical ? acc_y : acc_x; - i_0 = curr_indx; - - drag_rect = title_g.append('rect') - .attr('x', box.x) - .attr('y', box.y) - .attr('width', box.width) - .attr('height', box.height) - .style('cursor', 'move') - .call(addHighlightStyle, true); - // .style('pointer-events','none'); // let forward double click to underlying elements - }).on('drag', evnt => { - if (!drag_rect) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - acc_x += evnt.dx; - acc_y += evnt.dy; - - let set_x, set_y, besti = can_indx0 ? 0 : 1; - const p = vertical ? acc_y : acc_x; - - for (let i = 1; i < 3; ++i) { - if (Math.abs(p - alt_pos[i]) < Math.abs(p - alt_pos[besti])) - besti = i; - } - - if (vertical) { - set_x = acc_x; - set_y = alt_pos[besti]; - } else { - set_y = acc_y; - set_x = alt_pos[besti]; - } - - if (sign_0 === (vertical ? (set_x > 0) : (set_y > 0))) { - new_x = set_x; - new_y = set_y; - curr_indx = besti; - makeTranslate(title_g, new_x, new_y); - } - }).on('end', evnt => { - if (!drag_rect) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - title_g.property('shift_x', new_x) - .property('shift_y', new_y); - - const axis = this.getObject(), axis2 = this.source_axis, - setBit = (bit, on) => { - axis?.SetBit(bit, on); - axis2?.SetBit(bit, on); - }; - - this.titleOffset = (vertical ? new_x : new_y) / offset_k; - const offset = this.titleOffset / this.offsetScaling / this.titleSize; - if (axis) - axis.fTitleOffset = offset; - if (axis2) - axis2.fTitleOffset = offset; - - if (curr_indx === 1) { - setBit(EAxisBits.kCenterTitle, true); - this.titleCenter = true; - setBit(EAxisBits.kOppositeTitle, false); - this.titleOpposite = false; - } else if (curr_indx === 0) { - setBit(EAxisBits.kCenterTitle, false); - this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, true); - this.titleOpposite = true; - } else { - setBit(EAxisBits.kCenterTitle, false); - this.titleCenter = false; - setBit(EAxisBits.kOppositeTitle, false); - this.titleOpposite = false; - } - - drag_rect.remove(); - drag_rect = null; - - if ((x_0 !== new_x) || (y_0 !== new_y) || (i_0 !== curr_indx)) - this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter ? 1 : 0})`); - - if (this.hist_painter && this.hist_axis) - this.hist_painter.getCanvPainter()?.producePadEvent('select', this.hist_painter.getPadPainter(), this); - }); - - title_g.style('cursor', 'move').call(drag_move); - } - - /** @summary Configure hist painter which creates axis - to be able submit execs - * @private */ - setHistPainter(hist_painter, axis_name) { - this.hist_painter = hist_painter; - this.hist_axis = axis_name; - } - - /** @summary Submit exec for the axis - if possible - * @private */ - submitAxisExec(exec, only_gaxis) { - const snapid = this.hist_painter?.getSnapId(); - if (snapid && this.hist_axis && !only_gaxis) - this.submitCanvExec(exec, `${snapid}#${this.hist_axis}`); - else if (this.is_gaxis) - this.submitCanvExec(exec); - } - - /** @summary Produce svg path for axis ticks */ - produceTicksPath(handle, side, tickSize, ticksPlusMinus, secondShift, real_draw) { - let path1 = '', path2 = ''; - this.ticks = []; - - while (handle.next(true)) { - let h1 = Math.round(tickSize / 4), h2 = 0; - - if (handle.kind < 3) - h1 = Math.round(tickSize / 2); - - if (handle.kind === 1) { - // if not showing labels, not show large tick - // FIXME: for labels last tick is smaller - if (!this.isExtraLogTick(handle.tick) && (this.format(handle.tick, true) !== null)) - h1 = tickSize; - this.ticks.push(handle.grpos); // keep graphical positions of major ticks - } - - if (ticksPlusMinus > 0) - h2 = -h1; - else if (side < 0) { - h2 = -h1; - h1 = 0; - } - - path1 += this.vertical ? `M${h1},${handle.grpos}H${h2}` : `M${handle.grpos},${-h1}V${-h2}`; - - if (secondShift) - path2 += this.vertical ? `M${secondShift - h1},${handle.grpos}H${secondShift - h2}` : `M${handle.grpos},${secondShift + h1}V${secondShift + h2}`; - } - - return real_draw ? path1 + path2 : ''; - } - - /** @summary Returns modifier for axis label */ - findLabelModifier(axis, nlabel, positions) { - if (!axis.fModLabs) - return null; - for (let n = 0; n < axis.fModLabs.arr.length; ++n) { - const mod = axis.fModLabs.arr[n]; - - if ((mod.fLabValue !== undefined) && (mod.fLabNum === 0)) { - const eps = this.log ? positions[nlabel] * 1e-6 : (this.scale_max - this.scale_min) * 1e-6; - if (Math.abs(mod.fLabValue - positions[nlabel]) < eps) - return mod; - } - - if ((mod.fLabNum === nlabel + 1) || - ((mod.fLabNum < 0) && (nlabel === positions.length + mod.fLabNum))) - return mod; - } - return null; - } - - /** @summary Draw axis labels - * @return {Promise} with array label size and max width */ - async drawLabels(axis_g, axis, w, h, handle, labelsside, labelsFont, labeloffset, tickSize, ticksPlusMinus, max_text_width, frame_ygap) { - const center_lbls = this.isCenteredLabels(), - label_g = [axis_g.append('svg:g').attr('class', 'axis_labels')], - lbl_pos = handle.lbl_pos || handle.major, - tilt_angle = gStyle.AxisTiltAngle ?? 25; - let rotate_lbls = this.isRotateLabels(), - textscale = 1, flipscale = 1, maxtextlen = 0, applied_scale = 0, - lbl_tilt = false, any_modified = false, max_textwidth = 0, max_tiltsize = 0; - - if (this.lbls_both_sides) - label_g.push(axis_g.append('svg:g').attr('class', 'axis_labels').attr('transform', this.vertical ? `translate(${w})` : `translate(0,${-h})`)); - - if (frame_ygap > 0) - max_tiltsize = frame_ygap / Math.sin(tilt_angle / 180 * Math.PI) - Math.tan(tilt_angle / 180 * Math.PI); - - // function called when text is drawn to analyze width, required to correctly scale all labels - // must be function to correctly handle 'this' argument - function process_drawtext_ready(painter) { - const textwidth = this.result_width; - max_textwidth = Math.max(max_textwidth, textwidth); - - const maxwidth = !this.gap_before ? 0.9 * this.gap_after : (!this.gap_after ? 0.9 * this.gap_before : this.gap_before * 0.45 + this.gap_after * 0.45); - - if (!painter.vertical && !rotate_lbls && this.result_height && maxwidth) - flipscale = Math.min(flipscale, maxwidth / this.result_height); - - if (textwidth && ((!painter.vertical && !rotate_lbls) || (painter.vertical && rotate_lbls)) && !painter.log) - textscale = Math.min(textscale, maxwidth / textwidth); - else if (painter.vertical && max_text_width && this.normal_side && (max_text_width - labeloffset > 20) && (textwidth > max_text_width - labeloffset)) - textscale = Math.min(textscale, (max_text_width - labeloffset) / textwidth); - - if ((textscale > 0.0001) && (textscale < 0.7) && !any_modified && - !painter.vertical && !rotate_lbls && (label_g.length === 1) && (lbl_tilt === false)) { - if (maxtextlen > 5) - lbl_tilt = true; - } - - let scale = textscale; - - if (lbl_tilt) { - if (max_tiltsize && max_textwidth) { - scale = Math.min(1, 0.8 * max_tiltsize / max_textwidth); - if (scale < textscale) { - // if due to tilt scale is even smaller - ignore tilting - lbl_tilt = 0; - scale = textscale; - } - } else - scale *= 3; - } - - if (((scale > 0.0001) && (scale < 1)) || (lbl_tilt !== false)) { - applied_scale = 1 / scale; - painter.scaleTextDrawing(applied_scale, label_g[0]); - } - } - - // check if short labels can be rotated - if (!this.vertical && this.regular_labels && !rotate_lbls) { - let tlen = 0; - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - const text = this.format(lbl_pos[nmajor], true); - if (text) - tlen = Math.max(tlen, text.length); - } - - if ((tlen > 2) && (tlen <= 5) && (lbl_pos.length * labelsFont.size > w / 2)) { - rotate_lbls = true; - lbl_tilt = 0; - } - } - - const draw_labels = (lcnt, side) => { - return this.startTextDrawingAsync(labelsFont, 'font', label_g[lcnt]).then(() => { - let lastpos = 0; - const fix_coord = this.vertical ? -labeloffset * side : labeloffset * side + ticksPlusMinus * tickSize; - - for (let nmajor = 0; nmajor < lbl_pos.length; ++nmajor) { - let text = this.format(lbl_pos[nmajor], true); - if (text === null) - continue; - - const mod = this.findLabelModifier(axis, nmajor, lbl_pos); - if (mod?.fTextSize === 0) - continue; - - if (mod) - any_modified = true; - if (mod?.fLabText) - text = mod.fLabText; - - const arg = { text, color: labelsFont.color, latex: 1, draw_g: label_g[lcnt], normal_side: (lcnt === 0) }; - let pos = Math.round(this.func(lbl_pos[nmajor])); - - // exclude labels for extra log ticks - if (lastpos && this.vertical && (Math.abs(pos - lastpos) < labelsFont.size * 1.1) && this.isExtraLogTick(lbl_pos[nmajor])) { - lastpos = pos; - continue; - } - - if (mod?.fTextColor > 0) - arg.color = this.getColor(mod.fTextColor); - - arg.gap_before = (nmajor > 0) ? Math.abs(Math.round(pos - this.func(lbl_pos[nmajor - 1]))) : 0; - - arg.gap_after = (nmajor < lbl_pos.length - 1) ? Math.abs(Math.round(this.func(lbl_pos[nmajor + 1]) - pos)) : 0; - - if (center_lbls) { - const gap = arg.gap_after || arg.gap_before; - pos = Math.round(pos - ((this.vertical !== this.reverse) ? 0.5 * gap : -0.5 * gap)); - if ((pos < -5) || (pos > (this.vertical ? h : w) + 5)) - continue; - } - - maxtextlen = Math.max(maxtextlen, text.length); - - if (this.vertical) { - arg.x = fix_coord; - arg.y = pos; - const flag = this.optionLeft || this.reverseAlign || (side < 0); - arg.align = rotate_lbls ? (flag ? 23 : 21) : (flag ? 12 : 32); - if (this.cutLabels()) { - const gap = labelsFont.size * (rotate_lbls ? 1.5 : 0.6); - if ((pos < gap) || (pos > h - gap)) - continue; - } - } else { - arg.x = pos; - arg.y = fix_coord; - arg.align = rotate_lbls ? ((side < 0) ? 12 : 32) : ((side < 0) ? 21 : 23); - if (this.log && !this.noexp && !this.vertical && arg.align === 23) { - arg.align = 21; - arg.y += labelsFont.size; - } else if (arg.align % 10 === 3) - arg.y -= labelsFont.size * 0.1; // font takes 10% more by top align - - if (this.cutLabels()) { - const gap = labelsFont.size * (rotate_lbls ? 0.4 : 1.5); - if ((pos < gap) || (pos > w - gap)) - continue; - } - } - - if (rotate_lbls) - arg.rotate = 270; - else if (mod && mod.fTextAngle !== -1) - arg.rotate = -mod.fTextAngle; - - // only for major text drawing scale factor need to be checked - // for modified labels ignore scaling - if ((lcnt === 0) && !mod?.fLabText) - arg.post_process = process_drawtext_ready; - - this.drawText(arg); - - // workaround for symlog where labels can be compressed to close - if (this.symlog && lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { - const axis_step = Math.abs(pos - lastpos); - textscale = Math.min(textscale, 1.1 * axis_step / labelsFont.size); - } - - lastpos = pos; - } - - if (this.order) { - let xoff = 0, yoff = 0; - if (this.name === 'xaxis') { - xoff = gStyle.fXAxisExpXOffset || 0; - yoff = gStyle.fXAxisExpYOffset || 0; - } else if (this.name === 'yaxis') { - xoff = gStyle.fYAxisExpXOffset || 0; - yoff = gStyle.fYAxisExpYOffset || 0; - } - - if (xoff) - xoff = Math.round(xoff * (this.getPadPainter()?.getPadWidth() ?? 0)); - if (yoff) - yoff = Math.round(yoff * (this.getPadPainter()?.getPadHeight() ?? 0)); - - this.drawText({ - color: labelsFont.color, - x: xoff + (this.vertical ? side * 5 : w + 5), - y: yoff + (this.has_obstacle ? fix_coord : (this.vertical ? -3 : -3 * side)), - align: this.vertical ? ((side < 0) ? 30 : 10) : ((this.has_obstacle ^ (side < 0)) ? 13 : 10), - latex: 1, - text: '#times' + this.formatExp(10, this.order), - draw_g: label_g[lcnt] - }); - } - - if ((lcnt > 0) && applied_scale) - this.scaleTextDrawing(applied_scale, label_g[lcnt]); - - return this.finishTextDrawing(label_g[lcnt], true); - }); - }; - - return draw_labels(0, labelsside).then(() => { - return label_g.length < 2 ? true : draw_labels(1, -labelsside); - }).then(() => { - this._maxlbllen = maxtextlen; // for internal use in palette painter - - if (lbl_tilt) { - label_g[0].selectAll('text').each(function() { - const txt = d3_select(this), tr = txt.attr('transform'); - if (lbl_tilt) - txt.attr('transform', `${tr} rotate(${tilt_angle})`).style('text-anchor', 'start'); - }); - } - - return max_textwidth; - }); - } - - /** @summary Extract major draw attributes, which are also used in interactive operations - * @private */ - extractDrawAttributes(scalingSize, w, h) { - const axis = this.getObject(); - let pp = this.getPadPainter(); - if (axis.$use_top_pad) - pp = pp?.getPadPainter(); // workaround for ratio plot - const pad_w = pp?.getPadWidth() || scalingSize || w / 0.8, // use factor 0.8 as ratio between frame and pad size - pad_h = pp?.getPadHeight() || scalingSize || h / 0.8, - // if no external scaling size use scaling as in TGaxis.cxx:1448 - NDC axis length is in the scaling factor - tickScalingSize = scalingSize || (this.vertical ? h / pad_h * pad_w : w / pad_w * pad_h), - bit_plus = axis.TestBit(EAxisBits.kTickPlus), bit_minus = axis.TestBit(EAxisBits.kTickMinus); - - let tickSize, titleColor, titleFontId, offset; - - this.scalingSize = scalingSize || Math.max(Math.min(pad_w, pad_h), 10); - - if (this.is_gaxis) { - const optionSize = axis.fChopt.indexOf('S') >= 0; - this.optionUnlab = axis.fChopt.indexOf('U') >= 0; - this.optionMinus = (axis.fChopt.indexOf('-') >= 0) || bit_minus; - this.optionPlus = (axis.fChopt.indexOf('+') >= 0) || bit_plus; - this.optionNoopt = (axis.fChopt.indexOf('N') >= 0); // no ticks position optimization - this.optionInt = (axis.fChopt.indexOf('I') >= 0); // integer labels - this.optionText = (axis.fChopt.indexOf('T') >= 0); // text scaling? - this.optionLeft = (axis.fChopt.indexOf('L') >= 0); // left text align - this.optionRight = (axis.fChopt.indexOf('R') >= 0); // right text align - this.optionCenter = (axis.fChopt.indexOf('C') >= 0); // center text align - this.createAttLine({ attr: axis }); - tickSize = optionSize ? axis.fTickSize : 0.03; - titleColor = this.getColor(axis.fTextColor); - titleFontId = axis.fTextFont; - offset = axis.fLabelOffset; - // workaround for old reverse axes where offset is not properly working - if (this.reverse && (!this.vertical || (!this.optionMinus && (axis.fX1 !== axis.fX2)))) - offset = -offset; - } else { - this.optionUnlab = false; - if (!bit_plus && !bit_minus) { - this.optionMinus = this.vertical ^ this.invert_side; - this.optionPlus = !this.optionMinus; - } else { - this.optionPlus = bit_plus; - this.optionMinus = bit_minus; - } - this.optionNoopt = false; // no ticks position optimization - this.optionInt = false; // integer labels - this.optionText = false; - this.createAttLine({ color: axis.fAxisColor, width: 1, style: 1 }); - tickSize = axis.fTickLength; - titleColor = this.getColor(axis.fTitleColor); - titleFontId = axis.fTitleFont; - offset = axis.fLabelOffset; - } - - offset += (this.vertical ? 0.002 : 0.005); - - if (this.kind === kAxisLabels) - this.optionText = true; - - this.optionNoexp = axis.TestBit(EAxisBits.kNoExponent); - - this.ticksSize = Math.round(tickSize * tickScalingSize); - if (scalingSize && (this.ticksSize < 0)) - this.ticksSize = -this.ticksSize; - - if (this.maxTickSize && (this.ticksSize > this.maxTickSize)) - this.ticksSize = this.maxTickSize; - - // now used only in 3D drawing - this.ticksColor = this.lineatt.color; - this.ticksWidth = this.lineatt.width; - - const k = this.optionText ? 0.66666 : 1; // set TGaxis.cxx, line 1504 - this.labelSize = Math.round((axis.fLabelSize < 1) ? k * axis.fLabelSize * this.scalingSize : k * axis.fLabelSize); - this.labelsOffset = Math.round(offset * this.scalingSize); - this.labelsFont = new FontHandler(axis.fLabelFont, this.labelSize, scalingSize); - if ((this.labelSize <= 0) || (Math.abs(axis.fLabelOffset) > 1.1)) - this.optionUnlab = true; // disable labels when size not specified - this.labelsFont.setColor(this.getColor(axis.fLabelColor)); - - this.fTitle = axis.fTitle; - if (this.fTitle) { - this.titleSize = (axis.fTitleSize >= 1) ? axis.fTitleSize : Math.round(axis.fTitleSize * this.scalingSize); - this.titleFont = new FontHandler(titleFontId, this.titleSize, scalingSize); - this.titleFont.setColor(titleColor); - this.offsetScaling = (axis.fTitleSize >= 1) ? 1 : (this.vertical ? pad_w : pad_h) / this.scalingSize; - this.titleOffset = axis.fTitleOffset; - if (!this.titleOffset && this.name[0] === 'x') - this.titleOffset = gStyle.fXaxis.fTitleOffset; - this.titleOffset *= this.titleSize * this.offsetScaling; - this.titleCenter = axis.TestBit(EAxisBits.kCenterTitle); - this.titleOpposite = axis.TestBit(EAxisBits.kOppositeTitle); - } else { - delete this.titleSize; - delete this.titleFont; - delete this.offsetScaling; - delete this.titleOffset; - delete this.titleCenter; - delete this.titleOpposite; - } - } - - /** @summary function draws TAxis or TGaxis object - * @return {Promise} for drawing ready */ - async drawAxis(layer, w, h, transform, secondShift, disable_axis_drawing, max_text_width, calculate_position, frame_ygap) { - const axis = this.getObject(), - swap_side = this.swap_side || false; - let axis_g = layer, draw_lines = true; - - // shift for second ticks set (if any) - if (!secondShift) - secondShift = 0; - else if (this.invert_side) - secondShift = -secondShift; - - this.extractDrawAttributes(undefined, w, h); - - if (this.is_gaxis) - draw_lines = Boolean(axis.fLineColor); - - if (!this.is_gaxis || (this.name === 'zaxis')) { - axis_g = layer.selectChild(`.${this.name}_container`); - if (axis_g.empty()) - axis_g = layer.append('svg:g').attr('class', `${this.name}_container`); - else - axis_g.selectAll('*').remove(); - } - - let axis_lines = ''; - if (draw_lines) { - axis_lines = 'M0,0' + (this.vertical ? `v${h}` : `h${w}`); - if (secondShift) - axis_lines += this.vertical ? `M${secondShift},0v${h}` : `M0,${secondShift}h${w}`; - } - - axis_g.attr('transform', transform); - - let side = 1, ticksPlusMinus = 0; - - if (this.optionPlus && this.optionMinus) - side = ticksPlusMinus = 1; - else if (this.optionMinus) - side = (swap_side ^ this.vertical) ? 1 : -1; - else if (this.optionPlus) - side = (swap_side ^ this.vertical) ? -1 : 1; - - // first draw ticks - - const handle = this.createTicks(false, this.optionNoexp, this.optionNoopt, this.optionInt); - - axis_lines += this.produceTicksPath(handle, side, this.ticksSize, ticksPlusMinus, secondShift, draw_lines && !disable_axis_drawing && !this.disable_ticks); - - if (!disable_axis_drawing && axis_lines && !this.lineatt.empty()) { - axis_g.append('svg:path') - .attr('d', axis_lines) - .call(this.lineatt.func); - } - - let title_shift_x = 0, title_shift_y = 0, title_g, labelsMaxWidth = 0; - // draw labels (sometime on both sides) - const labelSize = Math.max(this.labelsFont.size, 5), - pr = (disable_axis_drawing || this.optionUnlab) - ? Promise.resolve(0) - : this.drawLabels(axis_g, axis, w, h, handle, side, this.labelsFont, this.labelsOffset, this.ticksSize, ticksPlusMinus, max_text_width, frame_ygap); - - return pr.then(maxw => { - labelsMaxWidth = maxw; - - if (settings.Zooming && !this.disable_zooming && !this.isBatchMode()) { - const r = axis_g.append('svg:rect') - .attr('class', 'axis_zoom') - .style('opacity', '0') - .style('cursor', 'crosshair'); - - if (this.vertical) { - const rw = Math.max(labelsMaxWidth, 2 * labelSize) + 3; - r.attr('x', (side > 0) ? -rw : 0).attr('y', 0) - .attr('width', rw).attr('height', h); - } else { - r.attr('x', 0).attr('y', (side > 0) ? 0 : -labelSize - 3) - .attr('width', w).attr('height', labelSize + 3); - } - } - - this.position = 0; - - if (calculate_position) { - const node1 = axis_g.node(), - node2 = this.getPadPainter()?.getPadSvg().node(); - if (isFunc(node1?.getBoundingClientRect) && isFunc(node2?.getBoundingClientRect)) { - const rect1 = node1.getBoundingClientRect(), - rect2 = node2.getBoundingClientRect(); - this.position = rect1.left - rect2.left; // use to control left position of Y scale - } - if (node1 && !node2) - console.warn('Why PAD element missing when search for position'); - } - - if (!this.fTitle || disable_axis_drawing) - return; - - title_g = axis_g.append('svg:g').attr('class', 'axis_title'); - - return this.startTextDrawingAsync(this.titleFont, 'font', title_g); - }).then(() => { - if (!title_g) - return; - - const rotate = this.isRotateTitle() ? -1 : 1, - xor_reverse = swap_side ^ this.titleOpposite, myxor = (rotate < 0) ^ xor_reverse; - - let title_offest_k = side; - - this.title_align = this.titleCenter ? 'middle' : (myxor ? 'begin' : 'end'); - - if (this.vertical) { - title_offest_k *= -1.6; - - title_shift_x = Math.round(title_offest_k * this.titleOffset); - - title_shift_y = Math.round(this.titleCenter ? h / 2 : (xor_reverse ? h : 0)); - - this.drawText({ - align: this.title_align + ';middle', - rotate: (rotate < 0) ? 90 : 270, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g - }); - } else { - title_offest_k *= 1.6; - - title_shift_x = Math.round(this.titleCenter ? w / 2 : (xor_reverse ? 0 : w)); - title_shift_y = Math.round(title_offest_k * this.titleOffset); - this.drawText({ - align: this.title_align + ';middle', - rotate: (rotate < 0) ? 180 : 0, - text: this.fTitle, color: this.titleFont.color, draw_g: title_g - }); - } - - this.addTitleDrag(title_g, this.vertical, title_offest_k, swap_side, this.vertical ? h : w); - - return this.finishTextDrawing(title_g); - }).then(() => { - if (title_g) { - if (!this.titleOffset && this.vertical) - title_shift_x = Math.round(-side * ((labelsMaxWidth || labelSize) + 0.7 * this.offsetScaling * this.titleSize)); - makeTranslate(title_g, title_shift_x, title_shift_y); - title_g.property('shift_x', title_shift_x) - .property('shift_y', title_shift_y); - } - - return this; - }); - } - -} // class TAxisPainter - - -export { EAxisBits, chooseTimeFormat, AxisPainterMethods, TAxisPainter }; diff --git a/js/modules/gpad/TCanvasPainter.mjs b/js/modules/gpad/TCanvasPainter.mjs deleted file mode 100644 index 6e969f7b9adc8..0000000000000 --- a/js/modules/gpad/TCanvasPainter.mjs +++ /dev/null @@ -1,1064 +0,0 @@ -import { BIT, settings, gStyle, internals, browser, create, parse, - toJSON, loadScript, isFunc, isStr, clTCanvas, clTList } from '../core.mjs'; -import { select as d3_select } from '../d3.mjs'; -import { closeCurrentWindow, showProgress, loadOpenui5, ToolbarIcons, getColorExec } from '../gui/utils.mjs'; -import { GridDisplay, getHPainter } from '../gui/display.mjs'; -import { cleanup, resize, selectActivePad, EAxisBits, getDomCanvasPainter } from '../base/ObjectPainter.mjs'; -import { TFramePainter } from './TFramePainter.mjs'; -import { TPadPainter, clTButton, createWebObjectOptions } from './TPadPainter.mjs'; - -const kShowEventStatus = BIT(15), - // kAutoExec = BIT(16), - kMenuBar = BIT(17), - kShowToolBar = BIT(18), - kShowEditor = BIT(19), - // kMoveOpaque = BIT(20), - // kResizeOpaque = BIT(21), - // kIsGrayscale = BIT(22), - kShowToolTips = BIT(23); - -/** @summary direct draw of TFrame object, - * @desc pad or canvas should already exist - * @private */ -function directDrawTFrame(dom, obj, opt) { - const fp = new TFramePainter(dom, obj); - fp.addToPadPrimitives(); - if (opt === '3d') - fp.mode3d = true; - return fp.redraw(); -} - -/** - * @summary Painter for TCanvas object - * - * @private - */ - -class TCanvasPainter extends TPadPainter { - - #websocket; // WebWindow handle used for communication with server - #changed_layout; // modified layout - #getmenu_callback; // function called when menu items get from server - #online_fixed_size; // when size fixed for online canvas - #all_sections_showed; // set once after online canvas drawn - #last_highlight_msg; // last highligh msg send to server - - /** @summary Constructor */ - constructor(dom, canvas, opt, kind = true) { - super(dom, canvas, opt, kind); - this.#websocket = null; - this.tooltip_allowed = settings.Tooltip; - } - - /** @summary Cleanup canvas painter */ - cleanup() { - if (this.#changed_layout) - this.setLayoutKind('simple'); - this.#changed_layout = undefined; - super.cleanup(); - } - - /** @summary Returns canvas name */ - getCanvasName() { return this.getObjectName(); } - - /** @summary Returns layout kind */ - getLayoutKind() { - const origin = this.selectDom('origin'), - layout = origin.empty() ? '' : origin.property('layout'); - - return layout || 'simple'; - } - - /** @summary Set canvas layout kind */ - setLayoutKind(kind, main_selector) { - const origin = this.selectDom('origin'); - if (!origin.empty()) { - if (!kind) - kind = 'simple'; - origin.property('layout', kind); - origin.property('layout_selector', (kind !== 'simple') && main_selector ? main_selector : null); - this.#changed_layout = (kind !== 'simple'); // use in cleanup - } - } - - /** @summary Changes layout - * @return {Promise} indicating when finished */ - async changeLayout(layout_kind, mainid) { - const current = this.getLayoutKind(); - if (current === layout_kind) - return true; - - const origin = this.selectDom('origin'), - sidebar2 = origin.select('.side_panel2'), - lst = []; - let sidebar = origin.select('.side_panel'), - main = this.selectDom(), force; - - while (main.node().firstChild) - lst.push(main.node().removeChild(main.node().firstChild)); - - if (!sidebar.empty()) - cleanup(sidebar.node()); - if (!sidebar2.empty()) - cleanup(sidebar2.node()); - - this.setLayoutKind('simple'); // restore defaults - origin.html(''); // cleanup origin - - if (layout_kind === 'simple') { - main = origin; - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); - this.setLayoutKind(layout_kind); - force = true; - } else { - const grid = new GridDisplay(origin.node(), layout_kind); - - if (mainid === undefined) - mainid = (layout_kind.indexOf('vert') === 0) ? 0 : 1; - - main = d3_select(grid.getGridFrame(mainid)); - main.classed('central_panel', true).style('position', 'relative'); - - if (mainid === 2) { - // left panel for Y - sidebar = d3_select(grid.getGridFrame(0)); - sidebar.classed('side_panel2', true).style('position', 'relative'); - // bottom panel for X - sidebar = d3_select(grid.getGridFrame(3)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } else { - sidebar = d3_select(grid.getGridFrame(1 - mainid)); - sidebar.classed('side_panel', true).style('position', 'relative'); - } - - // now append all childs to the new main - for (let k = 0; k < lst.length; ++k) - main.node().appendChild(lst[k]); - - this.setLayoutKind(layout_kind, '.central_panel'); - - // remove reference to MDIDisplay, solves resize problem - origin.property('mdi', null); - } - - // resize main drawing and let draw extras - resize(main.node(), force); - return true; - } - - /** @summary Toggle projection - * @return {Promise} indicating when ready - * @private */ - async toggleProjection(kind) { - delete this.proj_painter; - - if (kind) - this.proj_painter = { X: false, Y: false }; // just indicator that drawing can be preformed - - if (isFunc(this.showUI5ProjectionArea)) - return this.showUI5ProjectionArea(kind); - - let layout = 'simple', mainid; - - switch (kind) { - case 'XY': - layout = 'projxy'; - mainid = 2; - break; - case 'X': - case 'bottom': - layout = 'vert2_31'; - mainid = 0; - break; - case 'Y': - case 'left': - layout = 'horiz2_13'; - mainid = 1; - break; - case 'top': - layout = 'vert2_13'; - mainid = 1; - break; - case 'right': - layout = 'horiz2_31'; - mainid = 0; - break; - } - - return this.changeLayout(layout, mainid); - } - - /** @summary Draw projection for specified histogram - * @private */ - async drawProjection(kind, hist, hopt) { - if (!this.proj_painter) - return false; // ignore drawing if projection not configured - - if (hopt === undefined) - hopt = 'hist'; - if (!kind) - kind = 'X'; - - if (!this.proj_painter[kind]) { - this.proj_painter[kind] = 'init'; - - const canv = create(clTCanvas), - pad = this.getRootPad(), - fp = this.getFramePainter(); - let drawopt; - - if (kind === 'X') { - canv.fLeftMargin = pad.fLeftMargin; - canv.fRightMargin = pad.fRightMargin; - canv.fLogx = fp.logx; - canv.fUxmin = fp.logx ? Math.log10(fp.scale_xmin) : fp.scale_xmin; - canv.fUxmax = fp.logx ? Math.log10(fp.scale_xmax) : fp.scale_xmax; - drawopt = 'fixframe'; - } else if (kind === 'Y') { - canv.fBottomMargin = pad.fBottomMargin; - canv.fTopMargin = pad.fTopMargin; - canv.fLogx = fp.logy; - canv.fUxmin = fp.logy ? Math.log10(fp.scale_ymin) : fp.scale_ymin; - canv.fUxmax = fp.logy ? Math.log10(fp.scale_ymax) : fp.scale_ymax; - drawopt = 'rotate'; - } - - canv.fPrimitives.Add(hist, hopt); - - const promise = isFunc(this.drawInUI5ProjectionArea) - ? this.drawInUI5ProjectionArea(canv, drawopt, kind) - : this.drawInSidePanel(canv, drawopt, kind); - - return promise.then(painter => { - this.proj_painter[kind] = painter; - return painter; - }); - } else if (isStr(this.proj_painter[kind])) { - console.log('Not ready with first painting', kind); - return true; - } - - this.proj_painter[kind].getMainPainter()?.updateObject(hist, hopt); - return this.proj_painter[kind].redrawPad(); - } - - /** @summary Checks if canvas shown inside ui5 widget - * @desc Function should be used only from the func which supposed to be replaced by ui5 - * @private */ - testUI5() { - return this.use_openui ?? false; - } - - /** @summary Draw in side panel - * @private */ - async drawInSidePanel(canv, opt, kind) { - const sel = ((this.getLayoutKind() === 'projxy') && (kind === 'Y')) ? '.side_panel2' : '.side_panel', - side = this.selectDom('origin').select(sel); - return side.empty() ? null : this.drawObject(side.node(), canv, opt); - } - - /** @summary Show message - * @desc Used normally with web-based canvas and handled in ui5 - * @private */ - showMessage(msg) { - if (!this.testUI5()) - showProgress(msg, 7000); - } - - /** @summary Function called when canvas menu item Save is called */ - saveCanvasAsFile(fname) { - const pnt = fname.lastIndexOf('.'); - this.createImage(fname.slice(pnt + 1)) - .then(res => this.sendWebsocket(`SAVE:${fname}:${res}`)); - } - - /** @summary Send command to server to save canvas with specified name - * @desc Should be only used in web-based canvas - * @private */ - sendSaveCommand(fname) { - this.sendWebsocket('PRODUCE:' + fname); - } - - /** @summary Submit menu request - * @private */ - async submitMenuRequest(_painter, _kind, reqid) { - // only single request can be handled, no limit better in RCanvas - return new Promise(resolveFunc => { - this.#getmenu_callback = resolveFunc; - this.sendWebsocket('GETMENU:' + reqid); // request menu items for given painter - }); - } - - /** @summary Submit object exec request - * @private */ - submitExec(painter, exec, snapid) { - if (this.isReadonly() || !painter) - return; - - if (!snapid) - snapid = painter.getSnapId(); - if (snapid && isStr(snapid) && exec) - return this.sendWebsocket(`OBJEXEC:${snapid}:${exec}`); - } - - /** @summary Return assigned web socket - * @private */ - getWebsocket() { return this.#websocket; } - - /** @summary Return true if message can be send via web socket - * @private */ - canSendWebsocket(noper = 1) { return this.#websocket?.canSend(noper); } - - /** @summary Send text message with web socket - * @desc used for communication with server-side of web canvas - * @private */ - sendWebsocket(msg) { - if (this.#websocket?.canSend()) { - this.#websocket.send(msg); - return true; - } - console.warn(`DROP SEND: ${msg}`); - return false; - } - - /** @summary Close websocket connection to canvas - * @private */ - closeWebsocket(force) { - if (this.#websocket) { - this.#websocket.close(force); - this.#websocket.cleanup(); - this.#websocket = undefined; - } - } - - /** @summary Use provided connection for the web canvas - * @private */ - useWebsocket(handle) { - this.closeWebsocket(); - - this.#websocket = handle; - this.#websocket.setReceiver(this); - this.#websocket.connect(); - } - - /** @summary set, test or reset timeout of specified name - * @desc Used to prevent overloading of websocket for specific function */ - websocketTimeout(name, tm) { - if (!this.#websocket) - return; - if (!this.#websocket._tmouts) - this.#websocket._tmouts = {}; - - const handle = this.#websocket._tmouts[name]; - if (tm === undefined) - return handle !== undefined; - - if (tm === 'reset') { - if (handle) { - clearTimeout(handle); - delete this.#websocket._tmouts[name]; - } - } else if (!handle && Number.isInteger(tm)) - this.#websocket._tmouts[name] = setTimeout(() => { delete this.#websocket._tmouts[name]; }, tm); - } - - /** @summary Handler for websocket open event - * @private */ - onWebsocketOpened(/* handle */) { - // indicate that we are ready to receive any following commands - } - - /** @summary Handler for websocket close event - * @private */ - onWebsocketClosed(/* handle */) { - if (!this.embed_canvas) - closeCurrentWindow(); - } - - /** @summary Handle websocket messages - * @private */ - onWebsocketMsg(handle, msg) { - // console.log(`GET len:${msg.length} msg:${msg.slice(0,60)}`); - - if (msg === 'CLOSE') { - this.onWebsocketClosed(); - this.closeWebsocket(true); - } else if (msg.slice(0, 6) === 'SNAP6:') { - // This is snapshot, produced with TWebCanvas - const p1 = msg.indexOf(':', 6), - version = msg.slice(6, p1), - snap = parse(msg.slice(p1 + 1)); - - this.syncDraw(true) - .then(() => { - if (!this.getSnapId()) - this.resizeBrowser(snap.fSnapshot.fWindowWidth, snap.fSnapshot.fWindowHeight); - if (!this.getSnapId() && isFunc(this.setFixedCanvasSize)) - this.#online_fixed_size = this.setFixedCanvasSize(snap.fSnapshot.fCw, snap.fSnapshot.fCh, snap.fFixedSize); - }) - .then(() => this.redrawPadSnap(snap)) - .then(() => { - this.completeCanvasSnapDrawing(); - let ranges = this.getWebPadOptions(); // all data, including sub-pads - if (ranges) - ranges = ':' + ranges; - handle.send(`READY6:${version}${ranges}`); // send ready message back when drawing completed - this.confirmDraw(); - }).catch(err => { - if (isFunc(this.showConsoleError)) - this.showConsoleError(err); - else - console.log(err); - }); - } else if (msg.slice(0, 5) === 'MENU:') { - // this is menu with exact identifier for object - const lst = parse(msg.slice(5)); - if (isFunc(this.#getmenu_callback)) { - this.#getmenu_callback(lst); - this.#getmenu_callback = undefined; - } - } else if (msg.slice(0, 4) === 'CMD:') { - msg = msg.slice(4); - const p1 = msg.indexOf(':'), - cmdid = msg.slice(0, p1), - cmd = msg.slice(p1 + 1), - reply = `REPLY:${cmdid}:`; - if ((cmd === 'SVG') || (cmd === 'PNG') || (cmd === 'JPEG') || (cmd === 'WEBP') || (cmd === 'PDF')) { - this.createImage(cmd.toLowerCase()) - .then(res => handle.send(reply + res)); - } else { - console.log(`Unrecognized command ${cmd}`); - handle.send(reply); - } - } else if ((msg.slice(0, 7) === 'DXPROJ:') || (msg.slice(0, 7) === 'DYPROJ:')) { - const kind = msg[1], - hist = parse(msg.slice(7)); - this.websocketTimeout(`proj${kind}`, 'reset'); - this.drawProjection(kind, hist); - } else if (msg.slice(0, 5) === 'CTRL:') { - const ctrl = parse(msg.slice(5)) || {}; - let resized = false; - if ((ctrl.title !== undefined) && (typeof document !== 'undefined')) - document.title = ctrl.title; - if (ctrl.x && ctrl.y && typeof window !== 'undefined') { - window.moveTo(ctrl.x, ctrl.y); - resized = true; - } - if (ctrl.w && ctrl.h) { - this.resizeBrowser(Number.parseInt(ctrl.w), Number.parseInt(ctrl.h)); - resized = true; - } - if (ctrl.cw && ctrl.ch && isFunc(this.setFixedCanvasSize)) { - this.#online_fixed_size = this.setFixedCanvasSize(Number.parseInt(ctrl.cw), Number.parseInt(ctrl.ch), true); - resized = true; - } - const kinds = ['Menu', 'StatusBar', 'Editor', 'ToolBar', 'ToolTips']; - kinds.forEach(kind => { - if (ctrl[kind] !== undefined) - this.showSection(kind, ctrl[kind] === '1'); - }); - - if (ctrl.edit) { - const obj_painter = this.findSnap(ctrl.edit); - if (obj_painter) { - this.showSection('Editor', true) - .then(() => this.producePadEvent('select', obj_painter.getPadPainter(), obj_painter)); - } - } - - if (ctrl.winstate && typeof window !== 'undefined') { - if (ctrl.winstate === 'iconify') - window.blur(); - else - window.focus(); - } - - if (resized) - this.sendResized(true); - } else - console.log(`unrecognized msg ${msg}`); - } - - /** @summary Send RESIZED message to client to inform about changes in canvas/window geometry - * @private */ - sendResized(force) { - const pad = this.getRootPad(); - if (!pad || (typeof window === 'undefined')) - return; - const cw = this.getPadWidth(), ch = this.getPadHeight(), - wx = window.screenLeft, wy = window.screenTop, - ww = window.outerWidth, wh = window.outerHeight, - fixed = this.#online_fixed_size ? 1 : 0; - if (!force) { - force = (cw > 0) && (ch > 0) && ((pad.fCw !== cw) || (pad.fCh !== ch)); - if (force) { - pad.fCw = cw; - pad.fCh = ch; - } - } - if (force) - this.sendWebsocket(`RESIZED:${JSON.stringify([wx, wy, ww, wh, cw, ch, fixed])}`); - } - - /** @summary Handle pad button click event */ - clickPadButton(funcname, evnt) { - if (funcname === 'ToggleGed') - return this.activateGed(this, null, 'toggle'); - if (funcname === 'ToggleStatus') - return this.activateStatusBar('toggle'); - return super.clickPadButton(funcname, evnt); - } - - /** @summary Returns true if event status shown in the canvas */ - hasEventStatus() { - if (this.testUI5()) - return false; - if (this.brlayout) - return this.brlayout.hasStatus(); - return getHPainter()?.hasStatusLine() ?? false; - } - - /** @summary Check if status bar can be toggled - * @private */ - canStatusBar() { - return this.testUI5() || this.brlayout || getHPainter(); - } - - /** @summary Show/toggle event status bar - * @private */ - activateStatusBar(state) { - if (this.testUI5()) - return; - if (this.brlayout) - this.brlayout.createStatusLine(23, state); - else - getHPainter()?.createStatusLine(23, state); - this.processChanges('sbits', this); - } - - /** @summary Show online canvas status - * @private */ - showCanvasStatus(...msgs) { - if (this.testUI5()) - return; - - const br = this.brlayout || getHPainter()?.brlayout; - br?.showStatus(...msgs); - } - - /** @summary Returns true if GED is present on the canvas */ - hasGed() { - if (this.testUI5()) - return false; - return this.brlayout?.hasContent() ?? false; - } - - /** @summary Function used to de-activate GED - * @private */ - removeGed() { - if (this.testUI5()) - return; - - this.registerForPadEvents(null); - - if (this.ged_view) { - this.ged_view.getController().cleanupGed(); - this.ged_view.destroy(); - delete this.ged_view; - } - this.brlayout?.deleteContent(true); - this.processChanges('sbits', this); - } - - /** @summary Get view data for ui5 panel - * @private */ - getUi5PanelData(/* panel_name */) { - return { jsroot: { settings, create, parse, toJSON, loadScript, EAxisBits, getColorExec } }; - } - - /** @summary Function used to activate GED - * @return {Promise} when GED is there - * @private */ - async activateGed(objpainter, kind, mode) { - if (this.testUI5() || !this.brlayout) - return false; - - if (this.brlayout.hasContent()) { - if ((mode === 'toggle') || (mode === false)) - this.removeGed(); - else - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - - return true; - } - - if (mode === false) - return false; - - const btns = this.brlayout.createBrowserBtns(); - - ToolbarIcons.createSVG(btns, ToolbarIcons.diamand, 15, 'toggle fix-pos mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('fix')); - - ToolbarIcons.createSVG(btns, ToolbarIcons.circle, 15, 'toggle float mode', 'browser') - .style('margin', '3px').on('click', () => this.brlayout.toggleKind('float')); - - ToolbarIcons.createSVG(btns, ToolbarIcons.cross, 15, 'delete GED', 'browser') - .style('margin', '3px').on('click', () => this.removeGed()); - - // be aware, that jsroot_browser_hierarchy required for flexible layout that element use full browser area - this.brlayout.setBrowserContent('<div class=\'jsroot_browser_hierarchy\' id=\'ged_placeholder\'>Loading GED ...</div>'); - this.brlayout.setBrowserTitle('GED'); - this.brlayout.toggleBrowserKind(kind || 'float'); - - return new Promise(resolveFunc => { - loadOpenui5().then(sap => { - d3_select('#ged_placeholder').text(''); - - sap.ui.require(['sap/ui/model/json/JSONModel', 'sap/ui/core/mvc/XMLView'], (JSONModel, XMLView) => { - const oModel = new JSONModel({ handle: null }); - - XMLView.create({ - viewName: 'rootui5.canv.view.Ged', - viewData: this.getUi5PanelData('Ged') - }).then(oGed => { - oGed.setModel(oModel); - - oGed.placeAt('ged_placeholder'); - - this.ged_view = oGed; - - // TODO: should be moved into Ged controller - it must be able to detect canvas painter itself - this.registerForPadEvents(oGed.getController().padEventsReceiver.bind(oGed.getController())); - - objpainter?.getPadPainter()?.selectObjectPainter(objpainter); - - this.processChanges('sbits', this); - - resolveFunc(true); - }); - }); - }); - }); - } - - /** @summary Show section of canvas like menu or editor */ - async showSection(that, on) { - if (this.testUI5()) - return false; - - switch (that) { - case 'Menu': break; - case 'StatusBar': this.activateStatusBar(on); break; - case 'Editor': return this.activateGed(this, null, on); - case 'ToolBar': break; - case 'ToolTips': this.setTooltipAllowed(on); break; - } - return true; - } - - /** @summary Send command to start fit panel code on the server - * @private */ - startFitPanel(standalone) { - if (!this.getWebsocket()) - return false; - - const new_conn = standalone ? null : this.getWebsocket().createChannel(); - - this.sendWebsocket('FITPANEL:' + (standalone ? 'standalone' : new_conn.getChannelId())); - - return new_conn; - } - - /** @summary Complete handling of online canvas drawing - * @private */ - completeCanvasSnapDrawing() { - const pad = this.getRootPad(); - if (!pad) - return; - - this.addPadInteractive(); - - if ((typeof document !== 'undefined') && !this.embed_canvas && this.getWebsocket()) - document.title = pad.fTitle; - - if (this.#all_sections_showed) - return; - this.#all_sections_showed = true; - - // used in Canvas.controller.js to avoid browser resize because of initial sections show/hide - this._ignore_section_resize = true; - - this.showSection('Menu', pad.TestBit(kMenuBar)); - this.showSection('StatusBar', pad.TestBit(kShowEventStatus)); - this.showSection('ToolBar', pad.TestBit(kShowToolBar)); - this.showSection('Editor', pad.TestBit(kShowEditor)); - this.showSection('ToolTips', pad.TestBit(kShowToolTips) || this._highlight_connect); - - this._ignore_section_resize = false; - } - - /** @summary Handle highlight in canvas - deliver information to server - * @private */ - processHighlightConnect(hints) { - if (!hints?.length || !this._highlight_connect || - this.doingDraw() || !this.canSendWebsocket(2)) - return; - - const hint = hints[0] || hints[1]; - if (!hint || !hint.painter?.getSnapId() || !hint.user_info) - return; - const pp = hint.painter.getPadPainter() || this; - if (!pp.getSnapId()) - return; - - const arr = [pp.getSnapId(), hint.painter.getSnapId(), '0', '0']; - - if ((hint.user_info.binx !== undefined) && (hint.user_info.biny !== undefined)) { - arr[2] = hint.user_info.binx.toString(); - arr[3] = hint.user_info.biny.toString(); - } else if (hint.user_info.bin !== undefined) - arr[2] = hint.user_info.bin.toString(); - - const msg = JSON.stringify(arr); - - if (this.#last_highlight_msg !== msg) { - this.#last_highlight_msg = msg; - this.sendWebsocket(`HIGHLIGHT:${msg}`); - } - } - - /** @summary Method informs that something was changed in the canvas - * @desc used to update information on the server (when used with web6gui) - * @private */ - processChanges(kind, painter, subelem) { - // check if we could send at least one message more - for some meaningful actions - if (this.isReadonly() || !this.canSendWebsocket(2) || !isStr(kind)) - return; - - let msg = ''; - if (!painter) - painter = this; - switch (kind) { - case 'sbits': - msg = 'STATUSBITS:' + this.getStatusBits(); - break; - case 'frame': // when changing frame - case 'zoom': // when changing zoom inside frame - if (!isFunc(painter.getWebPadOptions)) - painter = painter.getPadPainter(); - if (isFunc(painter.getWebPadOptions)) - msg = 'OPTIONS6:' + painter.getWebPadOptions('only_this'); - break; - case 'padpos': // when changing pad position - msg = 'OPTIONS6:' + painter.getWebPadOptions('with_subpads'); - break; - case 'drawopt': - if (painter.getSnapId()) - msg = 'DRAWOPT:' + JSON.stringify([painter.getSnapId(), painter.getDrawOpt() || '']); - break; - case 'pave_moved': { - const info = createWebObjectOptions(painter); - if (info) - msg = 'PRIMIT6:' + toJSON(info); - break; - } - case 'logx': - case 'logy': - case 'logz': { - const pp = painter.getPadPainter(), - pad = pp?.getRootPad(); - if (pp?.getSnapId() && pad) { - const name = 'SetLog' + kind[3], value = pad['fLog' + kind[3]]; - painter = pp; - kind = `exec:${name}(${value})`; - } - break; - } - } - - if (!msg && isFunc(painter?.getSnapId) && (kind.slice(0, 5) === 'exec:')) { - const snapid = painter.getSnapId(subelem); - if (snapid) { - msg = 'PRIMIT6:' + toJSON({ - _typename: 'TWebObjectOptions', - snapid, opt: kind.slice(5), fcust: 'exec', fopt: [] - }); - } - } - - if (msg) { - // console.log(`Sending ${msg.length} ${msg.slice(0,40)}`); - this.sendWebsocket(msg); - } else - console.log(`Unprocessed changes ${kind} for painter of ${painter?.getObject()?._typename} subelem ${subelem}`); - } - - /** @summary Select active pad on the canvas */ - selectActivePad(pad_painter, obj_painter, click_pos) { - if (!this.getSnapId() || !pad_painter) - return; // only interactive canvas - - let arg = null, ischanged = false; - const is_button = pad_painter.matchObjectType(clTButton); - - if (pad_painter.getSnapId() && this.getWebsocket()) - arg = { _typename: 'TWebPadClick', padid: pad_painter.getSnapId(), objid: '', x: -1, y: -1, dbl: false }; - - if (!pad_painter.is_active_pad && !is_button) { - ischanged = true; - this.forEachPainterInPad(pp => pp.drawActiveBorder(null, pp === pad_painter), 'pads'); - } - - if (obj_painter?.hasSnapId() && arg) { - ischanged = true; - arg.objid = obj_painter.getSnapId(); - } - - if (click_pos && arg) { - ischanged = true; - arg.x = Math.round(click_pos.x || 0); - arg.y = Math.round(click_pos.y || 0); - if (click_pos.dbl) - arg.dbl = true; - } - - if (arg && (ischanged || is_button)) - this.sendWebsocket('PADCLICKED:' + toJSON(arg)); - } - - /** @summary Return actual TCanvas status bits */ - getStatusBits() { - let bits = 0; - if (this.hasEventStatus()) - bits |= kShowEventStatus; - if (this.hasGed()) - bits |= kShowEditor; - if (this.isTooltipAllowed()) - bits |= kShowToolTips; - if (this.use_openui) - bits |= kMenuBar; - return bits; - } - - /** @summary produce JSON for TCanvas, which can be used to display canvas once again */ - produceJSON(spacing) { - const canv = this.getObject(); - - if (canv._typename !== clTCanvas) - return; - - const fill0 = (canv.fFillStyle === 0), - axes = [], hists = [], prims = []; - - if (fill0) - canv.fFillStyle = 1001; - - this.forEachPainterInPad(pp => { - const pad = pp.getRootPad(true); - if (pp.getNumPainters() && pad && !pad.fPrimitives?.arr.length) { - prims.push(pad, pad.fPrimitives); // remember old value - // create list of primitives when missing - pad.fPrimitives = create(clTList); - pp.forEachPainterInPad(p => { - // ignore all secondary painters - if (p.isSecondary()) - return; - const subobj = p.getObject(); - if (subobj?._typename) - pad.fPrimitives.Add(subobj, p.getDrawOpt()); - }, 'objects'); - } - - const main = pp.getMainPainter(), - fp = pp.getFramePainter(); - if (!isFunc(main?.getHisto) || !isFunc(main?.getDimension)) - return; - - // write selected range into TAxis properties - const hist = main.getHisto(), - ndim = main.getDimension(); - if (!hist?.fXaxis) - return; - - const setAxisRange = (name, axis) => { - if (fp?.zoomChangedInteractive(name)) { - axes.push({ axis, f: axis.fFirst, l: axis.fLast, b: axis.fBits }); - axis.fFirst = main.getSelectIndex(name, 'left', 1); - axis.fLast = main.getSelectIndex(name, 'right'); - axis.SetBit(EAxisBits.kAxisRange, (axis.fFirst > 0) || (axis.fLast < axis.fNbins)); - } - }; - - setAxisRange('x', hist.fXaxis); - if (ndim > 1) - setAxisRange('y', hist.fYaxis); - if (ndim > 2) - setAxisRange('z', hist.fZaxis); - if ((ndim === 2) && fp?.zoomChangedInteractive('z')) { - hists.push({ hist, min: hist.fMinimum, max: hist.fMaximum }); - hist.fMinimum = fp.zoom_zmin ?? fp.zmin; - hist.fMaximum = fp.zoom_zmax ?? fp.zmax; - } - }, 'pads'); - - const res = toJSON(canv, spacing); - - if (fill0) - canv.fFillStyle = 0; - - axes.forEach(e => { - e.axis.fFirst = e.f; - e.axis.fLast = e.l; - e.axis.fBits = e.b; - }); - - hists.forEach(e => { - e.hist.fMinimum = e.min; - e.hist.fMaximum = e.max; - }); - - for (let k = 0; k < prims.length; k += 2) - prims[k].fPrimitives = prims[k + 1]; - - return res; - } - - /** @summary resize browser window */ - resizeBrowser(fullW, fullH) { - if (!fullW || !fullH || this.isBatchMode() || this.embed_canvas || this.batch_mode) - return; - - // workaround for qt-based display where inner window size is used - if (browser.qt6 && fullW > 100 && fullH > 60) { - fullW -= 3; - fullH -= 30; - } - - this.getWebsocket()?.resizeWindow(fullW, fullH); - } - - /** @summary create three.js object for TCanvas */ - static async build3d(can, opt, get_painter) { - const painter = new TCanvasPainter(null, can, opt, true); - painter.checkSpecialsInPrimitives(can, true); - - const fp = new TFramePainter(null, null); - // return dummy frame painter as result - painter.getFramePainter = () => fp; - - return painter.drawPrimitives().then(() => { - return get_painter ? painter : fp.create3DScene(-1, true); - }); - } - - /** @summary draw TCanvas */ - static async draw(dom, can, opt) { - const nocanvas = !can; - if (nocanvas) - can = create(clTCanvas); - - const painter = new TCanvasPainter(dom, can, opt, nocanvas ? 'auto' : true); - painter.checkSpecialsInPrimitives(can, true); - - if (!nocanvas && can.fCw && can.fCh) { - const d = painter.selectDom(); - let apply_size; - if (!painter.isBatchMode()) { - const rect0 = d.node().getBoundingClientRect(); - apply_size = !rect0.height && (rect0.width > 0.1 * can.fCw); - } else { - const arg = d.property('_batch_use_canvsize'); - apply_size = arg || (arg === undefined); - } - if (apply_size) { - d.style('width', can.fCw + 'px').style('height', can.fCh + 'px') - .attr('width', can.fCw).attr('height', can.fCh); - painter._setFixedSize(true); - } - } - - painter.createCanvasSvg(0); - - painter.addPadButtons(); - - if (nocanvas && opt.indexOf('noframe') < 0) - directDrawTFrame(painter, null); - - // select global reference - required for keys handling - selectActivePad({ pp: painter, active: true }); - - return painter.drawPrimitives().then(() => { - painter.addPadInteractive(); - painter.showPadButtons(); - return painter; - }); - } - -} // class TCanvasPainter - - -/** @summary Ensure TCanvas and TFrame for the painter object - * @param {object} painter - painter object to process - * @param {string|boolean} frame_kind - false for no frame or '3d' for special 3D mode - * @desc Assign dom, creates TCanvas if necessary, add to list of pad painters */ -async function ensureTCanvas(painter, frame_kind) { - if (!painter) - return Promise.reject(Error('Painter not provided in ensureTCanvas')); - - // simple check - if canvas there, can use painter - const noframe = (frame_kind === false) || (frame_kind === '3d') ? 'noframe' : '', - createCanv = () => { - if ((noframe !== 'noframe') || !isFunc(painter.getUserRanges)) - return null; - const ranges = painter.getUserRanges(); - if (!ranges) - return null; - const canv = create(clTCanvas), - dx = (ranges.maxx - ranges.minx) || 1, - dy = (ranges.maxy - ranges.miny) || 1; - canv.fX1 = ranges.minx - dx * gStyle.fPadLeftMargin; - canv.fX2 = ranges.maxx + dx * gStyle.fPadRightMargin; - canv.fY1 = ranges.miny - dy * gStyle.fPadBottomMargin; - canv.fY2 = ranges.maxy + dy * gStyle.fPadTopMargin; - return canv; - }, - pad_painter = painter.getPadPainter() || getDomCanvasPainter(painter.selectDom()), - promise = pad_painter ? Promise.resolve(pad_painter) : - TCanvasPainter.draw(painter.getDom(), createCanv(), noframe); - - return promise.then(pp => { - if ((frame_kind !== false) && pp.getFrameSvg().selectChild('.main_layer').empty() && !pp.getFramePainter()) - directDrawTFrame(pp, null, frame_kind); - - painter.addToPadPrimitives(pp); - return painter; - }); -} - -/** @summary draw TPad snapshot from TWebCanvas - * @private */ -async function drawTPadSnapshot(dom, snap, opt) { - const can = create(clTCanvas), - painter = new TCanvasPainter(dom, can, opt); - painter.addPadButtons(); - - return painter.syncDraw(true).then(() => painter.redrawPadSnap(snap)).then(() => { - painter.confirmDraw(); - painter.showPadButtons(); - return painter; - }); -} - -/** @summary draw TFrame object - * @private */ -async function drawTFrame(dom, obj, opt) { - const fp = new TFramePainter(dom, obj); - fp.mode3d = opt === '3d'; - return ensureTCanvas(fp, false).then(() => fp.redraw()); -} - -Object.assign(internals.jsroot, { ensureTCanvas, TPadPainter, TCanvasPainter }); - -export { ensureTCanvas, drawTPadSnapshot, drawTFrame, TPadPainter, TCanvasPainter }; diff --git a/js/modules/gpad/TFramePainter.mjs b/js/modules/gpad/TFramePainter.mjs deleted file mode 100644 index fea17f17bb321..0000000000000 --- a/js/modules/gpad/TFramePainter.mjs +++ /dev/null @@ -1,3331 +0,0 @@ -import { gStyle, settings, internals, isFunc, isStr, postponePromise, browser, - clTAxis, clTFrame, kNoZoom, urlClassPrefix } from '../core.mjs'; -import { select as d3_select, pointer as d3_pointer, - pointers as d3_pointers, drag as d3_drag, rgb as d3_rgb } from '../d3.mjs'; -import { getElementRect, getAbsPosInCanvas, makeTranslate, addHighlightStyle, getBoxDecorations } from '../base/BasePainter.mjs'; -import { getActivePad, ObjectPainter, EAxisBits, kAxisLabels } from '../base/ObjectPainter.mjs'; -import { getSvgLineStyle } from '../base/TAttLineHandler.mjs'; -import { TAxisPainter } from './TAxisPainter.mjs'; -import { FontHandler } from '../base/FontHandler.mjs'; -import { createMenu, closeMenu, showPainterMenu, hasMenu } from '../gui/menu.mjs'; -import { detectRightButton } from '../gui/utils.mjs'; - - -const logminfactorX = 0.0001, logminfactorY = 3e-4; - -/** @summary Configure tooltip enable flag for painter - * @private */ -function setPainterTooltipEnabled(painter, on) { - if (!painter) - return; - - const fp = painter.getFramePainter(); - if (isFunc(fp?.setTooltipEnabled)) { - fp.setTooltipEnabled(on); - fp.processFrameTooltipEvent(null); - } - // this is 3D control object - if (isFunc(painter.control?.setTooltipEnabled)) - painter.control.setTooltipEnabled(on); -} - -/** @summary Return pointers on touch event - * @private */ -function get_touch_pointers(event, node) { - return event.$touch_arr ?? d3_pointers(event, node); -} - -/** @summary Returns coordinates transformation func - * @private */ -function getEarthProjectionFunc(id) { - switch (id) { - // Aitoff2xy - case 1: return (l, b) => { - const DegToRad = Math.PI / 180, - alpha2 = (l / 2) * DegToRad, - delta = b * DegToRad, - r2 = Math.sqrt(2), - f = 2 * r2 / Math.PI, - cdec = Math.cos(delta), - denom = Math.sqrt(1.0 + cdec * Math.cos(alpha2)); - return { - x: cdec * Math.sin(alpha2) * 2.0 * r2 / denom / f / DegToRad, - y: Math.sin(delta) * r2 / denom / f / DegToRad - }; - }; - // mercator - case 2: return (l, b) => { return { x: l, y: Math.log(Math.tan((Math.PI / 2 + b / 180 * Math.PI) / 2)) }; }; - // sinusoidal - case 3: return (l, b) => { return { x: l * Math.cos(b / 180 * Math.PI), y: b }; }; - // parabolic - case 4: return (l, b) => { return { x: l * (2.0 * Math.cos(2 * b / 180 * Math.PI / 3) - 1), y: 180 * Math.sin(b / 180 * Math.PI / 3) }; }; - // Mollweide projection - case 5: return (l, b) => { - const theta0 = b * Math.PI / 180; - let theta = theta0, num, den; - for (let i = 0; i < 100; i++) { - num = 2 * theta + Math.sin(2 * theta) - Math.PI * Math.sin(theta0); - den = 4 * (Math.cos(theta) ** 2); - if (den < 1e-20) { - theta = theta0; - break; - } - theta -= num / den; - if (Math.abs(num / den) < 1e-4) - break; - } - return { - x: l * Math.cos(theta), - y: 90 * Math.sin(theta) - }; - }; - } -} - -/** @summary Unzoom preselected range for main histogram painter - * @desc Used with TGraph where Y zooming selected with fMinimum/fMaximum but histogram - * axis range can be wider. Or for normal histogram drawing when preselected range smaller than histogram range - * @private */ -function unzoomHistogramYRange(main) { - if (!isFunc(main?.getDimension) || main.getDimension() !== 1) - return; - - const ymin = main.draw_content ? main.hmin : main.ymin, - ymax = main.draw_content ? main.hmax : main.ymax; - - if ((main.zoom_ymin !== main.zoom_ymax) && (ymin !== ymax) && - (ymin <= main.zoom_ymin) && (main.zoom_ymax <= ymax)) - main.zoom_ymin = main.zoom_ymax = 0; -} - -// global, allow single drag at once -let drag_rect = null, drag_kind = '', drag_painter = null; - -/** @summary Check if dragging performed currently - * @private */ -function is_dragging(painter, kind) { - return drag_rect && (drag_painter === painter) && (drag_kind === kind); -} - -/** @summary Add drag for interactive rectangular elements for painter - * @private */ -function addDragHandler(_painter, arg) { - if (!settings.MoveResize) - return; - - const painter = _painter, pp = painter.getPadPainter(); - if (pp?.isFastDrawing() || pp?.isBatchMode()) - return; - // cleanup all drag elements when canvas is not editable - if (pp?.isEditable() === false) - arg.cleanup = true; - - if (!isFunc(arg.getDrawG)) - arg.getDrawG = () => painter?.getG(); - - function makeResizeElements(group, handler) { - function addElement(cursor, d) { - const clname = 'js_' + cursor.replace(/[-]/g, '_'); - let elem = group.selectChild('.' + clname); - if (arg.cleanup) - return elem.remove(); - if (elem.empty()) - elem = group.append('path').classed(clname, true); - elem.style('opacity', 0).style('cursor', cursor).attr('d', d); - if (handler) - elem.call(handler); - } - - addElement('nw-resize', 'M2,2h15v-5h-20v20h5Z'); - addElement('ne-resize', `M${arg.width - 2},2h-15v-5h20v20h-5 Z`); - addElement('sw-resize', `M2,${arg.height - 2}h15v5h-20v-20h5Z`); - addElement('se-resize', `M${arg.width - 2},${arg.height - 2}h-15v5h20v-20h-5Z`); - - if (!arg.no_change_x) { - addElement('w-resize', `M-3,18h5v${Math.max(0, arg.height - 2 * 18)}h-5Z`); - addElement('e-resize', `M${arg.width + 3},18h-5v${Math.max(0, arg.height - 2 * 18)}h5Z`); - } - if (!arg.no_change_y) { - addElement('n-resize', `M18,-3v5h${Math.max(0, arg.width - 2 * 18)}v-5Z`); - addElement('s-resize', `M18,${arg.height + 3}v-5h${Math.max(0, arg.width - 2 * 18)}v5Z`); - } - } - - const complete_drag = (newx, newy, newwidth, newheight) => { - drag_painter = null; - drag_kind = ''; - if (drag_rect) { - drag_rect.remove(); - drag_rect = null; - } - - const draw_g = arg.getDrawG(); - - if (!draw_g) - return false; - - const oldx = arg.x, oldy = arg.y; - - if (arg.minwidth && newwidth < arg.minwidth) - newwidth = arg.minwidth; - if (arg.minheight && newheight < arg.minheight) - newheight = arg.minheight; - - const change_size = (newwidth !== arg.width) || (newheight !== arg.height), - change_pos = (newx !== oldx) || (newy !== oldy); - - arg.x = newx; - arg.y = newy; - arg.width = newwidth; - arg.height = newheight; - - if (!arg.no_transform) - makeTranslate(draw_g, newx, newy); - - setPainterTooltipEnabled(painter, true); - - makeResizeElements(draw_g); - - if (change_size || change_pos) { - if (change_size && isFunc(arg.resize)) - arg.resize(newwidth, newheight); - - if (change_pos && isFunc(arg.move)) - arg.move(newx, newy, newx - oldx, newy - oldy); - - if (change_size || change_pos) { - if (arg.obj) { - const rect = arg.pad_rect ?? pp.getPadRect(); - arg.obj.fX1NDC = newx / rect.width; - arg.obj.fX2NDC = (newx + newwidth) / rect.width; - arg.obj.fY1NDC = 1 - (newy + newheight) / rect.height; - arg.obj.fY2NDC = 1 - newy / rect.height; - arg.obj.$modifiedNDC = true; // indicate that NDC was interactively changed, block in updated - } else if (isFunc(arg.move_resize)) - arg.move_resize(newx, newy, newwidth, newheight); - - if (isFunc(arg.redraw)) - arg.redraw(arg); - } - } - - return change_size || change_pos; - }, drag_move = d3_drag().subject(Object), drag_move_off = d3_drag().subject(Object); - - drag_move_off.on('start', null).on('drag', null).on('end', null); - - drag_move - .on('start', evnt => { - if (detectRightButton(evnt.sourceEvent) || drag_kind) - return; - if (isFunc(arg.is_disabled) && arg.is_disabled('move')) - return; - - closeMenu(); // close menu - setPainterTooltipEnabled(painter, false); // disable tooltip - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { - x: arg.x, y: arg.y, width: arg.width, height: arg.height, - acc_x1: arg.x, acc_y1: arg.y, - pad_w: pad_rect.width - arg.width, - pad_h: pad_rect.height - arg.height, - drag_tm: new Date(), - path: `v${arg.height}h${arg.width}v${-arg.height}z`, - evnt_x: evnt.x, evnt_y: evnt.y - }; - - drag_painter = painter; - drag_kind = 'move'; - drag_rect = d3_select(arg.getDrawG().node().parentNode).append('path') - .attr('d', `M${handle.acc_x1},${handle.acc_y1}${handle.path}`) - .style('cursor', 'move') - .style('pointer-events', 'none') // let forward double click to underlying elements - .property('drag_handle', handle) - .call(addHighlightStyle, true); - }).on('drag', evnt => { - if (!is_dragging(painter, 'move')) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const handle = drag_rect.property('drag_handle'); - - if (!arg.no_change_x) - handle.acc_x1 += evnt.dx; - if (!arg.no_change_y) - handle.acc_y1 += evnt.dy; - - handle.x = Math.min(Math.max(handle.acc_x1, 0), handle.pad_w); - handle.y = Math.min(Math.max(handle.acc_y1, 0), handle.pad_h); - - drag_rect.attr('d', `M${handle.x},${handle.y}${handle.path}`); - }).on('end', evnt => { - if (!is_dragging(painter, 'move')) - return; - - evnt.sourceEvent.stopPropagation(); - evnt.sourceEvent.preventDefault(); - - const handle = drag_rect.property('drag_handle'); - - if (complete_drag(handle.x, handle.y, arg.width, arg.height) === false) { - const spent = (new Date()).getTime() - handle.drag_tm.getTime(); - - if (arg.ctxmenu && (spent > 600)) - showPainterMenu({ clientX: handle.evnt_x, clientY: handle.evnt_y, skip_close: 1 }, painter); - else if (arg.canselect && (spent <= 600)) - painter.getPadPainter()?.selectObjectPainter(painter); - } - }); - - const drag_resize = d3_drag().subject(Object); - - drag_resize - .on('start', function(evnt) { - if (detectRightButton(evnt.sourceEvent) || drag_kind) - return; - if (isFunc(arg.is_disabled) && arg.is_disabled('resize')) - return; - - closeMenu(); // close menu - setPainterTooltipEnabled(painter, false); // disable tooltip - - evnt.sourceEvent.stopPropagation(); - evnt.sourceEvent.preventDefault(); - - const pad_rect = arg.pad_rect ?? pp.getPadRect(), handle = { - x: arg.x, y: arg.y, width: arg.width, height: arg.height, - acc_x1: arg.x, acc_y1: arg.y, - acc_x2: arg.x + arg.width, acc_y2: arg.y + arg.height, - pad_w: pad_rect.width, pad_h: pad_rect.height - }; - - drag_painter = painter; - drag_kind = 'resize'; - drag_rect = d3_select(arg.getDrawG().node().parentNode) - .append('rect') - .style('cursor', d3_select(this).style('cursor')) - .attr('x', handle.acc_x1) - .attr('y', handle.acc_y1) - .attr('width', handle.acc_x2 - handle.acc_x1) - .attr('height', handle.acc_y2 - handle.acc_y1) - .property('drag_handle', handle) - .call(addHighlightStyle, true); - }).on('drag', function(evnt) { - if (!is_dragging(painter, 'resize')) - return; - - evnt.sourceEvent.preventDefault(); - evnt.sourceEvent.stopPropagation(); - - const handle = drag_rect.property('drag_handle'), - elem = d3_select(this), - dx = arg.no_change_x ? 0 : evnt.dx, - dy = arg.no_change_y ? 0 : evnt.dy; - - if (elem.classed('js_nw_resize')) { - handle.acc_x1 += dx; - handle.acc_y1 += dy; - } else if (elem.classed('js_ne_resize')) { - handle.acc_x2 += dx; - handle.acc_y1 += dy; - } else if (elem.classed('js_sw_resize')) { - handle.acc_x1 += dx; - handle.acc_y2 += dy; - } else if (elem.classed('js_se_resize')) { - handle.acc_x2 += dx; - handle.acc_y2 += dy; - } else if (elem.classed('js_w_resize')) - handle.acc_x1 += dx; - else if (elem.classed('js_n_resize')) - handle.acc_y1 += dy; - else if (elem.classed('js_e_resize')) - handle.acc_x2 += dx; - else if (elem.classed('js_s_resize')) - handle.acc_y2 += dy; - - const x1 = Math.max(0, handle.acc_x1), x2 = Math.min(handle.acc_x2, handle.pad_w), - y1 = Math.max(0, handle.acc_y1), y2 = Math.min(handle.acc_y2, handle.pad_h); - - handle.x = Math.min(x1, x2); - handle.y = Math.min(y1, y2); - handle.width = Math.abs(x2 - x1); - handle.height = Math.abs(y2 - y1); - - drag_rect.attr('x', handle.x).attr('y', handle.y).attr('width', handle.width).attr('height', handle.height); - }).on('end', evnt => { - if (!is_dragging(painter, 'resize')) - return; - - evnt.sourceEvent.preventDefault(); - - const handle = drag_rect.property('drag_handle'); - - complete_drag(handle.x, handle.y, handle.width, handle.height); - }); - - if (!arg.only_resize) - arg.getDrawG().style('cursor', arg.cleanup ? null : 'move').call(arg.cleanup ? drag_move_off : drag_move); - - if (!arg.only_move) - makeResizeElements(arg.getDrawG(), drag_resize); -} - -/** @summary Tooltip handler class - * @private */ -class TooltipHandler extends ObjectPainter { - - // cannot use private members because of RFramePainter - - /** @desc only canvas info_layer can be used while other pads can overlay - * @return layer where frame tooltips are shown */ - hints_layer() { - return this.getCanvPainter()?.getLayerSvg('info_layer') ?? d3_select(null); - } - - /** @return true if tooltip is shown, use to prevent some other action */ - isTooltipShown() { - if (!this.tooltip_enabled || !this.isTooltipAllowed()) - return false; - const hintsg = this.hints_layer().selectChild('.objects_hints'); - return hintsg.empty() ? false : hintsg.property('hints_pad') === this.getPadPainter()?.getPadName(); - } - - /** @summary set tooltips enabled on/off */ - setTooltipEnabled(enabled) { - if (enabled !== undefined) - this.tooltip_enabled = enabled; - } - - /** @summary central function which let show selected hints for the object */ - processFrameTooltipEvent(pnt, evnt) { - if (pnt?.handler) { - // special use of interactive handler in the frame painter - const rect = this.getG()?.selectChild('.main_layer'); - if (!rect || rect.empty()) - pnt = null; // disable - else if (pnt.touch && evnt) { - const pos = get_touch_pointers(evnt, rect.node()); - pnt = (pos && pos.length === 1) ? { touch: true, x: pos[0][0], y: pos[0][1] } : null; - } else if (evnt) { - const pos = d3_pointer(evnt, rect.node()); - pnt = { touch: false, x: pos[0], y: pos[1] }; - } - } - - let nhints = 0, nexact = 0, maxlen = 0, lastcolor1 = 0, usecolor1 = false; - const hmargin = 3, wmargin = 3, hstep = 1.2, - frame_rect = this.getFrameRect(), - pp = this.getPadPainter(), - pad_width = pp?.getPadWidth(), - scale = pp?.getPadScale() ?? 1, - textheight = (pnt?.touch ? 15 : 11) * scale, - font = new FontHandler(160, textheight), - disable_tooltips = !this.isTooltipAllowed() || !this.tooltip_enabled; - - if (pnt) { - pnt.disabled = disable_tooltips; // indicate that highlighting is not required - pnt.painters = true; // get also painter - } - - // collect tooltips from pad painter - it has list of all drawn objects - const hints = pp?.processPadTooltipEvent(pnt) ?? []; - - if (pnt && frame_rect) - pp.deliverWebCanvasEvent('move', frame_rect.x + pnt.x, frame_rect.y + pnt.y, hints ? hints[0]?.painter?.getSnapId() : ''); - - for (let n = 0; n < hints.length; ++n) { - const hint = hints[n]; - if (!hint) - continue; - - if (hint.user_info !== undefined) - hint.painter?.provideUserTooltip(hint.user_info); - - if (!hint.lines?.length) { - hints[n] = null; - continue; - } - - // check if fully duplicated hint already exists - for (let k = 0; k < n; ++k) { - const hprev = hints[k]; - let diff = hprev?.lines.length !== hint.lines.length; - for (let l = 0; l < hint.lines.length && !diff; ++l) { - if (hprev.lines[l] !== hint.lines[l]) - diff = true; - } - if (!diff) { - hints[n] = null; - break; - } - } - if (!hints[n]) - continue; - - nhints++; - - if (hint.exact) - nexact++; - - hint.lines.forEach(line => { maxlen = Math.max(maxlen, line.length); }); - - hint.height = Math.round(hint.lines.length * textheight * hstep + 2 * hmargin - textheight * (hstep - 1)); - - if ((hint.color1 !== undefined) && (hint.color1 !== 'none')) { - if (lastcolor1 && (lastcolor1 !== hint.color1)) - usecolor1 = true; - lastcolor1 = hint.color1; - } - } - - let path_name = null, same_path = hints.length > 1; - for (let n = 0; n < hints.length; ++n) { - const hint = hints[n], p = hint?.lines ? hint.lines[0]?.lastIndexOf('/') : -1; - if (p > 0) { - const path = hint.lines[0].slice(0, p + 1); - if (path_name === null) - path_name = path; - else if (path_name !== path) - same_path = false; - } else - same_path = false; - } - - const layer = this.hints_layer(), - show_only_best = nhints > 15, - coordinates = pnt ? Math.round(pnt.x) + ',' + Math.round(pnt.y) : ''; - let hintsg = layer.selectChild('.objects_hints'), // group with all tooltips - title = '', name = '', info = '', - hint0 = null, best_dist2 = 1e10, best_hint = null; - - // try to select hint with exact match of the position when several hints available - for (let k = 0; k < hints.length; ++k) { - if (!hints[k]) - continue; - if (!hint0) - hint0 = hints[k]; - - // select exact hint if this is the only one - if (hints[k].exact && (nexact < 2) && (!hint0 || !hint0.exact)) { - hint0 = hints[k]; - break; - } - - if (!pnt || (hints[k].x === undefined) || (hints[k].y === undefined)) - continue; - - const dist2 = (pnt.x - hints[k].x) ** 2 + (pnt.y - hints[k].y) ** 2; - if (dist2 < best_dist2) { - best_dist2 = dist2; - best_hint = hints[k]; - } - } - - if ((!hint0 || !hint0.exact) && (best_dist2 < 400)) - hint0 = best_hint; - - if (hint0) { - name = (hint0.lines && hint0.lines.length > 1) ? hint0.lines[0] : hint0.name; - title = hint0.title || ''; - info = hint0.line; - if (!info && hint0.lines) - info = hint0.lines.slice(1).join(' '); - } - - this.showObjectStatus(name, title, info, coordinates); - - // end of closing tooltips - if (!pnt || disable_tooltips || !hints.length || (maxlen === 0) || (show_only_best && !best_hint)) { - hintsg.remove(); - return; - } - - // we need to set pointer-events=none for all elements while hints - // placed in front of so-called interactive rect in frame, used to catch mouse events - - if (hintsg.empty()) { - hintsg = layer.append('svg:g') - .attr('class', 'objects_hints') - .style('pointer-events', 'none'); - } - - let frame_shift = { x: 0, y: 0 }, trans = frame_rect.transform || ''; - if (!pp?.isCanvas()) { - frame_shift = getAbsPosInCanvas(pp.getPadSvg(), frame_shift); - trans = `translate(${frame_shift.x},${frame_shift.y}) ${trans}`; - } - - // copy transform attributes from frame itself - hintsg.attr('transform', trans) - .property('last_point', pnt) - .property('hints_pad', pp.getPadName()); - - let viewmode = hintsg.property('viewmode') || '', - actualw = 0, posx = pnt.x + frame_rect.hint_delta_x; - - if (show_only_best || (nhints === 1)) { - viewmode = 'single'; - posx += 15; - } else { - // if there are many hints, place them left or right - - let bleft = 0.5, bright = 0.5; - - if (viewmode === 'left') - bright = 0.7; - else if (viewmode === 'right') - bleft = 0.3; - - if (posx <= bleft * frame_rect.width) { - viewmode = 'left'; - posx = 20; - } else if (posx >= bright * frame_rect.width) { - viewmode = 'right'; - posx = frame_rect.width - 60; - } else - posx = hintsg.property('startx') ?? 20; - } - - if (viewmode !== hintsg.property('viewmode')) { - hintsg.property('viewmode', viewmode); - hintsg.selectAll('*').remove(); - } - - let curry = 10, // normal y coordinate - gapy = 10, // y coordinate, taking into account all gaps - gapminx = -1111, gapmaxx = -1111; - const minhinty = -frame_shift.y, - cp = this.getCanvPainter(), - maxhinty = cp.getPadHeight() - frame_rect.y - frame_shift.y; - - for (let n = 0; n < hints.length; ++n) { - let hint = hints[n], - group = hintsg.selectChild(`.painter_hint_${n}`); - - if (show_only_best && (hint !== best_hint)) - hint = null; - - if (hint === null) { - group.remove(); - continue; - } - - const was_empty = group.empty(); - - if (was_empty) { - group = hintsg.append('svg:svg') - .attr('class', `painter_hint_${n}`) - .attr('opacity', 0) // use attribute, not style to make animation with d3.transition() - .style('overflow', 'hidden') - .style('pointer-events', 'none'); - } - - if (viewmode === 'single') - curry = pnt.touch ? (pnt.y - hint.height - 5) : Math.min(pnt.y + 15, maxhinty - hint.height - 3) + frame_rect.hint_delta_y; - else { - for (let n2 = 0; (n2 < hints.length) && (gapy < maxhinty); ++n2) { - const hint2 = hints[n2]; - if (!hint2) - continue; - if ((hint2.y >= gapy - 5) && (hint2.y <= gapy + hint2.height + 5)) { - gapy = hint2.y + 10; - n2 = -1; - } - } - if ((gapminx === -1111) && (gapmaxx === -1111)) - gapminx = gapmaxx = hint.x; - gapminx = Math.min(gapminx, hint.x); - gapmaxx = Math.max(gapmaxx, hint.x); - } - - group.attr('x', posx) - .attr('y', curry) - .property('curry', curry) - .property('gapy', gapy); - - curry += hint.height + 5; - gapy += hint.height + 5; - - if (!was_empty) - group.selectAll('*').remove(); - - group.attr('width', 60) - .attr('height', hint.height); - - const r = group.append('rect') - .attr('x', 0) - .attr('y', 0) - .attr('width', 60) - .attr('height', hint.height) - .style('fill', 'lightgrey') - .style('pointer-events', 'none'); - - if (nhints > 1) { - const col = usecolor1 ? hint.color1 : hint.color2; - if (col && (col !== 'none')) - r.style('stroke', col); - } - r.attr('stroke-width', hint.exact ? 3 : 1); - - for (let l = 0; l < (hint.lines?.length ?? 0); l++) { - let line = hint.lines[l]; - if (l === 0 && path_name && same_path) - line = line.slice(path_name.length); - if (line) { - const txt = group.append('svg:text') - .attr('text-anchor', 'start') - .attr('x', wmargin) - .attr('y', hmargin + l * textheight * hstep) - .attr('dy', '.8em') - .style('fill', 'black') - .style('pointer-events', 'none') - .call(font.func) - .text(line), - box = getElementRect(txt, 'bbox'); - - actualw = Math.max(actualw, box.width); - } - } - - function translateFn() { - // We only use 'd', but list d,i,a as params just to show can have them as params. - // Code only really uses d and t. - return function(/* d, i, a */) { - return function(t) { - return t < 0.8 ? '0' : (t - 0.8) * 5; - }; - }; - } - - if (was_empty) { - if (settings.TooltipAnimation > 0) - group.transition().duration(settings.TooltipAnimation).attrTween('opacity', translateFn()); - else - group.attr('opacity', 1); - } - } - - actualw += 2 * wmargin; - - const svgs = hintsg.selectAll('svg'); - - if ((viewmode === 'right') && (posx + actualw > frame_rect.width - 20)) { - posx = frame_rect.width - actualw - 20; - svgs.attr('x', posx); - } - - if ((viewmode === 'single') && (posx + actualw > pad_width - frame_rect.x) && (posx > actualw + 20)) { - posx -= (actualw + 20); - svgs.attr('x', posx); - } - - // if gap not very big, apply gapy coordinate to open view on the histogram - if ((viewmode !== 'single') && (gapy < maxhinty) && (gapy !== curry)) { - if ((gapminx <= posx + actualw + 5) && (gapmaxx >= posx - 5)) - svgs.attr('y', function() { return d3_select(this).property('gapy'); }); - } else if ((viewmode !== 'single') && (curry > maxhinty)) { - const shift = Math.max((maxhinty - curry - 10), minhinty); - if (shift < 0) - svgs.attr('y', function() { return d3_select(this).property('curry') + shift; }); - } - - if (actualw > 10) - svgs.attr('width', actualw).select('rect').attr('width', actualw); - - hintsg.property('startx', posx); - - if (cp._highlight_connect && isFunc(cp.processHighlightConnect)) - cp.processHighlightConnect(hints); - } - -} // class TooltipHandler - - -/** @summary Frame interactivity class - * @private */ -class FrameInteractive extends TooltipHandler { - - // cannot use private members because of RFramePainter - - /** @summary Adding basic interactivity */ - addBasicInteractivity() { - this.setTooltipEnabled(true); - - if (this.$can_drag) { - addDragHandler(this, { - obj: this, x: this.getFrameX(), y: this.getFrameY(), width: this.getFrameWidth(), height: this.getFrameHeight(), - is_disabled: kind => { return (kind === 'move') && this.mode3d; }, - only_resize: true, minwidth: 20, minheight: 20, redraw: () => this.sizeChanged() - }); - } - - const top_rect = this.getG().selectChild('path'), - main_svg = this.getG().selectChild('.main_layer'); - - top_rect.style('pointer-events', 'visibleFill') // let process mouse events inside frame - .style('cursor', 'default'); // show normal cursor - - main_svg.style('pointer-events', 'visibleFill') - .style('cursor', 'default') - .property('handlers_set', 0); - - const handlers_set = this.getPadPainter()?.isFastDrawing() ? 0 : 1; - - if (main_svg.property('handlers_set') !== handlers_set) { - const close_handler = handlers_set ? evnt => this.processFrameTooltipEvent(null, evnt) : null, - mouse_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: false }, evnt) : null; - - main_svg.property('handlers_set', handlers_set) - .on('mouseenter', mouse_handler) - .on('mousemove', mouse_handler) - .on('mouseleave', close_handler); - - if (browser.touches) { - const touch_handler = handlers_set ? evnt => this.processFrameTooltipEvent({ handler: true, touch: true }, evnt) : null; - - main_svg.on('touchstart', touch_handler) - .on('touchmove', touch_handler) - .on('touchend', close_handler) - .on('touchcancel', close_handler); - } - } - - main_svg.attr('x', 0) - .attr('y', 0) - .attr('width', this.getFrameWidth()) - .attr('height', this.getFrameHeight()); - - const hintsg = this.hints_layer().selectChild('.objects_hints'); - // if tooltips were visible before, try to reconstruct them after short timeout - if (!hintsg.empty() && this.isTooltipAllowed() && (hintsg.property('hints_pad') === this.getPadPainter()?.getPadName())) - setTimeout(() => this.processFrameTooltipEvent(hintsg.property('last_point'), null), 10); - } - - getFrameSvg() { return this.getPadPainter().getFrameSvg(); } - - /** @summary Add interactive handlers */ - async addFrameInteractivity(for_second_axes) { - const pp = this.getPadPainter(), - svg = this.getFrameSvg(); - if (pp?.isFastDrawing() || svg.empty()) - return this; - - if (for_second_axes) { - // add extra handlers for second axes - const svg_x2 = svg.selectAll('.x2axis_container'), - svg_y2 = svg.selectAll('.y2axis_container'); - if (settings.ContextMenu) { - svg_x2.on('contextmenu', evnt => this.showContextMenu('x2', evnt)); - svg_y2.on('contextmenu', evnt => this.showContextMenu('y2', evnt)); - } - svg_x2.on('mousemove', evnt => this.showAxisStatus('x2', evnt)); - svg_y2.on('mousemove', evnt => this.showAxisStatus('y2', evnt)); - return this; - } - - const svg_x = svg.selectAll('.xaxis_container'), - svg_y = svg.selectAll('.yaxis_container'); - - this.can_zoom_x = this.can_zoom_y = settings.Zooming; - - if (pp?.options) { - if (pp.options.NoZoomX) - this.can_zoom_x = false; - if (pp.options.NoZoomY) - this.can_zoom_y = false; - } - - if (!svg.property('interactive_set')) { - this.addKeysHandler(); - - this.zoom_kind = 0; // 0 - none, 1 - XY, 2 - only X, 3 - only Y, (+100 for touches) - this.zoom_rect = null; - this.zoom_origin = null; // original point where zooming started - this.zoom_curr = null; // current point for zooming - } - - if (settings.Zooming) { - if (settings.ZoomMouse) { - svg.on('mousedown', evnt => this.startRectSel(evnt)); - svg.on('dblclick', evnt => this.mouseDoubleClick(evnt)); - } - if (settings.ZoomWheel) - svg.on('wheel', evnt => this.mouseWheel(evnt)); - } - - if (browser.touches && ((settings.Zooming && settings.ZoomTouch) || settings.ContextMenu)) - svg.on('touchstart', evnt => this.startTouchZoom(evnt)); - - if (settings.ContextMenu) { - if (browser.touches) { - svg_x.on('touchstart', evnt => this.startSingleTouchHandling('x', evnt)); - svg_y.on('touchstart', evnt => this.startSingleTouchHandling('y', evnt)); - } - svg.on('contextmenu', evnt => this.showContextMenu('', evnt)); - svg_x.on('contextmenu', evnt => this.showContextMenu('x', evnt)); - svg_y.on('contextmenu', evnt => this.showContextMenu('y', evnt)); - } - - svg_x.on('mousemove', evnt => this.showAxisStatus('x', evnt)); - svg_y.on('mousemove', evnt => this.showAxisStatus('y', evnt)); - - svg.property('interactive_set', true); - - return this; - } - - /** @summary Handle key press */ - processKeyPress(evnt) { - // no custom keys handling when menu is present - if (hasMenu()) - return true; - - const allowed = ['PageUp', 'PageDown', 'ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'PrintScreen', 'Escape', '*'], - main = this.selectDom(), - pp = this.getPadPainter(); - let key = evnt.key; - - if (!settings.HandleKeys || main.empty() || (this.isEnabledKeys() === false) || - (getActivePad() !== pp) || (allowed.indexOf(key) < 0)) - return false; - - if (evnt.shiftKey) - key = `Shift ${key}`; - if (evnt.altKey) - key = `Alt ${key}`; - if (evnt.ctrlKey) - key = `Ctrl ${key}`; - - const zoom = { name: 'x', dleft: 0, dright: 0 }; - - switch (key) { - case 'ArrowLeft': - zoom.dleft = -1; - zoom.dright = 1; - break; - case 'ArrowRight': - zoom.dleft = 1; - zoom.dright = -1; - break; - case 'Ctrl ArrowLeft': - zoom.dleft = zoom.dright = -1; - break; - case 'Ctrl ArrowRight': - zoom.dleft = zoom.dright = 1; - break; - case 'ArrowUp': - zoom.name = 'y'; - zoom.dleft = 1; - zoom.dright = -1; - break; - case 'ArrowDown': - zoom.name = 'y'; - zoom.dleft = -1; - zoom.dright = 1; - break; - case 'Ctrl ArrowUp': - zoom.name = 'y'; - zoom.dleft = zoom.dright = 1; - break; - case 'Ctrl ArrowDown': - zoom.name = 'y'; - zoom.dleft = zoom.dright = -1; - break; - case 'Escape': - pp?.enlargePad(null, false, true); - return true; - } - - if (zoom.dleft || zoom.dright) { - if (!settings.Zooming) - return false; - // in 3d mode with orbit control ignore simple arrows, - // intentionally no compare for indexOf while only Ctrl is allowed - if (this.mode3d && key.indexOf('Ctrl')) - return false; - this.analyzeMouseWheelEvent(null, zoom, 0.5); - if (zoom.changed) - this.zoomSingle(zoom.name, zoom.min, zoom.max, true); - evnt.stopPropagation(); - evnt.preventDefault(); - } else { - const func = pp?.findPadButton(key); - if (func) { - pp.clickPadButton(func); - evnt.stopPropagation(); - evnt.preventDefault(); - } - } - - return true; // just process any key press - } - - /** @summary Function called when frame is clicked and object selection can be performed - * @desc such event can be used to select */ - processFrameClick(pnt, dblckick) { - const pp = this.getPadPainter(); - if (!pp) - return; - - pnt.painters = true; // provide painters reference in the hints - pnt.disabled = true; // do not invoke graphics - - // collect tooltips from pad painter - it has list of all drawn objects - const hints = pp.processPadTooltipEvent(pnt); - let exact = null, res; - for (let k = 0; (k < hints.length) && !exact; ++k) { - if (hints[k] && hints[k].exact) - exact = hints[k]; - } - - if (exact) { - const handler = dblckick ? this.getDblclickHandler() : this.getClickHandler(); - if (isFunc(handler)) - res = handler(exact.user_info, pnt); - } - - if (!dblckick) { - pp.selectObjectPainter(exact ? exact.painter : this, - { x: pnt.x + this.getFrameX(), y: pnt.y + this.getFrameY() }); - } - - return res; - } - - /** @summary Check mouse moving */ - shiftMoveHanlder(evnt, pos0) { - if (evnt.buttons === this._shifting_buttons) { - const frame = this.getFrameSvg(), - pos = d3_pointer(evnt, frame.node()), - main_svg = this.getG().selectChild('.main_layer'), - dx = pos0[0] - pos[0], - dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], - w = this.getFrameWidth(), h = this.getFrameHeight(); - - this._shifting_dx = dx; - this._shifting_dy = dy; - - main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); - - evnt.preventDefault(); - evnt.stopPropagation(); - } - } - - /** @summary mouse up handler for shifting */ - shiftUpHanlder(evnt) { - evnt.preventDefault(); - - d3_select(window).on('mousemove.shiftHandler', null) - .on('mouseup.shiftHandler', null); - - if ((this._shifting_dx !== undefined) && (this._shifting_dy !== undefined)) - this.performScalesShift(); - } - - /** @summary Shift scales on defined positions */ - performScalesShift() { - const w = this.getFrameWidth(), h = this.getFrameHeight(), - main_svg = this.getG().selectChild('.main_layer'), - gr = this.getGrFuncs(), - xmin = gr.revertAxis('x', this._shifting_dx), - xmax = gr.revertAxis('x', this._shifting_dx + w), - ymin = gr.revertAxis('y', this._shifting_dy + h), - ymax = gr.revertAxis('y', this._shifting_dy); - - main_svg.attr('viewBox', `0 0 ${w} ${h}`); - - this._shifting_dx = this._shifting_dy = undefined; - - setPainterTooltipEnabled(this, true); - - if (this.scales_ndim === 1) - this.zoomSingle('x', xmin, xmax); - else - this.zoom(xmin, xmax, ymin, ymax); - } - - /** @summary Start mouse rect zooming */ - startRectSel(evnt) { - // ignore when touch selection is activated - if (this.zoom_kind > 100) - return; - - const frame = this.getFrameSvg(), - pos = d3_pointer(evnt, frame.node()); - - if ((evnt.buttons === 3) || (evnt.button === 1)) { - this.clearInteractiveElements(); - this._shifting_buttons = evnt.buttons; - - if (!evnt.$emul) { - d3_select(window).on('mousemove.shiftHandler', evnt2 => this.shiftMoveHanlder(evnt2, pos)) - .on('mouseup.shiftHandler', evnt2 => this.shiftUpHanlder(evnt2), true); - } - - setPainterTooltipEnabled(this, false); - evnt.preventDefault(); - evnt.stopPropagation(); - return; - } - - // ignore all events from non-left button - if (evnt.button) - return; - - evnt.preventDefault(); - - this.clearInteractiveElements(); - - const w = this.getFrameWidth(), h = this.getFrameHeight(); - - this.zoom_lastpos = pos; - this.zoom_curr = [Math.max(0, Math.min(w, pos[0])), Math.max(0, Math.min(h, pos[1]))]; - - this.zoom_origin = [0, 0]; - this.zoom_second = false; - - if ((pos[0] < 0) || (pos[0] > w)) { - this.zoom_second = (pos[0] > w) && this.y2_handle; - this.zoom_kind = 3; // only y - this.zoom_origin[1] = this.zoom_curr[1]; - this.zoom_curr[0] = w; - this.zoom_curr[1] += 1; - } else if ((pos[1] < 0) || (pos[1] > h)) { - this.zoom_second = (pos[1] < 0) && this.x2_handle; - this.zoom_kind = 2; // only x - this.zoom_origin[0] = this.zoom_curr[0]; - this.zoom_curr[0] += 1; - this.zoom_curr[1] = h; - } else { - this.zoom_kind = 1; // x and y - this.zoom_origin[0] = this.zoom_curr[0]; - this.zoom_origin[1] = this.zoom_curr[1]; - } - - if (!evnt.$emul) { - d3_select(window).on('mousemove.zoomRect', evnt2 => this.moveRectSel(evnt2)) - .on('mouseup.zoomRect', evnt2 => this.endRectSel(evnt2), true); - } - - this.zoom_rect = null; - - // disable tooltips in frame painter - setPainterTooltipEnabled(this, false); - - evnt.stopPropagation(); - - if (this.zoom_kind !== 1) - return postponePromise(() => this.startLabelsMove(), 500); - } - - /** @summary Starts labels move */ - startLabelsMove() { - if (this.zoom_rect) - return; - - const handle = (this.zoom_kind === 2) ? this.x_handle : this.y_handle; - - if (!isFunc(handle?.processLabelsMove) || !this.zoom_lastpos) - return; - - if (handle.processLabelsMove('start', this.zoom_lastpos)) - this.zoom_labels = handle; - } - - /** @summary Process mouse rect zooming */ - moveRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) - return; - - evnt.preventDefault(); - const m = d3_pointer(evnt, this.getFrameSvg().node()); - - if (this.zoom_labels) - return this.zoom_labels.processLabelsMove('move', m); - - this.zoom_lastpos[0] = m[0]; - this.zoom_lastpos[1] = m[1]; - - m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); - m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); - - switch (this.zoom_kind) { - case 1: - this.zoom_curr[0] = m[0]; - this.zoom_curr[1] = m[1]; - break; - case 2: - this.zoom_curr[0] = m[0]; - break; - case 3: - this.zoom_curr[1] = m[1]; - break; - } - - const x = Math.min(this.zoom_origin[0], this.zoom_curr[0]), - y = Math.min(this.zoom_origin[1], this.zoom_curr[1]), - w = Math.abs(this.zoom_curr[0] - this.zoom_origin[0]), - h = Math.abs(this.zoom_curr[1] - this.zoom_origin[1]); - - if (!this.zoom_rect) { - // ignore small changes, can be switching to labels move - if ((this.zoom_kind !== 1) && ((w < 2) || (h < 2))) - return; - - this.zoom_rect = this.getFrameSvg() - .append('rect') - .style('pointer-events', 'none') - .call(addHighlightStyle, true); - } - - this.zoom_rect.attr('x', x).attr('y', y).attr('width', w).attr('height', h); - } - - /** @summary Finish mouse rect zooming */ - endRectSel(evnt) { - if ((this.zoom_kind === 0) || (this.zoom_kind > 100)) - return; - - evnt.preventDefault(); - - if (!evnt.$emul) { - d3_select(window).on('mousemove.zoomRect', null) - .on('mouseup.zoomRect', null); - } - - const m = d3_pointer(evnt, this.getFrameSvg().node()); - let kind = this.zoom_kind, pr; - - if (this.zoom_labels) - this.zoom_labels.processLabelsMove('stop', m); - else { - const changed = [this.can_zoom_x, this.can_zoom_y]; - m[0] = Math.max(0, Math.min(this.getFrameWidth(), m[0])); - m[1] = Math.max(0, Math.min(this.getFrameHeight(), m[1])); - - switch (this.zoom_kind) { - case 1: - this.zoom_curr[0] = m[0]; - this.zoom_curr[1] = m[1]; - break; - case 2: // only X - this.zoom_curr[0] = m[0]; - changed[1] = false; - break; - case 3: // only Y - this.zoom_curr[1] = m[1]; - changed[0] = false; - break; - } - - let xmin, xmax, ymin, ymax, isany = false, - namex = 'x', namey = 'y'; - - if (changed[0] && (Math.abs(this.zoom_curr[0] - this.zoom_origin[0]) > 5)) { - if (this.zoom_second && (this.zoom_kind === 2)) - namex = 'x2'; - const v1 = this.revertAxis(namex, this.zoom_origin[0]), - v2 = this.revertAxis(namex, this.zoom_curr[0]); - xmin = Math.min(v1, v2); - xmax = Math.max(v1, v2); - isany = true; - } - - if (changed[1] && (Math.abs(this.zoom_curr[1] - this.zoom_origin[1]) > 5)) { - if (this.zoom_second && (this.zoom_kind === 3)) - namey = 'y2'; - - const v1 = this.revertAxis(namey, this.zoom_origin[1]), - v2 = this.revertAxis(namey, this.zoom_curr[1]); - ymin = Math.min(v1, v2); - ymax = Math.max(v1, v2); - isany = true; - } - - if (this.swap_xy() && !this.zoom_second) - [xmin, xmax, ymin, ymax] = [ymin, ymax, xmin, xmax]; - - if (namex === 'x2') { - pr = this.zoomSingle(namex, xmin, xmax, true); - kind = 0; - } else if (namey === 'y2') { - pr = this.zoomSingle(namey, ymin, ymax, true); - kind = 0; - } else if (isany) { - pr = this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); - kind = 0; - } - } - - const pnt = (kind === 1) ? { x: this.zoom_origin[0], y: this.zoom_origin[1] } : null; - - this.clearInteractiveElements(); - - // if no zooming was done, select active object instead - switch (kind) { - case 1: - this.processFrameClick(pnt); - break; - case 2: - this.getPadPainter()?.selectObjectPainter(this.x_handle); - break; - case 3: - this.getPadPainter()?.selectObjectPainter(this.y_handle); - break; - } - - // return promise - if any - return pr; - } - - /** @summary Handle mouse double click on frame */ - mouseDoubleClick(evnt) { - evnt.preventDefault(); - const m = d3_pointer(evnt, this.getFrameSvg().node()), - fw = this.getFrameWidth(), fh = this.getFrameHeight(); - this.clearInteractiveElements(); - - const valid_x = (m[0] >= 0) && (m[0] <= fw), - valid_y = (m[1] >= 0) && (m[1] <= fh); - - if (valid_x && valid_y && this.getDblclickHandler()) { - if (this.processFrameClick({ x: m[0], y: m[1] }, true)) - return; - } - - let kind = (this.can_zoom_x ? 'x' : '') + (this.can_zoom_y ? 'y' : '') + 'z'; - if (!valid_x) { - if (!this.can_zoom_y) - return; - kind = this.swap_xy() ? 'x' : 'y'; - if ((m[0] > fw) && this[kind + '2_handle']) - kind += '2'; // let unzoom second axis - } else if (!valid_y) { - if (!this.can_zoom_x) - return; - kind = this.swap_xy() ? 'y' : 'x'; - if ((m[1] < 0) && this[kind + '2_handle']) - kind += '2'; // let unzoom second axis - } - return this.unzoom(kind).then(changed => { - if (changed) - return; - const pp = this.getPadPainter(), rect = this.getFrameRect(); - return pp?.selectObjectPainter(pp, { x: m[0] + rect.x, y: m[1] + rect.y, dbl: true }); - }); - } - - /** @summary Start touch zoom */ - startTouchZoom(evnt) { - evnt.preventDefault(); - evnt.stopPropagation(); - - // in case when zooming was started, block any other kind of events - // also prevent zooming together with active dragging - if (this.zoom_kind || drag_kind) - return; - - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - - // normally double-touch will be handled - // touch with single click used for context menu - if (arr.length === 1) { - // this is touch with single element - - const now = new Date().getTime(); - let tmdiff = 1e10, dx = 100, dy = 100; - - if (this.last_touch_time && this.last_touch_pos) { - tmdiff = now - this.last_touch_time; - dx = Math.abs(arr[0][0] - this.last_touch_pos[0]); - dy = Math.abs(arr[0][1] - this.last_touch_pos[1]); - } - - this.last_touch_time = now; - this.last_touch_pos = arr[0]; - - if ((tmdiff < 500) && (dx < 20) && (dy < 20)) { - this.clearInteractiveElements(); - this.unzoom('xyz'); - - delete this.last_touch_time; - } else if (settings.ContextMenu) - this.startSingleTouchHandling('', evnt); - } - - if ((arr.length !== 2) || !settings.Zooming || !settings.ZoomTouch) - return; - - this.clearInteractiveElements(); - - // clear single touch handler - this.endSingleTouchHandling(null); - - const pnt1 = arr[0], pnt2 = arr[1], w = this.getFrameWidth(), h = this.getFrameHeight(); - - this.zoom_curr = [Math.min(pnt1[0], pnt2[0]), Math.min(pnt1[1], pnt2[1])]; - this.zoom_origin = [Math.max(pnt1[0], pnt2[0]), Math.max(pnt1[1], pnt2[1])]; - this.zoom_second = false; - - if ((this.zoom_curr[0] < 0) || (this.zoom_curr[0] > w)) { - this.zoom_second = (this.zoom_curr[0] > w) && this.y2_handle; - this.zoom_kind = 103; // only y - this.zoom_curr[0] = 0; - this.zoom_origin[0] = w; - } else if ((this.zoom_origin[1] > h) || (this.zoom_origin[1] < 0)) { - this.zoom_second = (this.zoom_origin[1] < 0) && this.x2_handle; - this.zoom_kind = 102; // only x - this.zoom_curr[1] = 0; - this.zoom_origin[1] = h; - } else - this.zoom_kind = 101; // x and y - - drag_kind = 'zoom'; // block other possible dragging - - setPainterTooltipEnabled(this, false); - - this.zoom_rect = this.getFrameSvg().append('rect') - .attr('id', 'zoomRect') - .attr('x', this.zoom_curr[0]) - .attr('y', this.zoom_curr[1]) - .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) - .attr('height', this.zoom_origin[1] - this.zoom_curr[1]) - .call(addHighlightStyle, true); - - if (!evnt.$emul) { - d3_select(window).on('touchmove.zoomRect', evnt2 => this.moveTouchZoom(evnt2)) - .on('touchcancel.zoomRect', evnt2 => this.endTouchZoom(evnt2)) - .on('touchend.zoomRect', evnt2 => this.endTouchZoom(evnt2)); - } - } - - /** @summary Move touch zooming */ - moveTouchZoom(evnt) { - if (this.zoom_kind < 100) - return; - - evnt.preventDefault(); - - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - - if (arr.length !== 2) - return this.clearInteractiveElements(); - - const pnt1 = arr[0], pnt2 = arr[1]; - - if (this.zoom_kind !== 103) { - this.zoom_curr[0] = Math.min(pnt1[0], pnt2[0]); - this.zoom_origin[0] = Math.max(pnt1[0], pnt2[0]); - } - if (this.zoom_kind !== 102) { - this.zoom_curr[1] = Math.min(pnt1[1], pnt2[1]); - this.zoom_origin[1] = Math.max(pnt1[1], pnt2[1]); - } - - this.zoom_rect.attr('x', this.zoom_curr[0]) - .attr('y', this.zoom_curr[1]) - .attr('width', this.zoom_origin[0] - this.zoom_curr[0]) - .attr('height', this.zoom_origin[1] - this.zoom_curr[1]); - - if ((this.zoom_origin[0] - this.zoom_curr[0] > 10) || (this.zoom_origin[1] - this.zoom_curr[1] > 10)) - setPainterTooltipEnabled(this, false); - - evnt.stopPropagation(); - } - - /** @summary End touch zooming handler */ - endTouchZoom(evnt) { - if (this.zoom_kind < 100) - return; - - drag_kind = ''; // reset global flag - - evnt.preventDefault(); - if (!evnt.$emul) { - d3_select(window).on('touchmove.zoomRect', null) - .on('touchend.zoomRect', null) - .on('touchcancel.zoomRect', null); - } - - let xmin, xmax, ymin, ymax, isany = false, namex = 'x', namey = 'y'; - const xid = this.swap_xy() ? 1 : 0, yid = 1 - xid, changed = [true, true]; - - if (this.zoom_kind === 102) - changed[1] = false; - if (this.zoom_kind === 103) - changed[0] = false; - - if (changed[xid] && (Math.abs(this.zoom_curr[xid] - this.zoom_origin[xid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 102)) - namex = 'x2'; - xmin = Math.min(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); - xmax = Math.max(this.revertAxis(namex, this.zoom_origin[xid]), this.revertAxis(namex, this.zoom_curr[xid])); - isany = true; - } - - if (changed[yid] && (Math.abs(this.zoom_curr[yid] - this.zoom_origin[yid]) > 10)) { - if (this.zoom_second && (this.zoom_kind === 103)) - namey = 'y2'; - ymin = Math.min(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); - ymax = Math.max(this.revertAxis(namey, this.zoom_origin[yid]), this.revertAxis(namey, this.zoom_curr[yid])); - isany = true; - } - - this.clearInteractiveElements(); - delete this.last_touch_time; - - if (namex === 'x2') - this.zoomSingle(namex, xmin, xmax, true); - else if (namey === 'y2') - this.zoomSingle(namey, ymin, ymax, true); - else if (isany) - this.zoom(xmin, xmax, ymin, ymax, undefined, undefined, true); - - evnt.stopPropagation(); - } - - /** @summary Analyze zooming with mouse wheel */ - analyzeMouseWheelEvent(event, item, dmin, test_ignore, second_side) { - // if there is second handle, use it - const handle2 = second_side ? this[item.name + '2_handle'] : null; - if (handle2) { - item.second = Object.assign({}, item); - return handle2.analyzeWheelEvent(event, dmin, item.second, test_ignore); - } - const handle = this[item.name + '_handle']; - return handle?.analyzeWheelEvent(event, dmin, item, test_ignore); - } - - /** @summary return true if default Y zooming should be enabled - * @desc it is typically for 2-Dim histograms or - * when histogram not draw, defined by other painters */ - isAllowedDefaultYZooming() { - if (this.self_drawaxes) - return true; - - let res; - this.forEachPainter(objp => { - res = res ?? objp._wheel_zoomy; - }, 'objects'); - return res; - } - - /** @summary Handles mouse wheel event */ - mouseWheel(evnt) { - evnt.stopPropagation(); - evnt.preventDefault(); - this.clearInteractiveElements(); - - const itemx = { name: 'x', reverse: this.reverse_x() }, - itemy = { name: 'y', reverse: this.reverse_y(), ignore: !this.isAllowedDefaultYZooming() }, - cur = d3_pointer(evnt, this.getFrameSvg().node()), - w = this.getFrameWidth(), h = this.getFrameHeight(); - - if (this.can_zoom_x) - this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemy : itemx, cur[0] / w, (cur[1] >= 0) && (cur[1] <= h), cur[1] < 0); - - if (this.can_zoom_y) - this.analyzeMouseWheelEvent(evnt, this.swap_xy() ? itemx : itemy, 1 - cur[1] / h, (cur[0] >= 0) && (cur[0] <= w), cur[0] > w); - - let pr = this.zoom(itemx.min, itemx.max, itemy.min, itemy.max, undefined, undefined, itemx.changed || itemy.changed); - - if (itemx.second) - pr = pr.then(() => this.zoomSingle('x2', itemx.second.min, itemx.second.max, itemx.second.changed)); - - if (itemy.second) - pr = pr.then(() => this.zoomSingle('y2', itemy.second.min, itemy.second.max, itemy.second.changed)); - - return pr; - } - - /** @summary Show frame context menu */ - showContextMenu(kind, evnt, obj) { - // disable context menu left/right buttons clicked - if (evnt?.buttons === 3) - return evnt.preventDefault(); - - // ignore context menu when touches zooming is ongoing or - if (this.zoom_kind && (this.zoom_kind > 100)) - return; - - let pnt, menu_painter = this, exec_painter = null, - frame_corner = false, fp = null; // object used to show context menu - const svg_node = this.getFrameSvg().node(); - - if (isFunc(evnt?.stopPropagation)) { - evnt.preventDefault(); - evnt.stopPropagation(); // disable main context menu - const ms = d3_pointer(evnt, svg_node), - tch = get_touch_pointers(evnt, svg_node); - if (tch.length === 1) - pnt = { x: tch[0][0], y: tch[0][1], touch: true }; - else if (ms.length === 2) - pnt = { x: ms[0], y: ms[1], touch: false }; - } else if ((evnt?.x !== undefined) && (evnt?.y !== undefined) && (evnt?.clientX === undefined)) { - pnt = evnt; - const rect = svg_node.getBoundingClientRect(); - evnt = { clientX: rect.left + pnt.x, clientY: rect.top + pnt.y }; - } - - if ((kind === 'painter') && obj) { - menu_painter = obj; - kind = ''; - } else if (kind === 'main') { - menu_painter = this.getMainPainter(true); - kind = ''; - } else if (!kind) { - const pp = this.getPadPainter(); - let sel = null; - - fp = this; - if (pnt && pp) { - pnt.painters = true; // assign painter for every tooltip - const hints = pp.processPadTooltipEvent(pnt); - let bestdist = 1000; - for (let n = 0; n < hints.length; ++n) { - if (hints[n]?.menu) { - const dist = hints[n].menu_dist ?? 7; - if (dist < bestdist) { - sel = hints[n].painter; - bestdist = dist; - } - } - } - } - - if (sel) - menu_painter = sel; - else - kind = 'frame'; - - if (pnt) - frame_corner = (pnt.x > 0) && (pnt.x < 20) && (pnt.y > 0) && (pnt.y < 20); - - fp.setLastEventPos(pnt); - } else if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'pal')) { - exec_painter = this.getMainPainter(true); // histogram painter delivers items for axis menu - - if (this.v7_frame && isFunc(exec_painter?.v7EvalAttr)) - exec_painter = null; - } - - if (!exec_painter) - exec_painter = menu_painter; - - if (!isFunc(menu_painter?.fillContextMenu)) - return; - - this.clearInteractiveElements(); - - return createMenu(evnt, menu_painter).then(menu => { - let domenu = menu.painter.fillContextMenu(menu, kind, obj); - - // fill frame menu by default - or append frame elements when activated in the frame corner - if (fp && (!domenu || (frame_corner && (kind !== 'frame')))) - domenu = fp.fillContextMenu(menu); - - if (domenu) { - return exec_painter.fillObjectExecMenu(menu, kind).then(menu2 => { - // suppress any running zooming - setPainterTooltipEnabled(menu2.painter, false); - return menu2.show().then(() => setPainterTooltipEnabled(menu2.painter, true)); - }); - } - }); - } - - /** @summary Activate touch handling on frame - * @private */ - startSingleTouchHandling(kind, evnt) { - const arr = get_touch_pointers(evnt, this.getFrameSvg().node()); - if (arr.length !== 1) - return; - - evnt.preventDefault(); - evnt.stopPropagation(); - closeMenu(); - - const tm = new Date().getTime(); - - this._shifting_dx = 0; - this._shifting_dy = 0; - - setPainterTooltipEnabled(this, false); - - d3_select(window).on('touchmove.singleTouch', kind ? null : evnt2 => this.moveTouchHandling(evnt2, kind, arr[0])) - .on('touchcancel.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)) - .on('touchend.singleTouch', evnt2 => this.endSingleTouchHandling(evnt2, kind, arr[0], tm)); - } - - /** @summary Moving of touch pointer - * @private */ - moveTouchHandling(evnt, kind, pos0) { - const frame = this.getFrameSvg(), - main_svg = this.getG().selectChild('.main_layer'); - let pos; - - try { - pos = get_touch_pointers(evnt, frame.node())[0]; - } catch { - pos = [0, 0]; - if (evnt?.changedTouches) - pos = [evnt.changedTouches[0].clientX, evnt.changedTouches[0].clientY]; - } - - const dx = pos0[0] - pos[0], - dy = (this.scales_ndim === 1) ? 0 : pos0[1] - pos[1], - w = this.getFrameWidth(), h = this.getFrameHeight(); - - this._shifting_dx = dx; - this._shifting_dy = dy; - - main_svg.attr('viewBox', `${dx} ${dy} ${w} ${h}`); - } - - /** @summary Process end-touch event, which can cause content menu to appear - * @private */ - endSingleTouchHandling(evnt, kind, pos, tm) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - setPainterTooltipEnabled(this, true); - - d3_select(window).on('touchmove.singleTouch', null) - .on('touchcancel.singleTouch', null) - .on('touchend.singleTouch', null); - - if (evnt === null) - return; - - if (Math.abs(this._shifting_dx) > 2 || Math.abs(this._shifting_dy) > 2) - this.performScalesShift(); - else if (new Date().getTime() - tm > 700) - this.showContextMenu(kind, { x: pos[0], y: pos[1] }); - } - - /** @summary Clear frame interactive elements */ - clearInteractiveElements() { - closeMenu(); - this.zoom_kind = 0; - this.zoom_rect?.remove(); - delete this.zoom_rect; - delete this.zoom_curr; - delete this.zoom_origin; - delete this.zoom_lastpos; - delete this.zoom_labels; - - // enable tooltip in frame painter - setPainterTooltipEnabled(this, true); - } - - /** @summary Assign interactive methods to frame painter */ - static assign(fpainter) { - const src = FrameInteractive.prototype, - src2 = TooltipHandler.prototype; - Object.getOwnPropertyNames(src).forEach(name => { - if (name !== 'constructor') - fpainter[name] = src[name]; - }); - Object.getOwnPropertyNames(src2).forEach(name => { - if (name !== 'constructor') - fpainter[name] = src2[name]; - }); - } - -} // class FrameInteractive - -/** - * @summary Painter class for TFrame, main handler for interactivity - * @private - */ - -class TFramePainter extends FrameInteractive { - - #frame_x; // frame X coordinate - #frame_y; // frame Y coordinate - #frame_width; // frame width - #frame_height; // frame height - #frame_trans; // transform of frame element - #swap_xy; // swap X/Y axis on the frame - #reverse_x; // reverse X axis - #reverse_y; // reverse Y axis - #reverse_x2; // reverse X2 axis - #reverse_y2; // reverse Y2 axis - #border_mode; // frame border mode - #border_size; // frame border size - #axes_drawn; // when axes are drawn - #axes2_drawn; // when axes are drawn - #shrink_frame_left; // shrink frame on left side - #projection; // id of projection function - #click_handler; // handle for click events - #dblclick_handler; // handle for double click events - #keys_handler; // assigned handler for keyboard events - #enabled_keys; // when keyboard press handling enabled - #last_event_pos; // position of last event - - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} frame - TFrame object */ - constructor(dom, frame) { - super(dom, frame?.$dummy ? null : frame); - this.zoom_kind = 0; - this.mode3d = false; - this.#shrink_frame_left = 0.0; - this.xmin = this.xmax = 0; // no scale specified, wait for objects drawing - this.ymin = this.ymax = 0; // no scale specified, wait for objects drawing - this.ranges_set = false; - this.#axes_drawn = false; - this.#axes2_drawn = false; - this.#border_mode = gStyle.fFrameBorderMode; - this.#border_size = gStyle.fFrameBorderSize; - this.#projection = 0; // different projections - } - - /** @summary Returns frame painter - object itself */ - getFramePainter() { return this; } - - /** @summary Returns true if it is ROOT6 frame - * @private */ - is_root6() { return true; } - - /** @summary Returns frame or sub-objects, used in GED editor */ - getObject(place) { - if (place === 'xaxis') - return this.xaxis; - if (place === 'yaxis') - return this.yaxis; - return super.getObject(); - } - - /** @summary Set active flag for frame - can block some events - * @private */ - setFrameActive(on) { - this.#enabled_keys = on && settings.HandleKeys; - // used only in 3D mode where control is used - if (this.control) - this.control.enableKeys = this.#enabled_keys; - } - - /** @summary Returns true if keys handling enabled - * @private */ - isEnabledKeys() { return this.#enabled_keys; } - - /** @summary Returns true if X/Y axis swapped */ - swap_xy() { return this.#swap_xy; } - - /** @summary Is reverse x */ - reverse_x() { return this.#reverse_x; } - - /** @summary Is reverse x */ - reverse_y() { return this.#reverse_y; } - - /** @summary Shrink frame size - * @private */ - shrinkFrame(shrink_left, shrink_right) { - this.fX1NDC += shrink_left; - this.fX2NDC -= shrink_right; - } - - /** @summary Set position of last context menu event - * @private */ - setLastEventPos(pnt) { this.#last_event_pos = pnt; } - - /** @summary Return position of last event - * @private */ - getLastEventPos() { return this.#last_event_pos; } - - /** @summary Returns coordinates transformation func */ - getProjectionFunc() { return getEarthProjectionFunc(this.#projection); } - - /** @summary Recalculate frame ranges using specified projection functions */ - recalculateRange(Proj, change_x, change_y) { - this.#projection = Proj || 0; - - if ((this.#projection === 2) && ((this.scale_ymin <= -90) || (this.scale_ymax >= 90))) { - console.warn(`Mercator Projection: Latitude out of range ${this.scale_ymin} ${this.scale_ymax}`); - this.#projection = 0; - } - - const func = this.getProjectionFunc(); - if (!func) - return; - - const pnts = [func(this.scale_xmin, this.scale_ymin), - func(this.scale_xmin, this.scale_ymax), - func(this.scale_xmax, this.scale_ymax), - func(this.scale_xmax, this.scale_ymin)]; - if (this.scale_xmin < 0 && this.scale_xmax > 0) { - pnts.push(func(0, this.scale_ymin)); - pnts.push(func(0, this.scale_ymax)); - } - if (this.scale_ymin < 0 && this.scale_ymax > 0) { - pnts.push(func(this.scale_xmin, 0)); - pnts.push(func(this.scale_xmax, 0)); - } - - this.original_xmin = this.scale_xmin; - this.original_xmax = this.scale_xmax; - this.original_ymin = this.scale_ymin; - this.original_ymax = this.scale_ymax; - - if (change_x) - this.scale_xmin = this.scale_xmax = pnts[0].x; - if (change_y) - this.scale_ymin = this.scale_ymax = pnts[0].y; - - for (let n = 1; n < pnts.length; ++n) { - if (change_x) { - this.scale_xmin = Math.min(this.scale_xmin, pnts[n].x); - this.scale_xmax = Math.max(this.scale_xmax, pnts[n].x); - } - if (change_y) { - this.scale_ymin = Math.min(this.scale_ymin, pnts[n].y); - this.scale_ymax = Math.max(this.scale_ymax, pnts[n].y); - } - } - } - - /** @summary Configure frame axes ranges */ - setAxesRanges(xaxis, xmin, xmax, yaxis, ymin, ymax, zaxis, zmin, zmax, hpainter) { - this.ranges_set = true; - - this.xaxis = xaxis; - this.xmin = xmin; - this.xmax = xmax; - - this.yaxis = yaxis; - this.ymin = ymin; - this.ymax = ymax; - - this.zaxis = zaxis; - this.zmin = zmin; - this.zmax = zmax; - - if (hpainter?.check_pad_range) { - delete hpainter.check_pad_range; - const ndim = hpainter.getDimension(); - this.applyAxisZoom('x'); - if (ndim > 1) - this.applyAxisZoom('y'); - if (ndim > 2) - this.applyAxisZoom('z'); - } - - if (hpainter && !hpainter._checked_zooming) { - hpainter._checked_zooming = true; - - if (hpainter.options.minimum !== kNoZoom) { - this.zoom_zmin = hpainter.options.minimum; - this.zoom_zmax = this.zmax; - } - if (hpainter.options.maximum !== kNoZoom) { - this.zoom_zmax = hpainter.options.maximum; - this.zoom_zmin ??= this.zmin; - } - } - } - - /** @summary Configure secondary frame axes ranges */ - setAxes2Ranges(second_x, xaxis, xmin, xmax, second_y, yaxis, ymin, ymax) { - if (second_x) { - this.x2axis = xaxis; - this.x2min = xmin; - this.x2max = xmax; - } - if (second_y) { - this.y2axis = yaxis; - this.y2min = ymin; - this.y2max = ymax; - } - } - - /** @summary Returns associated axis object */ - getAxis(name) { - switch (name) { - case 'x': return this.xaxis; - case 'y': return this.yaxis; - case 'z': return this.zaxis; - case 'x2': return this.x2axis; - case 'y2': return this.y2axis; - } - return null; - } - - /** @summary Apply axis zooming from pad user range - * @private */ - applyPadUserRange(pad, name) { - if (!pad) - return; - - // seems to be, not always user range calculated - let umin = pad[`fU${name}min`], - umax = pad[`fU${name}max`], - eps = 1e-7; - - if (name === 'x') { - if ((Math.abs(pad.fX1) > eps) || (Math.abs(pad.fX2 - 1) > eps)) { - const dx = pad.fX2 - pad.fX1; - umin = pad.fX1 + dx * pad.fLeftMargin; - umax = pad.fX2 - dx * pad.fRightMargin; - } - } else if ((Math.abs(pad.fY1) > eps) || (Math.abs(pad.fY2 - 1) > eps)) { - const dy = pad.fY2 - pad.fY1; - umin = pad.fY1 + dy * pad.fBottomMargin; - umax = pad.fY2 - dy * pad.fTopMargin; - } - - if ((umin >= umax) || (Math.abs(umin) < eps && Math.abs(umax - 1) < eps)) - return; - - if (pad[`fLog${name}`] > 0) { - umin = Math.exp(umin * Math.log(10)); - umax = Math.exp(umax * Math.log(10)); - } - - const aname = !this.#swap_xy ? name : (name === 'x' ? 'y' : 'x'), - smin = this[`scale_${aname}min`], - smax = this[`scale_${aname}max`]; - - eps = (smax - smin) * 1e-7; - - if ((Math.abs(umin - smin) > eps) || (Math.abs(umax - smax) > eps)) { - this[`zoom_${aname}min`] = umin; - this[`zoom_${aname}max`] = umax; - } - } - - /** @summary Apply zooming from TAxis attributes */ - applyAxisZoom(name) { - if (this.zoomChangedInteractive(name)) - return; - this[`zoom_${name}min`] = this[`zoom_${name}max`] = 0; - - const axis = this.getAxis(name); - - if (axis?.TestBit(EAxisBits.kAxisRange)) { - if ((axis.fFirst !== axis.fLast) && ((axis.fFirst > 1) || (axis.fLast < axis.fNbins))) { - this[`zoom_${name}min`] = axis.fFirst > 1 ? axis.GetBinLowEdge(axis.fFirst) : axis.fXmin; - this[`zoom_${name}max`] = axis.fLast < axis.fNbins ? axis.GetBinLowEdge(axis.fLast + 1) : axis.fXmax; - // reset user range for main painter - axis.SetBit(EAxisBits.kAxisRange, false); - axis.fFirst = 1; - axis.fLast = axis.fNbins; - } - } - } - - /** @summary Create x,y objects which maps user coordinates into pixels - * @desc While only first painter really need such object, all others just reuse it - * following functions are introduced - * this.GetBin[X/Y] return bin coordinate - * this.[x,y] these are d3.scale objects - * this.gr[x,y] converts root scale into graphical value - * @private */ - createXY(opts) { - this.cleanXY(); // remove all previous configurations - - if (!opts) - opts = { ndim: 1 }; - - this.#swap_xy = opts.swap_xy || false; - this.#reverse_x = opts.reverse_x || false; - this.#reverse_y = opts.reverse_y || false; - - this.logx = this.logy = 0; - - const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), pad = pp.getRootPad(), - pad_logx = pad.fLogx, - pad_logy = (opts.ndim === 1 ? pad.fLogv : undefined) ?? pad.fLogy; - - this.scales_ndim = opts.ndim; - - this.scale_xmin = this.xmin; - this.scale_xmax = this.xmax; - - this.scale_ymin = this.ymin; - this.scale_ymax = this.ymax; - - if (opts.extra_y_space) { - const log_scale = this.#swap_xy ? pad_logx : pad_logy; - if (log_scale && (this.scale_ymax > 0)) - this.scale_ymax = Math.exp(Math.log(this.scale_ymax) * 1.1); - else - this.scale_ymax += (this.scale_ymax - this.scale_ymin) * 0.1; - } - - if (opts.check_pad_range) { - // take zooming out of pad or axis attributes - this.applyAxisZoom('x'); - if (opts.ndim > 1) - this.applyAxisZoom('y'); - if (opts.ndim > 2) - this.applyAxisZoom('z'); - - // Use configured pad range - only when main histogram drawn with SAME draw option - if (opts.check_pad_range === 'pad_range') { - this.applyPadUserRange(pad, 'x'); - this.applyPadUserRange(pad, 'y'); - } - } - - if ((opts.zoom_xmin !== opts.zoom_xmax) && ((this.zoom_xmin === this.zoom_xmax) || !this.zoomChangedInteractive('x'))) { - this.zoom_xmin = opts.zoom_xmin; - this.zoom_xmax = opts.zoom_xmax; - } - - if ((opts.zoom_ymin !== opts.zoom_ymax) && ((this.zoom_ymin === this.zoom_ymax) || !this.zoomChangedInteractive('y'))) { - this.zoom_ymin = opts.zoom_ymin; - this.zoom_ymax = opts.zoom_ymax; - } - - let orig_x = true, orig_y = true; - - if (this.zoom_xmin !== this.zoom_xmax) { - this.scale_xmin = this.zoom_xmin; - this.scale_xmax = this.zoom_xmax; - orig_x = false; - } - - if (this.zoom_ymin !== this.zoom_ymax) { - this.scale_ymin = this.zoom_ymin; - this.scale_ymax = this.zoom_ymax; - orig_y = false; - } - - // projection should be assigned - this.recalculateRange(opts.Proj, orig_x, orig_y); - - this.x_handle = new TAxisPainter(pp, this.xaxis, true); - this.x_handle.setHistPainter(opts.hist_painter, 'x'); - - this.x_handle.configureAxis('xaxis', this.xmin, this.xmax, this.scale_xmin, this.scale_xmax, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { - reverse: this.#reverse_x, - log: this.#swap_xy ? pad_logy : pad_logx, - ignore_labels: this.x_ignore_labels, - noexp_changed: this.x_noexp_changed, - fixed_ticks: opts.xticks, - symlog: this.#swap_xy ? opts.symlog_y : opts.symlog_x, - log_min_nz: opts.xmin_nz && (opts.xmin_nz <= this.xmax) ? 0.9 * opts.xmin_nz : 0, - logcheckmin: (opts.ndim > 1) || !this.#swap_xy, - logminfactor: logminfactorX - }); - - this.x_handle.assignFrameMembers(this, 'x'); - - this.y_handle = new TAxisPainter(pp, this.yaxis, true); - this.y_handle.setHistPainter(opts.hist_painter, 'y'); - - this.y_handle.configureAxis('yaxis', this.ymin, this.ymax, this.scale_ymin, this.scale_ymax, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { - value_axis: opts.ndim === 1, - reverse: this.#reverse_y, - log: this.#swap_xy ? pad_logx : pad_logy, - ignore_labels: this.y_ignore_labels, - noexp_changed: this.y_noexp_changed, - fixed_ticks: opts.yticks, - symlog: this.#swap_xy ? opts.symlog_x : opts.symlog_y, - log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5 * opts.ymin_nz : 0, - logcheckmin: (opts.ndim > 1) || this.#swap_xy, - logminfactor: logminfactorY - }); - - this.y_handle.assignFrameMembers(this, 'y'); - - this.setRootPadRange(pad); - } - - /** @summary Create x,y objects for drawing of second axes - * @private */ - createXY2(opts) { - if (!opts) - opts = { ndim: this.scales_ndim ?? 1 }; - - this.#reverse_x2 = opts.reverse_x || false; - this.#reverse_y2 = opts.reverse_y || false; - - this.logx2 = this.logy2 = 0; - - const w = this.getFrameWidth(), h = this.getFrameHeight(), - pp = this.getPadPainter(), pad = pp.getRootPad(); - - if (opts.second_x) { - this.scale_x2min = this.x2min; - this.scale_x2max = this.x2max; - } - - if (opts.second_y) { - this.scale_y2min = this.y2min; - this.scale_y2max = this.y2max; - } - - if (opts.extra_y_space && opts.second_y) { - const log_scale = this.#swap_xy ? pad.fLogx : pad.fLogy; - if (log_scale && (this.scale_y2max > 0)) - this.scale_y2max = Math.exp(Math.log(this.scale_y2max) * 1.1); - else - this.scale_y2max += (this.scale_y2max - this.scale_y2min) * 0.1; - } - - if ((this.zoom_x2min !== this.zoom_x2max) && opts.second_x) { - this.scale_x2min = this.zoom_x2min; - this.scale_x2max = this.zoom_x2max; - } - - if ((this.zoom_y2min !== this.zoom_y2max) && opts.second_y) { - this.scale_y2min = this.zoom_y2min; - this.scale_y2max = this.zoom_y2max; - } - - if (opts.second_x) { - this.x2_handle = new TAxisPainter(pp, this.x2axis, true); - this.x2_handle.setHistPainter(opts.hist_painter, 'x'); - - this.x2_handle.configureAxis('x2axis', this.x2min, this.x2max, this.scale_x2min, this.scale_x2max, this.#swap_xy, this.#swap_xy ? [0, h] : [0, w], { - reverse: this.#reverse_x2, - log: this.#swap_xy ? pad.fLogy : pad.fLogx, - ignore_labels: this.x2_ignore_labels, - noexp_changed: this.x2_noexp_changed, - logcheckmin: (opts.ndim > 1) || !this.#swap_xy, - logminfactor: logminfactorX - }); - - this.x2_handle.assignFrameMembers(this, 'x2'); - } - - if (opts.second_y) { - this.y2_handle = new TAxisPainter(pp, this.y2axis, true); - this.y2_handle.setHistPainter(opts.hist_painter, 'y'); - - this.y2_handle.configureAxis('y2axis', this.y2min, this.y2max, this.scale_y2min, this.scale_y2max, !this.#swap_xy, this.#swap_xy ? [0, w] : [0, h], { - reverse: this.#reverse_y2, - log: this.#swap_xy ? pad.fLogx : pad.fLogy, - ignore_labels: this.y2_ignore_labels, - noexp_changed: this.y2_noexp_changed, - logcheckmin: (opts.ndim > 1) || this.#swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < this.y2max) ? 0.5 * opts.ymin_nz : 0, - logminfactor: logminfactorY - }); - - this.y2_handle.assignFrameMembers(this, 'y2'); - } - } - - /** @summary Return functions to create x/y points based on coordinates - * @desc In default case returns frame painter itself - * @private */ - getGrFuncs(second_x, second_y) { - const use_x2 = second_x && this.grx2, - use_y2 = second_y && this.gry2; - if (!use_x2 && !use_y2) - return this; - - return { - use_x2, - grx: use_x2 ? this.grx2 : this.grx, - logx: this.logx, - x_handle: use_x2 ? this.x2_handle : this.x_handle, - scale_xmin: use_x2 ? this.scale_x2min : this.scale_xmin, - scale_xmax: use_x2 ? this.scale_x2max : this.scale_xmax, - use_y2, - gry: use_y2 ? this.gry2 : this.gry, - logy: this.logy, - y_handle: use_y2 ? this.y2_handle : this.y_handle, - scale_ymin: use_y2 ? this.scale_y2min : this.scale_ymin, - scale_ymax: use_y2 ? this.scale_y2max : this.scale_ymax, - fp: this, - _remap(name) { - if ((name === 'x') && this.use_x2) - return 'x2'; - if ((name === 'y') && this.use_y2) - return 'y2'; - return name; - }, - swap_xy() { return this.fp.swap_xy(); }, - isAxisZoomed(name) { return this.fp.isAxisZoomed(this._remap(name)); }, - revertAxis(name, v) { return this.fp.revertAxis(this._remap(name), v); }, - axisAsText(name, v) { return this.fp.axisAsText(this._remap(name), v); }, - getFrameWidth() { return this.fp.getFrameWidth(); }, - getFrameHeight() { return this.fp.getFrameHeight(); } - }; - } - - /** @summary Set selected range back to TPad object - * @private */ - setRootPadRange(pad, is3d) { - if (!pad || !this.ranges_set) - return; - - if (is3d) { - // this is fake values, algorithm should be copied from TView3D class of ROOT - pad.fUxmin = pad.fUymin = -0.9; - pad.fUxmax = pad.fUymax = 0.9; - } else { - pad.fLogx = this.#swap_xy ? this.logy : this.logx; - pad.fUxmin = pad.fLogx ? Math.log10(this.scale_xmin) : this.scale_xmin; - pad.fUxmax = pad.fLogx ? Math.log10(this.scale_xmax) : this.scale_xmax; - pad.fLogy = this.#swap_xy ? this.logx : this.logy; - pad.fUymin = pad.fLogy ? Math.log10(this.scale_ymin) : this.scale_ymin; - pad.fUymax = pad.fLogy ? Math.log10(this.scale_ymax) : this.scale_ymax; - } - - const rx = pad.fUxmax - pad.fUxmin, - ry = pad.fUymax - pad.fUymin, - mx = Math.max(0.001, 1 - pad.fLeftMargin - pad.fRightMargin), - my = Math.max(0.001, 1 - pad.fBottomMargin - pad.fTopMargin); - - pad.fX1 = pad.fUxmin - rx / mx * pad.fLeftMargin; - pad.fX2 = pad.fUxmax + rx / mx * pad.fRightMargin; - pad.fY1 = pad.fUymin - ry / my * pad.fBottomMargin; - pad.fY2 = pad.fUymax + ry / my * pad.fTopMargin; - } - - /** @summary Draw axes grids - * @desc Called immediately after axes drawing */ - drawGrids(draw_grids) { - const layer = this.getFrameSvg().selectChild('.axis_layer'); - - layer.selectAll('.xgrid').remove(); - layer.selectAll('.ygrid').remove(); - - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - h = this.getFrameHeight(), - w = this.getFrameWidth(), - grid_style = gStyle.fGridStyle; - - // add a grid on x axis, if the option is set - if (pad?.fGridx && draw_grids && this.x_handle?.ticks) { - const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('x')?.fAxisColor ?? 1); - let gridx = ''; - this.x_handle.ticks.forEach(pos => { - gridx += this.#swap_xy ? `M0,${pos}h${w}` : `M${pos},0v${h}`; - }); - - layer.append('svg:path') - .attr('class', 'xgrid') - .attr('d', gridx) - .style('stroke', this.getColor(colid) || 'black') - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', getSvgLineStyle(grid_style)); - } - - // add a grid on y axis, if the option is set - if (pad?.fGridy && draw_grids && this.y_handle?.ticks) { - const colid = (gStyle.fGridColor > 0) ? gStyle.fGridColor : (this.getAxis('y')?.fAxisColor ?? 1); - let gridy = ''; - this.y_handle.ticks.forEach(pos => { - gridy += this.#swap_xy ? `M${pos},0v${h}` : `M0,${pos}h${w}`; - }); - - layer.append('svg:path') - .attr('class', 'ygrid') - .attr('d', gridy) - .style('stroke', this.getColor(colid) || 'black') - .style('stroke-width', gStyle.fGridWidth) - .style('stroke-dasharray', getSvgLineStyle(grid_style)); - } - } - - /** @summary Converts 'raw' axis value into text */ - axisAsText(axis, value) { - const handle = this[`${axis}_handle`]; - - if (handle) - return handle.axisAsText(value, settings[axis.toUpperCase() + 'ValuesFormat']); - - return value.toPrecision(4); - } - - /** @summary Identify if requested axes are drawn - * @desc Checks if x/y axes are drawn. Also if second side is already there */ - hasDrawnAxes(second_x, second_y) { return !second_x && !second_y ? this.#axes_drawn : this.#axes2_drawn; } - - /** @summary draw axes, - * @return {Promise} which ready when drawing is completed */ - async drawAxes(shrink_forbidden, disable_x_draw, disable_y_draw, AxisPos, has_x_obstacle, has_y_obstacle, enable_grids) { - this.cleanAxesDrawings(); - - if ((this.xmin === this.xmax) || (this.ymin === this.ymax)) - return false; - - if (AxisPos === undefined) - AxisPos = 0; - - const layer = this.getFrameSvg().selectChild('.axis_layer'), - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(true), - draw_grids = enable_grids && (pad?.fGridx || pad?.fGridy); - - this.x_handle.invert_side = (AxisPos >= 10); - this.x_handle.lbls_both_sides = !this.x_handle.invert_side && (pad?.fTickx > 1); // labels on both sides - this.x_handle.has_obstacle = has_x_obstacle; - - this.y_handle.invert_side = ((AxisPos % 10) === 1); - this.y_handle.lbls_both_sides = !this.y_handle.invert_side && (pad?.fTicky > 1); // labels on both sides - this.y_handle.has_obstacle = has_y_obstacle; - - const draw_horiz = this.#swap_xy ? this.y_handle : this.x_handle, - draw_vertical = this.#swap_xy ? this.x_handle : this.y_handle; - - if ((!disable_x_draw || !disable_y_draw) && pp.isFastDrawing()) - disable_x_draw = disable_y_draw = true; - - let pr = Promise.resolve(true); - - if (!disable_x_draw || !disable_y_draw || draw_grids) { - draw_vertical.optionLeft = draw_vertical.invert_side; // text align - - const can_adjust_frame = !shrink_forbidden && settings.CanAdjustFrame, - - pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - pad?.fTickx ? -h : 0, disable_x_draw, - undefined, false, pp.getPadHeight() - h - this.getFrameY()), - - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - pad?.fTicky ? w : 0, disable_y_draw, - draw_vertical.invert_side ? 0 : this.#frame_x, can_adjust_frame); - - pr = Promise.all([pr1, pr2]).then(() => { - this.drawGrids(draw_grids); - - if (!can_adjust_frame) - return; - - let shrink = 0.0; - const ypos = draw_vertical.position; - - if ((-0.2 * w < ypos) && (ypos < 0)) { - shrink = -ypos / w + 0.001; - this.#shrink_frame_left += shrink; - } else if ((ypos > 0) && (ypos < 0.3 * w) && (this.#shrink_frame_left > 0) && (ypos / w > this.#shrink_frame_left)) { - shrink = -this.#shrink_frame_left; - this.#shrink_frame_left = 0.0; - } - - if (shrink) { - this.shrinkFrame(shrink, 0); - return this.redraw().then(() => this.drawAxes(true)); - } - }); - } - - return pr.then(() => { - if (!shrink_forbidden) - this.#axes_drawn = true; - return true; - }); - } - - /** @summary draw second axes (if any) */ - async drawAxes2(second_x, second_y) { - const layer = this.getFrameSvg().selectChild('.axis_layer'), - w = this.getFrameWidth(), - h = this.getFrameHeight(), - pp = this.getPadPainter(), - pad = pp.getRootPad(true); - - if (second_x) { - this.x2_handle.invert_side = true; - this.x2_handle.lbls_both_sides = false; - this.x2_handle.has_obstacle = false; - } - - if (second_y) { - this.y2_handle.invert_side = true; - this.y2_handle.lbls_both_sides = false; - } - - let draw_horiz = this.#swap_xy ? this.y2_handle : this.x2_handle, - draw_vertical = this.#swap_xy ? this.x2_handle : this.y2_handle; - - if ((draw_horiz || draw_vertical) && pp.isFastDrawing()) - draw_horiz = draw_vertical = null; - - let pr1, pr2; - - if (draw_horiz) { - pr1 = draw_horiz.drawAxis(layer, w, h, - draw_horiz.invert_side ? null : `translate(0,${h})`, - pad?.fTickx ? -h : 0, false, - undefined, false); - } - - if (draw_vertical) { - draw_vertical.optionLeft = draw_vertical.invert_side; - pr2 = draw_vertical.drawAxis(layer, w, h, - draw_vertical.invert_side ? `translate(${w})` : null, - pad?.fTicky ? w : 0, false, - draw_vertical.invert_side ? 0 : this.#frame_x, false); - } - - return Promise.all([pr1, pr2]).then(() => { - this.#axes2_drawn = true; - return true; - }); - } - - - /** @summary Update frame attributes - * @private */ - updateAttributes(force) { - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - tframe = this.getObject(); - - if ((this.fX1NDC === undefined) || (force && !this.$modifiedNDC)) { - if (!pad) { - this.fX1NDC = gStyle.fPadLeftMargin; - this.fX2NDC = 1 - gStyle.fPadRightMargin; - this.fY1NDC = gStyle.fPadBottomMargin; - this.fY2NDC = 1 - gStyle.fPadTopMargin; - } else { - this.fX1NDC = pad.fLeftMargin; - this.fX2NDC = 1 - pad.fRightMargin; - this.fY1NDC = pad.fBottomMargin; - this.fY2NDC = 1 - pad.fTopMargin; - } - } - - if (tframe) { - this.createAttFill({ attr: tframe }); - this.#border_mode = tframe.fBorderMode; - this.#border_size = tframe.fBorderSize; - } else if (!this.fillatt) { - if (pad?.fFrameFillColor) - this.createAttFill({ pattern: pad.fFrameFillStyle, color: pad.fFrameFillColor }); - else if (pad) - this.createAttFill({ attr: pad }); - else - this.createAttFill({ pattern: gStyle.fFrameFillStyle, color: gStyle.fFrameFillColor }); - - // force white color for the canvas frame - if (!tframe && this.fillatt.empty() && pp?.isCanvas()) - this.fillatt.setSolidColor('white'); - else if ((pad?.fFillStyle === 4000) && !this.fillatt.empty()) // special case of transpad.C macro, which set transparent pad - this.fillatt.setOpacity(0); - - if (pad && (pad.fFrameBorderMode || (pad.fFrameBorderSize !== 1))) { - this.#border_mode = pad.fFrameBorderMode; - this.#border_size = pad.fFrameBorderSize; - } - } - - if (!tframe && (pad?.fFrameLineColor !== undefined)) - this.createAttLine({ color: pad.fFrameLineColor, width: pad.fFrameLineWidth, style: pad.fFrameLineStyle }); - else if (tframe) - this.createAttLine({ attr: tframe, color: 'black' }); - else - this.createAttLine({ color: gStyle.fFrameLineColor, width: gStyle.fFrameLineWidth, style: gStyle.fFrameLineStyle }); - } - - /** @summary Function called at the end of resize of frame - * @desc One should apply changes to the pad - * @private */ - sizeChanged() { - const pad = this.getPadPainter()?.getRootPad(true); - - if (pad) { - pad.fLeftMargin = this.fX1NDC; - pad.fRightMargin = 1 - this.fX2NDC; - pad.fBottomMargin = this.fY1NDC; - pad.fTopMargin = 1 - this.fY2NDC; - this.setRootPadRange(pad); - } - - this.interactiveRedraw('pad', 'frame'); - } - - /** @summary Remove all kinds of X/Y function for axes transformation */ - cleanXY() { - delete this.grx; - delete this.gry; - delete this.grz; - delete this.grx2; - delete this.gry2; - - this.x_handle?.cleanup(); - this.y_handle?.cleanup(); - this.z_handle?.cleanup(); - this.x2_handle?.cleanup(); - this.y2_handle?.cleanup(); - - delete this.x_handle; - delete this.y_handle; - delete this.z_handle; - delete this.x2_handle; - delete this.y2_handle; - } - - /** @summary remove all axes drawings */ - cleanAxesDrawings() { - this.x_handle?.removeG(); - this.y_handle?.removeG(); - this.z_handle?.removeG(); - this.x2_handle?.removeG(); - this.y2_handle?.removeG(); - - this.getG()?.selectChild('.axis_layer').selectAll('*').remove(); - this.#axes_drawn = this.#axes2_drawn = false; - } - - /** @summary Returns frame rectangle plus extra info for hint display */ - cleanFrameDrawings() { - // cleanup all 3D drawings if any - if (isFunc(this.create3DScene)) - this.create3DScene(-1); - - this.cleanAxesDrawings(); - this.cleanXY(); - - this.ranges_set = false; - - this.xmin = this.xmax = 0; - this.ymin = this.ymax = 0; - this.zmin = this.zmax = 0; - - this.zoom_xmin = this.zoom_xmax = 0; - this.zoom_ymin = this.zoom_ymax = 0; - this.zoom_zmin = this.zoom_zmax = 0; - - this.scale_xmin = this.scale_xmax = 0; - this.scale_ymin = this.scale_ymax = 0; - this.scale_zmin = this.scale_zmax = 0; - - this.getG()?.selectChild('.main_layer').selectAll('*').remove(); - this.getG()?.selectChild('.upper_layer').selectAll('*').remove(); - - this.xaxis = null; - this.yaxis = null; - this.zaxis = null; - - this.getG()?.selectAll('*').remove(); - this.getG()?.on('mousedown', null) - .on('dblclick', null) - .on('wheel', null) - .on('contextmenu', null) - .property('interactive_set', null) - .remove(); - - this.setG(undefined); // frame <g> element managed by the pad - - if (this.#keys_handler) { - window.removeEventListener('keydown', this.#keys_handler, false); - this.#keys_handler = undefined; - } - } - - /** @summary Cleanup frame */ - cleanup() { - this.cleanFrameDrawings(); - this.#click_handler = undefined; - this.#dblclick_handler = undefined; - this.#enabled_keys = undefined; - - this.getPadPainter()?.setFramePainter(this, false); - - super.cleanup(); - } - - /** @summary Redraw TFrame */ - redraw(/* reason */) { - const pp = this.getPadPainter(); - pp?.setFramePainter(this, true); - - // first update all attributes from objects - this.updateAttributes(); - - const rect = pp?.getPadRect() ?? { width: 10, height: 10 }, - lm = Math.round(rect.width * this.fX1NDC), - tm = Math.round(rect.height * (1 - this.fY2NDC)), - rotate = pp?.options?.RotateFrame, - w = Math.round(rect.width * (this.fX2NDC - this.fX1NDC)), - h = Math.round(rect.height * (this.fY2NDC - this.fY1NDC)); - - this.#frame_x = lm; - this.#frame_y = tm; - this.#frame_width = rotate ? h : w; - this.#frame_height = rotate ? w : h; - this.#frame_trans = rotate ? `rotate(-90,${lm},${tm}) translate(${lm - h},${tm})` : makeTranslate(lm, tm); - this.$can_drag = !rotate && !pp?.options?.FixFrame; - - return this.mode3d ? this : this.createFrameG(); - } - - /** @summary Create frame element and update all attributes - * @private */ - createFrameG() { - // this is svg:g object - container for every other items belonging to frame - let g = this.setG(this.getFrameSvg()), - top_rect, main_svg; - - if (g.empty()) { - g = this.setG(this.getPadPainter().getLayerSvg('primitives_layer').append('svg:g').attr('class', 'root_frame')); - - // empty title on the frame required to suppress title of the canvas - if (!this.isBatchMode()) - g.append('svg:title').text(''); - - top_rect = g.append('svg:path'); - - main_svg = g.append('svg:svg') - .attr('class', 'main_layer') - .attr('x', 0) - .attr('y', 0) - .attr('overflow', 'hidden'); - - g.append('svg:g').attr('class', 'axis_layer'); - g.append('svg:g').attr('class', 'upper_layer'); - } else { - top_rect = g.selectChild('path'); - main_svg = g.selectChild('.main_layer'); - } - - this.#axes_drawn = this.#axes2_drawn = false; - - g.attr('transform', this.#frame_trans); - - top_rect.attr('d', `M0,0H${this.#frame_width}V${this.#frame_height}H0Z`) - .call(this.fillatt.func) - .call(this.lineatt.func); - - main_svg.attr('width', this.#frame_width) - .attr('height', this.#frame_height) - .attr('viewBox', `0 0 ${this.#frame_width} ${this.#frame_height}`); - - g.selectAll('.frame_deco').remove(); - if (this.#border_mode && this.fillatt.hasColor()) { - const paths = getBoxDecorations(0, 0, this.#frame_width, this.#frame_height, this.#border_mode, this.#border_size || 2, this.#border_size || 2); - g.insert('svg:path', '.main_layer') - .attr('class', 'frame_deco') - .attr('d', paths[0]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); - g.insert('svg:path', '.main_layer') - .attr('class', 'frame_deco') - .attr('d', paths[1]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); - } - - return this; - } - - /** @summary Change log state of specified axis - * @param {string} axis - name of axis like 'x' or 'y' - * @param {number} value - 0 (linear), 1 (log) or 2 (log2) */ - changeAxisLog(axis, value) { - const pp = this.getPadPainter(), - pad = pp?.getRootPad(true); - if (!pad) - return; - - pp.options._interactively_changed = true; - - const name = `fLog${axis}`; - - // do not allow log scale for labels - if (!pad[name]) { - if (this.#swap_xy && axis === 'x') - axis = 'y'; - else if (this.#swap_xy && axis === 'y') - axis = 'x'; - const handle = this[`${axis}_handle`]; - if (handle?.kind === kAxisLabels) - return; - } - - if ((value === 'toggle') || (value === undefined)) - value = pad[name] ? 0 : 1; - - // directly change attribute in the pad - pad[name] = value; - - return this.interactiveRedraw('pad', `log${axis}`); - } - - /** @summary Toggle log state on the specified axis */ - toggleAxisLog(axis) { - return this.changeAxisLog(axis, 'toggle'); - } - - /** @summary Fill context menu for the frame - * @desc It could be appended to the histogram menus */ - fillContextMenu(menu, kind, obj) { - const main = this.getMainPainter(true), - wrk = main?.$stack_hist ? main.getPrimary() : main, - pp = this.getPadPainter(), - pad = pp?.getRootPad(true), - is_pal = kind === 'pal'; - - if (is_pal) - kind = 'z'; - - if ((kind === 'x') || (kind === 'y') || (kind === 'z') || (kind === 'x2') || (kind === 'y2')) { - const faxis = obj || this[kind + 'axis'], - handle = this[`${kind}_handle`]; - if (!isFunc(faxis?.TestBit)) - return false; - const hist_painter = handle?.hist_painter || main; - - menu.header(`${kind.toUpperCase()} axis`, `${urlClassPrefix}${clTAxis}.html`); - - menu.sub('Range'); - menu.add('Zoom', () => { - let min = this[`zoom_${kind}min`] ?? this[`${kind}min`], - max = this[`zoom_${kind}max`] ?? this[`${kind}max`]; - if (min === max) { - min = this[`${kind}min`]; - max = this[`${kind}max`]; - } - menu.input('Enter zoom range like: [min, max]', `[${min}, ${max}]`).then(v => { - const arr = JSON.parse(v); - if (arr && Array.isArray(arr) && (arr.length === 2)) { - let flag = false; - if (arr[0] < faxis.fXmin) { - faxis.fFirst = 0; - flag = true; - } else - faxis.fFirst = 1; - if (arr[1] > faxis.fXmax) { - faxis.fLast = faxis.fNbins + 1; - flag = true; - } else - faxis.fLast = faxis.fNbins; - faxis.SetBit(EAxisBits.kAxisRange, flag); - hist_painter?.scanContent(); - this.zoomSingle(kind, arr[0], arr[1], true).then(res => { - if (!res && flag) - this.interactiveRedraw('pad'); - }); - } - }); - }); - menu.add('Unzoom', () => { - this.unzoomSingle(kind).then(res => { - if (!res && (faxis.fFirst !== faxis.fLast)) { - faxis.fFirst = faxis.fLast = 0; - hist_painter?.scanContent(); - this.interactiveRedraw('pad'); - } - }); - }); - if (handle?.value_axis && isFunc(wrk?.accessMM)) { - menu.add('Minimum', () => { - menu.input(`Enter minimum value or ${kNoZoom} as default`, wrk.accessMM(true), 'float').then(v => { - this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; - wrk.accessMM(true, v); - }); - }); - menu.add('Maximum', () => { - menu.input(`Enter maximum value or ${kNoZoom} as default`, wrk.accessMM(false), 'float').then(v => { - this[`zoom_${kind}min`] = this[`zoom_${kind}max`] = undefined; - wrk.accessMM(false, v); - }); - }); - } - menu.endsub(); - - if (pad) - menu.addPadLogMenu(kind[0], pad[`fLog${kind[0]}`], v => this.changeAxisLog(kind[0], v)); - - menu.addchk(faxis.TestBit(EAxisBits.kMoreLogLabels), 'More log', flag => { - faxis.SetBit(EAxisBits.kMoreLogLabels, flag); - if (hist_painter?.getSnapId() && (kind.length === 1)) - hist_painter.interactiveRedraw('pad', `exec:SetMoreLogLabels(${flag})`, kind); - else - this.interactiveRedraw('pad'); - }); - menu.addchk(handle?.noexp ?? faxis.TestBit(EAxisBits.kNoExponent), 'No exponent', flag => { - faxis.SetBit(EAxisBits.kNoExponent, flag); - if (handle) - handle.noexp_changed = true; - this[`${kind}_noexp_changed`] = true; - if (hist_painter?.getSnapId() && (kind.length === 1)) - hist_painter.interactiveRedraw('pad', `exec:SetNoExponent(${flag})`, kind); - else - this.interactiveRedraw('pad'); - }); - - if ((kind === 'z') && isFunc(hist_painter?.fillPaletteMenu)) - hist_painter.fillPaletteMenu(menu, !is_pal); - - menu.addTAxisMenu(EAxisBits, hist_painter || this, faxis, kind, handle, this); - return true; - } - - const alone = menu.size() === 0; - - if (alone) - menu.header('Frame', `${urlClassPrefix}${clTFrame}.html`); - else - menu.separator(); - - if (this.zoom_xmin !== this.zoom_xmax) - menu.add('Unzoom X', () => this.unzoom('x')); - if (this.zoom_ymin !== this.zoom_ymax) - menu.add('Unzoom Y', () => this.unzoom('y')); - if (this.zoom_zmin !== this.zoom_zmax) - menu.add('Unzoom Z', () => this.unzoom('z')); - if (this.zoom_x2min !== this.zoom_x2max) - menu.add('Unzoom X2', () => this.unzoom('x2')); - if (this.zoom_y2min !== this.zoom_y2max) - menu.add('Unzoom Y2', () => this.unzoom('y2')); - menu.add('Unzoom all', () => this.unzoom('all')); - - if (pad) { - menu.addchk(pad.fLogx, 'SetLogx', () => this.toggleAxisLog('x')); - menu.addchk(pad.fLogy, 'SetLogy', () => this.toggleAxisLog('y')); - - if (isFunc(main?.getDimension) && (main.getDimension() > 1)) - menu.addchk(pad.fLogz, 'SetLogz', () => this.toggleAxisLog('z')); - menu.separator(); - } - - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - menu.addAttributesMenu(this, alone ? '' : 'Frame '); - - menu.sub('Border'); - menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], this.#border_mode + 1, v => { - this.#border_mode = v - 1; - this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); - }, 'Frame border mode'); - menu.addSizeMenu('Size', 0, 20, 2, this.#border_size, v => { - this.#border_size = v; - this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); - }, 'Frame border size'); - menu.endsub(); - - menu.add('Save to gStyle', () => { - gStyle.fPadBottomMargin = this.fY1NDC; - gStyle.fPadTopMargin = 1 - this.fY2NDC; - gStyle.fPadLeftMargin = this.fX1NDC; - gStyle.fPadRightMargin = 1 - this.fX2NDC; - this.fillatt?.saveToStyle('fFrameFillColor', 'fFrameFillStyle'); - this.lineatt?.saveToStyle('fFrameLineColor', 'fFrameLineWidth', 'fFrameLineStyle'); - gStyle.fFrameBorderMode = this.#border_mode; - gStyle.fFrameBorderSize = this.#border_size; - }, 'Store frame position and graphical attributes to gStyle'); - - menu.separator(); - - menu.sub('Save as'); - const fmts = ['svg', 'png', 'jpeg', 'webp']; - if (internals.makePDF) - fmts.push('pdf'); - fmts.forEach(fmt => menu.add(`frame.${fmt}`, () => pp.saveAs(fmt, 'frame', `frame.${fmt}`))); - menu.endsub(); - - return true; - } - - /** @summary Fill option object used in TWebCanvas - * @private */ - fillWebObjectOptions(res) { - res.fcust = 'frame'; - res.fopt = [this.scale_xmin || 0, this.scale_ymin || 0, this.scale_xmax || 0, this.scale_ymax || 0]; - } - - /** @summary Returns frame X position */ - getFrameX() { return this.#frame_x || 0; } - - /** @summary Returns frame Y position */ - getFrameY() { return this.#frame_y || 0; } - - /** @summary Returns frame width */ - getFrameWidth() { return this.#frame_width || 0; } - - /** @summary Returns frame height */ - getFrameHeight() { return this.#frame_height || 0; } - - /** @summary Returns frame rectangle plus extra info for hint display */ - getFrameRect() { - return { - x: this.#frame_x || 0, - y: this.#frame_y || 0, - width: this.getFrameWidth(), - height: this.getFrameHeight(), - transform: this.getG()?.attr('transform') || '', - hint_delta_x: 0, - hint_delta_y: 0 - }; - } - - /** @summary Configure user-defined click handler - * @desc Function will be called every time when frame click was performed - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of click will be disabled */ - configureUserClickHandler(handler) { - this.#click_handler = isFunc(handler) ? handler : null; - } - - /** @summary Returns actual click handler */ - getClickHandler() { return this.#click_handler; } - - /** @summary Configure user-defined dblclick handler - * @desc Function will be called every time when double click was called - * As argument, tooltip object with selected bins will be provided - * If handler function returns true, default handling of dblclick (unzoom) will be disabled */ - configureUserDblclickHandler(handler) { - this.#dblclick_handler = isFunc(handler) ? handler : null; - } - - /** @summary Returns actual double-click handler */ - getDblclickHandler() { return this.#dblclick_handler; } - - /** @summary Function can be used for zooming into specified range - * @desc if both limits for each axis 0 (like xmin === xmax === 0), axis will be un-zoomed - * @param {number} xmin - X minimum - * @param {number} xmax - X maximum - * @param {number} [ymin] - Y minimum - * @param {number} [ymax] - Y maximum - * @param {number} [zmin] - Z minimum - * @param {number} [zmax] - Z maximum - * @param {boolean} [interactive] - if changes was performed interactively - * @return {Promise} with boolean flag if zoom operation was performed */ - async zoom(xmin, xmax, ymin, ymax, zmin, zmax, interactive) { - if (xmin === 'x') { - xmin = xmax; - xmax = ymin; - interactive = ymax; - ymin = ymax = undefined; - } else if (xmin === 'y') { - interactive = ymax; - ymax = ymin; - ymin = xmax; - xmin = xmax = undefined; - } else if (xmin === 'z') { - interactive = ymax; - zmin = xmax; - zmax = ymin; - xmin = xmax = ymin = ymax = undefined; - } - - let zoom_x = (xmin !== xmax), zoom_y = (ymin !== ymax), zoom_z = (zmin !== zmax), - unzoom_x = false, unzoom_y = false, unzoom_z = false; - - if (zoom_x) { - let cnt = 0; - xmin = this.x_handle?.checkZoomMin(xmin) ?? xmin; - if (xmin <= this.xmin) { - xmin = this.xmin; - cnt++; - } - if (xmax >= this.xmax) { - xmax = this.xmax; - cnt++; - } - if (cnt === 2) { - zoom_x = false; - unzoom_x = true; - } - } else - unzoom_x = (xmin === xmax) && (xmin === 0); - - if (zoom_y) { - let cnt = 0; - ymin = this.y_handle?.checkZoomMin(ymin) ?? ymin; - if (ymin <= this.ymin) { - ymin = this.ymin; - cnt++; - } - if (ymax >= this.ymax) { - ymax = this.ymax; - cnt++; - } - if ((cnt === 2) && (this.scales_ndim !== 1)) { - zoom_y = false; - unzoom_y = true; - } - } else - unzoom_y = (ymin === ymax) && (ymin === 0); - - if (zoom_z) { - let cnt = 0; - zmin = this.z_handle?.checkZoomMin(zmin) ?? zmin; - if (zmin <= this.zmin) { - zmin = this.zmin; - cnt++; - } - if (zmax >= this.zmax) { - zmax = this.zmax; - cnt++; - } - if ((cnt === 2) && (this.scales_ndim > 2)) { - zoom_z = false; - unzoom_z = true; - } - } else - unzoom_z = (zmin === zmax) && (zmin === 0); - - let changed = false; - - // first process zooming (if any) - if (zoom_x || zoom_y || zoom_z) { - this.forEachPainter(obj => { - if (!isFunc(obj.canZoomInside)) - return; - if (zoom_x && obj.canZoomInside('x', xmin, xmax)) { - this.zoom_xmin = xmin; - this.zoom_xmax = xmax; - changed = true; - zoom_x = false; - if (interactive) - this.zoomChangedInteractive('x', interactive); - } - if (zoom_y && obj.canZoomInside('y', ymin, ymax)) { - this.zoom_ymin = ymin; - this.zoom_ymax = ymax; - changed = true; - zoom_y = false; - if (interactive) - this.zoomChangedInteractive('y', interactive); - } - if (zoom_z && obj.canZoomInside('z', zmin, zmax)) { - this.zoom_zmin = zmin; - this.zoom_zmax = zmax; - changed = true; - zoom_z = false; - if (interactive) - this.zoomChangedInteractive('z', interactive); - } - }); - } - - // and process unzoom, if any - if (unzoom_x || unzoom_y || unzoom_z) { - if (unzoom_x) { - if (this.zoom_xmin !== this.zoom_xmax) - changed = true; - this.zoom_xmin = this.zoom_xmax = 0; - if (interactive) - this.zoomChangedInteractive('x', interactive); - } - if (unzoom_y) { - if (this.zoom_ymin !== this.zoom_ymax) { - changed = true; - unzoomHistogramYRange(this.getMainPainter()); - } - this.zoom_ymin = this.zoom_ymax = 0; - if (interactive) - this.zoomChangedInteractive('y', interactive); - } - if (unzoom_z) { - if (this.zoom_zmin !== this.zoom_zmax) - changed = true; - this.zoom_zmin = this.zoom_zmax = 0; - if (interactive) - this.zoomChangedInteractive('z', interactive); - } - - // than try to unzoom all overlapped objects - if (!changed) { - this.forEachPainter(painter => { - if (isFunc(painter?.unzoomUserRange)) { - if (painter.unzoomUserRange(unzoom_x, unzoom_y, unzoom_z)) - changed = true; - } - }, 'objects'); - } - } - if (!changed) - return false; - - return this.interactiveRedraw('pad', 'zoom').then(() => true); - } - - /** @summary Zooming of single axis - * @param {string} name - axis name like x/y/z but also second axis x2 or y2 - * @param {number} vmin - axis minimal value, 0 for unzoom - * @param {number} vmax - axis maximal value, 0 for unzoom - * @param {boolean} [interactive] - if change was performed interactively - * @protected */ - async zoomSingle(name, vmin, vmax, interactive) { - const handle = this[`${name}_handle`], name_min = `zoom_${name}min`, name_max = `zoom_${name}max`; - if (!handle && (name !== 'z')) - return false; - - let zoom_v = (vmin !== vmax), unzoom_v = false; - - if (zoom_v) { - let cnt = 0; - vmin = handle?.checkZoomMin(vmin) ?? vmin; - if (vmin <= this[name + 'min']) { - vmin = this[name + 'min']; - cnt++; - } - if (vmax >= this[name + 'max']) { - vmax = this[name + 'max']; - cnt++; - } - if (cnt === 2) { - zoom_v = false; - unzoom_v = true; - } - } else - unzoom_v = (vmin === vmax) && (vmin === 0); - - let changed = false; - - // first process zooming - if (zoom_v) { - this.forEachPainter(obj => { - if (zoom_v && isFunc(obj.canZoomInside) && obj.canZoomInside(name[0], vmin, vmax)) { - this[name_min] = vmin; - this[name_max] = vmax; - changed = true; - zoom_v = false; - } - }); - } - - // and process unzoom, if any - if (unzoom_v) { - if (this[name_min] !== this[name_max]) { - changed = true; - if (name === 'y') - unzoomHistogramYRange(this.getMainPainter()); - } - this[name_min] = this[name_max] = 0; - } - - if (!changed) - return false; - - if (interactive) - this.zoomChangedInteractive(name, interactive); - - return this.interactiveRedraw('pad', 'zoom').then(() => true); - } - - /** @summary Unzoom single axis */ - async unzoomSingle(name, interactive) { - return this.zoomSingle(name, 0, 0, typeof interactive === 'undefined' ? 'unzoom' : interactive); - } - - /** @summary Checks if specified axis zoomed */ - isAxisZoomed(axis) { - return this[`zoom_${axis}min`] !== this[`zoom_${axis}max`]; - } - - /** @summary Unzoom specified axes - * @return {Promise} with boolean flag if zooming changed */ - async unzoom(dox, doy, doz) { - if (dox === 'all') - return this.unzoomSingle('x2').then(() => this.unzoomSingle('y2')).then(() => this.unzoom('xyz')); - - if ((dox === 'x2') || (dox === 'y2')) - return this.unzoomSingle(dox); - - if (typeof dox === 'undefined') - dox = doy = doz = true; - else if (isStr(dox)) { - doz = dox.indexOf('z') >= 0; - doy = dox.indexOf('y') >= 0; - dox = dox.indexOf('x') >= 0; - } - - return this.zoom(dox ? 0 : undefined, dox ? 0 : undefined, - doy ? 0 : undefined, doy ? 0 : undefined, - doz ? 0 : undefined, doz ? 0 : undefined, - 'unzoom'); - } - - /** @summary Reset all zoom attributes - * @private */ - resetZoom() { - ['x', 'y', 'z', 'x2', 'y2'].forEach(n => { - this[`zoom_${n}min`] = undefined; - this[`zoom_${n}max`] = undefined; - this[`zoom_changed_${n}`] = undefined; - }); - } - - /** @summary Mark/check if zoom for specific axis was changed interactively - * @private */ - zoomChangedInteractive(axis, value) { - if (axis === 'reset') { - this.zoom_changed_x = this.zoom_changed_y = this.zoom_changed_z = undefined; - return; - } - if (!axis || axis === 'any') - return this.zoom_changed_x || this.zoom_changed_y || this.zoom_changed_z; - - if ((axis !== 'x') && (axis !== 'y') && (axis !== 'z')) - return; - - const fld = 'zoom_changed_' + axis; - if (value === undefined) - return this[fld]; - - // special handling of unzoom, only if was never changed before flag set to true - if (value === 'unzoom') - this[fld] = (this[fld] === undefined); - else if (value) - this[fld] = true; - } - - /** @summary Convert graphical coordinate into axis value */ - revertAxis(axis, pnt) { - if (this.#swap_xy) - axis = (axis[0] === 'x') ? 'y' : 'x'; - return this[`${axis}_handle`]?.revertPoint(pnt) ?? 0; - } - - /** @summary Show axis status message - * @desc method called normally when mouse enter main object element - * @private */ - showAxisStatus(axis_name, evnt) { - const taxis = this.getAxis(axis_name), - m = d3_pointer(evnt, this.getFrameSvg().node()); - let hint_name = axis_name, - hint_title = clTAxis, - id = (axis_name === 'x') ? 0 : 1; - - if (taxis) { - hint_name = taxis.fName; - hint_title = taxis.fTitle || `TAxis object for ${axis_name}`; - } - if (this.#swap_xy) - id = 1 - id; - - const axis_value = this.revertAxis(axis_name, m[id]); - - this.showObjectStatus(hint_name, hint_title, `${axis_name} : ${this.axisAsText(axis_name, axis_value)}`, `${m[0]},${m[1]}`); - } - - /** @summary Add interactive keys handlers - * @private */ - addKeysHandler() { - if (this.isBatchMode() || this.#keys_handler || (typeof window === 'undefined')) - return; - - this.#keys_handler = evnt => this.processKeyPress(evnt); - - window.addEventListener('keydown', this.#keys_handler, false); - } - - /** @summary Add interactive functionality to the frame - * @private */ - addInteractivity(for_second_axes) { - if (this.isBatchMode() || (!settings.Zooming && !settings.ContextMenu)) - return false; - - if (!for_second_axes) - this.addBasicInteractivity(); - - return this.addFrameInteractivity(for_second_axes); - } - -} // class TFramePainter - -export { addDragHandler, TooltipHandler, FrameInteractive, TFramePainter, getEarthProjectionFunc }; diff --git a/js/modules/gpad/TPadPainter.mjs b/js/modules/gpad/TPadPainter.mjs deleted file mode 100644 index 8b3370ab7f487..0000000000000 --- a/js/modules/gpad/TPadPainter.mjs +++ /dev/null @@ -1,2949 +0,0 @@ -import { gStyle, settings, constants, browser, source_dir, version_id, version, internals, BIT, - create, toJSON, isBatchMode, loadModules, loadScript, injectCode, isPromise, getPromise, postponePromise, - isObject, isFunc, isStr, clTObjArray, clTColor, clTPad, clTFrame, clTStyle, clTLegend, - clTHStack, clTMultiGraph, clTLegendEntry, nsSVG, kTitle, clTList, urlClassPrefix } from '../core.mjs'; -import { select as d3_select, rgb as d3_rgb } from '../d3.mjs'; -import { ColorPalette, adoptRootColors, getColorPalette, getGrayColors, extendRootColors, - getRGBfromTColor, decodeWebCanvasColors } from '../base/colors.mjs'; -import { prSVG, prJSON, prHTML, getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, - getTDatime, convertDate, svgToImage, getBoxDecorations } from '../base/BasePainter.mjs'; -import { ObjectPainter, selectActivePad, getActivePad, isPadPainter } from '../base/ObjectPainter.mjs'; -import { TAttLineHandler } from '../base/TAttLineHandler.mjs'; -import { addCustomFont } from '../base/FontHandler.mjs'; -import { addDragHandler } from './TFramePainter.mjs'; -import { createMenu, closeMenu } from '../gui/menu.mjs'; -import { ToolbarIcons, registerForResize, saveFile } from '../gui/utils.mjs'; -import { BrowserLayout, getHPainter } from '../gui/display.mjs'; - - -const clTButton = 'TButton', kIsGrayscale = BIT(22), - // identifier used in TWebCanvas painter - webSnapIds = { kNone: 0, kObject: 1, kSVG: 2, kSubPad: 3, kColors: 4, kStyle: 5, kFont: 6 }; - -// eslint-disable-next-line one-var -const PadButtonsHandler = { - - getButtonSize(fact) { - const cp = this.getCanvPainter(); - return Math.round((fact || 1) * (cp?.getPadScale() || 1) * (cp === this ? 16 : 12)); - }, - - toggleButtonsVisibility(action, evnt) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - const group = this.getLayerSvg('btns_layer'), - btn = group.select('[name=\'Toggle\']'); - if (btn.empty()) - return; - - let state = btn.property('buttons_state'); - - if (btn.property('timout_handler')) { - if ((action !== 'timeout') && (action !== 'timeout2')) - clearTimeout(btn.property('timout_handler')); - btn.property('timout_handler', null); - } - - let is_visible = false; - switch (action) { - case 'enable': - is_visible = true; - this.btns_active_flag = true; - break; - case 'enterbtn': - this.btns_active_flag = true; - return; // do nothing, just cleanup timeout - case 'hidemain': - case 'timeout': - if (!browser.touches) - btn.property('timout_handler', setTimeout(() => this.toggleButtonsVisibility('timeout2'), 5000)); - break; - case 'timeout2': - btn.style('opacity', 0); // hide JSROOT button, but keep handling - return; - case 'toggle': - state = !state; - btn.property('buttons_state', state); - is_visible = state; - break; - case 'disable': - case 'leavebtn': - this.btns_active_flag = false; - if (!state) - btn.property('timout_handler', setTimeout(() => this.toggleButtonsVisibility('timeout'), 1200)); - return; - } - - group.selectAll('svg').each(function() { - if (this !== btn.node()) - d3_select(this).style('display', is_visible ? '' : 'none'); - else if (is_visible) - btn.style('opacity', null); // default opacity - }); - }, - - - alignButtons(btns, width, height) { - const isfast = this.isFastDrawing(), - isvert = btns.property('vertical'), - sz0x = isfast || isvert ? this.getButtonSize(1.25) : this.$first_button_width, - nextx = (btns.property('nextx') || 0) + sz0x; - let btns_x, btns_y; - - if (isvert) { - btns_x = btns.property('leftside') ? 2 : (width - sz0x); - btns_y = height - nextx; - } else { - btns_x = btns.property('leftside') ? 2 : (width - nextx); - btns_y = height - this.getButtonSize(1.25); - } - - makeTranslate(btns, btns_x, btns_y); - }, - - findPadButton(keyname) { - const group = this.getLayerSvg('btns_layer'); - let found_func = ''; - if (!group.empty()) { - group.selectAll('svg').each(function() { - if (d3_select(this).attr('key') === keyname) - found_func = d3_select(this).attr('name'); - }); - } - return found_func; - }, - - removePadButtons() { - const group = this.getLayerSvg('btns_layer'); - if (!group.empty()) { - group.selectAll('*').remove(); - group.property('nextx', null); - } - }, - - showPadButtons() { - const group = this.getLayerSvg('btns_layer'); - if (group.empty()) - return; - - // clean all previous buttons - group.selectAll('*').remove(); - if (!this._buttons) - return; - - const istop = this.isTopPad(), - isfast = this.isFastDrawing(), - y = 0; - this.$first_button_width = this.getButtonSize(isfast || !istop ? 1.25 : 3.5); - let ctrl, x = group.property('leftside') ? this.$first_button_width : 0; - - if (isfast) { - ctrl = ToolbarIcons.createSVG(group, ToolbarIcons.circle, this.getButtonSize(), 'enlargePad', false) - .attr('name', 'Enlarge').attr('x', 0).attr('y', 0) - .on('click', evnt => this.clickPadButton('enlargePad', evnt)); - } else { - ctrl = ToolbarIcons.createSVG(group, istop ? ToolbarIcons.logo : ToolbarIcons.rect, this.getButtonSize(), istop ? `JSROOT version: ${version}` : `Toggle buttons on ${this.getPadName()}`, false) - .attr('name', 'Toggle').attr('x', 0).attr('y', 0) - .property('buttons_state', (settings.ToolBar !== 'popup') || browser.touches) - .property('pointer-events', 'visibleFill') - .on('click', evnt => this.toggleButtonsVisibility('toggle', evnt)); - - ctrl.node()._mouseenter = () => this.toggleButtonsVisibility('enable'); - ctrl.node()._mouseleave = () => this.toggleButtonsVisibility('disable'); - - if (istop && settings.ContextMenu) { - ctrl.on('contextmenu', evnt => { - evnt.preventDefault(); - evnt.stopPropagation(); - createMenu(evnt).then(menu => { - menu.addSettingsMenu(false, 'JSROOT', arg => { - if (arg === 'dark') - this.changeDarkMode(); - }); - menu.show(); - }); - }); - } - - for (let k = 0; k < this._buttons.length; ++k) { - const item = this._buttons[k]; - let btn = item.btn; - - if (isStr(btn)) - btn = ToolbarIcons[btn]; - if (!btn) - btn = ToolbarIcons.circle; - - const svg = ToolbarIcons.createSVG(group, btn, this.getButtonSize(), - item.tooltip + (istop ? '' : (` on pad ${this.getPadName()}`)) + (item.keyname ? ` (keyshortcut ${item.keyname})` : ''), false); - - if (group.property('vertical')) - svg.attr('x', y).attr('y', x); - else - svg.attr('x', x).attr('y', y); - - svg.attr('name', item.funcname) - .style('display', ctrl.property('buttons_state') ? '' : 'none') - .attr('key', item.keyname || null) - .on('click', evnt => this.clickPadButton(item.funcname, evnt)); - - svg.node()._mouseenter = () => this.toggleButtonsVisibility('enterbtn'); - svg.node()._mouseleave = () => this.toggleButtonsVisibility('leavebtn'); - - x += this.getButtonSize(1.25); - } - } - - group.property('nextx', x); - - this.alignButtons(group, this.getPadWidth(), this.getPadHeight()); - - if (group.property('vertical')) - ctrl.attr('y', x); - else if (!group.property('leftside')) - ctrl.attr('x', x); - - if (!browser.touches) - this.toggleButtonsVisibility('hidemain'); - }, - - assign(painter) { - Object.assign(painter, this); - } - -}; // PadButtonsHandler - - -/** @summary Fill TWebObjectOptions for painter - * @private */ -function createWebObjectOptions(painter) { - const snapid = painter?.getSnapId(); - if (!snapid) - return null; - - const obj = { _typename: 'TWebObjectOptions', snapid, opt: painter.getDrawOpt(true), fcust: '', fopt: [] }; - if (isFunc(painter.fillWebObjectOptions)) - painter.fillWebObjectOptions(obj); - return obj; -} - - -/** - * @summary Painter for TPad object - * @private - */ - -class TPadPainter extends ObjectPainter { - - #iscan; // is canvas flag - #pad_name; // name of the pad - #pad; // TPad object - #painters; // painters in the pad - #pad_scale; // scale factor of the pad - #pad_x; // pad x coordinate - #pad_y; // pad y coordinate - #pad_width; // pad width - #pad_height; // pad height - #doing_draw; // drawing handles - #pad_draw_disabled; // disable drawing of the pad - #last_grayscale; // grayscale change flag - #custom_palette; // custom palette - #custom_colors; // custom colors - #custom_palette_indexes; // custom palette indexes - #custom_palette_colors; // custom palette colors - #frame_painter_ref; // frame painter - #main_painter_ref; // main painter on the pad - #snap_primitives; // stored snap primitives from web canvas - #has_execs; // indicate is pad has TExec objects assigned - #deliver_move_events; // deliver move events to server - #readonly; // if changes on pad is not allowed - #num_primitives; // number of primitives - #num_specials; // number of special objects - if counted - #auto_color_cnt; // counter used in assigning auto colors - #auto_palette; // palette for creating of automatic colors - #fixed_size; // fixed size flag - #has_canvas; // indicate if top canvas painter exists - #fast_drawing; // fast drawing flag - #resize_tmout; // timeout handle for resize - #start_draw_tm; // time when start drawing primitives - - /** @summary constructor - * @param {object|string} dom - DOM element for drawing or element id - * @param {object} pad - TPad object to draw - * @param {string} [opt] - draw option - * @param {boolean} [iscan] - if TCanvas object - * @param {boolean} [add_to_primitives] - add pad painter to canvas - * */ - constructor(dom, pad, opt, iscan, add_to_primitives) { - super(dom, pad); - this.#pad = pad; - this.#iscan = iscan; // indicate if working with canvas - this.#pad_name = ''; - if (!iscan && pad?.fName) { - this.#pad_name = pad.fName.replace(' ', '_'); // avoid empty symbol in pad name - const regexp = /^[A-Za-z][A-Za-z0-9_]*$/; - if (!regexp.test(this.#pad_name) || ((this.#pad_name === 'button') && (pad._typename === clTButton))) - this.#pad_name = 'jsroot_pad_' + internals.id_counter++; - } - this.#painters = []; // complete list of all painters in the pad - this.#has_canvas = true; - this.forEachPainter = this.forEachPainterInPad; - const d = this.selectDom(); - if (!d.empty() && d.property('_batch_mode')) - this.batch_mode = true; - - if (opt !== undefined) - this.decodeOptions(opt); - - if (add_to_primitives) { - if ((add_to_primitives !== 'webpad') && this.getCanvSvg().empty()) { - // one can draw pad without canvas - this.#has_canvas = false; - this.#pad_name = ''; - this.setTopPainter(); - } else { - // pad painter will be registered in the parent pad - this.addToPadPrimitives(); - } - } - - if (pad?.$disable_drawing) - this.#pad_draw_disabled = true; - } - - /** @summary returns pad painter - * @protected */ - getPadPainter() { return this.isTopPad() ? null : super.getPadPainter(); } - - /** @summary returns canvas painter - * @protected */ - getCanvPainter(try_select) { return this.isTopPad() ? this : super.getCanvPainter(try_select); } - - /** @summary Returns pad name - * @protected */ - getPadName() { return this.#pad_name; } - - /** @summary Indicates that drawing runs in batch mode - * @private */ - isBatchMode() { - if (this.batch_mode !== undefined) - return this.batch_mode; - - if (isBatchMode()) - return true; - - return this.isTopPad() ? false : this.getCanvPainter()?.isBatchMode(); - } - - /** @summary Indicates that is is Root6 pad painter - * @private */ - isRoot6() { return true; } - - /** @summary Returns true if pad is editable */ - isEditable() { return this.#pad?.fEditable ?? true; } - - /** @summary Returns true if button */ - isButton() { return this.matchObjectType(clTButton); } - - /** @summary Returns true if read-only mode is enabled */ - isReadonly() { return this.#readonly; } - - /** @summary Returns true if it is canvas - * @param {boolean} [is_online = false] - if specified, checked if it is canvas with configured connection to server */ - isCanvas(is_online = false) { - if (!this.#iscan) - return false; - if (is_online === true) - return isFunc(this.getWebsocket) && this.getWebsocket(); - return isStr(is_online) ? this.#iscan === is_online : true; - } - - /** @summary Returns true if it is canvas or top pad without canvas */ - isTopPad() { return this.isCanvas() || !this.#has_canvas; } - - /** @summary Canvas main svg element - * @return {object} d3 selection with canvas svg - * @protected */ - getCanvSvg() { return this.selectDom().select('.root_canvas'); } - - /** @summary Pad svg element - * @return {object} d3 selection with pad svg - * @protected */ - getPadSvg() { - const c = this.getCanvSvg(); - if (!this.#pad_name || c.empty()) - return c; - - return c.select('.primitives_layer .__root_pad_' + this.#pad_name); - } - - /** @summary Method selects immediate layer under canvas/pad main element - * @param {string} name - layer name lik 'primitives_layer', 'btns_layer', 'info_layer' - * @protected */ - getLayerSvg(name) { return this.getPadSvg().selectChild('.' + name); } - - /** @summary Returns svg element for the frame in current pad - * @protected */ - getFrameSvg() { - const layer = this.getLayerSvg('primitives_layer'); - if (layer.empty()) - return layer; - let node = layer.node().firstChild; - while (node) { - const elem = d3_select(node); - if (elem.classed('root_frame')) - return elem; - node = node.nextSibling; - } - return d3_select(null); - } - - /** @summary Returns main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - getMainPainter() { return this.#main_painter_ref || null; } - - /** @summary Assign main painter on the pad - * @desc Typically main painter is TH1/TH2 object which is drawing axes - * @private */ - setMainPainter(painter, force) { - if (!this.#main_painter_ref || force) - this.#main_painter_ref = painter; - } - - /** @summary cleanup pad and all primitives inside */ - cleanup() { - if (this.#doing_draw) - console.error('pad drawing is not completed when cleanup is called'); - - this.#painters.forEach(p => p.cleanup()); - - const svg_p = this.getPadSvg(); - if (!svg_p.empty()) { - svg_p.property('pad_painter', null); - if (!this.isCanvas()) - svg_p.remove(); - } - - this.#main_painter_ref = undefined; - this.#frame_painter_ref = undefined; - this.#pad_x = this.#pad_y = this.#pad_width = this.#pad_height = undefined; - this.#doing_draw = undefined; - this.#snap_primitives = undefined; - this.#last_grayscale = undefined; - this.#custom_palette = this.#custom_colors = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; - - this.#painters = []; - this.#pad = undefined; - this.#pad_name = undefined; - this.#has_canvas = false; - - selectActivePad({ pp: this, active: false }); - - super.cleanup(); - } - - /** @summary Returns frame painter inside the pad - * @private */ - getFramePainter() { return this.#frame_painter_ref; } - - /** @summary Assign actual frame painter - * @private */ - setFramePainter(fp, on) { - if (on) - this.#frame_painter_ref = fp; - else if (this.#frame_painter_ref === fp) - this.#frame_painter_ref = undefined; - } - - /** @summary get pad width */ - getPadWidth() { return this.#pad_width || 0; } - - /** @summary get pad height */ - getPadHeight() { return this.#pad_height || 0; } - - /** @summary get pad height */ - getPadScale() { return this.#pad_scale || 1; } - - /** @summary get pad rect */ - getPadRect() { - return { - x: this.#pad_x || 0, - y: this.#pad_y || 0, - width: this.getPadWidth(), - height: this.getPadHeight() - }; - } - - /** @summary return pad log state x or y are allowed */ - getPadLog(name) { - const pad = this.getRootPad(); - if (name === 'x') - return pad?.fLogx; - if (name === 'y') - return pad?.fLogv ?? pad?.fLogy; - return false; - } - - /** @summary Returns frame coordinates - also when frame is not drawn */ - getFrameRect() { - const fp = this.getFramePainter(); - if (fp) - return fp.getFrameRect(); - - const w = this.getPadWidth(), - h = this.getPadHeight(), - rect = {}; - - if (this.#pad) { - rect.szx = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fLeftMargin, this.#pad.fRightMargin)) * w); - rect.szy = Math.round(Math.max(0, 0.5 - Math.max(this.#pad.fBottomMargin, this.#pad.fTopMargin)) * h); - } else { - rect.szx = Math.round(0.5 * w); - rect.szy = Math.round(0.5 * h); - } - - rect.width = 2 * rect.szx; - rect.height = 2 * rect.szy; - rect.x = Math.round(w / 2 - rect.szx); - rect.y = Math.round(h / 2 - rect.szy); - rect.hint_delta_x = rect.szx; - rect.hint_delta_y = rect.szy; - rect.transform = makeTranslate(rect.x, rect.y) || ''; - - return rect; - } - - /** @summary return RPad object */ - getRootPad(is_root6) { - return (is_root6 === undefined) || is_root6 ? this.#pad : null; - } - - /** @summary Cleanup primitives from pad - selector lets define which painters to remove - * @return true if any painter was removed */ - cleanPrimitives(selector) { - // remove all primitives - if (selector === true) - selector = () => true; - - if (!isFunc(selector)) - return false; - - let is_any = false; - - for (let k = this.#painters.length - 1; k >= 0; --k) { - const subp = this.#painters[k]; - if (!subp || selector(subp)) { - subp?.cleanup(); - this.#painters.splice(k, 1); - is_any = true; - } - } - - return is_any; - } - - /** @summary Removes and cleanup specified primitive - * @desc also secondary primitives will be removed - * @return new index to continue loop or -111 if main painter removed - * @private */ - removePrimitive(arg, clean_only_secondary) { - let indx, prim; - if (Number.isInteger(arg)) { - indx = arg; - prim = this.#painters[indx]; - } else { - indx = this.#painters.indexOf(arg); - prim = arg; - } - if (indx < 0) - return indx; - - const arr = [], get_main = clean_only_secondary ? this.getMainPainter() : null; - let resindx = indx - 1; // object removed itself - arr.push(prim); - this.#painters.splice(indx, 1); - - // loop to extract all dependent painters - let len0 = 0; - while (len0 < arr.length) { - for (let k = this.#painters.length - 1; k >= 0; --k) { - if (this.#painters[k].isSecondary(arr[len0])) { - arr.push(this.#painters[k]); - this.#painters.splice(k, 1); - if (k < indx) - resindx--; - } - } - len0++; - } - - arr.forEach(painter => { - if ((painter !== prim) || !clean_only_secondary) - painter.cleanup(); - if (this.getMainPainter() === painter) { - this.setMainPainter(undefined, true); - resindx = -111; - } - }); - - // when main painter disappears because of special cleanup - also reset zooming - if (clean_only_secondary && get_main && !this.getMainPainter()) - this.getFramePainter()?.resetZoom(); - - return resindx; - } - - /** @summary returns custom palette associated with pad or top canvas - * @private */ - getCustomPalette(no_recursion) { - return this.#custom_palette || (no_recursion ? null : this.getCanvPainter()?.getCustomPalette(true)); - } - - _getCustomPaletteIndexes() { return this.#custom_palette_indexes; } - - /** @summary Provides automatic color - * @desc Uses ROOT colors palette if possible - * @private */ - getAutoColor(numprimitives, indx) { - if (!numprimitives || indx === undefined) { - numprimitives = Math.max(numprimitives || (this.#num_primitives || 5) - (this.#num_specials || 0), 2); - indx = this.#auto_color_cnt ?? 0; - this.#auto_color_cnt = (indx + 1) % numprimitives; - if (indx >= numprimitives) - indx = numprimitives - 1; - } - - let indexes = this._getCustomPaletteIndexes(); - if (!indexes) { - const cp = this.getCanvPainter(); - if ((cp !== this) && isFunc(cp?._getCustomPaletteIndexes)) - indexes = cp._getCustomPaletteIndexes(); - } - - if (indexes?.length) { - const p = Math.round(indx * (indexes.length - 3) / (numprimitives - 1)); - return indexes[p]; - } - - if (!this.#auto_palette) - this.#auto_palette = getColorPalette(settings.Palette, this.isGrayscale()); - const palindx = Math.round(indx * (this.#auto_palette.getLength() - 3) / (numprimitives - 1)), - colvalue = this.#auto_palette.getColor(palindx); - - return this.addColor(colvalue); - } - - /** @summary Returns number of painters - * @protected */ - getNumPainters() { return this.#painters.length; } - - /** @summary Add painter to pad list of painters - * @protected */ - addToPrimitives(painter) { - if (this.#painters.indexOf(painter) < 0) - this.#painters.push(painter); - return this; - } - - /** @summary Call function for each painter in pad - * @param {Function} userfunc - function to call - * @param {string} kind - 'all' for all objects (default), 'pads' only pads and sub-pads, 'objects' only for object in current pad - * @private */ - forEachPainterInPad(userfunc, kind) { - if (!kind) - kind = 'all'; - if (kind !== 'objects') - userfunc(this); - for (let k = 0; k < this.#painters.length; ++k) { - const sub = this.#painters[k]; - if (isFunc(sub.forEachPainterInPad)) { - if (kind !== 'objects') - sub.forEachPainterInPad(userfunc, kind); - } else if (kind !== 'pads') - userfunc(sub); - } - } - - /** @summary register for pad events receiver - * @desc in pad painter, while pad may be drawn without canvas */ - registerForPadEvents(receiver) { - this.pad_events_receiver = receiver; - } - - /** @summary Generate pad events, normally handled by GED - * @desc in pad painter, while pad may be drawn without canvas - * @private */ - producePadEvent(what, padpainter, painter, position) { - if ((what === 'select') && isFunc(this.selectActivePad)) - this.selectActivePad(padpainter, painter, position); - - if (isFunc(this.pad_events_receiver)) - this.pad_events_receiver({ what, padpainter, painter, position }); - } - - /** @summary method redirect call to pad events receiver */ - selectObjectPainter(painter, pos) { - const canp = this.isTopPad() ? this : this.getCanvPainter(); - - if (painter === undefined) - painter = this; - - if (pos && !this.isTopPad()) - pos = getAbsPosInCanvas(this.getPadSvg(), pos); - - selectActivePad({ pp: this, active: true }); - - canp?.producePadEvent('select', this, painter, pos); - } - - /** @summary Draw pad active border - * @private */ - drawActiveBorder(svg_rect, is_active) { - if (is_active !== undefined) { - if (this.is_active_pad === is_active) - return; - this.is_active_pad = is_active; - } - - if (this.is_active_pad === undefined) - return; - - if (!svg_rect) - svg_rect = this.isCanvas() ? this.getCanvSvg().selectChild('.canvas_fillrect') : this.getPadSvg().selectChild('.root_pad_border'); - - const cp = this.getCanvPainter(); - - let lineatt = this.is_active_pad && cp?.highlight_gpad ? new TAttLineHandler({ style: 1, width: 1, color: 'red' }) : this.lineatt; - if (!lineatt) - lineatt = new TAttLineHandler({ color: 'none' }); - - svg_rect.call(lineatt.func); - } - - /** @summary Set fast drawing property depending on the size - * @private */ - setFastDrawing(w, h) { - const was_fast = this.#fast_drawing; - this.#fast_drawing = !this.hasSnapId() && settings.SmallPad && ((w < settings.SmallPad.width) || (h < settings.SmallPad.height)); - if (was_fast !== this.#fast_drawing) - this.showPadButtons(); - } - - /** @summary Return fast drawing flag - * @private */ - isFastDrawing() { return this.#fast_drawing; } - - /** @summary Returns true if canvas configured with grayscale - * @private */ - isGrayscale() { - if (!this.isCanvas()) - return false; - return this.#pad?.TestBit(kIsGrayscale) ?? false; - } - - /** @summary Returns true if default pad range is configured - * @private */ - isDefaultPadRange() { - if (!this.#pad) - return true; - return (this.#pad.fX1 === 0) && (this.#pad.fX2 === 1) && (this.#pad.fY1 === 0) && (this.#pad.fY2 === 1); - } - - /** @summary Set grayscale mode for the canvas - * @private */ - setGrayscale(flag) { - if (!this.isTopPad()) - return; - - let changed = false; - - if (flag === undefined) { - flag = this.#pad?.TestBit(kIsGrayscale) ?? false; - changed = (this.#last_grayscale !== undefined) && (this.#last_grayscale !== flag); - } else if (flag !== this.#pad?.TestBit(kIsGrayscale)) { - this.#pad?.InvertBit(kIsGrayscale); - changed = true; - } - - if (changed) { - this.forEachPainter(p => { - if (isFunc(p.clearHistPalette)) - p.clearHistPalette(); - }); - } - - this.setColors(flag ? getGrayColors(this.#custom_colors) : this.#custom_colors); - - this.#last_grayscale = flag; - - this.#custom_palette = this.#custom_palette_colors ? new ColorPalette(this.#custom_palette_colors, flag) : null; - } - - /** @summary Set fixed-size canvas - * @private */ - _setFixedSize(on) { this.#fixed_size = on; } - - /** @summary Create SVG element for canvas */ - createCanvasSvg(check_resize, new_size) { - const is_batch = this.isBatchMode(), lmt = 5; - let factor, svg, rect, btns, info, frect; - - if (check_resize > 0) { - if (this.#fixed_size) - return check_resize > 1; // flag used to force re-drawing of all sub-pads - - svg = this.getCanvSvg(); - if (svg.empty()) - return false; - - factor = svg.property('height_factor'); - - rect = this.testMainResize(check_resize, null, factor); - - if (!rect.changed && (check_resize === 1)) - return false; - - if (!is_batch) - btns = this.getLayerSvg('btns_layer'); - - info = this.getLayerSvg('info_layer'); - frect = svg.selectChild('.canvas_fillrect'); - } else { - const render_to = this.selectDom(); - - if (render_to.style('position') === 'static') - render_to.style('position', 'relative'); - - svg = render_to.append('svg') - .attr('class', 'jsroot root_canvas') - .property('pad_painter', this) // this is custom property - .property('redraw_by_resize', false); // could be enabled to force redraw by each resize - - this.setTopPainter(); // assign canvas as top painter of that element - - if (is_batch) - svg.attr('xmlns', nsSVG); - else if (!this.online_canvas) - svg.append('svg:title').text('ROOT canvas'); - - if (!is_batch) - svg.style('user-select', settings.UserSelect || null); - - if (!is_batch || (this.#pad.fFillStyle > 0)) - frect = svg.append('svg:path').attr('class', 'canvas_fillrect'); - - if (!is_batch) { - frect.style('pointer-events', 'visibleFill') - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter()) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } - - svg.append('svg:g').attr('class', 'primitives_layer'); - info = svg.append('svg:g').attr('class', 'info_layer'); - if (!is_batch) { - btns = svg.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide === 'left') - .property('vertical', settings.ToolBarVert); - } - - factor = 0.66; - if (this.#pad?.fCw && this.#pad?.fCh && (this.#pad?.fCw > 0)) { - factor = this.#pad.fCh / this.#pad.fCw; - if ((factor < 0.1) || (factor > 10)) - factor = 0.66; - } - - if (this.#fixed_size) { - render_to.style('overflow', 'auto'); - rect = { width: this.#pad.fCw, height: this.#pad.fCh }; - if (!rect.width || !rect.height) - rect = getElementRect(render_to); - } else - rect = this.testMainResize(2, new_size, factor); - } - - this.setGrayscale(); - - this.createAttFill({ attr: this.#pad }); - - if ((rect.width <= lmt) || (rect.height <= lmt)) { - if (!this.hasSnapId()) { - svg.style('display', 'none'); - console.warn(`Hide canvas while geometry too small w=${rect.width} h=${rect.height}`); - } - if (this.#pad_width && this.#pad_height) { - // use last valid dimensions - rect.width = this.#pad_width; - rect.height = this.#pad_height; - } else { - // just to complete drawing. - rect.width = 800; - rect.height = 600; - } - } else - svg.style('display', null); - - svg.attr('x', 0).attr('y', 0).style('position', 'absolute'); - - if (this.#fixed_size) - svg.attr('width', rect.width).attr('height', rect.height); - else - svg.style('width', '100%').style('height', '100%').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); - - svg.style('filter', settings.DarkMode || this.#pad?.$dark ? 'invert(100%)' : null); - - this.#pad_scale = settings.CanvasScale || 1; - this.#pad_x = 0; - this.#pad_y = 0; - this.#pad_width = rect.width * this.#pad_scale; - this.#pad_height = rect.height * this.#pad_scale; - - svg.attr('viewBox', `0 0 ${this.#pad_width} ${this.#pad_height}`) - .attr('preserveAspectRatio', 'none') // we do not preserve relative ratio - .property('height_factor', factor) - .property('draw_x', this.#pad_x) - .property('draw_y', this.#pad_y) - .property('draw_width', this.#pad_width) - .property('draw_height', this.#pad_height); - - this.addPadBorder(svg, frect); - - this.setFastDrawing(this.#pad_width * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), this.#pad_height * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); - - if (this.alignButtons && btns) - this.alignButtons(btns, this.#pad_width, this.#pad_height); - - let dt = info.selectChild('.canvas_date'); - if (!gStyle.fOptDate) - dt.remove(); - else { - if (dt.empty()) - dt = info.append('text').attr('class', 'canvas_date'); - const posy = Math.round(this.#pad_height * (1 - gStyle.fDateY)), - date = new Date(); - let posx = Math.round(this.#pad_width * gStyle.fDateX); - if (!is_batch && (posx < 25)) - posx = 25; - if (gStyle.fOptDate > 3) - date.setTime(gStyle.fOptDate * 1000); - - makeTranslate(dt, posx, posy) - .style('text-anchor', 'start') - .text(convertDate(date)); - } - - const iname = this.getItemName(); - if (iname) - this.drawItemNameOnCanvas(iname); - else if (!gStyle.fOptFile) - info.selectChild('.canvas_item').remove(); - - return true; - } - - /** @summary Draw item name on canvas if gStyle.fOptFile is configured - * @private */ - drawItemNameOnCanvas(item_name) { - const info = this.getLayerSvg('info_layer'); - let df = info.selectChild('.canvas_item'); - const fitem = getHPainter().findRootFileForItem(item_name), - fname = (gStyle.fOptFile === 3) ? item_name : ((gStyle.fOptFile === 2) ? fitem?._fullurl : fitem?._name); - - if (!gStyle.fOptFile || !fname) - df.remove(); - else { - if (df.empty()) - df = info.append('text').attr('class', 'canvas_item'); - const rect = this.getPadRect(); - makeTranslate(df, Math.round(rect.width * (1 - gStyle.fDateX)), Math.round(rect.height * (1 - gStyle.fDateY))) - .style('text-anchor', 'end') - .text(fname); - } - if (((gStyle.fOptDate === 2) || (gStyle.fOptDate === 3)) && fitem?._file) { - info.selectChild('.canvas_date') - .text(convertDate(getTDatime(gStyle.fOptDate === 2 ? fitem._file.fDatimeC : fitem._file.fDatimeM))); - } - } - - /** @summary Return true if this pad enlarged */ - isPadEnlarged() { - if (this.isTopPad()) - return this.enlargeMain('state') === 'on'; - return this.getCanvSvg().property('pad_enlarged') === this.#pad; - } - - /** @summary Enlarge pad draw element when possible */ - enlargePad(evnt, is_dblclick, is_escape) { - evnt?.preventDefault(); - evnt?.stopPropagation(); - - // ignore double click on online canvas itself for enlarge - if (is_dblclick && this.isCanvas(true) && (this.enlargeMain('state') === 'off')) - return; - - const svg_can = this.getCanvSvg(), - pad_enlarged = svg_can.property('pad_enlarged'); - - if (this.isTopPad() || (!pad_enlarged && !this.hasObjectsToDraw() && !this.#painters)) { - if (this.#fixed_size) - return; // canvas cannot be enlarged in such mode - if (!this.enlargeMain(is_escape ? false : 'toggle')) - return; - if (this.enlargeMain('state') === 'off') - svg_can.property('pad_enlarged', null); - else - selectActivePad({ pp: this, active: true }); - } else if (!pad_enlarged && !is_escape) { - this.enlargeMain(true, true); - svg_can.property('pad_enlarged', this.#pad); - selectActivePad({ pp: this, active: true }); - } else if (pad_enlarged === this.#pad) { - this.enlargeMain(false); - svg_can.property('pad_enlarged', null); - } else if (!is_escape && is_dblclick) - console.error('missmatch with pad double click events'); - - return this.checkResize(true); - } - - /** @summary Create main SVG element for pad - * @return true when pad is displayed and all its items should be redrawn */ - createPadSvg(only_resize) { - if (this.isTopPad()) { - this.createCanvasSvg(only_resize ? 2 : 0); - return true; - } - - const svg_can = this.getCanvSvg(), - width = svg_can.property('draw_width'), - height = svg_can.property('draw_height'), - pad_enlarged = svg_can.property('pad_enlarged'), - pad_visible = !this.#pad_draw_disabled && (!pad_enlarged || (pad_enlarged === this.#pad)), - is_batch = this.isBatchMode(); - let w = Math.round(this.#pad.fAbsWNDC * width), - h = Math.round(this.#pad.fAbsHNDC * height), - x = Math.round(this.#pad.fAbsXlowNDC * width), - y = Math.round(height * (1 - this.#pad.fAbsYlowNDC)) - h, - svg_pad, svg_border, btns; - - if (pad_enlarged === this.#pad) { - w = width; - h = height; - x = y = 0; - } - - if (only_resize) { - svg_pad = this.getPadSvg(); - svg_border = svg_pad.selectChild('.root_pad_border'); - if (!is_batch) - btns = this.getLayerSvg('btns_layer'); - this.addPadInteractive(true); - } else { - svg_pad = svg_can.selectChild('.primitives_layer') - .append('svg:svg') // svg used to blend all drawings outside - .classed('__root_pad_' + this.#pad_name, true) - .attr('pad', this.#pad_name) // set extra attribute to mark pad name - .property('pad_painter', this); // this is custom property - - if (!is_batch) - svg_pad.append('svg:title').text('subpad ' + this.#pad_name); - - // need to check attributes directly while attributes objects will be created later - if (!is_batch || (this.#pad.fFillStyle > 0) || ((this.#pad.fLineStyle > 0) && (this.#pad.fLineColor > 0))) - svg_border = svg_pad.append('svg:path').attr('class', 'root_pad_border'); - - if (!is_batch) { - svg_border.style('pointer-events', 'visibleFill') // get events also for not visible rect - .on('dblclick', evnt => this.enlargePad(evnt, true)) - .on('click', () => this.selectObjectPainter()) - .on('mouseenter', () => this.showObjectStatus()) - .on('contextmenu', settings.ContextMenu ? evnt => this.padContextMenu(evnt) : null); - } - - svg_pad.append('svg:g').attr('class', 'primitives_layer'); - if (!is_batch) { - btns = svg_pad.append('svg:g') - .attr('class', 'btns_layer') - .property('leftside', settings.ToolBarSide !== 'left') - .property('vertical', settings.ToolBarVert); - } - } - - this.createAttFill({ attr: this.#pad }); - this.createAttLine({ attr: this.#pad, color0: !this.#pad.fBorderMode ? 'none' : '' }); - - svg_pad.style('display', pad_visible ? null : 'none') - .attr('viewBox', `0 0 ${w} ${h}`) // due to svg - .attr('preserveAspectRatio', 'none') // due to svg, we do not preserve relative ratio - .attr('x', x) // due to svg - .attr('y', y) // due to svg - .attr('width', w) // due to svg - .attr('height', h) // due to svg - .property('draw_x', x) // this is to make similar with canvas - .property('draw_y', y) - .property('draw_width', w) - .property('draw_height', h); - - this.#pad_scale = this.getCanvPainter().getPadScale(); - this.#pad_x = x; - this.#pad_y = y; - this.#pad_width = w; - this.#pad_height = h; - - this.addPadBorder(svg_pad, svg_border, true); - - this.setFastDrawing(w * (1 - this.#pad.fLeftMargin - this.#pad.fRightMargin), h * (1 - this.#pad.fBottomMargin - this.#pad.fTopMargin)); - - // special case of 3D canvas overlay - if (svg_pad.property('can3d') === constants.Embed3D.Overlay) { - this.selectDom().select('.draw3d_' + this.#pad_name) - .style('display', pad_visible ? '' : 'none'); - } - - if (this.alignButtons && btns) - this.alignButtons(btns, this.#pad_width, this.#pad_height); - - return pad_visible; - } - - /** @summary Add border decorations - * @private */ - addPadBorder(svg_pad, svg_border, draw_line) { - if (!svg_border) - return; - - svg_border.attr('d', `M0,0H${this.#pad_width}V${this.#pad_height}H0Z`) - .call(this.fillatt.func); - if (draw_line) - svg_border.call(this.lineatt.func); - - this.drawActiveBorder(svg_border); - - let svg_border1 = svg_pad.selectChild('.root_pad_border1'), - svg_border2 = svg_pad.selectChild('.root_pad_border2'); - - if (this.#pad.fBorderMode && this.#pad.fBorderSize) { - const arr = getBoxDecorations(0, 0, this.#pad_width, this.#pad_height, this.#pad.fBorderMode, this.#pad.fBorderSize, this.#pad.fBorderSize); - - if (svg_border2.empty()) - svg_border2 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border2'); - if (svg_border1.empty()) - svg_border1 = svg_pad.insert('svg:path', '.primitives_layer').attr('class', 'root_pad_border1'); - - svg_border1.attr('d', arr[0]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).brighter(0.5).formatRgb()); - svg_border2.attr('d', arr[1]) - .call(this.fillatt.func) - .style('fill', d3_rgb(this.fillatt.color).darker(0.5).formatRgb()); - } else { - svg_border1.remove(); - svg_border2.remove(); - } - } - - /** @summary Add pad interactive features like dragging and resize - * @private */ - addPadInteractive(cleanup = false) { - if (isFunc(this.$userInteractive)) { - this.$userInteractive(); - delete this.$userInteractive; - } - - if (this.isBatchMode() || this.isCanvas() || !this.isEditable()) - return; - - const svg_can = this.getCanvSvg(), - width = svg_can.property('draw_width'), - height = svg_can.property('draw_height'); - - addDragHandler(this, { - cleanup, // do cleanup to let assign new handlers later on - x: this.#pad_x, y: this.#pad_y, width: this.#pad_width, height: this.#pad_height, no_transform: true, - only_resize: true, - is_disabled: kind => svg_can.property('pad_enlarged') || this.btns_active_flag || - (kind === 'move' && (this.options._disable_dragging || this.getFramePainter()?.mode3d)), - getDrawG: () => this.getPadSvg(), - pad_rect: { width, height }, - minwidth: 20, minheight: 20, - move_resize: (_x, _y, _w, _h) => { - const x0 = this.#pad.fAbsXlowNDC, - y0 = this.#pad.fAbsYlowNDC, - scale_w = _w / width / this.#pad.fAbsWNDC, - scale_h = _h / height / this.#pad.fAbsHNDC, - shift_x = _x / width - x0, - shift_y = 1 - (_y + _h) / height - y0; - this.forEachPainterInPad(p => { - const subpad = p.getRootPad(); - subpad.fAbsXlowNDC += (subpad.fAbsXlowNDC - x0) * (scale_w - 1) + shift_x; - subpad.fAbsYlowNDC += (subpad.fAbsYlowNDC - y0) * (scale_h - 1) + shift_y; - subpad.fAbsWNDC *= scale_w; - subpad.fAbsHNDC *= scale_h; - }, 'pads'); - }, - redraw: () => this.interactiveRedraw('pad', 'padpos') - }); - } - - /** @summary Disable pad drawing - * @desc Complete SVG element will be hidden */ - disablePadDrawing() { - if (!this.#pad_draw_disabled && !this.isTopPad()) { - this.#pad_draw_disabled = true; - this.createPadSvg(true); - } - } - - /** @summary Check if it is special object, which should be handled separately - * @desc It can be TStyle or list of colors or palette object - * @return {boolean} true if any */ - checkSpecial(obj) { - if (!obj) - return false; - - if (obj._typename === clTStyle) { - Object.assign(gStyle, obj); - return true; - } - - const o = this.getOptions(true); - - if ((obj._typename === clTObjArray) && (obj.name === 'ListOfColors')) { - if (o?.CreatePalette) { - let arr = []; - for (let n = obj.arr.length - o.CreatePalette; n < obj.arr.length; ++n) { - const col = getRGBfromTColor(obj.arr[n]); - if (!col) { - console.log('Fail to create color for palette'); - arr = null; - break; - } - arr.push(col); - } - if (arr.length) - this.#custom_palette = new ColorPalette(arr); - } - - if (!o || o.GlobalColors) // set global list of colors - adoptRootColors(obj); - - // copy existing colors and extend with new values - this.#custom_colors = o?.LocalColors ? extendRootColors(null, obj) : null; - return true; - } - - if ((obj._typename === clTObjArray) && (obj.name === 'CurrentColorPalette')) { - const arr = [], indx = []; - let missing = false; - for (let n = 0; n < obj.arr.length; ++n) { - const col = obj.arr[n]; - if (col?._typename === clTColor) { - indx[n] = col.fNumber; - arr[n] = getRGBfromTColor(col); - } else { - console.log(`Missing color with index ${n}`); - missing = true; - } - } - - const apply = (!o || (!missing && !o.IgnorePalette)); - this.#custom_palette_indexes = apply ? indx : null; - this.#custom_palette_colors = apply ? arr : null; - - return true; - } - - return false; - } - - /** @summary Check if special objects appears in primitives - * @desc it could be list of colors or palette */ - checkSpecialsInPrimitives(can, count_specials) { - const lst = can?.fPrimitives; - if (count_specials) - this.#num_specials = 0; - if (!lst) - return; - for (let i = 0; i < lst.arr?.length; ++i) { - if (lst.arr[i] === lst) { - lst.arr[i] = null; - console.error('list of primitives includes itself - endless recursion'); - } else if (this.checkSpecial(lst.arr[i])) { - lst.arr[i].$special = true; // mark object as special one, do not use in drawing - if (count_specials) - this.#num_specials++; - } - } - } - - /** @summary try to find object by name in list of pad primitives - * @desc used to find title drawing - * @private */ - findInPrimitives(objname, objtype) { - const match = obj => obj && (obj?.fName === objname) && (objtype ? (obj?._typename === objtype) : true), - snap = this.#snap_primitives?.find(s => match((s.fKind === webSnapIds.kObject) ? s.fSnapshot : null)); - - return snap ? snap.fSnapshot : this.#pad?.fPrimitives?.arr.find(match); - } - - /** @summary Try to find painter for specified object - * @desc can be used to find painter for some special objects, registered as - * histogram functions - * @param {object} selobj - object to which painter should be search, set null to ignore parameter - * @param {string} [selname] - object name, set to null to ignore - * @param {string} [seltype] - object type, set to null to ignore - * @return {object} - painter for specified object (if any) - * @private */ - findPainterFor(selobj, selname, seltype) { - return this.#painters.find(p => { - const pobj = p.getObject(); - if (!pobj) - return false; - - if (selobj && (pobj === selobj)) - return true; - if (!selname && !seltype) - return false; - if (selname && (pobj.fName !== selname)) - return false; - if (seltype && (pobj._typename !== seltype)) - return false; - return true; - }); - } - - /** @summary Return true if any objects beside sub-pads exists in the pad */ - hasObjectsToDraw() { - return this.#pad?.fPrimitives?.arr?.find(obj => obj._typename !== clTPad); - } - - /** @summary sync drawing/redrawing/resize of the pad - * @param {string} kind - kind of draw operation, if true - always queued - * @return {Promise} when pad is ready for draw operation or false if operation already queued - * @private */ - syncDraw(kind) { - const entry = { kind: kind || 'redraw' }; - if (this.#doing_draw === undefined) { - this.#doing_draw = [entry]; - return Promise.resolve(true); - } - // if queued operation registered, ignore next calls, indx === 0 is running operation - if ((entry.kind !== true) && (this.#doing_draw.findIndex((e, i) => (i > 0) && (e.kind === entry.kind)) > 0)) - return false; - this.#doing_draw.push(entry); - return new Promise(resolveFunc => { - entry.func = resolveFunc; - }); - } - - /** @summary indicates if painter performing objects draw - * @private */ - doingDraw() { return this.#doing_draw !== undefined; } - - /** @summary confirms that drawing is completed, may trigger next drawing immediately - * @private */ - confirmDraw() { - if (this.#doing_draw === undefined) - return console.warn('failure, should not happen'); - this.#doing_draw.shift(); - if (!this.#doing_draw.length) - this.#doing_draw = undefined; - else { - const entry = this.#doing_draw[0]; - if (entry.func) { - entry.func(); - delete entry.func; - } - } - } - - /** @summary Draw single primitive */ - async drawObject(/* dom, obj, opt */) { - console.log('Not possible to draw object without loading of draw.mjs'); - return null; - } - - /** @summary Draw pad primitives - * @return {Promise} when drawing completed - * @private */ - async drawPrimitives(indx) { - if (indx === undefined) { - if (this.isCanvas()) - this.#start_draw_tm = new Date().getTime(); - - // set number of primitives - this.#num_primitives = this.#pad?.fPrimitives?.arr?.length || 0; - - // sync to prevent immediate pad redraw during normal drawing sequence - return this.syncDraw(true).then(() => this.drawPrimitives(0)); - } - - if (!this.#pad || (indx >= this.#num_primitives)) { - if (this.#start_draw_tm) { - const spenttm = new Date().getTime() - this.#start_draw_tm; - if (spenttm > 1000) - console.log(`Canvas ${this.#pad?.fName || '---'} drawing took ${(spenttm * 1e-3).toFixed(2)}s`); - this.#start_draw_tm = undefined; - } - - this.confirmDraw(); - return; - } - - const obj = this.#pad.fPrimitives.arr[indx]; - - if (!obj || obj.$special || ((indx > 0) && (obj._typename === clTFrame) && this.getFramePainter())) - return this.drawPrimitives(indx + 1); - - // use of Promise should avoid large call-stack depth when many primitives are drawn - return this.drawObject(this, obj, this.#pad.fPrimitives.opt[indx]).then(op => { - if (isObject(op)) - op._primitive = true; // mark painter as belonging to primitives - - return this.drawPrimitives(indx + 1); - }); - } - - /** @summary Divide pad on sub-pads - * @return {Promise} when finished - * @private */ - async divide(nx, ny, use_existing) { - let color = this.#pad.fFillColor; - if (!use_existing) { - if (color < 15) - color = 19; - else if (color < 20) - color--; - } - - if (nx && !ny && use_existing) { - for (let k = 0; k < nx; ++k) { - if (!this.getSubPadPainter(k + 1)) { - use_existing = false; - break; - } - } - if (use_existing) - return this; - } - - this.cleanPrimitives(isPadPainter); - if (!this.#pad.fPrimitives) - this.#pad.fPrimitives = create(clTList); - this.#pad.fPrimitives.Clear(); - - if ((!nx && !ny) || !this.#pad.Divide(nx, ny, 0.01, 0.01, color)) - return this; - - const drawNext = indx => { - if (indx >= this.#pad.fPrimitives.arr.length) - return this; - return this.drawObject(this, this.#pad.fPrimitives.arr[indx]).then(() => drawNext(indx + 1)); - }; - - return drawNext(0); - } - - /** @summary Return sub-pads painter, only direct childs are checked - * @private */ - getSubPadPainter(n) { - for (let k = 0; k < this.#painters.length; ++k) { - const sub = this.#painters[k]; - if (isPadPainter(sub) && (sub.getRootPad()?.fNumber === n)) - return sub; - } - return null; - } - - /** @summary Process tooltip event in the pad - * @private */ - processPadTooltipEvent(pnt) { - const painters = [], hints = []; - - // first count - how many processors are there - this.#painters?.forEach(obj => { - if (isFunc(obj.processTooltipEvent)) - painters.push(obj); - }); - - if (pnt) - pnt.nproc = painters.length; - - painters.forEach(obj => { - const hint = obj.processTooltipEvent(pnt) || { user_info: null }; - hints.push(hint); - if (pnt?.painters) - hint.painter = obj; - }); - - return hints; - } - - /** @summary Changes canvas dark mode - * @private */ - changeDarkMode(mode) { - this.getCanvSvg().style('filter', (mode ?? settings.DarkMode) ? 'invert(100%)' : null); - } - - /** @summary Fill pad context menu - * @private */ - fillContextMenu(menu) { - const pad = this.getRootPad(true); - if (!pad) - return false; - - menu.header(`${pad._typename}::${pad.fName}`, `${urlClassPrefix}${pad._typename}.html`); - - menu.addchk(this.isTooltipAllowed(), 'Show tooltips', () => this.setTooltipAllowed('toggle')); - - menu.addchk(pad.fGridx, 'Grid x', flag => { - pad.fGridx = flag ? 1 : 0; - this.interactiveRedraw('pad', `exec:SetGridx(${flag ? 1 : 0})`); - }); - menu.addchk(pad.fGridy, 'Grid y', flag => { - pad.fGridy = flag ? 1 : 0; - this.interactiveRedraw('pad', `exec:SetGridy(${flag ? 1 : 0})`); - }); - menu.sub('Ticks x'); - menu.addchk(pad.fTickx === 0, 'normal', () => { - pad.fTickx = 0; - this.interactiveRedraw('pad', 'exec:SetTickx(0)'); - }); - menu.addchk(pad.fTickx === 1, 'ticks on both sides', () => { - pad.fTickx = 1; - this.interactiveRedraw('pad', 'exec:SetTickx(1)'); - }); - menu.addchk(pad.fTickx === 2, 'labels on both sides', () => { - pad.fTickx = 2; - this.interactiveRedraw('pad', 'exec:SetTickx(2)'); - }); - menu.endsub(); - menu.sub('Ticks y'); - menu.addchk(pad.fTicky === 0, 'normal', () => { - pad.fTicky = 0; - this.interactiveRedraw('pad', 'exec:SetTicky(0)'); - }); - menu.addchk(pad.fTicky === 1, 'ticks on both sides', () => { - pad.fTicky = 1; - this.interactiveRedraw('pad', 'exec:SetTicky(1)'); - }); - menu.addchk(pad.fTicky === 2, 'labels on both sides', () => { - pad.fTicky = 2; - this.interactiveRedraw('pad', 'exec:SetTicky(2)'); - }); - menu.endsub(); - - menu.addchk(pad.fEditable, 'Editable', flag => { - pad.fEditable = flag; - this.interactiveRedraw('pad', `exec:SetEditable(${flag})`); - }); - - if (this.isCanvas()) { - menu.addchk(pad.TestBit(kIsGrayscale), 'Gray scale', flag => { - this.setGrayscale(flag); - this.interactiveRedraw('pad', `exec:SetGrayscale(${flag})`); - }); - } - - menu.sub('Border'); - menu.addSelectMenu('Mode', ['Down', 'Off', 'Up'], pad.fBorderMode + 1, v => { - pad.fBorderMode = v - 1; - this.interactiveRedraw(true, `exec:SetBorderMode(${v - 1})`); - }, 'Pad border mode'); - menu.addSizeMenu('Size', 0, 20, 2, pad.fBorderSize, v => { - pad.fBorderSize = v; - this.interactiveRedraw(true, `exec:SetBorderSize(${v})`); - }, 'Pad border size'); - menu.endsub(); - - menu.addAttributesMenu(this); - - if (!this.isCanvas(true)) { - // if not online canvas - - const do_divide = arg => { - if (!arg || !isStr(arg)) - return; - // delete auto_canvas flag to prevent deletion - if (this.#iscan === 'auto') - this.#iscan = true; - this.cleanPrimitives(true); - if (arg === 'reset') - return; - const arr = arg.split('x'); - if (arr.length === 1) - this.divide(Number.parseInt(arr[0])); - else if (arr.length === 2) - this.divide(Number.parseInt(arr[0]), Number.parseInt(arr[1])); - }; - - if (isFunc(this.drawObject)) - menu.add('Build legend', () => this.buildLegend()); - - menu.sub('Divide', () => menu.input('Input divide arg', '2x2').then(do_divide), 'Divide on sub-pads'); - ['1x2', '2x1', '2x2', '2x3', '3x2', '3x3', '4x4', 'reset'].forEach(item => menu.add(item, item, do_divide)); - menu.endsub(); - - menu.add('Save to gStyle', () => { - this.fillatt?.saveToStyle(this.isCanvas() ? 'fCanvasColor' : 'fPadColor'); - gStyle.fPadGridX = pad.fGridx; - gStyle.fPadGridY = pad.fGridy; - gStyle.fPadTickX = pad.fTickx; - gStyle.fPadTickY = pad.fTicky; - gStyle.fOptLogx = pad.fLogx; - gStyle.fOptLogy = pad.fLogy; - gStyle.fOptLogz = pad.fLogz; - }, 'Store pad fill attributes, grid, tick and log scale settings to gStyle'); - - if (this.isCanvas()) { - menu.addSettingsMenu(false, false, arg => { - if (arg === 'dark') - this.changeDarkMode(); - }); - } - } - - menu.separator(); - - // keep typo in function name "actiavteMenuBar" while assigned from ROOT - if (isFunc(this.hasMenuBar) && isFunc(this.actiavteMenuBar)) - menu.addchk(this.hasMenuBar(), 'Menu bar', flag => this.actiavteMenuBar(flag)); - - if (isFunc(this.hasEventStatus) && isFunc(this.activateStatusBar) && isFunc(this.canStatusBar)) { - if (this.canStatusBar()) - menu.addchk(this.hasEventStatus(), 'Event status', () => this.activateStatusBar('toggle')); - } - - if (this.enlargeMain() || (!this.isTopPad() && this.hasObjectsToDraw())) - menu.addchk(this.isPadEnlarged(), 'Enlarge ' + (this.isCanvas() ? 'canvas' : 'pad'), () => this.enlargePad()); - - const fname = this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad'); - menu.sub('Save as'); - const fmts = ['svg', 'png', 'jpeg', 'webp']; - if (internals.makePDF) - fmts.push('pdf'); - fmts.forEach(fmt => menu.add(`${fname}.${fmt}`, () => this.saveAs(fmt, this.isCanvas(), `${fname}.${fmt}`), `Produce ${fmt} image`)); - if (this.isCanvas()) { - menu.separator(); - menu.add(`${fname}.html`, () => this.saveAs('html', true, `${fname}.html`), 'Produce html with canvas display'); - menu.add(`${fname}0.html`, () => this.saveAs('html', false, `${fname}0.html`), 'Produce compact html with canvas display'); - menu.add(`${fname}.json`, () => this.saveAs('json', true, `${fname}.json`), 'Produce JSON with line spacing'); - menu.add(`${fname}0.json`, () => this.saveAs('json', false, `${fname}0.json`), 'Produce JSON without line spacing'); - } - menu.endsub(); - - return true; - } - - /** @summary Show pad context menu - * @private */ - async padContextMenu(evnt) { - if (evnt.stopPropagation) { - // this is normal event processing and not emulated jsroot event - evnt.stopPropagation(); // disable main context menu - evnt.preventDefault(); // disable browser context menu - this.getFramePainter()?.setLastEventPos(); - } - - return createMenu(evnt, this).then(menu => { - this.fillContextMenu(menu); - return this.fillObjectExecMenu(menu, ''); - }).then(menu => menu.show()); - } - - /** @summary Redraw TLegend object - * @desc Used when object attributes are changed to ensure that legend is up to date - * @private */ - async redrawLegend() { - return this.findPainterFor(null, '', clTLegend)?.redraw(); - } - - /** @summary Redraw pad means redraw ourself - * @return {Promise} when redrawing ready */ - async redrawPad(reason) { - const sync_promise = this.syncDraw(reason); - if (sync_promise === false) { - console.log(`Prevent redrawing of ${this.#pad.fName}`); - return false; - } - - let showsubitems = true; - const redrawNext = indx => { - while (indx < this.#painters.length) { - const sub = this.#painters[indx++]; - let res = 0; - if (showsubitems || isPadPainter(sub)) - res = sub.redraw(reason); - - if (isPromise(res)) - return res.then(() => redrawNext(indx)); - } - return true; - }; - - return sync_promise.then(() => { - if (this.isCanvas()) - this.createCanvasSvg(2); - else - showsubitems = this.createPadSvg(true); - return redrawNext(0); - }).then(() => { - this.addPadInteractive(); - this.confirmDraw(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - return true; - }); - } - - /** @summary redraw pad */ - redraw(reason) { - // intentionally do not return Promise to let re-draw sub-pads in parallel - this.redrawPad(reason); - } - - /** @summary Checks if pad should be redrawn by resize - * @private */ - needRedrawByResize() { - const elem = this.getPadSvg(); - if (!elem.empty() && elem.property('can3d') === constants.Embed3D.Overlay) - return true; - - return this.#painters.findIndex(objp => { - return isFunc(objp.needRedrawByResize) ? objp.needRedrawByResize() : false; - }) >= 0; - } - - /** @summary Check resize of canvas - * @return {Promise} with result or false */ - checkCanvasResize(size, force) { - if (this._ignore_resize || !this.isTopPad()) - return false; - - const sync_promise = this.syncDraw('canvas_resize'); - if (sync_promise === false) - return false; - - if ((size === true) || (size === false)) { - force = size; - size = null; - } - - if (isObject(size) && size.force) - force = true; - - if (!force) - force = this.needRedrawByResize(); - - let changed = false; - const redrawNext = indx => { - if (!changed || (indx >= this.#painters.length)) { - this.confirmDraw(); - return changed; - } - - return getPromise(this.#painters[indx].redraw(force ? 'redraw' : 'resize')).then(() => redrawNext(indx + 1)); - }; - - // return sync_promise.then(() => this.ensureBrowserSize(this.#pad?.fCw, this.#pad?.fCh)).then(() => { - - return sync_promise.then(() => { - changed = this.createCanvasSvg(force ? 2 : 1, size); - - if (changed && this.isCanvas() && this.#pad && this.online_canvas && !this.embed_canvas && !this.isBatchMode()) { - if (this.#resize_tmout) - clearTimeout(this.#resize_tmout); - this.#resize_tmout = setTimeout(() => { - this.#resize_tmout = undefined; - if (isFunc(this.sendResized)) - this.sendResized(); - }, 1000); // long enough delay to prevent multiple occurrence - } - - // if canvas changed, redraw all its subitems. - // If redrawing was forced for canvas, same applied for sub-elements - return redrawNext(0); - }); - } - - /** @summary Update TPad object */ - updateObject(obj) { - const pad = this.getRootPad(); - if (!obj || !pad) - return false; - - pad.fBits = obj.fBits; - pad.fTitle = obj.fTitle; - - pad.fGridx = obj.fGridx; - pad.fGridy = obj.fGridy; - pad.fTickx = obj.fTickx; - pad.fTicky = obj.fTicky; - pad.fLogx = obj.fLogx; - pad.fLogy = obj.fLogy; - pad.fLogz = obj.fLogz; - - pad.fUxmin = obj.fUxmin; - pad.fUxmax = obj.fUxmax; - pad.fUymin = obj.fUymin; - pad.fUymax = obj.fUymax; - - pad.fX1 = obj.fX1; - pad.fX2 = obj.fX2; - pad.fY1 = obj.fY1; - pad.fY2 = obj.fY2; - - // this is main coordinates for sub-pad relative to canvas - pad.fAbsWNDC = obj.fAbsWNDC; - pad.fAbsHNDC = obj.fAbsHNDC; - pad.fAbsXlowNDC = obj.fAbsXlowNDC; - pad.fAbsYlowNDC = obj.fAbsYlowNDC; - - pad.fLeftMargin = obj.fLeftMargin; - pad.fRightMargin = obj.fRightMargin; - pad.fBottomMargin = obj.fBottomMargin; - pad.fTopMargin = obj.fTopMargin; - - pad.fFillColor = obj.fFillColor; - pad.fFillStyle = obj.fFillStyle; - pad.fLineColor = obj.fLineColor; - pad.fLineStyle = obj.fLineStyle; - pad.fLineWidth = obj.fLineWidth; - - pad.fPhi = obj.fPhi; - pad.fTheta = obj.fTheta; - pad.fEditable = obj.fEditable; - - if (this.isCanvas()) - this.checkSpecialsInPrimitives(obj); - - const fp = this.getFramePainter(); - fp?.updateAttributes(!fp.$modifiedNDC); - - if (!obj.fPrimitives) - return false; - - let isany = false, p = 0; - for (let n = 0; n < obj.fPrimitives.arr?.length; ++n) { - if (obj.fPrimitives.arr[n].$special) - continue; - while (p < this.#painters.length) { - const op = this.#painters[p++]; - if (!op._primitive) - continue; - if (op.updateObject(obj.fPrimitives.arr[n], obj.fPrimitives.opt[n])) - isany = true; - break; - } - } - - return isany; - } - - /** @summary add legend object to the pad and redraw it - * @private */ - async buildLegend(x1, y1, x2, y2, title, opt) { - const lp = this.findPainterFor(null, '', clTLegend); - - if (!lp && !isFunc(this.drawObject)) - return Promise.reject(Error('Not possible to build legend while module draw.mjs was not load')); - - const leg = lp?.getObject() ?? create(clTLegend), - pad = this.getRootPad(true); - - leg.fPrimitives.Clear(); - - for (let k = 0; k < this.#painters.length; ++k) { - const painter = this.#painters[k], - obj = painter.getObject(); - if (!obj || obj.fName === kTitle || obj.fName === 'stats' || painter.draw_content === false || - obj._typename === clTLegend || obj._typename === clTHStack || obj._typename === clTMultiGraph) - continue; - - const entry = create(clTLegendEntry); - entry.fObject = obj; - entry.fLabel = painter.getItemName(); - if ((opt === 'all') || !entry.fLabel) - entry.fLabel = obj.fName; - entry.fOption = ''; - if (!entry.fLabel) - continue; - - if (painter.lineatt?.used) - entry.fOption += 'l'; - if (painter.fillatt?.used) - entry.fOption += 'f'; - if (painter.markeratt?.used) - entry.fOption += 'p'; - if (!entry.fOption) - entry.fOption = 'l'; - - leg.fPrimitives.Add(entry); - } - - if (lp) - return lp.redraw(); - - const szx = 0.4; - let szy = leg.fPrimitives.arr.length; - // no entries - no need to draw legend - if (!szy) - return null; - if (szy > 8) - szy = 8; - szy *= 0.1; - - if ((x1 === x2) || (y1 === y2)) { - leg.fX1NDC = szx * pad.fLeftMargin + (1 - szx) * (1 - pad.fRightMargin); - leg.fY1NDC = (1 - szy) * (1 - pad.fTopMargin) + szy * pad.fBottomMargin; - leg.fX2NDC = 0.99 - pad.fRightMargin; - leg.fY2NDC = 0.99 - pad.fTopMargin; - if (opt === undefined) - opt = 'autoplace'; - } else { - leg.fX1NDC = x1; - leg.fY1NDC = y1; - leg.fX2NDC = x2; - leg.fY2NDC = y2; - } - leg.fFillStyle = 1001; - leg.fTitle = title ?? ''; - - return this.drawObject(this, leg, opt); - } - - /** @summary Add object painter to list of primitives - * @private */ - addObjectPainter(objpainter, lst, indx) { - if (objpainter && lst && lst[indx] && !objpainter.hasSnapId()) { - // keep snap id in painter, will be used for the - if (this.#painters.indexOf(objpainter) < 0) - this.#painters.push(objpainter); - - objpainter.assignSnapId(lst[indx].fObjectID); - const setSubSnaps = p => { - if (!p.isPrimary()) - return; - for (let k = 0; k < this.#painters.length; ++k) { - const sub = this.#painters[k]; - if (sub.isSecondary(p) && sub.getSecondaryId()) { - sub.assignSnapId(p.getSnapId() + '#' + sub.getSecondaryId()); - setSubSnaps(sub); - } - } - }; - - setSubSnaps(objpainter); - } - } - - /** @summary Process snap with style - * @private */ - processSnapStyle(snap) { - Object.assign(gStyle, snap.fSnapshot); - } - - /** @summary Process snap with colors - * @private */ - processSnapColors(snap) { - const ListOfColors = decodeWebCanvasColors(snap.fSnapshot.fOper), - o = this.getOptions(true); - - // set global list of colors - if (!o || o.GlobalColors) - adoptRootColors(ListOfColors); - - const greyscale = this.#pad?.TestBit(kIsGrayscale) ?? false, - colors = extendRootColors(null, ListOfColors, greyscale); - - // copy existing colors and extend with new values - this.#custom_colors = o?.LocalColors ? colors : null; - - // set palette - if (snap.fSnapshot.fBuf && (!o || !o.IgnorePalette)) { - const indexes = [], palette = []; - for (let n = 0; n < snap.fSnapshot.fBuf.length; ++n) { - indexes[n] = Math.round(snap.fSnapshot.fBuf[n]); - palette[n] = colors[indexes[n]]; - } - this.#custom_palette_indexes = indexes; - this.#custom_palette_colors = palette; - this.#custom_palette = new ColorPalette(palette, greyscale); - } else - this.#custom_palette = this.#custom_palette_indexes = this.#custom_palette_colors = undefined; - } - - /** @summary Process snap with custom font - * @private */ - processSnapFont(snap) { - const arr = snap.fSnapshot.fOper.split(':'); - addCustomFont(Number.parseInt(arr[0]), arr[1], arr[2], arr[3]); - } - - /** @summary Process special snaps like colors or style objects - * @return {Promise} index where processing should start - * @private */ - processSpecialSnaps(lst) { - while (lst?.length) { - const snap = lst[0]; - - // gStyle object - if (snap.fKind === webSnapIds.kStyle) { - lst.shift(); - this.processSnapStyle(snap); - } else if (snap.fKind === webSnapIds.kColors) { - lst.shift(); - this.processSnapColors(snap); - } else if (snap.fKind === webSnapIds.kFont) { - lst.shift(); - this.processSnapFont(snap); - } else - break; - } - } - - /** @summary Function called when drawing next snapshot from the list - * @return {Promise} for drawing of the snap - * @private */ - async drawNextSnap(lst, pindx, indx) { - if (indx === undefined) { - indx = -1; - this.#num_primitives = lst?.length ?? 0; - } - - ++indx; // change to the next snap - - if (!lst || (indx >= lst.length)) - return this; - - const snap = lst[indx], is_subpad = (snap.fKind === webSnapIds.kSubPad); - - // gStyle object - if (snap.fKind === webSnapIds.kStyle) { - this.processSnapStyle(snap); - return this.drawNextSnap(lst, pindx, indx); // call next - } - - // list of colors - if (snap.fKind === webSnapIds.kColors) { - this.processSnapColors(snap); - return this.drawNextSnap(lst, pindx, indx); // call next - } - - // try to locate existing object painter, only allowed when redrawing pad snap - let objpainter, promise; - - while ((pindx !== undefined) && (pindx < this.#painters.length)) { - const subp = this.#painters[pindx++]; - if (subp.getSnapId() === snap.fObjectID) { - objpainter = subp; - break; - } else if (subp.getSnapId() && !subp.isSecondary() && !is_subpad) { - console.warn(`Mismatch in snapid between painter ${subp.getSnapId()} secondary: ${subp.isSecondary()} type: ${subp.getClassName()} and primitive ${snap.fObjectID} kind ${snap.fKind} type ${snap.fSnapshot?._typename}`); - break; - } - } - - if (objpainter) { - // painter exists - try to update drawing - if (is_subpad) - promise = objpainter.redrawPadSnap(snap); - else if (snap.fKind === webSnapIds.kObject) { // object itself - if (objpainter.updateObject(snap.fSnapshot, snap.fOption, true)) - promise = objpainter.redraw(); - } else if (snap.fKind === webSnapIds.kSVG) { // update SVG - if (objpainter.updateObject(snap.fSnapshot)) - promise = objpainter.redraw(); - } - } else if (is_subpad) { - const subpad = snap.fSnapshot; - - subpad.fPrimitives = null; // clear primitives, they just because of I/O - - const padpainter = new TPadPainter(this, subpad, snap.fOption, false, 'webpad'); - padpainter.assignSnapId(snap.fObjectID); - padpainter.is_active_pad = Boolean(snap.fActive); // enforce boolean flag - padpainter.#readonly = snap.fReadOnly ?? false; // readonly flag - padpainter.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive - padpainter.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features - - padpainter.processSpecialSnaps(snap.fPrimitives); // need to process style and colors before creating graph elements - - padpainter.createPadSvg(); - - if (padpainter.matchObjectType(clTPad) && snap.fPrimitives.length) - padpainter.addPadButtons(true); - pindx++; // new painter will be add - promise = padpainter.drawNextSnap(snap.fPrimitives).then(() => padpainter.addPadInteractive()); - } else if (((snap.fKind === webSnapIds.kObject) || (snap.fKind === webSnapIds.kSVG)) && (snap.fOption !== '__ignore_drawing__')) { - // here the case of normal drawing - pindx++; // new painter will be add - promise = this.drawObject(this, snap.fSnapshot, snap.fOption).then(objp => this.addObjectPainter(objp, lst, indx)); - } - - return getPromise(promise).then(() => this.drawNextSnap(lst, pindx, indx)); // call next - } - - /** @summary Return painter with specified id - * @private */ - findSnap(snapid) { - if (this.getSnapId() === snapid) - return this; - - if (!this.#painters) - return null; - - for (let k = 0; k < this.#painters.length; ++k) { - let sub = this.#painters[k]; - - if (isFunc(sub.findSnap)) - sub = sub.findSnap(snapid); - else if (sub.getSnapId() !== snapid) - sub = null; - - if (sub) - return sub; - } - - return null; - } - - /** @summary Redraw pad snap - * @desc Online version of drawing pad primitives - * for the canvas snapshot contains list of objects - * as first entry, graphical properties of canvas itself is provided - * in ROOT6 it also includes primitives, but we ignore them - * @return {Promise} with pad painter when drawing completed - * @private */ - async redrawPadSnap(snap) { - if (!snap?.fPrimitives) - return this; - - this.is_active_pad = Boolean(snap.fActive); // enforce boolean flag - this.#readonly = snap.fReadOnly ?? false; // readonly flag - this.#snap_primitives = snap.fPrimitives; // keep list to be able find primitive - this.#has_execs = snap.fHasExecs ?? false; // are there pad execs, enables some interactive features - - const first = snap.fSnapshot; - first.fPrimitives = null; // primitives are not interesting, they are disabled in IO - - // if there are execs in the pad, deliver events to the server - this.#deliver_move_events = this.#has_execs || first.fExecs?.arr?.length; - - if (!this.hasSnapId()) { - // first time getting snap, create all gui elements first - - this.assignSnapId(snap.fObjectID); - - this.assignObject(first); - this.#pad = first; // first object is pad - - // this._setFixedSize(true); - - // if canvas size not specified in batch mode, temporary use 900x700 size - if (this.isBatchMode() && (!first.fCw || !first.fCh)) { - first.fCw = 900; - first.fCh = 700; - } - - // case of ROOT7 with always dummy TPad as first entry - if (!first.fCw || !first.fCh) - this._setFixedSize(false); - - const mainid = this.selectDom().attr('id'); - - if (!this.isBatchMode() && this.online_canvas && !this.use_openui && !this.brlayout && mainid && isStr(mainid) && !getHPainter()) { - this.brlayout = new BrowserLayout(mainid, null, this); - this.brlayout.create(mainid, true); - this.setDom(this.brlayout.drawing_divid()); // need to create canvas - registerForResize(this.brlayout); - } - - this.processSpecialSnaps(snap.fPrimitives); - - this.createCanvasSvg(0); - - if (!this.isBatchMode()) - this.addPadButtons(true); - - if (typeof snap.fHighlightConnect !== 'undefined') - this._highlight_connect = snap.fHighlightConnect; - - let pr = Promise.resolve(true); - - if (isStr(snap.fScripts) && snap.fScripts) { - let src = '', m = null; - - if (snap.fScripts.indexOf('modules:') === 0) - m = snap.fScripts.slice(8).split(';'); - else if (snap.fScripts.indexOf('load:') === 0) - src = snap.fScripts.slice(5).split(';'); - else if (snap.fScripts.indexOf('assert:') === 0) - src = snap.fScripts.slice(7); - - pr = (m !== null) ? loadModules(m) : (src ? loadScript(src) : injectCode(snap.fScripts)); - } - - return pr.then(() => this.drawNextSnap(snap.fPrimitives)).then(() => { - if (isFunc(this.onCanvasUpdated)) - this.onCanvasUpdated(this); - return this; - }); - } - - this.updateObject(first); // update only object attributes - - // apply all changes in the object (pad or canvas) - if (this.isCanvas()) - this.createCanvasSvg(2); - else - this.createPadSvg(true); - - let missmatch = false; - - // match painters with new list of primitives - if (!snap.fWithoutPrimitives) { - let i = 0, k = 0; - while (k < this.#painters.length) { - const sub = this.#painters[k]; - - // skip check secondary painters or painters without snapid - if (!sub.hasSnapId() || sub.isSecondary()) { - k++; - continue; // look only for painters with snapid - } - - if (i >= snap.fPrimitives.length) - break; - - const prim = snap.fPrimitives[i]; - - // only real objects drawing checked for existing painters - if ((prim.fKind !== webSnapIds.kSubPad) && (prim.fKind !== webSnapIds.kObject) && (prim.fKind !== webSnapIds.kSVG)) { - i++; - continue; // look only for primitives of real objects - } - - if (prim.fObjectID === sub.getSnapId()) { - i++; - k++; - } else { - missmatch = true; - break; - } - } - - let cnt = 1000; - // remove painters without primitives, limit number of checks - while (!missmatch && (k < this.#painters.length) && (--cnt >= 0)) { - if (this.removePrimitive(k) === -111) - missmatch = true; - } - if (cnt < 0) - missmatch = true; - } - - if (missmatch) { - const old_painters = this.#painters; - this.#painters = []; - old_painters.forEach(objp => objp.cleanup()); - this.setMainPainter(undefined, true); - if (isFunc(this.removePadButtons)) - this.removePadButtons(); - this.addPadButtons(true); - } - - return this.drawNextSnap(snap.fPrimitives, missmatch ? undefined : 0).then(() => { - this.addPadInteractive(); - if (getActivePad() === this) - this.getCanvPainter()?.producePadEvent('padredraw', this); - if (isFunc(this.onCanvasUpdated)) - this.onCanvasUpdated(this); - return this; - }); - } - - /** @summary Deliver mouse move or click event to the web canvas - * @private */ - deliverWebCanvasEvent(kind, x, y, snapid) { - if (!this.is_active_pad || this.doingDraw() || x === undefined || y === undefined) - return; - if ((kind === 'move') && !this.#deliver_move_events) - return; - const cp = this.getCanvPainter(); - if (!cp || !cp.canSendWebsocket(2) || cp.isReadonly()) - return; - - const msg = JSON.stringify([this.getSnapId(), kind, x.toString(), y.toString(), snapid || '']); - cp.sendWebsocket(`EVENT:${msg}`); - } - - /** @summary Create image for the pad - * @desc Used with web-based canvas to create images for server side - * @return {Promise} with image data, coded with btoa() function - * @private */ - async createImage(format) { - if ((format === 'png') || (format === 'jpeg') || (format === 'html') || (format === 'svg') || (format === 'webp') || (format === 'pdf')) { - return this.produceImage(true, format).then(res => { - if (!res || (format === 'svg') || (format === 'html')) - return res; - const separ = res.indexOf('base64,'); - return (separ > 0) ? res.slice(separ + 7) : ''; - }); - } - return ''; - } - - /** @summary Collects pad information for TWebCanvas - * @desc need to update different states - * @private */ - getWebPadOptions(arg, cp) { - let is_top = (arg === undefined), elem = null, scan_subpads = true; - // no any options need to be collected in readonly mode - if (is_top && this.isReadonly()) - return ''; - if (arg === 'only_this') { - is_top = true; - scan_subpads = false; - } else if (arg === 'with_subpads') { - is_top = true; - scan_subpads = true; - } - if (is_top) - arg = []; - if (!cp) - cp = this.isCanvas() ? this : this.getCanvPainter(); - - if (this.getSnapId()) { - elem = { - _typename: 'TWebPadOptions', snapid: this.getSnapId(), - active: Boolean(this.is_active_pad), - cw: 0, ch: 0, w: [], - bits: 0, primitives: [], - logx: this.#pad.fLogx, logy: this.#pad.fLogy, logz: this.#pad.fLogz, - gridx: this.#pad.fGridx, gridy: this.#pad.fGridy, - tickx: this.#pad.fTickx, ticky: this.#pad.fTicky, - mleft: this.#pad.fLeftMargin, mright: this.#pad.fRightMargin, - mtop: this.#pad.fTopMargin, mbottom: this.#pad.fBottomMargin, - xlow: 0, ylow: 0, xup: 1, yup: 1, - zx1: 0, zx2: 0, zy1: 0, zy2: 0, zz1: 0, zz2: 0, phi: 0, theta: 0 - }; - - if (this.isCanvas()) { - elem.bits = this.getStatusBits(); - elem.cw = this.getPadWidth(); - elem.ch = this.getPadHeight(); - elem.w = [window.screenLeft, window.screenTop, window.outerWidth, window.outerHeight]; - } else if (cp) { - const cw = cp.getPadWidth(), ch = cp.getPadHeight(), rect = this.getPadRect(); - elem.cw = cw; - elem.ch = ch; - elem.xlow = rect.x / cw; - elem.ylow = 1 - (rect.y + rect.height) / ch; - elem.xup = elem.xlow + rect.width / cw; - elem.yup = elem.ylow + rect.height / ch; - } - - if ((this.#pad.fTheta !== 30) || (this.#pad.fPhi !== 30)) { - elem.phi = this.#pad.fPhi; - elem.theta = this.#pad.fTheta; - } - - if (this.getPadRanges(elem)) - arg.push(elem); - else - console.log(`fail to get ranges for pad ${this.#pad.fName}`); - } - - this.#painters.forEach(sub => { - if (isFunc(sub.getWebPadOptions)) { - if (scan_subpads) - sub.getWebPadOptions(arg, cp); - } else { - const opt = createWebObjectOptions(sub); - if (opt) - elem.primitives.push(opt); - } - }); - - if (is_top) - return toJSON(arg); - } - - /** @summary returns actual ranges in the pad, which can be applied to the server - * @private */ - getPadRanges(r) { - if (!r) - return false; - - const fp = this.getFramePainter(), - p = this.getPadSvg(); - - r.ranges = fp?.ranges_set ?? false; // indicate that ranges are assigned - - r.ux1 = r.px1 = r.ranges ? fp.scale_xmin : 0; // need to initialize for JSON reader - r.uy1 = r.py1 = r.ranges ? fp.scale_ymin : 0; - r.ux2 = r.px2 = r.ranges ? fp.scale_xmax : 0; - r.uy2 = r.py2 = r.ranges ? fp.scale_ymax : 0; - r.uz1 = r.ranges ? (fp.scale_zmin ?? 0) : 0; - r.uz2 = r.ranges ? (fp.scale_zmax ?? 0) : 0; - - if (fp) { - if (fp.zoom_xmin !== fp.zoom_xmax) { - r.zx1 = fp.zoom_xmin; - r.zx2 = fp.zoom_xmax; - } - - if (fp.zoom_ymin !== fp.zoom_ymax) { - r.zy1 = fp.zoom_ymin; - r.zy2 = fp.zoom_ymax; - } - - if (fp.zoom_zmin !== fp.zoom_zmax) { - r.zz1 = fp.zoom_zmin; - r.zz2 = fp.zoom_zmax; - } - } - - if (!r.ranges || p.empty()) - return true; - - // calculate user range for full pad - const func = (log, value, err) => { - if (!log) - return value; - if (value <= 0) - return err; - if (log === 3) - return Math.log(value); - value = Math.log10(value); - if (log > 1) - value /= Math.log10(log); - return value; - }, frect = fp.getFrameRect(); - - r.ux1 = func(fp.logx, r.ux1, 0); - r.ux2 = func(fp.logx, r.ux2, 1); - - let k = (r.ux2 - r.ux1) / (frect.width || 10); - r.px1 = r.ux1 - k * frect.x; - r.px2 = r.px1 + k * this.getPadWidth(); - - r.uy1 = func(fp.logy, r.uy1, 0); - r.uy2 = func(fp.logy, r.uy2, 1); - - k = (r.uy2 - r.uy1) / (frect.height || 10); - r.py1 = r.uy1 - k * frect.y; - r.py2 = r.py1 + k * this.getPadHeight(); - - return true; - } - - /** @summary Show context menu for specified item - * @private */ - itemContextMenu(name) { - const rrr = this.getPadSvg().node().getBoundingClientRect(), - evnt = { clientX: rrr.left + 10, clientY: rrr.top + 10 }; - - // use timeout to avoid conflict with mouse click and automatic menu close - if (name === 'pad') - return postponePromise(() => this.padContextMenu(evnt), 50); - - let selp = null, selkind; - - switch (name) { - case 'xaxis': - case 'yaxis': - case 'zaxis': - selp = this.getFramePainter(); - selkind = name[0]; - break; - case 'frame': - selp = this.getFramePainter(); - break; - default: { - const indx = parseInt(name); - if (Number.isInteger(indx)) - selp = this.#painters[indx]; - } - } - - if (!isFunc(selp?.fillContextMenu)) - return; - - return createMenu(evnt, selp).then(menu => { - const offline_menu = selp.fillContextMenu(menu, selkind); - if (offline_menu || selp.getSnapId()) - return selp.fillObjectExecMenu(menu, selkind).then(() => postponePromise(() => menu.show(), 50)); - }); - } - - /** @summary Save pad as image - * @param {string} kind - format of saved image like 'png', 'svg' or 'jpeg' - * @param {boolean} full_canvas - does complete canvas (true) or only frame area (false) should be saved - * @param {string} [filename] - name of the file which should be stored - * @desc Normally used from context menu - * @example - * import { getElementCanvPainter } from 'https://root.cern/js/latest/modules/base/ObjectPainter.mjs'; - * let canvas_painter = getElementCanvPainter('drawing_div_id'); - * canvas_painter.saveAs('png', true, 'canvas.png'); */ - saveAs(kind, full_canvas, filename) { - if (!filename) - filename = (this.#pad_name || (this.isCanvas() ? 'canvas' : 'pad')) + '.' + kind; - - this.produceImage(full_canvas, kind).then(imgdata => { - if (!imgdata) - return console.error(`Fail to produce image ${filename}`); - - if ((browser.qt6 || browser.cef3) && this.getSnapId()) { - console.warn(`sending file ${filename} to server`); - let res = imgdata; - if (kind !== 'svg') { - const separ = res.indexOf('base64,'); - res = (separ > 0) ? res.slice(separ + 7) : ''; - } - if (res) - this.getCanvPainter()?.sendWebsocket(`SAVE:${filename}:${res}`); - } else { - let prefix = ''; - switch (kind) { - case 'svg': prefix = prSVG; break; - case 'json': prefix = prJSON; break; - case 'html': prefix = prHTML; break; - } - saveFile(filename, prefix ? prefix + encodeURIComponent(imgdata) : imgdata); - } - }); - } - - /** @summary Search active pad - * @return {object} pad painter for active pad */ - findActivePad() { - let active_pp; - this.forEachPainterInPad(pp => { - if (pp.is_active_pad && !active_pp) - active_pp = pp; - }, 'pads'); - return active_pp; - } - - /** @summary Produce image for the pad - * @return {Promise} with created image */ - async produceImage(full_canvas, file_format, args) { - if ((file_format === 'json') || (file_format === 'html')) { - const json = isFunc(this.produceJSON) ? this.produceJSON(full_canvas ? 2 : 0) : ''; - if (!json || (file_format === 'json')) - return json; - let url = source_dir; - if (url.indexOf('http://localhost') === 0) { - url = 'https://root.cern/js/'; - url += (version_id === 'dev') || /^\d+\.\d+\.\d+$/.test(version_id) ? version_id : 'latest'; - } - return '<!DOCTYPE html>\n' + - '<html lang="en">\n' + - '<head>\n' + - ' <meta charset="utf-8">\n' + - ' <title>Dsiplay ROOT canvas\n' + - ` \n` + - '