diff --git a/.clang-format b/.clang-format index 7efb6e139a2e7..112004a90d7ca 100644 --- a/.clang-format +++ b/.clang-format @@ -1,14 +1,14 @@ # BasedOnStyle: LLVM AccessModifierOffset: -3 AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false +AlignConsecutiveAssignments: None # This would be nice to have but seems to also (mis)align function parameters -AlignConsecutiveDeclarations: false -AlignEscapedNewlinesLeft: true +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: false +AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: false @@ -17,12 +17,12 @@ AllowShortLoopsOnASingleLine: false # AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: true +AlwaysBreakTemplateDeclarations: Yes BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false - AfterControlStatement: false + AfterControlStatement: Never AfterEnum: false AfterFunction: true AfterNamespace: false @@ -32,7 +32,7 @@ BraceWrapping: BeforeCatch: false BeforeElse: false IndentBraces: false -BreakBeforeBinaryOperators: false +BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false @@ -64,8 +64,8 @@ PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 10 PointerAlignment: Right -ReflowComments: true -SortIncludes: false +ReflowComments: true +SortIncludes: Never SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true # You want this : enable it if you have https://reviews.llvm.org/D32525 @@ -78,7 +78,7 @@ SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false -Standard: Cpp11 +Standard: c++11 TabWidth: 3 UseTab: Never diff --git a/.git-commit-template b/.git-commit-template new file mode 100644 index 0000000000000..e51bd8b3dedd5 --- /dev/null +++ b/.git-commit-template @@ -0,0 +1,17 @@ +# Commit summary: Written in the present imperative mood. +# Prefixed by a [scope] tag, not exceeding 50 characters (excl. scope tag). +# # <- 50 chars is here. +[scope] Add/fix/improve/... X + +# Commit message: Describe the 'why' behind the change. Wrap at 72 characters. +# # <- 72 chars is here. + + +# In case the commit fixes a specific issue, uncomment and add the issue ID: +# Fixes https://github.com/root-project/root/issues/ISSUE_ID + +# Tell git to use this commit template by adding it to the repository configuration: +# git config commit.template .git-commit-template + +# See https://github.com/root-project/root/blob/master/CONTRIBUTING.md for more +# information on how to contribute to ROOT. diff --git a/.github/workflows/code_analysis.yml b/.github/workflows/code_analysis.yml index c61d91dbca04e..32eb13cce1324 100644 --- a/.github/workflows/code_analysis.yml +++ b/.github/workflows/code_analysis.yml @@ -8,13 +8,19 @@ on: pull_request jobs: clang-format: + # For any event that is not a PR, the CI will always run. In PRs, the CI + # can be skipped if the tag [skip-ci] is written in the title. + if: | + (github.repository_owner == 'root-project' && github.event_name != 'pull_request') || + (github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '[skip-ci]')) + runs-on: ubuntu-latest env: TRAVIS_BRANCH: ${{ github.base_ref }} TRAVIS_PULL_REQUEST_BRANCH: ${{ github.head_ref }} BASE_COMMIT: ${{ github.event.pull_request.base.sha }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Fetch base_ref HEAD run: git fetch --depth=1 origin +refs/heads/${{github.base_ref}}:refs/remotes/origin/${{github.base_ref}} - name: install clang-format diff --git a/.github/workflows/issues-nudge.yml b/.github/workflows/issues-nudge.yml index 234c580a24966..51c761cc621cd 100644 --- a/.github/workflows/issues-nudge.yml +++ b/.github/workflows/issues-nudge.yml @@ -9,7 +9,7 @@ jobs: if: ${{ github.repository == 'root-project/root' }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: '20' diff --git a/.github/workflows/llvm-diff.yml b/.github/workflows/llvm-diff.yml index 58f4460e323b7..bbe2aad731e64 100644 --- a/.github/workflows/llvm-diff.yml +++ b/.github/workflows/llvm-diff.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out ROOT - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: root - name: Determine tag in fork of monorepo @@ -19,7 +19,7 @@ jobs: echo "tag=$(cat $tag_file)" >> $GITHUB_OUTPUT rm $tag_file - name: Check out llvm-project - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: root-project/llvm-project ref: ${{ steps.determine-tag.outputs.tag }} diff --git a/.github/workflows/root-630.yml b/.github/workflows/root-630.yml new file mode 100644 index 0000000000000..5a0ec1bc8e9b3 --- /dev/null +++ b/.github/workflows/root-630.yml @@ -0,0 +1,37 @@ + +name: 'ROOT 6.30' + +on: + schedule: + - cron: '25 14 * * *' + + workflow_dispatch: + inputs: + incremental: + description: 'Do incremental build' + type: boolean + required: true + default: true + binaries: + description: Create binary packages and upload them as artifacts + type: boolean + required: true + default: false + buildtype: + description: The CMAKE_BUILD_TYPE to use for non-Windows. + type: choice + options: + - Debug + - RelWithDebInfo + - Release + - MinSizeRel + default: Debug + required: true + +jobs: + run_nightlies: + uses: root-project/root/.github/workflows/root-ci.yml@master + with: + base_ref: 'v6-30-00-patches' + head_ref: 'v6-30-00-patches' + secrets: inherit diff --git a/.github/workflows/root-ci-config/build_root.py b/.github/workflows/root-ci-config/build_root.py index c7a63874a8e2d..a52bae5c01a2d 100755 --- a/.github/workflows/root-ci-config/build_root.py +++ b/.github/workflows/root-ci-config/build_root.py @@ -16,6 +16,7 @@ import argparse import datetime import os +import re import shutil import subprocess import sys @@ -29,6 +30,7 @@ github_log_group, load_config, subprocess_with_log, + subprocess_with_capture, upload_file ) import build_utils @@ -101,10 +103,35 @@ def main(): build_utils.print_warning(f'Failed to download: {err}') args.incremental = False - git_pull(args.repository, args.base_ref) + git_pull("src", args.repository, args.base_ref) if pull_request: - rebase(args.base_ref, args.head_ref) + base_head_sha = get_base_head_sha("src", args.repository, args.sha, args.head_sha) + + head_ref_src, _, head_ref_dst = args.head_ref.partition(":") + head_ref_dst = head_ref_dst or "__tmp" + + rebase("src", "origin", base_head_sha, head_ref_dst, args.head_sha) + + testing: bool = options_dict['testing'].lower() == "on" and options_dict['roottest'].lower() == "on" + + if testing: + # Where to put the roottest directory + if os.path.exists(os.path.join(WORKDIR, "src", "roottest", ".git")): + roottest_dir = "src/roottest" + else: + roottest_dir = "roottest" + + # Where to find the target branch + roottest_origin_repository = re.sub( "/root(.git)*$", "/roottest.git", args.repository) + + # Where to find the incoming branch + roottest_repository, roottest_head_ref = relatedrepo_GetClosestMatch("roottest", args.pull_repository, args.repository) + + git_pull(roottest_dir, roottest_origin_repository, args.base_ref) + + if pull_request: + rebase(roottest_dir, roottest_repository, args.base_ref, roottest_head_ref, roottest_head_ref) if not WINDOWS: show_node_state() @@ -121,8 +148,6 @@ def main(): if args.binaries: create_binaries(args.buildtype) - testing: bool = options_dict['testing'].lower() == "on" and options_dict['roottest'].lower() == "on" - if testing: extra_ctest_flags = "" if WINDOWS: @@ -140,7 +165,7 @@ def main(): print_trace() def handle_test_failure(ctest_returncode): - logloc = f'{WORKDIR}/build/Testing/Temporary/LastTestsFailed.log' + logloc = os.path.join(WORKDIR, "build", "Testing", "Temporary", "LastTestsFailed.log") if os.path.isfile(logloc): with open(logloc, 'r') as logf: print("TEST FAILURES:") @@ -155,20 +180,23 @@ def handle_test_failure(ctest_returncode): def parse_args(): - # it is difficult to use boolean flags from github actions, use strings to convey + # it is difficult to use boolean flags from github actions, use strings to convey # true/false for boolean arguments instead. parser = argparse.ArgumentParser() - parser.add_argument("--platform", help="Platform to build on") - parser.add_argument("--image", default=None, help="Container image, if any") - parser.add_argument("--dockeropts", default=None, help="Extra docker options, if any") - parser.add_argument("--incremental", default="false", help="Do incremental build") - parser.add_argument("--buildtype", default="Release", help="Release|Debug|RelWithDebInfo") - parser.add_argument("--coverage", default="false", help="Create Coverage report in XML") - parser.add_argument("--base_ref", default=None, help="Ref to target branch") - parser.add_argument("--head_ref", default=None, help="Ref to feature branch; it may contain a : part") - parser.add_argument("--binaries", default="false", help="Whether to create binary artifacts") - parser.add_argument("--architecture", default=None, help="Windows only, target arch") - parser.add_argument("--repository", default="https://github.com/root-project/root.git", + parser.add_argument("--platform", help="Platform to build on") + parser.add_argument("--image", default=None, help="Container image, if any") + parser.add_argument("--dockeropts", default=None, help="Extra docker options, if any") + parser.add_argument("--incremental", default="false", help="Do incremental build") + parser.add_argument("--buildtype", default="Release", help="Release|Debug|RelWithDebInfo") + parser.add_argument("--coverage", default="false", help="Create Coverage report in XML") + parser.add_argument("--sha", default=None, help="sha that triggered the event") + parser.add_argument("--base_ref", default=None, help="Ref to target branch") + parser.add_argument("--pull_repository", default="", help="Url to the pull request incoming repository") + parser.add_argument("--head_ref", default=None, help="Ref to feature branch; it may contain a : part") + parser.add_argument("--head_sha", default=None, help="Sha of commit that triggered the event") + parser.add_argument("--binaries", default="false", help="Whether to create binary artifacts") + parser.add_argument("--architecture", default=None, help="Windows only, target arch") + parser.add_argument("--repository", default="https://github.com/root-project/root.git", help="url to repository") args = parser.parse_args() @@ -222,23 +250,24 @@ def cleanup_previous_build(): @github_log_group("Pull/clone branch") -def git_pull(repository: str, branch: str): +def git_pull(directory: str, repository: str, branch: str): returncode = 1 for _ in range(5): if returncode == 0: break - if os.path.exists(f"{WORKDIR}/src/.git"): + targetdir = os.path.join(WORKDIR, directory) + if os.path.exists(os.path.join(targetdir, ".git")): returncode = subprocess_with_log(f""" - cd '{WORKDIR}/src' + cd '{targetdir}' git checkout {branch} git fetch git reset --hard @{{u}} """) else: returncode = subprocess_with_log(f""" - git clone --branch {branch} --single-branch {repository} "{WORKDIR}/src" + git clone --branch {branch} --single-branch {repository} "{targetdir}" """) if returncode != 0: @@ -259,8 +288,8 @@ def download_artifacts(obj_prefix: str): except Exception as err: build_utils.print_warning("failed to download/extract:", err) - shutil.rmtree(f'{WORKDIR}/src', ignore_errors=True) - shutil.rmtree(f'{WORKDIR}/build', ignore_errors=True) + shutil.rmtree(os.path.join(WORKDIR, "src"), ignore_errors=True) + shutil.rmtree(os.path.join(WORKDIR, "build"), ignore_errors=True) raise err @@ -281,12 +310,15 @@ def show_node_state() -> None: if result != 0: build_utils.print_warning("Failed to extract node state") -# Just return the exit code in case of test failures instead of `die()`-ing; report test -# failures in main(). @github_log_group("Run tests") def run_ctest(extra_ctest_flags: str) -> int: + """ + Just return the exit code in case of test failures instead of `die()`-ing; report test + failures in main(). + """ + builddir = os.path.join(WORKDIR, "build") ctest_result = subprocess_with_log(f""" - cd '{WORKDIR}/build' + cd '{builddir}' ctest --output-on-failure --parallel {os.cpu_count()} --output-junit TestResults.xml {extra_ctest_flags} """) @@ -313,8 +345,10 @@ def archive_and_upload(archive_name, prefix): @github_log_group("Configure") def cmake_configure(options, buildtype): + srcdir = os.path.join(WORKDIR, "src") + builddir = os.path.join(WORKDIR, "build") result = subprocess_with_log(f""" - cmake -S '{WORKDIR}/src' -B '{WORKDIR}/build' -DCMAKE_BUILD_TYPE={buildtype} {options} + cmake -S '{srcdir}' -B '{builddir}' -DCMAKE_BUILD_TYPE={buildtype} {options} """) if result != 0: @@ -324,8 +358,10 @@ def cmake_configure(options, buildtype): @github_log_group("Dump existing configuration") def cmake_dump_config(): # Print CMake cached config + srcdir = os.path.join(WORKDIR, "src") + builddir = os.path.join(WORKDIR, "build") result = subprocess_with_log(f""" - cmake -S '{WORKDIR}/src' -B '{WORKDIR}/build' -N -L + cmake -S '{srcdir}' -B '{builddir}' -N -L """) if result != 0: @@ -341,8 +377,9 @@ def dump_requested_config(options): def cmake_build(buildtype): generator_flags = "-- '-verbosity:minimal'" if WINDOWS else "" + builddir = os.path.join(WORKDIR, "build") result = subprocess_with_log(f""" - cmake --build '{WORKDIR}/build' --config '{buildtype}' --parallel '{os.cpu_count()}' {generator_flags} + cmake --build '{builddir}' --config '{buildtype}' --parallel '{os.cpu_count()}' {generator_flags} """) if result != 0: @@ -350,13 +387,14 @@ def cmake_build(buildtype): def build(options, buildtype): - if not os.path.isdir(f'{WORKDIR}/build'): - result = subprocess_with_log(f"mkdir {WORKDIR}/build") + if not os.path.isdir(os.path.join(WORKDIR, "build")): + builddir = os.path.join(WORKDIR, "build") + result = subprocess_with_log(f"mkdir {builddir}") if result != 0: die(result, "Failed to create build directory") - if not os.path.exists(f'{WORKDIR}/build/CMakeCache.txt'): + if not os.path.exists(os.path.join(WORKDIR, "build", "CMakeCache.txt")): cmake_configure(options, buildtype) else: cmake_dump_config() @@ -368,10 +406,12 @@ def build(options, buildtype): @github_log_group("Create binary packages") def create_binaries(buildtype): - os.makedirs(f"{WORKDIR}/packages/", exist_ok=True) + builddir = os.path.join(WORKDIR, "build") + packagedir = os.path.join(WORKDIR, "packages") + os.makedirs(packagedir, exist_ok=True) result = subprocess_with_log(f""" - cd '{WORKDIR}/build' - cpack -B {WORKDIR}/packages/ --verbose -C {buildtype} + cd '{builddir}' + cpack -B {packagedir} --verbose -C {buildtype} """) if result != 0: @@ -379,29 +419,145 @@ def create_binaries(buildtype): @github_log_group("Rebase") -def rebase(base_ref, head_ref) -> None: - head_ref_src, _, head_ref_dst = head_ref.partition(":") - head_ref_dst = head_ref_dst or "__tmp" +def rebase(directory: str, repository:str, base_ref: str, head_ref: str, head_sha: str) -> None: # rebase fails unless user.email and user.name is set + targetdir = os.path.join(WORKDIR, directory) + if (head_sha and head_ref): + branch = f"{head_sha}:{head_ref}" + else: + branch = "" + result = subprocess_with_log(f""" - cd '{WORKDIR}/src' + cd '{targetdir}' git config user.email "rootci@root.cern" git config user.name 'ROOT Continous Integration' - git fetch origin {head_ref_src}:{head_ref_dst} - git checkout {head_ref_dst} + git fetch {repository} {branch} + git checkout {head_ref} git rebase {base_ref} """) if result != 0: die(result, "Rebase failed") +def get_stdout_subprocess(command: str, error_message: str) -> str: + """ + get_stdout_subprocess + execute and log a command. + capture the stdout, strip white space and return it + die in case of failed execution unless the error_message is empty. + """ + result = subprocess_with_capture(command) + if result.returncode != 0: + if error_message != "": + die(result, error_message) + else: + print("\033[90m", end='') + print(result.stdout) + print(result.stderr) + print("\033[0m", end='') + return "" + if result.stderr != "": + print("\033[90m", end='') + print(result.stdout) + print(result.stderr) + print("\033[0m", end='') + string_result = result.stdout + string_result = string_result.strip() + return string_result + + +@github_log_group("Rebase") +def get_base_head_sha(directory: str, repository: str, merge_sha: str, head_sha: str) -> str: + """ + get_base_head_sha + + Given a pull request merge commit and the incoming commit return + the commit corresponding to the head of the branch we are merging into. + """ + targetdir = os.path.join(WORKDIR, directory) + command = f""" + cd '{targetdir}' + git fetch {repository} {merge_sha} + """ + result = subprocess_with_log(command) + if result != 0: + die("Failed to fetch {merge_sha} from {repository}") + command = f""" + cd '{targetdir}' + git rev-list --parents -1 {merge_sha} + """ + result = get_stdout_subprocess(command, "Failed to find the base branch head for this pull request") + + for s in result.split(' '): + if (s != merge_sha and s != head_sha): + return s + + return "" + +@github_log_group("Pull/clone roottest branch") +def relatedrepo_GetClosestMatch(repo_name: str, origin: str, upstream: str): + """ + relatedrepo_GetClosestMatch(REPO_NAME ORIGIN_PREFIX UPSTREAM_PREFIX + FETCHURL_VARIABLE FETCHREF_VARIABLE ) + Return the clone URL and head/tag of the closest match for `repo` (e.g. roottest), based on the + current head name. + + See relatedrepo_GetClosestMatch in toplevel CMakeLists.txt + """ + + # Alternatively, we could use: re.sub( "/root(.git)*$", "", varname) + origin_prefix = origin[:origin.rfind('/')] + upstream_prefix = upstream[:upstream.rfind('/')] + + fetch_url = upstream_prefix + "/" + repo_name + + gitdir = os.path.join(WORKDIR, "src", ".git") + current_head = get_stdout_subprocess(f""" + git --git-dir={gitdir} rev-parse --abbrev-ref HEAD + """, "Failed capture of current branch name") + + # `current_head` is a well-known branch, e.g. master, or v6-28-00-patches. Use the matching branch + # upstream as the fork repository may be out-of-sync + branch_regex = re.compile("^(master|latest-stable|v[0-9]+-[0-9]+-[0-9]+(-patches)?)$") + known_head = branch_regex.match(current_head) + + if known_head: + if current_head == "latest-stable": + # Resolve the 'latest-stable' branch to the latest merged head/tag + current_head = get_stdout_subprocess(f""" + git --git-dir={gitdir} for-each-ref --points-at=latest-stable^2 --format=%\(refname:short\)) + """, "Failed capture of lastest-stable underlying branch name") + return fetch_url, current_head + + # Otherwise, try to use a branch that matches `current_head` in the fork repository + matching_refs = get_stdout_subprocess(f""" + git ls-remote --heads --tags {origin_prefix}/{repo_name} {current_head} + """, "") + if matching_refs != "": + fetch_url = origin_prefix + "/" + repo_name + return fetch_url, current_head + + # Finally, try upstream using the closest head/tag below the parent commit of the current head + closest_ref = get_stdout_subprocess(f""" + git --git-dir={gitdir} describe --all --abbrev=0 HEAD^ + """, "") # Empty error means, ignore errors. + candidate_head = re.sub("^(heads|tags)/", "", closest_ref) + + matching_refs = get_stdout_subprocess(f""" + git ls-remote --heads --tags {upstream_prefix}/{repo_name} {candidate_head} + """, "") + if matching_refs != "": + return fetch_url, candidate_head + return "", "" + @github_log_group("Create Test Coverage in XML") def create_coverage_xml() -> None: + builddir = os.path.join(WORKDIR, "build") result = subprocess_with_log(f""" - cd '{WORKDIR}/build' + cd '{builddir}' gcovr --output=cobertura-cov.xml --cobertura-pretty --gcov-ignore-errors=no_working_dir_found --merge-mode-functions=merge-use-line-min --exclude-unreachable-branches --exclude-directories="roottest|runtutorials|interpreter" --exclude='.*/G__.*' --exclude='.*/(roottest|runtutorials|externals|ginclude|googletest-prefix|macosx|winnt|geombuilder|cocoa|quartz|win32gdk|x11|x11ttf|eve|fitpanel|ged|gui|guibuilder|guihtml|qtgsi|qtroot|recorder|sessionviewer|tmvagui|treeviewer|geocad|fitsio|gviz|qt|gviz3d|x3d|spectrum|spectrumpainter|dcache|hdfs|foam|genetic|mlp|quadp|splot|memstat|rpdutils|proof|odbc|llvm|test|interpreter)/.*' --gcov-exclude='.*_ACLiC_dict[.].*' '--exclude=.*_ACLiC_dict[.].*' -v -r ../src ../build """) diff --git a/.github/workflows/root-ci-config/build_utils.py b/.github/workflows/root-ci-config/build_utils.py index 80fc60e4b0ef1..5c88a8c6c673b 100755 --- a/.github/workflows/root-ci-config/build_utils.py +++ b/.github/workflows/root-ci-config/build_utils.py @@ -8,6 +8,7 @@ from functools import wraps from http import HTTPStatus from typing import Callable, Dict +from collections import namedtuple from openstack.connection import Connection from requests import get @@ -109,6 +110,28 @@ def subprocess_with_log(command: str) -> int: return result.returncode +def subprocess_with_capture(command: str): + """Runs in shell, capture output and appends to log""" + + print_fancy(textwrap.dedent(command), sgr=1) + + print("\033[90m", end='') + + if os.name == 'nt': + command = "$env:comspec = 'cmd.exe'; " + command + + result = subprocess.run(command, capture_output=True, text=True, shell=True, check=False) + + print(result.stdout) + print(result.stderr) + print("\033[0m", end='') + + # Since we are capturing the result and using it in other command later, + # we don't need it for the reproducing steps. + # So no call to: log.add(command) + + return result + def die(code: int = 1, msg: str = "") -> None: log.print() diff --git a/.github/workflows/root-ci-config/buildconfig/alma8.txt b/.github/workflows/root-ci-config/buildconfig/alma8.txt index 9f0be875c6b70..ebf2e6d9a9d62 100644 --- a/.github/workflows/root-ci-config/buildconfig/alma8.txt +++ b/.github/workflows/root-ci-config/buildconfig/alma8.txt @@ -1,3 +1,13 @@ builtin_gtest=ON builtin_nlohmannjson=ON builtin_vdt=On +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On +tmva-cpu=On +tmva-pymva=On +tmva-sofie=On diff --git a/.github/workflows/root-ci-config/buildconfig/alma9.txt b/.github/workflows/root-ci-config/buildconfig/alma9.txt index 7fba1416b08b7..f5f99536a01f0 100644 --- a/.github/workflows/root-ci-config/buildconfig/alma9.txt +++ b/.github/workflows/root-ci-config/buildconfig/alma9.txt @@ -1,2 +1,12 @@ builtin_nlohmannjson=ON builtin_vdt=On +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On +tmva-cpu=On +tmva-pymva=On +tmva-sofie=On diff --git a/.github/workflows/root-ci-config/buildconfig/fedora37.txt b/.github/workflows/root-ci-config/buildconfig/fedora37.txt index d3765b684de78..6c0b537767b77 100644 --- a/.github/workflows/root-ci-config/buildconfig/fedora37.txt +++ b/.github/workflows/root-ci-config/buildconfig/fedora37.txt @@ -2,3 +2,10 @@ builtin_nlohmannjson=ON builtin_vdt=On tmva-pymva=Off test_distrdf_pyspark=OFF +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On diff --git a/.github/workflows/root-ci-config/buildconfig/fedora38.txt b/.github/workflows/root-ci-config/buildconfig/fedora38.txt index 6e269f6e7cb82..3d1b979caecc4 100644 --- a/.github/workflows/root-ci-config/buildconfig/fedora38.txt +++ b/.github/workflows/root-ci-config/buildconfig/fedora38.txt @@ -3,3 +3,10 @@ builtin_vdt=On pythia8=Off tmva-pymva=Off test_distrdf_pyspark=OFF +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On diff --git a/.github/workflows/root-ci-config/buildconfig/fedora39.txt b/.github/workflows/root-ci-config/buildconfig/fedora39.txt index cd824fc4b5cfc..0eb808171072b 100644 --- a/.github/workflows/root-ci-config/buildconfig/fedora39.txt +++ b/.github/workflows/root-ci-config/buildconfig/fedora39.txt @@ -2,3 +2,10 @@ builtin_nlohmannjson=On builtin_vdt=On pythia8=Off tmva-pymva=Off +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On diff --git a/.github/workflows/root-ci-config/buildconfig/global.txt b/.github/workflows/root-ci-config/buildconfig/global.txt index dbc9d76db4719..c56650c07b392 100644 --- a/.github/workflows/root-ci-config/buildconfig/global.txt +++ b/.github/workflows/root-ci-config/buildconfig/global.txt @@ -54,7 +54,6 @@ fftw3=ON fitsio=ON fortran=OFF gdml=ON -gfal=OFF gminimal=OFF gnuinstall=OFF gsl_shared=OFF @@ -87,7 +86,7 @@ roofit_multiprocess=OFF root7=ON rootbench=OFF roottest=ON -roottest_force_checkout=ON +roottest_force_checkout=OFF rpath=ON runtime_cxxmodules=ON shadowpw=OFF diff --git a/.github/workflows/root-ci-config/buildconfig/mac12.txt b/.github/workflows/root-ci-config/buildconfig/mac12.txt new file mode 100644 index 0000000000000..32e5be627fb97 --- /dev/null +++ b/.github/workflows/root-ci-config/buildconfig/mac12.txt @@ -0,0 +1,39 @@ +builtin_afterimage=ON +builtin_cfitsio=ON +builtin_cppzmq=ON +builtin_davix=ON +builtin_fftw3=ON +builtin_freetype=ON +builtin_ftgl=ON +builtin_gl2ps=ON +builtin_glew=ON +builtin_gsl=ON +builtin_gtest=ON +builtin_lz4=ON +builtin_lzma=ON +builtin_nlohmannjson=ON +builtin_pcre=ON +builtin_openssl=ON +builtin_tbb=ON +builtin_unuran=ON +builtin_vc=ON +builtin_vdt=ON +builtin_veccore=ON +builtin_xrootd=ON +builtin_xxhash=ON +builtin_zeromq=ON +builtin_zstd=ON +ccache=ON +cocoa=ON +cudnn=OFF +mysql=OFF +oracle=OFF +pgsql=OFF +pythia6=OFF +pythia8=OFF +unfold=ON +tmva-pymva=ON +test_distrdf_dask=OFF +test_distrdf_pyspark=OFF +tmva-cpu=ON +x11=OFF diff --git a/.github/workflows/root-ci-config/buildconfig/mac13.txt b/.github/workflows/root-ci-config/buildconfig/mac13.txt new file mode 100644 index 0000000000000..6c6492a038daf --- /dev/null +++ b/.github/workflows/root-ci-config/buildconfig/mac13.txt @@ -0,0 +1,40 @@ +builtin_afterimage=ON +builtin_cfitsio=ON +builtin_cppzmq=ON +builtin_davix=ON +builtin_fftw3=ON +builtin_freetype=ON +builtin_ftgl=ON +builtin_gl2ps=ON +builtin_glew=ON +builtin_gsl=ON +builtin_gtest=ON +builtin_lz4=ON +builtin_lzma=ON +builtin_nlohmannjson=ON +builtin_pcre=ON +builtin_openssl=ON +builtin_tbb=ON +builtin_unuran=ON +builtin_vc=ON +builtin_vdt=ON +builtin_veccore=ON +builtin_xrootd=ON +builtin_xxhash=ON +builtin_zeromq=ON +builtin_zstd=ON +ccache=ON +cocoa=ON +cudnn=OFF +mysql=OFF +oracle=OFF +pgsql=OFF +pythia6=OFF +pythia8=OFF +test_distrdf_dask=OFF +test_distrdf_pyspark=OFF +tmva-cpu=ON +tmva-pymva=ON +tmva-sofie=ON +unfold=ON +x11=OFF diff --git a/.github/workflows/root-ci-config/buildconfig/mac14.txt b/.github/workflows/root-ci-config/buildconfig/mac14.txt index 58dfd23e786be..be61d56139c72 100644 --- a/.github/workflows/root-ci-config/buildconfig/mac14.txt +++ b/.github/workflows/root-ci-config/buildconfig/mac14.txt @@ -23,6 +23,7 @@ builtin_xrootd=ON builtin_xxhash=ON builtin_zeromq=ON builtin_zstd=ON +ccache=ON cocoa=ON cudnn=OFF mysql=OFF @@ -30,7 +31,10 @@ oracle=OFF pgsql=OFF pythia6=OFF pythia8=OFF +unfold=ON test_distrdf_dask=OFF test_distrdf_pyspark=OFF tmva-cpu=OFF +tmva-pymva=ON +tmva-sofie=ON x11=OFF diff --git a/.github/workflows/root-ci-config/buildconfig/ubuntu20.txt b/.github/workflows/root-ci-config/buildconfig/ubuntu20.txt index 1e761be9f3340..1c153341986c2 100644 --- a/.github/workflows/root-ci-config/buildconfig/ubuntu20.txt +++ b/.github/workflows/root-ci-config/buildconfig/ubuntu20.txt @@ -2,3 +2,13 @@ builtin_nlohmannjson=ON builtin_vdt=ON builtin_xrootd=ON builtin_xxhash=ON +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On +tmva-cpu=On +tmva-pymva=On +tmva-sofie=On diff --git a/.github/workflows/root-ci-config/buildconfig/ubuntu22.txt b/.github/workflows/root-ci-config/buildconfig/ubuntu22.txt index 86fb048702272..104414e7f8a95 100644 --- a/.github/workflows/root-ci-config/buildconfig/ubuntu22.txt +++ b/.github/workflows/root-ci-config/buildconfig/ubuntu22.txt @@ -1,3 +1,13 @@ builtin_vdt=ON pythia8=OFF - +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On +tmva-cpu=On +tmva-pymva=On +tmva-sofie=Off +r=ON diff --git a/.github/workflows/root-ci-config/buildconfig/ubuntu2304.txt b/.github/workflows/root-ci-config/buildconfig/ubuntu2304.txt index de3738bb78c22..124ebcd49866e 100644 --- a/.github/workflows/root-ci-config/buildconfig/ubuntu2304.txt +++ b/.github/workflows/root-ci-config/buildconfig/ubuntu2304.txt @@ -1,2 +1,12 @@ pythia8=OFF +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On +tmva-cpu=On +tmva-pymva=On +tmva-sofie=Off diff --git a/.github/workflows/root-ci-config/buildconfig/ubuntu2310.txt b/.github/workflows/root-ci-config/buildconfig/ubuntu2310.txt index de3738bb78c22..124ebcd49866e 100644 --- a/.github/workflows/root-ci-config/buildconfig/ubuntu2310.txt +++ b/.github/workflows/root-ci-config/buildconfig/ubuntu2310.txt @@ -1,2 +1,12 @@ pythia8=OFF +builtin_gsl=On +builtin_fftw3=On +builtin_unuran=On +fftw3=On +unfold=On +unuran=On +mathmore=On +tmva-cpu=On +tmva-pymva=On +tmva-sofie=Off diff --git a/.github/workflows/root-ci-config/buildconfig/windows10.txt b/.github/workflows/root-ci-config/buildconfig/windows10.txt index ed14ecf0eb627..8f77021b5f9c5 100644 --- a/.github/workflows/root-ci-config/buildconfig/windows10.txt +++ b/.github/workflows/root-ci-config/buildconfig/windows10.txt @@ -18,7 +18,6 @@ builtin_zstd=ON cudnn=OFF davix=OFF fftw3=OFF -gfal=OFF llvm13_broken_tests=OFF minuit2_mpi=OFF minuit2_omp=OFF @@ -36,6 +35,7 @@ ssl=OFF test_distrdf_dask=OFF test_distrdf_pyspark=OFF unuran=ON +unfold=ON vdt=OFF x11=OFF xml=OFF diff --git a/.github/workflows/root-ci.yml b/.github/workflows/root-ci.yml index cf18d831b3dfd..b4a2d8f11364a 100644 --- a/.github/workflows/root-ci.yml +++ b/.github/workflows/root-ci.yml @@ -81,7 +81,11 @@ concurrency: jobs: build-macos: - if: github.repository_owner == 'root-project' || github.event_name == 'pull_request' + # For any event that is not a PR, the CI will always run. In PRs, the CI + # can be skipped if the tag [skip-ci] is written in the title. + if: | + (github.repository_owner == 'root-project' && github.event_name != 'pull_request') || + (github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '[skip-ci]')) permissions: contents: read @@ -91,13 +95,21 @@ jobs: matrix: # Specify platform + arch + (optional) build option overrides # - # Available platforms: mac14 + # Available platforms: mac14, mac-beta # Common configs: {Release,Debug,RelWithDebInfo) # Build options: https://root.cern/install/build_from_source/#all-build-options include: + - platform: mac12 + arch: X64 + - platform: mac13 + arch: ARM64 + overrides: ["LLVM_ENABLE_ASSERTIONS=On"] - platform: mac14 arch: ARM64 overrides: ["LLVM_ENABLE_ASSERTIONS=On", "CMAKE_CXX_STANDARD=20"] +# - platform: mac-beta +# arch: X64 +# overrides: ["LLVM_ENABLE_ASSERTIONS=On", "CMAKE_CXX_STANDARD=20"] runs-on: # Using '[self-hosted, ..., ...]' does not work for some reason :) - self-hosted @@ -109,7 +121,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Apply option overrides from matrix for this job for non-release builds if: ${{ github.event_name != 'schedule' && matrix.overrides != NaN }} @@ -146,12 +158,15 @@ jobs: INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') }} GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }} run: ".github/workflows/root-ci-config/build_root.py - --buildtype RelWithDebInfo - --platform ${{ matrix.platform }} - --incremental $INCREMENTAL - --base_ref ${{ github.base_ref }} - --head_ref refs/pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} - --repository ${{ github.server_url }}/${{ github.repository }}" + --buildtype RelWithDebInfo + --incremental $INCREMENTAL + --base_ref ${{ github.base_ref }} + --sha ${{ github.sha }} + --pull_repository ${{ github.event.pull_request.head.repo.clone_url }} + --head_ref refs/pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} + --head_sha ${{ github.event.pull_request.head.sha }} + --repository ${{ github.server_url }}/${{ github.repository }} + --platform ${{ matrix.platform }}" - name: Workflow dispatch if: github.event_name == 'workflow_dispatch' @@ -171,7 +186,7 @@ jobs: --platform ${{ matrix.platform }} --incremental false --binaries true - --base_ref ${{ github.ref_name }} + --base_ref ${{ inputs.ref_name }} --repository ${{ github.server_url }}/${{ github.repository }}" - name: Update build cache after push to release branch @@ -191,13 +206,6 @@ jobs: name: Test Results ${{ matrix.platform }} ${{ matrix.arch }} path: /Users/sftnight/ROOT-CI/build/TestResults.xml - - name: Upload build artifacts - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v3 - with: - name: Test Results ${{ matrix.platform }} ${{ matrix.arch }} - path: /Users/sftnight/ROOT-CI/build/TestResults.xml - - name: Upload binaries if: ${{ !cancelled() && (inputs.binaries || github.event_name == 'schedule' || startsWith(github.ref, 'refs/tags/')) }} uses: actions/upload-artifact@v3 @@ -208,7 +216,11 @@ jobs: build-windows: - if: github.repository_owner == 'root-project' || github.event_name == 'pull_request' + # For any event that is not a PR, the CI will always run. In PRs, the CI + # can be skipped if the tag [skip-ci] is written in the title. + if: | + (github.repository_owner == 'root-project' && github.event_name != 'pull_request') || + (github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '[skip-ci]')) permissions: contents: read @@ -241,7 +253,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Pull Request Build if: github.event_name == 'pull_request' @@ -251,13 +263,16 @@ jobs: shell: cmd run: "C:\\setenv.bat ${{ matrix.target_arch }} && python .github/workflows/root-ci-config/build_root.py - --buildtype ${{ matrix.config }} - --platform windows10 - --incremental $INCREMENTAL - --base_ref ${{ github.base_ref }} - --head_ref refs/pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} - --repository ${{ github.server_url }}/${{ github.repository }} - --architecture ${{ matrix.target_arch }}" + --buildtype ${{ matrix.config }} + --platform windows10 + --incremental $INCREMENTAL + --base_ref ${{ github.base_ref }} + --sha ${{ github.sha }} + --pull_repository ${{ github.event.pull_request.head.repo.clone_url }} + --head_ref refs/pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} + --head_sha ${{ github.event.pull_request.head.sha }} + --repository ${{ github.server_url }}/${{ github.repository }} + --architecture ${{ matrix.target_arch }}" - name: Workflow dispatch/call if: github.event_name == 'workflow_dispatch' @@ -316,7 +331,11 @@ jobs: build-linux: - if: github.repository_owner == 'root-project' || github.event_name == 'pull_request' + # For any event that is not a PR, the CI will always run. In PRs, the CI + # can be skipped if the tag [skip-ci] is written in the title. + if: | + (github.repository_owner == 'root-project' && github.event_name != 'pull_request') || + (github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '[skip-ci]')) permissions: contents: read @@ -375,7 +394,7 @@ jobs: run: 'if [ -d /py-venv/ROOT-CI/bin/ ]; then . /py-venv/ROOT-CI/bin/activate && echo PATH=$PATH >> $GITHUB_ENV; fi' - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Dump GitHub context env: @@ -422,14 +441,17 @@ jobs: INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') }} GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }} run: ".github/workflows/root-ci-config/build_root.py - --buildtype RelWithDebInfo - --platform ${{ matrix.image }} - --image registry.cern.ch/root-ci/${{ matrix.image }}:buildready - --dockeropts '--security-opt label=disable --rm' - --incremental $INCREMENTAL - --base_ref ${{ github.base_ref }} - --head_ref refs/pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} - --repository ${{ github.server_url }}/${{ github.repository }} + --buildtype RelWithDebInfo + --platform ${{ matrix.image }} + --image registry.cern.ch/root-ci/${{ matrix.image }}:buildready + --dockeropts '--security-opt label=disable --rm' + --incremental $INCREMENTAL + --base_ref ${{ github.base_ref }} + --sha ${{ github.sha }} + --pull_repository ${{ github.event.pull_request.head.repo.clone_url }} + --head_ref refs/pull/${{ github.event.pull_request.number }}/head:${{ github.event.pull_request.head.ref }} + --head_sha ${{ github.event.pull_request.head.sha }} + --repository ${{ github.server_url }}/${{ github.repository }} " - name: Workflow dispatch @@ -482,7 +504,12 @@ jobs: if-no-files-found: error event_file: - if: github.repository_owner == 'root-project' || github.event_name == 'pull_request' + # For any event that is not a PR, the CI will always run. In PRs, the CI + # can be skipped if the tag [skip-ci] is written in the title. + if: | + (github.repository_owner == 'root-project' && github.event_name != 'pull_request') || + (github.event_name == 'pull_request' && !contains(github.event.pull_request.title, '[skip-ci]')) + name: "Upload Event Payload" runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 4b34be7fa950e..43292a6ac84ed 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -72,7 +72,7 @@ jobs: pip3 install gcovr - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Dump GitHub context env: diff --git a/.github/workflows/test-result-comment.yml b/.github/workflows/test-result-comment.yml index 718b621512df2..fc71857ff0309 100644 --- a/.github/workflows/test-result-comment.yml +++ b/.github/workflows/test-result-comment.yml @@ -13,7 +13,7 @@ jobs: comment-test-results: name: Publish Test Results - if: github.event.workflow_run.event == 'pull_request' + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion != 'skipped' runs-on: ubuntu-latest @@ -44,4 +44,4 @@ jobs: commit: ${{ github.event.workflow_run.head_sha }} event_file: artifacts/Event File/event.json event_name: ${{ github.event.workflow_run.event }} - files: "artifacts/**/*.xml" \ No newline at end of file + files: "artifacts/**/*.xml" diff --git a/CMakeLists.txt b/CMakeLists.txt index 6646e21c8b021..ea63c10b7a03f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -722,16 +722,30 @@ if(testing) if(roottest) find_package(Git REQUIRED) - # The fetch URL of the 'origin' remote is used to determine the prefix for other repositories by - # removing the `/root(\.git)?` part. If `GITHUB_PR_ORIGIN` is defined in the environment, its - # value is used instead. - if(DEFINED ENV{GITHUB_PR_ORIGIN}) - set(originurl $ENV{GITHUB_PR_ORIGIN}) - else() - execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git + + # Check whether the repository exists in the source directory or its parent + get_filename_component(source_dir ${CMAKE_CURRENT_SOURCE_DIR} REALPATH) + if(IS_DIRECTORY ${source_dir}/roottest/.git) + set(repo_dir ${source_dir}/roottest) + elseif(IS_DIRECTORY ${source_dir}/../roottest/.git) + set(repo_dir ${source_dir}/../roottest) + endif() + if(DEFINED repo_dir) + execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${repo_dir}/.git remote get-url origin OUTPUT_VARIABLE originurl OUTPUT_STRIP_TRAILING_WHITESPACE) + + else() + # The fetch URL of the 'origin' remote is used to determine the prefix for other repositories by + # removing the `/root(\.git)?` part. If `GITHUB_PR_ORIGIN` is defined in the environment, its + # value is used instead. + if(DEFINED ENV{GITHUB_PR_ORIGIN}) + set(originurl $ENV{GITHUB_PR_ORIGIN}) + else() + execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git + remote get-url origin OUTPUT_VARIABLE originurl OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() endif() - string(REGEX REPLACE "/root(\.git)?$" "" originprefix ${originurl}) + string(REGEX REPLACE "/root(test)?(\.git)?$" "" originprefix ${originurl}) relatedrepo_GetClosestMatch(REPO_NAME roottest ORIGIN_PREFIX ${originprefix} UPSTREAM_PREFIX ${upstreamprefix} FETCHURL_VARIABLE roottest_url FETCHREF_VARIABLE roottest_ref) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e53aff241a6d4..acc522c43e7e5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,51 +1,107 @@ - -How to Contribute Code to ROOT -============================== +# How to Contribute Code to ROOT Thank you for your interest in contributing to ROOT! We strongly welcome and appreciate such contributions! This short guide tries to make contributing as quick and painless as possible. -Your Pull Request ------------------------ +> [!NOTE] +> These guidelines should be applicable to most contributes. At the same time, these are not 'one-size-fits-all' rules, +> and there might be cases where diverging from these guidelines is warranted. If you are unsure about how to structure +> your contribution, don't hesitate to reach out! We are always happy to provide help and feedback. + +## Your Code Contribution The source code for ROOT is kept in [GitHub](https://github.com/root-project/root). Changes go through pull requests ("PRs"). The primary branch for development is `master`. -Visit [this page](https://root.cern/for_developers/creating_pr) for the mechanics on how to -create pull requests. + +> [!IMPORTANT] +> We require PRs to cleanly apply to master without a merge commit, i.e. through "fast-forward". +> Please follow the [coding conventions](https://root.cern.ch/coding-conventions), as this is a simple item for +> reviewers to otherwise get stuck on. +> To make your (and our own) life easier, we provide a +> [`clang-format` configuration file](https://github.com/root-project/root/blob/master/.clang-format). By providing code, you agree to transfer your copyright on the code to the "ROOT project". Of course you will be duly credited: for sizable contributions your name will appear in the -[CREDITS](https://raw.githubusercontent.com/root-project/root/master/README/CREDITS){:target="_blank"} +[CREDITS](https://raw.githubusercontent.com/root-project/root/master/README/CREDITS) file shipped with every binary and source distribution. The copyright transfer helps us with effectively defending the project in case of litigation. -:warning: We require PRs to cleanly apply to master without a merge commit, i.e. through "fast-forward". -Please follow the [coding conventions](https://root.cern.ch/coding-conventions), -as this is a simple item for reviewers to otherwise get stuck on. +## Your Commit + +Each commit is a self-contained, _atomic_ change. This means that: +1. **Each commit should be able to successfully build ROOT.** +Doing so makes traveling through the git history, for example during a `git bisect` much easier. +Ideally, the commit also should not depend on other commits to _run_ ROOT. +2. **Each commit does not contain more than one independent change.** +This allows us to revert changes when needed, without affecting anything else. + +> [!TIP] +> During a code review, it may be useful to make smaller commits to track intermediate changes, and rebase after the PR +> is approved to ensure the above points are met and to reduce clutter. + +### Your Commit Message + +The commit summary (i.e. the first line of the commit message) should be preceded by the a tag indicating the scope of +ROOT that is affected by your commit, in square brackets. Most tags are self-describing (e.g., `[tree]` indicates a +change to TTree, `[RF]` indicates a change to RooFit). If you are unsure about which scope tags to use, we are happy to +point you in the right direction! See also the [commit log](https://github.com/root-project/root/commits/master/) for +examples. The summary itself should not exceed 50 characters (excluding the scope tag), be meaningful (i.e., it +describes the change) and should be written in the +[present imperative mood](https://git.kernel.org/pub/scm/git/git.git/tree/Documentation/SubmittingPatches?id=HEAD#n239) +(e.g. `Add this awesome feature` instead of `Adds this awesome feature` or `Added this awesome feature`). + +The commit message that follow the summary can be used to provide more context to the change. +It should describe the **why**, rather than the **what** and **how** (we can gather this from the commit summary and the +change diff, respectively). +The commit message should be wrapped at 72 characters. + +> [!TIP] +> We provide a commit message template to help with following the above guidelines. It can be found in the root of this +> repository as [`.git-commit-template`](https://github.com/root-project/root/blob/master/.git-commit-template), +> and can be set to automatically be used for every commit with the following command: +> ```sh +> $ git config commit.template .git-commit-template +> ``` + +## Your Pull Request + +> [!NOTE] +> For the mechanics on how to create pull requests, please visit +> [this page](https://root.cern/for_developers/creating_pr). + +The title of your PR follows the same principle as the commit summary. If your PR only involves one commit, you can +reuse this summary. For non-functional changes (e.g. to the documentation) or changes for which you want to +**temporarily** prevent Jenkins from being triggered (e.g., for a draft PR), use `[skip-CI]` as the first tag. +Note that for functional changes this tag needs to be removed and it has to pass the CI before merging to ensure +the change does not break anything. + +The PR description describes (and in case of multiple commits, summarizes) the change in more detail. +Again, try to describe the **why** (and in this case, to a lesser extent the **what**), rather than the **how**. + +If your PR is related to an open [issue](https://github.com/root-project/root/issues), make sure to link it. +This will be done automatically if you add +[closing keywords](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) +to the PR description. Once a PR is created, a member of the ROOT team will review it as quickly as possible. If you are familiar with the ROOT community, it may be beneficial to add a suggested reviewer to the PR in order to get quicker attention. Please ping people :wave: should you not get timely feedback, for instance with `@root-project/core ping!` -Tests ------ +## Tests As you contribute code, this code will likely fix an issue or add a feature. -Whatever it is: this requires you to add a new test, or to extend an existing test. -We have concise unittests in the `test/` subdirectory of each part of ROOT; -see for instance [`tree/dataframe/test`](https://github.com/root-project/root/tree/master/tree/dataframe/test). -These tests are generally based on [Google Test](https://github.com/google/googletest) and easily extended. - -For more involved tests, such as tests requiring custom dictionaries or data -files, we have [roottest](https://github.com/root-project/roottest.git). -Suppose for your PR you create a branch on `root.git`. -Our CI infrastructure automatically picks up a branch with the same name in your fork of `roottest.git` -and use that for testing your PR. - - -Continuous Integration ----------------------- +Whatever it is: this requires you to add a new test, or to extend an existing test. Depending on the size and complexity +of this test, it exists either in the `test/` subdirectory of each part of ROOT (see for instance +[`tree/dataframe/test`](https://github.com/root-project/root/tree/master/tree/dataframe/test)), or in +[roottest](https://github.com/root-project/roottest.git). Tests in `test/` subdirectories are unit tests, mostly based on +[Google Test](https://github.com/google/googletest) and easily extended. Tests in +[roottest](https://github.com/root-project/roottest.git) are more involved (e.g., tests requiring custom dictionaries or +data files). When you create a branch in the main ROOT repository (i.e., this repository) and add a test to `roottest`, +make sure to do this under the same branch name (and open a PR for it). Our CI infrastructure automatically picks up the +changes defined in the `roottest` PR based on this branch name, and uses that for testing your PR here. + +## Continuous Integration To prevent bad surprises and make a better first impression, we strongly encourage new developers to [run the tests](https://root.cern/for_developers/run_the_tests/) @@ -53,7 +109,8 @@ _before_ submitting a pull request. ROOT has automated CI tests :cop: that are used for pull requests: - *Build and test*: a [Jenkins-based CI workflow](https://github.com/phsft-bot/build-configuration/blob/master/README.md) - tests PRs automatically; a project member might need to initiate this build. + as well as a GitHub Actions CI workflow tests PRs automatically; only a + [project member](https://github.com/orgs/root-project/people) is allowed to initiate this build. The results are posted to the pull request. Compared to ROOT's nightly builds, PRs are tested with less tests, on less platforms. - *Formatting check*: `clang-format` automatically checks that a PR diff --git a/README/ReleaseNotes/v632/index.md b/README/ReleaseNotes/v632/index.md index f6de3c0d75ff5..7d88a0f4fbf72 100644 --- a/README/ReleaseNotes/v632/index.md +++ b/README/ReleaseNotes/v632/index.md @@ -44,6 +44,8 @@ The following people have contributed to this new version: - Some redundant **RooDataSet** constructors are deprecated and will be removed in ROOT 6.34. Please use the RooDataSet constructors that take RooFit command arguments instead - ROOT does not longer support Python 2. The minimum required Python version to build ROOT is 3.8. +- Support for wildcard imports like `from ROOT import *` is dropped from PyROOT +- Support for external (ie. non-builtin) libAfterImage is now deprecated and it will be removed in next release 6.34. ## Core Libraries @@ -70,9 +72,24 @@ This grabs all the root files in subdirectories that have a name starting with ` ## Math Libraries +## Parallelism + - The ROOT::Experimental::TFuture template has been removed. ## RooFit Libraries +### New CPU likelihood evaluation backend by default + +The new vectorizing CPU evaluation backend is not the default for RooFit likelihoods. +Likelihood minimization is now up to 10x faster on a single CPU core. + +If you experience unexpected problems related to the likelihood evaluation, you +can revert back to the old backend by passing `RooFit::EvalBackend("legacy")` +to `RooAbsPdf::fitTo()` or `RooAbsPdf::createNLL()`. + +In case you observe any slowdowns with the new likelihood evaluation, please +open a GitHub issue about this, as such a performance regression is considered +a bug. + ### Compile your code with memory safe interfaces If you define the `ROOFIT_MEMORY_SAFE_INTERFACES` preprocessor macro, the diff --git a/bindings/experimental/distrdf/python/DistRDF/HeadNode.py b/bindings/experimental/distrdf/python/DistRDF/HeadNode.py index 7bd4e9cc89294..38a8bf57e8f56 100644 --- a/bindings/experimental/distrdf/python/DistRDF/HeadNode.py +++ b/bindings/experimental/distrdf/python/DistRDF/HeadNode.py @@ -272,6 +272,9 @@ def get_headnode(backend: BaseBackend, npartitions: int, *args) -> HeadNode: # RDataFrame(std::string_view treeName, dirPtr, defaultBranches = {}) # RDataFrame(TTree &tree, const ColumnNames_t &defaultBranches = {}) return TreeHeadNode(backend, npartitions, localdf, *args) + elif isinstance(firstarg, ROOT.RDF.Experimental.RDatasetSpec): + # RDataFrame(rdatasetspec) + return RDatasetSpecHeadNode(backend, npartitions, localdf, *args) else: raise RuntimeError( ("First argument {} of type {} is not recognised as a supported " @@ -585,3 +588,196 @@ def _handle_returned_values(self, values: TaskResult) -> Iterable: f"but {entries_in_trees.processed_entries} were processed.") return values.mergeables + + +class RDatasetSpecHeadNode(HeadNode): + """ + The head node of a computation graph where the RDataFrame data source is + an RDatasetSpec object. This head node is responsible for the following + RDataFrame constructors: + RDataFrame(ROOT.RDF.Experimental.RDatasetSpec spec) + + Attributes: + npartitions (int): The number of partitions the dataset will be split in + for distributed execution. + + rdatasetspec (ROOT.RDF.Experimental.RDatasetSpec): rdataset spec object + used to construct the RDataFrame + + subtreenames (list[str]): List of tree names in the dataset. + + inputfiles (list[str]): List of file names where the dataset is stored. + + friendinfo (ROOT.Internal.TreeUtils.RFriendInfo, None): Optional + information about friend trees of the dataset. Retrieved from + RDatasetSpec.GetFriendInfo(). Defaults to None. + """ + + def __init__(self, backend: BaseBackend, npartitions: Optional[int], localdf: ROOT.RDataFrame, *args): + """ + Creates a new RDataFrame instance for the given arguments. + + Args: + *args (iterable): Iterable with the arguments to the RDataFrame constructor. + + npartitions (int): The number of partitions the dataset will be + split in for distributed execution. + """ + super().__init__(backend, npartitions, localdf) + + # Information about friend trees, if they are present. + self.friendinfo: Optional[ROOT.Internal.TreeUtils.RFriendInfo] = None + + # Retrieve the RDatasetSpec that will be processed + if isinstance(args[0], ROOT.RDF.Experimental.RDatasetSpec): + # RDataFrame(rdatasetspec) + self.rdatasetspec = args[0] + fi = self.rdatasetspec.GetFriendInfo() + self.friendinfo = fi if not fi.fFriendNames.empty() else None + else: + raise RuntimeError( + f"First argument {args[0]} of type {type(args[0])} is not supported " + "in RDatasetSpecHeaNode") + + # subtreenames: names of all subtrees in the chain or full path to the tree in the file it belongs to + self.subtreenames = [str(treename) + for treename in self.rdatasetspec.GetTreeNames()] + self.inputfiles = [str(filename) + for filename in self.rdatasetspec.GetFileNameGlobs()] + + def _build_ranges(self) -> List[Ranges.DataRange]: + """Build the ranges for this dataset.""" + logger.debug("Building ranges from dataset info:\n" + "names of subtrees: %s\n" + "input files: %s\n", self.subtreenames, self.inputfiles) + + if logger.isEnabledFor(logging.DEBUG): + # Compute clusters and entries of the first tree in the dataset. + # This will call once TFile::Open, but we pay this cost to get an estimate + # on whether the number of requested partitions is reasonable. + # Depending on the cluster setup, this may still be quite costly, so + # we decide to pay the price only if the user explicitly requested + # warning logging. + clusters, entries = Ranges.get_clusters_and_entries( + self.subtreenames[0], self.inputfiles[0]) + # The file could contain an empty tree. In that case, the estimate will not be computed. + if entries > 0: + partitionsperfile = self.npartitions / len(self.inputfiles) + if partitionsperfile > len(clusters): + logger.debug( + "The number of requested partitions could be higher than the maximum amount of " + "chunks the dataset can be split in. Some tasks could be doing no work. Consider " + "setting the 'npartitions' parameter of the RDataFrame constructor to a lower value.") + + return Ranges.get_percentage_ranges(self.subtreenames, self.inputfiles, self.npartitions, self.friendinfo, self.exec_id) + + def _generate_rdf_creator(self) -> Callable[[Ranges.DataRange], TaskObjects]: + """ + Generates a function that is responsible for building an instance of + RDataFrame on a distributed mapper for a given entry range. Specific for + the TTree data source. + """ + + def attach_friend_info_if_present(current_range: Ranges.TreeRange, + ds: ROOT.RDF.Experimental.RDatasetSpec) -> None: + """ + Adds info about friend trees to the input chain. Also aligns the + starting and ending entry of the friend chain cache to those of the + main chain. + """ + # Gather information about friend trees. Check that we got an + # RFriendInfo struct and that it's not empty + if (current_range.friendinfo is not None): + # If the friend is a TChain, the zipped information looks like: + # (name, alias), (file1.root, file2.root, ...), (subname1, subname2, ...) + # If the friend is a TTree, the file list is made of + # only one filename and the list of names of the sub trees + # is empty, so the zipped information looks like: + # (name, alias), (filename.root, ), () + zipped_friendinfo = zip( + current_range.friendinfo.fFriendNames, + current_range.friendinfo.fFriendFileNames, + current_range.friendinfo.fFriendChainSubNames + ) + for (friend_name, friend_alias), friend_filenames, friend_chainsubnames in zipped_friendinfo: + friend_chainsubnames = ( + friend_chainsubnames if len(friend_chainsubnames) > 0 + else [friend_name]*len(friend_filenames) + ) + ds.WithGlobalFriends( + friend_chainsubnames, friend_filenames, friend_alias) + + def build_rdf_from_range(current_range: Ranges.TreeRangePerc) -> TaskObjects: + """ + Builds an RDataFrame instance for a distributed mapper. + + The function creates a TChain from the information contained in the + input range object. If the chain cannot be built, returns None. + """ + + clustered_range, entries_in_trees = Ranges.get_clustered_range_from_percs( + current_range) + + if clustered_range is None: + return TaskObjects(None, entries_in_trees) + + ds = ROOT.RDF.Experimental.RDatasetSpec() + # add a sample with no name to represent the whole dataset + ds.AddSample( + ("", clustered_range.treenames, clustered_range.filenames)) + ds.WithGlobalRange( + (clustered_range.globalstart, clustered_range.globalend)) + + attach_friend_info_if_present(clustered_range, ds) + + if current_range.exec_id not in _graph_cache._RDF_REGISTER: + rdf_toprocess = ROOT.RDataFrame(ds) + # Fill the cache with the new RDataFrame + _graph_cache._RDF_REGISTER[current_range.exec_id] = rdf_toprocess + else: + # Retrieve an already present RDataFrame from the cache + rdf_toprocess = _graph_cache._RDF_REGISTER[current_range.exec_id] + # Update it to the range of entries for this task + ROOT.Internal.RDF.ChangeSpec( + ROOT.RDF.AsRNode(rdf_toprocess), ROOT.std.move(ds)) + + return TaskObjects(rdf_toprocess, entries_in_trees) + + return build_rdf_from_range + + def _handle_returned_values(self, values: TaskResult) -> Iterable: + """ + Handle values returned after distributed execution. When the data source + is a TTree, check that exactly the input files and all the entries in + the dataset were processed during distributed execution. + """ + if values.mergeables is None: + raise RuntimeError("The distributed execution returned no values. " + "This can happen if all files in your dataset contain empty trees.") + + # User could have requested to read the same file multiple times indeed + input_files_and_trees = [ + f"{filename}/{treename}" for filename, treename in zip(self.inputfiles, self.subtreenames) + ] + files_counts = Counter(input_files_and_trees) + + entries_in_trees = values.entries_in_trees + # Keys should be exactly the same + if files_counts.keys() != entries_in_trees.trees_with_entries.keys(): + raise RuntimeError("The specified input files and the files that were " + "actually processed are not the same:\n" + f"Input files: {list(files_counts.keys())}\n" + f"Processed files: {list(entries_in_trees.trees_with_entries.keys())}") + + # Multiply the entries of each tree by the number of times it was + # requested by the user + for fullpath in files_counts: + entries_in_trees.trees_with_entries[fullpath] *= files_counts[fullpath] + + total_dataset_entries = sum( + entries_in_trees.trees_with_entries.values()) + if entries_in_trees.processed_entries != total_dataset_entries: + raise RuntimeError(f"The dataset has {total_dataset_entries} entries, " + f"but {entries_in_trees.processed_entries} were processed.") + + return values.mergeables diff --git a/bindings/experimental/distrdf/python/DistRDF/Proxy.py b/bindings/experimental/distrdf/python/DistRDF/Proxy.py index e47e7926f7745..d8fddd0b4c659 100644 --- a/bindings/experimental/distrdf/python/DistRDF/Proxy.py +++ b/bindings/experimental/distrdf/python/DistRDF/Proxy.py @@ -12,6 +12,7 @@ from __future__ import annotations import logging +import textwrap from abc import ABC, abstractmethod from contextlib import contextmanager from functools import singledispatch @@ -151,10 +152,13 @@ def GetKeys(self) -> List[str]: # TODO: # The event loop has not been triggered yet. Currently we can't retrieve # the list of variation names without starting the distributed computations - raise RuntimeError("The list of variation names cannot be (yet) retrieved without starting the " - "distributed computation graph. Please try to retrieve at least one variation value, " - "then the list of variation names will be available. In the future, it will be possible " - "to get the names without triggering.") + raise RuntimeError(textwrap.dedent( + """ + A list of names of systematic variations was requested, but the corresponding map of variations is not + present. The variation names cannot be retrieved unless the computation graph has properly run and + finished. Something may have gone wrong in the distributed execution, or no variation values were + explicitly requested. In the future, it will be possible to get the variation names without triggering. + """)) else: if self._keys is None: self._keys = [str(key) for key in self.proxied_node.value.GetKeys()] diff --git a/bindings/experimental/distrdf/test/test_headnode.py b/bindings/experimental/distrdf/test/test_headnode.py index 1a57ff19a95da..caa79238f648a 100644 --- a/bindings/experimental/distrdf/test/test_headnode.py +++ b/bindings/experimental/distrdf/test/test_headnode.py @@ -330,27 +330,56 @@ def test_num_entries_with_ttree_arg(self): hn = create_dummy_headnode(tree) self.assertEqual(hn.tree.GetEntries(), self.test_tree_entries) + def test_num_entries_with_rdatasetspec(self): + """Compute number of entries from an RDatasetSpec-based headnode.""" + + spec = ROOT.RDF.Experimental.RDatasetSpec() + spec.AddSample(("", self.test_treename, self.test_filename)) + + hn = create_dummy_headnode(spec) + + self.assertListEqual(hn.inputfiles, [self.test_filename]) + self.assertListEqual(hn.subtreenames, [self.test_treename]) + + with ROOT.TFile(hn.inputfiles[0]) as f: + t = f.Get(hn.subtreenames[0]) + self.assertEqual(t.GetEntries(), self.test_tree_entries) + class InternalDataFrameTests(unittest.TestCase): """The HeadNode stores an internal RDataFrame for certain information""" - def test_getcolumnnames(self): - treename = "tree" - filename = "test_distrdf_getcolumnnames.root" - f = ROOT.TFile(filename, "recreate") - tree = ROOT.TTree(treename, "test") - x = array("i", [0]) - tree.Branch("myColumn", x, "myColumn/I") + @classmethod + def setUpClass(cls): + """Create a dummy file to use for the RDataFrame constructor.""" + cls.test_treename = "treename" + cls.test_filename = "test_distrdf_getcolumnnames.root" + cls.test_tree_entries = 1 - for i in range(3): - x[0] = i + with ROOT.TFile(cls.test_filename, "RECREATE") as f: + tree = ROOT.TTree(cls.test_treename, cls.test_treename) + + x = array("f", [0]) + tree.Branch("myColumn", x, "myColumn/F") + + x[0] = 42 tree.Fill() - f.Write() - f.Close() + f.Write() + + @classmethod + def tearDownClass(cls): + os.remove(cls.test_filename) - hn = create_dummy_headnode(treename, filename) + def test_getcolumnnames_from_strings(self): + hn = create_dummy_headnode(self.test_treename, self.test_filename) cn_vec = hn.GetColumnNames() self.assertListEqual([str(col) for col in cn_vec], ["myColumn"]) - os.remove(filename) + def test_getcolumnnames_from_rdatasetspec(self): + spec = ROOT.RDF.Experimental.RDatasetSpec() + spec.AddSample(("", self.test_treename, self.test_filename)) + + hn = create_dummy_headnode(spec) + cn_vec = hn.GetColumnNames() + self.assertListEqual([str(col) for col in cn_vec], ["myColumn"]) diff --git a/bindings/jupyroot/python/JupyROOT/helpers/utils.py b/bindings/jupyroot/python/JupyROOT/helpers/utils.py index 757caa5006153..45a7d6082434f 100644 --- a/bindings/jupyroot/python/JupyROOT/helpers/utils.py +++ b/bindings/jupyroot/python/JupyROOT/helpers/utils.py @@ -170,9 +170,6 @@ def _getLibExtension(thePlatform): } return pExtMap.get(thePlatform, '.so') -def welcomeMsg(): - print("Welcome to JupyROOT %s" %ROOT.gROOT.GetVersion()) - @contextmanager def _setIgnoreLevel(level): originalLevel = ROOT.gErrorIgnoreLevel @@ -693,5 +690,3 @@ def iPythonize(): declareProcessLineWrapper() #enableCppHighlighting() enhanceROOTModule() - welcomeMsg() - diff --git a/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt b/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt index 17aafad3909b1..6c812d6f13ea5 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt +++ b/bindings/pyroot/cppyy/CPyCppyy/CMakeLists.txt @@ -90,7 +90,6 @@ target_include_directories(${libname} ${CMAKE_BINARY_DIR}/ginclude PUBLIC $ - $ ) set_property(GLOBAL APPEND PROPERTY ROOT_EXPORTED_TARGETS ${libname}) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/capi.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/capi.h index f3bef757ccf6e..b7d7164fe2650 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/capi.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/capi.h @@ -104,6 +104,8 @@ extern "C" { int cppyy_is_abstract(cppyy_type_t type); RPY_EXPORTED int cppyy_is_enum(const char* type_name); + RPY_EXPORTED + int cppyy_is_aggregate(cppyy_type_t type); RPY_EXPORTED const char** cppyy_get_all_cpp_names(cppyy_scope_t scope, size_t* count); diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index 6ded72e668498..b9e57e8433dc2 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -993,6 +993,15 @@ bool Cppyy::IsEnum(const std::string& type_name) return gInterpreter->ClassInfo_IsEnum(tn_short.c_str()); } +bool Cppyy::IsAggregate(TCppType_t klass) +{ +// Test if this type is an aggregate type + TClassRef& cr = type_from_handle(klass); + if (cr.GetClass()) + return cr->ClassProperty() & kClassIsAggregate; + return false; +} + // helpers for stripping scope names static std::string outer_with_template(const std::string& name) @@ -2485,6 +2494,10 @@ int cppyy_is_enum(const char* type_name) { return (int)Cppyy::IsEnum(type_name); } +int cppyy_is_aggregate(cppyy_type_t type) { + return (int)Cppyy::IsAggregate(type); +} + const char** cppyy_get_all_cpp_names(cppyy_scope_t scope, size_t* count) { std::set cppnames; Cppyy::GetAllCppNames(scope, cppnames); diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h index 24f1a5feffe62..d835aad39c795 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -114,6 +114,8 @@ namespace Cppyy { bool IsAbstract(TCppType_t type); RPY_EXPORTED bool IsEnum(const std::string& type_name); + RPY_EXPORTED + bool IsAggregate(TCppType_t type); RPY_EXPORTED void GetAllCppNames(TCppScope_t scope, std::set& cppnames); diff --git a/bindings/pyroot/pythonizations/python/ROOT/__init__.py b/bindings/pyroot/pythonizations/python/ROOT/__init__.py index ef89379f4178a..839b33c03c54e 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/__init__.py +++ b/bindings/pyroot/pythonizations/python/ROOT/__init__.py @@ -55,6 +55,26 @@ _is_ipython = hasattr(builtins, "__IPYTHON__") + +class _PoisonedDunderAll: + """ + Dummy class used to trigger an ImportError on wildcard imports if the + `__all__` attribute of a module is an instance of this class. + """ + + def __getitem__(self, _): + import textwrap + + message = """ + Wildcard import e.g. `from module import *` is bad practice, so it is disallowed in ROOT. Please import explicitly. + """ + raise ImportError(textwrap.dedent(message)) + + +# Prevent `from ROOT import *` by setting the __all__ attribute to something +# that will raise an ImportError on item retrieval. +__all__ = _PoisonedDunderAll() + # Configure ROOT facade module import sys from ._facade import ROOTFacade @@ -127,7 +147,12 @@ def is_package(self, fullname: str) -> bool: return _lookup_root_module(fullname) is not None def create_module(self, spec: ModuleSpec): - return _lookup_root_module(spec.name) + out = _lookup_root_module(spec.name) + # Prevent wildcard import for the submodule by setting the __all__ + # attribute to something that will raise an ImportError on item + # retrieval. + out.__all__ = _PoisonedDunderAll() + return out def exec_module(self, module): pass diff --git a/bindings/pyroot/pythonizations/python/ROOT/_facade.py b/bindings/pyroot/pythonizations/python/ROOT/_facade.py index d90094832eb1b..b086204aaba70 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_facade.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_facade.py @@ -82,9 +82,8 @@ def __init__(self, module, is_ipython): types.ModuleType.__init__(self, module.__name__) self.module = module - # Importing all will be customised later - self.module.__all__ = [] + self.__all__ = module.__all__ self.__name__ = module.__name__ self.__file__ = module.__file__ self.__cached__ = module.__cached__ @@ -145,22 +144,6 @@ def AddressOf(self, obj): # Create a buffer (LowLevelView) from address return cppyy.ll.cast[out_type](addr) - def _handle_import_all(self): - # Called if "from ROOT import *" is executed in the app. - # Customises lookup in Python's main module to also - # check in C++'s global namespace - - # Get caller module (jump over the facade frames) - num_frame = 2 - frame = sys._getframe(num_frame).f_globals["__name__"] - while frame == "ROOT._facade": - num_frame += 1 - frame = sys._getframe(num_frame).f_globals["__name__"] - caller = sys.modules[frame] - - # Install the hook - cppyy_backend._set_cpp_lazy_lookup(caller.__dict__) - def _fallback_getattr(self, name): # Try: # - in the global namespace @@ -170,13 +153,8 @@ def _fallback_getattr(self, name): # The first two attempts allow to lookup # e.g. ROOT.ROOT.Math as ROOT.Math - if name == "__all__": - self._handle_import_all() - # Make the attributes of the facade be injected in the - # caller module - raise AttributeError() # Note that hasattr caches the lookup for getattr - elif hasattr(gbl_namespace, name): + if hasattr(gbl_namespace, name): return getattr(gbl_namespace, name) elif hasattr(gbl_namespace.ROOT, name): return getattr(gbl_namespace.ROOT, name) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tclass.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tclass.py index 8bb06e19e90cc..49b1664fc3ea7 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tclass.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tclass.py @@ -16,6 +16,7 @@ def pythonize_tclass(): klass = cppyy.gbl.TClass # DynamicCast + klass._TClass__DynamicCast = klass.DynamicCast AddTClassDynamicCastPyz(klass) # Instant pythonization (executed at `import ROOT` time), no need of a diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_gnn.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_gnn.py index a7b1d923a43d3..54559fef35075 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_gnn.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_gnn.py @@ -257,6 +257,8 @@ def ParseFromMemory(graph_module, graph_data, filename = "gnn_network"): blas_routines.push_back("Axpy") blas_routines.push_back("Gemv") gnn_model.AddBlasRoutines(blas_routines) + gnn_model.AddNeededStdLib("algorithm") + gnn_model.AddNeededStdLib("cmath") return gnn_model @@ -323,6 +325,8 @@ def ParseFromMemory(graph_module, graph_data, filename = "graph_independent_netw blas_routines = gbl_namespace.std.vector['std::string']() blas_routines.push_back("Gemm") graph_independent_model.AddBlasRoutines(blas_routines) + graph_independent_model.AddNeededStdLib("algorithm") + graph_independent_model.AddNeededStdLib("cmath") return graph_independent_model diff --git a/bindings/pyroot/pythonizations/src/CPPInstancePyz.cxx b/bindings/pyroot/pythonizations/src/CPPInstancePyz.cxx index d5acfe6035081..85e7e0192ddc6 100644 --- a/bindings/pyroot/pythonizations/src/CPPInstancePyz.cxx +++ b/bindings/pyroot/pythonizations/src/CPPInstancePyz.cxx @@ -10,15 +10,15 @@ *************************************************************************/ // Bindings -#include "CPyCppyy.h" +#include "CPyCppyy/API.h" + +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/CustomPyTypes.h" + #include "PyROOTPythonize.h" -#include "CPPInstance.h" -#include "ProxyWrappers.h" -#include "Converters.h" -#include "Utility.h" #include "PyzCppHelpers.hxx" #include "TBufferFile.h" -#include "CustomPyTypes.h" using namespace CPyCppyy; @@ -39,7 +39,7 @@ extern PyObject *gRootModule; PyObject *PyROOT::CPPInstanceExpand(PyObject * /*self*/, PyObject *args) { PyObject *pybuf = 0, *pyname = 0; - if (!PyArg_ParseTuple(args, const_cast("O!O!:__expand__"), &PyBytes_Type, &pybuf, &PyBytes_Type, &pyname)) + if (!PyArg_ParseTuple(args, "O!O!:__expand__", &PyBytes_Type, &pybuf, &PyBytes_Type, &pyname)) return 0; const char *clname = PyBytes_AS_STRING(pyname); // TBuffer and its derived classes can't write themselves, but can be created @@ -55,12 +55,7 @@ PyObject *PyROOT::CPPInstanceExpand(PyObject * /*self*/, PyObject *args) TBufferFile buf(TBuffer::kRead, PyBytes_GET_SIZE(pybuf), PyBytes_AS_STRING(pybuf), kFALSE); newObj = buf.ReadObjectAny(0); } - PyObject *result = BindCppObject(newObj, Cppyy::GetScope(clname)); - if (result) { - // this object is to be owned by the Python interpreter, assuming that the call - // originated from there - ((CPPInstance *)result)->PythonOwns(); - } + PyObject *result = CPyCppyy::Instance_FromVoidPtr(newObj, clname, /*python_owns=*/ true); return result; } @@ -68,22 +63,23 @@ PyObject *PyROOT::CPPInstanceExpand(PyObject * /*self*/, PyObject *args) /// Turn the object proxy instance into a character stream and return for /// pickle, together with the callable object that can restore the stream /// into the object proxy instance. -PyObject *op_reduce(CPPInstance *self, PyObject * /*args*/) +PyObject *op_reduce(PyObject *self, PyObject * /*args*/) { // keep a borrowed reference around to the callable function for expanding; // because it is borrowed, it means that there can be no pickling during the // shutdown of the libPyROOT module static PyObject *s_expand = - PyDict_GetItemString(PyModule_GetDict(PyROOT::gRootModule), const_cast("_CPPInstance__expand__")); + PyDict_GetItemString(PyModule_GetDict(PyROOT::gRootModule), "_CPPInstance__expand__"); // TBuffer and its derived classes can't write themselves, but can be created // directly from the buffer, so handle them in a special case static Cppyy::TCppType_t s_bfClass = Cppyy::GetScope("TBufferFile"); TBufferFile *buff = 0; - if (s_bfClass == self->ObjectIsA()) { - buff = (TBufferFile *)self->GetObject(); + Cppyy::TCppType_t selfClass = ((CPPInstance*)self)->ObjectIsA(); + if (selfClass == s_bfClass) { + buff = (TBufferFile *)CPyCppyy::Instance_AsVoidPtr(self); } else { - auto className = Cppyy::GetScopedFinalName(self->ObjectIsA()); + auto className = GetScopedFinalNameFromPyObject(self); if (className.find("__cppyy_internal::Dispatcher") == 0) { PyErr_Format(PyExc_IOError, "generic streaming of Python objects whose class derives from a C++ class is not supported. " "Please refer to the Python pickle documentation for instructions on how to define " @@ -95,10 +91,10 @@ PyObject *op_reduce(CPPInstance *self, PyObject * /*args*/) static TBufferFile s_buff(TBuffer::kWrite); s_buff.Reset(); // to delete - if (s_buff.WriteObjectAny(self->GetObject(), + if (s_buff.WriteObjectAny(CPyCppyy::Instance_AsVoidPtr(self), TClass::GetClass(className.c_str())) != 1) { PyErr_Format(PyExc_IOError, "could not stream object of type %s", - Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str()); + GetScopedFinalNameFromPyObject(self).c_str()); return 0; } buff = &s_buff; @@ -108,7 +104,7 @@ PyObject *op_reduce(CPPInstance *self, PyObject * /*args*/) // on reading back in (see CPPInstanceExpand defined above) PyObject *res2 = PyTuple_New(2); PyTuple_SET_ITEM(res2, 0, PyBytes_FromStringAndSize(buff->Buffer(), buff->Length())); - PyTuple_SET_ITEM(res2, 1, PyBytes_FromString(Cppyy::GetScopedFinalName(self->ObjectIsA()).c_str())); + PyTuple_SET_ITEM(res2, 1, PyBytes_FromString(GetScopedFinalNameFromPyObject(self).c_str())); PyObject *result = PyTuple_New(2); Py_INCREF(s_expand); @@ -145,7 +141,7 @@ PyObject *PyROOT::AddCPPInstancePickling(PyObject * /*self*/, PyObject *args) // attribute assignment using PyObject_SetAttr // for more info refer to: // https://bitbucket.org/wlav/cppyy/issues/110/user-defined-classes-in-c-dont-seem-to-be - PyObject_GenericSetAttr(pyclass, CPyCppyy_PyText_FromString(attr), method); + PyObject_GenericSetAttr(pyclass, PyUnicode_FromString(attr), method); Py_DECREF(method); Py_DECREF(func); diff --git a/bindings/pyroot/pythonizations/src/GenericPyz.cxx b/bindings/pyroot/pythonizations/src/GenericPyz.cxx index 5af103d455f47..46b0ec53e5ac1 100644 --- a/bindings/pyroot/pythonizations/src/GenericPyz.cxx +++ b/bindings/pyroot/pythonizations/src/GenericPyz.cxx @@ -11,10 +11,14 @@ #include "Python.h" -#include "CPyCppyy.h" +#include "CPyCppyy/API.h" + +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/Utility.h" + #include "PyROOTPythonize.h" -#include "CPPInstance.h" -#include "Utility.h" +#include "PyzCppHelpers.hxx" + #include "TClass.h" #include "TInterpreter.h" #include "TInterpreterValue.h" @@ -23,11 +27,6 @@ using namespace CPyCppyy; -static std::string GetCppName(const CPPInstance *self) -{ - return Cppyy::GetScopedFinalName(self->ObjectIsA()); -} - // We take as unique identifier the declId of the class to // treat the case where a class is loaded, an instance printed, // the class unloaded and reloaded with changes. @@ -41,16 +40,16 @@ static ULong64_t GetClassID(const char *clName) return 0; } -PyObject *ClingPrintValue(CPPInstance *self, PyObject * /* args */) +PyObject *ClingPrintValue(PyObject *self, PyObject * /* args */) { // Map holding the classID of the classes and the pointer // to the printer function. static std::map declIDPrinterMap; - auto cppObj = self->GetObject(); + auto cppObj = CPyCppyy::Instance_AsVoidPtr(self); if (!cppObj) // Proxied cpp object is null, use cppyy's generic __repr__ - return PyObject_Repr((PyObject*)self); + return PyObject_Repr(self); // We jit the helper only once, at the first invocation of any // printer. The integer parameter is there to make sure we have @@ -67,7 +66,7 @@ PyObject *ClingPrintValue(CPPInstance *self, PyObject * /* args */) gInterpreter->Declare(printerCode.c_str()); } - const std::string className = GetCppName(self); + const std::string className = GetScopedFinalNameFromPyObject(self); std::string printResult; @@ -93,9 +92,9 @@ PyObject *ClingPrintValue(CPPInstance *self, PyObject * /* args */) if (printResult.find("@0x") == 0) { // Fall back to __repr__ if we just get an address from cling - return PyObject_Repr((PyObject*)self); + return PyObject_Repr(self); } else { - return CPyCppyy_PyText_FromString(printResult.c_str()); + return PyUnicode_FromString(printResult.c_str()); } } diff --git a/bindings/pyroot/pythonizations/src/PyROOTModule.cxx b/bindings/pyroot/pythonizations/src/PyROOTModule.cxx index 3f882a0226c61..4d54a5a8da389 100644 --- a/bindings/pyroot/pythonizations/src/PyROOTModule.cxx +++ b/bindings/pyroot/pythonizations/src/PyROOTModule.cxx @@ -15,10 +15,9 @@ #include "RPyROOTApplication.h" // Cppyy -#include "CPyCppyy.h" -#include "CallContext.h" -#include "ProxyWrappers.h" -#include "Utility.h" +#include "CPyCppyy/API.h" +#include "../../cppyy/CPyCppyy/src/CallContext.h" +#include "../../cppyy/CPyCppyy/src/ProxyWrappers.h" // ROOT #include "TROOT.h" diff --git a/bindings/pyroot/pythonizations/src/PyROOTWrapper.cxx b/bindings/pyroot/pythonizations/src/PyROOTWrapper.cxx index 65cf1b3b64a95..634cd74edae83 100644 --- a/bindings/pyroot/pythonizations/src/PyROOTWrapper.cxx +++ b/bindings/pyroot/pythonizations/src/PyROOTWrapper.cxx @@ -14,8 +14,7 @@ #include "TMemoryRegulator.h" // Cppyy -#include "CPyCppyy.h" -#include "ProxyWrappers.h" +#include "CPyCppyy/API.h" // ROOT #include "TROOT.h" @@ -32,11 +31,11 @@ using namespace PyROOT; namespace { -static void AddToGlobalScope(const char *label, const char * /* hdr */, TObject *obj, Cppyy::TCppType_t klass) +static void AddToGlobalScope(const char *label, TObject *obj, const char * classname) { // Bind the given object with the given class in the global scope with the // given label for its reference. - PyModule_AddObject(gRootModule, const_cast(label), CPyCppyy::BindCppObjectNoCast(obj, klass)); + PyModule_AddObject(gRootModule, label, CPyCppyy::Instance_FromVoidPtr(obj, classname)); } } // unnamed namespace @@ -58,9 +57,9 @@ void PyROOT::Init() gROOT->GetListOfCleanups()->Add(&GetMemoryRegulator()); // Bind ROOT globals that will be needed in ROOT.py - AddToGlobalScope("gROOT", "TROOT.h", gROOT, Cppyy::GetScope(gROOT->IsA()->GetName())); - AddToGlobalScope("gSystem", "TSystem.h", gSystem, Cppyy::GetScope(gSystem->IsA()->GetName())); - AddToGlobalScope("gInterpreter", "TInterpreter.h", gInterpreter, Cppyy::GetScope(gInterpreter->IsA()->GetName())); + AddToGlobalScope("gROOT", gROOT, gROOT->IsA()->GetName()); + AddToGlobalScope("gSystem", gSystem, gSystem->IsA()->GetName()); + AddToGlobalScope("gInterpreter", gInterpreter, gInterpreter->IsA()->GetName()); } PyObject *PyROOT::ClearProxiedObjects(PyObject * /* self */, PyObject * /* args */) diff --git a/bindings/pyroot/pythonizations/src/PyzCppHelpers.cxx b/bindings/pyroot/pythonizations/src/PyzCppHelpers.cxx index e26b5ee4e1b47..9c2aed46ef060 100644 --- a/bindings/pyroot/pythonizations/src/PyzCppHelpers.cxx +++ b/bindings/pyroot/pythonizations/src/PyzCppHelpers.cxx @@ -16,16 +16,18 @@ pythonizations. */ #include "PyzCppHelpers.hxx" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" + // Call method with signature: obj->meth() PyObject *CallPyObjMethod(PyObject *obj, const char *meth) { - return PyObject_CallMethod(obj, const_cast(meth), const_cast("")); + return PyObject_CallMethod(obj, meth, ""); } // Call method with signature: obj->meth(arg1) PyObject *CallPyObjMethod(PyObject *obj, const char *meth, PyObject *arg1) { - return PyObject_CallMethod(obj, const_cast(meth), const_cast("O"), arg1); + return PyObject_CallMethod(obj, meth, "O", arg1); } // Convert generic python object into a boolean value @@ -41,9 +43,14 @@ PyObject *BoolNot(PyObject *value) } // Get the TClass of the C++ object proxied by pyobj -TClass *GetTClass(const CPyCppyy::CPPInstance *pyobj) +TClass *GetTClass(const PyObject *pyobj) +{ + return TClass::GetClass(GetScopedFinalNameFromPyObject(pyobj).c_str()); +} + +std::string GetScopedFinalNameFromPyObject(const PyObject *pyobj) { - return TClass::GetClass(Cppyy::GetScopedFinalName(pyobj->ObjectIsA()).c_str()); + return Cppyy::GetScopedFinalName(((CPyCppyy::CPPInstance*)pyobj)->ObjectIsA()); } //////////////////////////////////////////////////////////////////////////// @@ -114,7 +121,7 @@ std::string GetTypestrFromArrayInterface(PyObject *obj) PyErr_SetString(PyExc_RuntimeError, "Object not convertible: __array_interface__['typestr'] does not exist."); return ""; } - std::string typestr = CPyCppyy_PyText_AsString(pytypestr); + std::string typestr = PyUnicode_AsUTF8(pytypestr); const auto length = typestr.length(); if(length != 3) { PyErr_SetString(PyExc_RuntimeError, diff --git a/bindings/pyroot/pythonizations/src/PyzCppHelpers.hxx b/bindings/pyroot/pythonizations/src/PyzCppHelpers.hxx index cd619e72ed397..e5614ba62418c 100644 --- a/bindings/pyroot/pythonizations/src/PyzCppHelpers.hxx +++ b/bindings/pyroot/pythonizations/src/PyzCppHelpers.hxx @@ -11,8 +11,8 @@ #ifndef PYROOT_PYZCPPHELPERS #define PYROOT_PYZCPPHELPERS -#include "CPyCppyy.h" -#include "CPPInstance.h" +#include "CPyCppyy/API.h" + #include "TClass.h" #include "ROOT/RConfig.hxx" @@ -21,7 +21,8 @@ PyObject *CallPyObjMethod(PyObject *obj, const char *meth); PyObject *CallPyObjMethod(PyObject *obj, const char *meth, PyObject *arg1); PyObject *BoolNot(PyObject *value); -TClass *GetTClass(const CPyCppyy::CPPInstance *pyobj); +TClass *GetTClass(const PyObject *pyobj); +std::string GetScopedFinalNameFromPyObject(const PyObject *pyobj); std::string GetCppTypeFromNumpyType(const std::string& dtype); PyObject *GetArrayInterface(PyObject *obj); unsigned long long GetDataPointerFromArrayInterface(PyObject *obj); diff --git a/bindings/pyroot/pythonizations/src/PyzPythonHelpers.cxx b/bindings/pyroot/pythonizations/src/PyzPythonHelpers.cxx index 14e8cd038b829..1e44d5eeae427 100644 --- a/bindings/pyroot/pythonizations/src/PyzPythonHelpers.cxx +++ b/bindings/pyroot/pythonizations/src/PyzPythonHelpers.cxx @@ -17,9 +17,7 @@ PyROOT extension module. */ -#include "CPyCppyy.h" -#include "CPPInstance.h" -#include "CPPOverload.h" +#include "CPyCppyy/API.h" #include "PyROOTPythonize.h" @@ -28,13 +26,6 @@ PyROOT extension module. #include -// needed to properly resolve (dllimport) symbols on Windows -namespace CPyCppyy { - namespace PyStrings { - R__EXTERN PyObject *gMRO; - } -} - //////////////////////////////////////////////////////////////////////////// /// \brief Get size of C++ data-type /// \param[in] self Always null, since this is a module function. @@ -46,7 +37,7 @@ PyObject *PyROOT::GetSizeOfType(PyObject * /*self*/, PyObject *args) { // Get name of data-type PyObject *pydtype = PyTuple_GetItem(args, 0); - std::string dtype = CPyCppyy_PyText_AsString(pydtype); + std::string dtype = PyUnicode_AsUTF8(pydtype); // Call interpreter to get size of data-type using `sizeof` size_t size = 0; @@ -55,7 +46,7 @@ PyObject *PyROOT::GetSizeOfType(PyObject * /*self*/, PyObject *args) gInterpreter->Calc(code.str().c_str()); // Return size of data-type as integer - PyObject *pysize = PyInt_FromLong(size); + PyObject *pysize = PyLong_FromLong(size); return pysize; } @@ -72,16 +63,15 @@ PyObject *PyROOT::GetDataPointer(PyObject * /*self*/, PyObject *args) { // Get pointer of C++ object PyObject *pyobj = PyTuple_GetItem(args, 0); - auto instance = (CPyCppyy::CPPInstance *)(pyobj); - auto cppobj = instance->GetObject(); + void* cppobj = CPyCppyy::Instance_AsVoidPtr(pyobj); // Get name of C++ object as string PyObject *pycppname = PyTuple_GetItem(args, 1); - std::string cppname = CPyCppyy_PyText_AsString(pycppname); + std::string cppname = PyUnicode_AsUTF8(pycppname); // Get name of method to be called to get the data pointer PyObject *pymethodname = PyTuple_GetItem(args, 2); - std::string methodname = CPyCppyy_PyText_AsString(pymethodname); + std::string methodname = PyUnicode_AsUTF8(pymethodname); // Call interpreter to get pointer to data uintptr_t pointer = 0; @@ -106,8 +96,8 @@ PyObject *PyROOT::GetDataPointer(PyObject * /*self*/, PyObject *args) PyObject *PyROOT::GetEndianess(PyObject * /* self */, PyObject * /* args */) { #ifdef R__BYTESWAP - return CPyCppyy_PyText_FromString("<"); + return PyUnicode_FromString("<"); #else - return CPyCppyy_PyText_FromString(">"); + return PyUnicode_FromString(">"); #endif } diff --git a/bindings/pyroot/pythonizations/src/RDataFramePyz.cxx b/bindings/pyroot/pythonizations/src/RDataFramePyz.cxx index 189ca9bd8691c..cadcf01e3723a 100644 --- a/bindings/pyroot/pythonizations/src/RDataFramePyz.cxx +++ b/bindings/pyroot/pythonizations/src/RDataFramePyz.cxx @@ -9,13 +9,15 @@ * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ -#include "CPyCppyy.h" -#include "CPPInstance.h" -#include "ProxyWrappers.h" +#include "CPyCppyy/API.h" + +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/ProxyWrappers.h" + #include "PyROOTPythonize.h" #include "ROOT/RConfig.hxx" #include "TInterpreter.h" -#include "CPyCppyy/API.h" #include // std::pair #include // std::stringstream diff --git a/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx b/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx index 8429498cc44fb..d0a27d141e5f7 100644 --- a/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx +++ b/bindings/pyroot/pythonizations/src/RPyROOTApplication.cxx @@ -11,7 +11,6 @@ // Bindings #include "Python.h" -#include "CPyCppyy.h" #include "RPyROOTApplication.h" // ROOT @@ -50,14 +49,14 @@ bool PyROOT::RPyROOTApplication::CreateApplication(int ignoreCmdLineOpts) argv = new char *[argc]; } else { // Retrieve sys.argv list from Python - PyObject *argl = PySys_GetObject(const_cast("argv")); + PyObject *argl = PySys_GetObject("argv"); if (argl && 0 < PyList_Size(argl)) argc = (int)PyList_GET_SIZE(argl); argv = new char *[argc]; for (int i = 1; i < argc; ++i) { - char *argi = const_cast(CPyCppyy_PyText_AsString(PyList_GET_ITEM(argl, i))); + char *argi = const_cast(PyUnicode_AsUTF8(PyList_GET_ITEM(argl, i))); if (strcmp(argi, "-") == 0 || strcmp(argi, "--") == 0) { // Stop collecting options, the remaining are for the Python script argc = i; // includes program name diff --git a/bindings/pyroot/pythonizations/src/RTensorPyz.cxx b/bindings/pyroot/pythonizations/src/RTensorPyz.cxx index b2538ac5b4eef..a8bcf94732672 100644 --- a/bindings/pyroot/pythonizations/src/RTensorPyz.cxx +++ b/bindings/pyroot/pythonizations/src/RTensorPyz.cxx @@ -9,9 +9,10 @@ * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ -#include "CPyCppyy.h" -#include "CPPInstance.h" -#include "ProxyWrappers.h" +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/ProxyWrappers.h" + #include "PyROOTPythonize.h" #include "TInterpreter.h" #include "PyzCppHelpers.hxx" diff --git a/bindings/pyroot/pythonizations/src/RVecPyz.cxx b/bindings/pyroot/pythonizations/src/RVecPyz.cxx index 5cf3a5d0f7592..c3311d6862469 100644 --- a/bindings/pyroot/pythonizations/src/RVecPyz.cxx +++ b/bindings/pyroot/pythonizations/src/RVecPyz.cxx @@ -9,9 +9,10 @@ * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ -#include "CPyCppyy.h" -#include "CPPInstance.h" -#include "ProxyWrappers.h" +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/ProxyWrappers.h" + #include "PyROOTPythonize.h" #include "TInterpreter.h" #include "PyzCppHelpers.hxx" diff --git a/bindings/pyroot/pythonizations/src/TClassPyz.cxx b/bindings/pyroot/pythonizations/src/TClassPyz.cxx index 86fdb6280218c..78f3328d3faeb 100644 --- a/bindings/pyroot/pythonizations/src/TClassPyz.cxx +++ b/bindings/pyroot/pythonizations/src/TClassPyz.cxx @@ -10,12 +10,13 @@ *************************************************************************/ // Bindings -#include "CPyCppyy.h" #include "CPyCppyy/API.h" + +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/Utility.h" + #include "PyROOTPythonize.h" -#include "CPPInstance.h" -#include "Utility.h" -#include "ProxyWrappers.h" #include "PyzCppHelpers.hxx" // ROOT @@ -30,7 +31,7 @@ PyObject *TClassDynamicCastPyz(PyObject *self, PyObject *args) PyObject *pyclass = nullptr; PyObject *pyobject = nullptr; int up = 1; - if (!PyArg_ParseTuple(args, const_cast("O!O|i:DynamicCast"), + if (!PyArg_ParseTuple(args, "O!O|i:DynamicCast", &CPPInstance_Type, &pyclass, &pyobject, &up)) @@ -42,8 +43,8 @@ PyObject *TClassDynamicCastPyz(PyObject *self, PyObject *args) void *address = cl1->DynamicCast(cl2, CPyCppyy::Instance_AsVoidPtr(pyobject), up); - if (CPPInstance_Check(pyobject)) { - address = ((CPPInstance *)pyobject)->GetObject(); + if (CPyCppyy::Instance_Check(pyobject)) { + address = CPyCppyy::Instance_AsVoidPtr(pyobject); } else if (PyInt_Check(pyobject) || PyLong_Check(pyobject)) { address = (void *)PyLong_AsLongLong(pyobject); } else { @@ -54,13 +55,13 @@ PyObject *TClassDynamicCastPyz(PyObject *self, PyObject *args) TClass *klass = nullptr; if (up) { // Upcast: result is a base - klass = (TClass *)GetTClass((CPPInstance *)pyclass)->DynamicCast(TClass::Class(), CPyCppyy::Instance_AsVoidPtr(pyclass)); + klass = (TClass *)GetTClass(pyclass)->DynamicCast(TClass::Class(), CPyCppyy::Instance_AsVoidPtr(pyclass)); } else { // Downcast: result is a derived - klass = (TClass *)GetTClass((CPPInstance *)self)->DynamicCast(TClass::Class(), cl1); + klass = (TClass *)GetTClass(self)->DynamicCast(TClass::Class(), cl1); } - return BindCppObjectNoCast(address, Cppyy::GetScope(klass->GetName())); + return CPyCppyy::Instance_FromVoidPtr(address, klass->GetName()); } //////////////////////////////////////////////////////////////////////////// @@ -75,7 +76,6 @@ PyObject *TClassDynamicCastPyz(PyObject *self, PyObject *args) PyObject *PyROOT::AddTClassDynamicCastPyz(PyObject * /* self */, PyObject *args) { PyObject *pyclass = PyTuple_GetItem(args, 0); - Utility::AddToClass(pyclass, "_TClass__DynamicCast", "DynamicCast"); Utility::AddToClass(pyclass, "DynamicCast", (PyCFunction)TClassDynamicCastPyz); Py_RETURN_NONE; } diff --git a/bindings/pyroot/pythonizations/src/TMemoryRegulator.cxx b/bindings/pyroot/pythonizations/src/TMemoryRegulator.cxx index 46f35aea4a6cd..4cde91e2ac63f 100644 --- a/bindings/pyroot/pythonizations/src/TMemoryRegulator.cxx +++ b/bindings/pyroot/pythonizations/src/TMemoryRegulator.cxx @@ -11,9 +11,8 @@ #include "TMemoryRegulator.h" -#include "ProxyWrappers.h" -#include "CPPInstance.h" -#include "CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/ProxyWrappers.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" using namespace CPyCppyy; diff --git a/bindings/pyroot/pythonizations/src/TMemoryRegulator.h b/bindings/pyroot/pythonizations/src/TMemoryRegulator.h index caa78a4c12522..bc46bd2c8ecf3 100644 --- a/bindings/pyroot/pythonizations/src/TMemoryRegulator.h +++ b/bindings/pyroot/pythonizations/src/TMemoryRegulator.h @@ -34,8 +34,8 @@ // Bindings // CPyCppyy.h must be go first, since it includes Python.h, which must be // included before any standard header -#include "CPyCppyy.h" -#include "MemoryRegulator.h" +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/MemoryRegulator.h" // ROOT #include "TObject.h" diff --git a/bindings/pyroot/pythonizations/src/TObjectPyz.cxx b/bindings/pyroot/pythonizations/src/TObjectPyz.cxx index cca118411576b..7a52918e10e06 100644 --- a/bindings/pyroot/pythonizations/src/TObjectPyz.cxx +++ b/bindings/pyroot/pythonizations/src/TObjectPyz.cxx @@ -10,9 +10,12 @@ *************************************************************************/ // Bindings -#include "CPyCppyy.h" -#include "CPPInstance.h" -#include "Utility.h" +#include "CPyCppyy/API.h" + +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/Utility.h" + #include "PyROOTPythonize.h" #include "PyzCppHelpers.hxx" @@ -24,7 +27,7 @@ using namespace CPyCppyy; // Implement Python's __eq__ with TObject::IsEqual PyObject *TObjectIsEqual(PyObject *self, PyObject *obj) { - if (!CPPInstance_Check(obj) || !((CPPInstance *)obj)->fObject) + if (!CPyCppyy::Instance_Check(obj) || !((CPPInstance *)obj)->fObject) return CPPInstance_Type.tp_richcompare(self, obj, Py_EQ); return CallPyObjMethod(self, "IsEqual", obj); @@ -33,7 +36,7 @@ PyObject *TObjectIsEqual(PyObject *self, PyObject *obj) // Implement Python's __ne__ with TObject::IsEqual PyObject *TObjectIsNotEqual(PyObject *self, PyObject *obj) { - if (!CPPInstance_Check(obj) || !((CPPInstance *)obj)->fObject) + if (!CPyCppyy::Instance_Check(obj) || !((CPPInstance *)obj)->fObject) return CPPInstance_Type.tp_richcompare(self, obj, Py_NE); return BoolNot(CallPyObjMethod(self, "IsEqual", obj)); diff --git a/bindings/pyroot/pythonizations/src/TPyDispatcher.cxx b/bindings/pyroot/pythonizations/src/TPyDispatcher.cxx index 159612b67e542..c5786cb388451 100644 --- a/bindings/pyroot/pythonizations/src/TPyDispatcher.cxx +++ b/bindings/pyroot/pythonizations/src/TPyDispatcher.cxx @@ -9,10 +9,10 @@ * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ -// Bindings -#include "CPyCppyy.h" #include "TPyDispatcher.h" -#include "ProxyWrappers.h" + +// Bindings +#include "CPyCppyy/API.h" // ROOT #include "TClass.h" @@ -113,7 +113,7 @@ PyObject *TPyDispatcher::DispatchVA(const char *format, ...) PyObject *TPyDispatcher::DispatchVA1(const char *clname, void *obj, const char *format, ...) { - PyObject *pyobj = CPyCppyy::BindCppObject(obj, Cppyy::GetScope(clname), kFALSE /* isRef */); + PyObject *pyobj = CPyCppyy::Instance_FromVoidPtr(obj, clname); if (!pyobj) { PyErr_Print(); return 0; @@ -171,9 +171,9 @@ PyObject *TPyDispatcher::DispatchVA1(const char *clname, void *obj, const char * PyObject *TPyDispatcher::Dispatch(TPad *selpad, TObject *selected, Int_t event) { PyObject *args = PyTuple_New(3); - PyTuple_SET_ITEM(args, 0, CPyCppyy::BindCppObject(selpad, Cppyy::GetScope("TPad"))); - PyTuple_SET_ITEM(args, 1, CPyCppyy::BindCppObject(selected, Cppyy::GetScope("TObject"))); - PyTuple_SET_ITEM(args, 2, PyInt_FromLong(event)); + PyTuple_SET_ITEM(args, 0, CPyCppyy::Instance_FromVoidPtr(selpad, "TPad")); + PyTuple_SET_ITEM(args, 1, CPyCppyy::Instance_FromVoidPtr(selected, "TObject")); + PyTuple_SET_ITEM(args, 2, PyLong_FromLong(event)); PyObject *result = PyObject_CallObject(fCallable, args); Py_XDECREF(args); @@ -191,10 +191,10 @@ PyObject *TPyDispatcher::Dispatch(TPad *selpad, TObject *selected, Int_t event) PyObject *TPyDispatcher::Dispatch(Int_t event, Int_t x, Int_t y, TObject *selected) { PyObject *args = PyTuple_New(4); - PyTuple_SET_ITEM(args, 0, PyInt_FromLong(event)); - PyTuple_SET_ITEM(args, 1, PyInt_FromLong(x)); - PyTuple_SET_ITEM(args, 2, PyInt_FromLong(y)); - PyTuple_SET_ITEM(args, 3, CPyCppyy::BindCppObject(selected, Cppyy::GetScope("TObject"))); + PyTuple_SET_ITEM(args, 0, PyLong_FromLong(event)); + PyTuple_SET_ITEM(args, 1, PyLong_FromLong(x)); + PyTuple_SET_ITEM(args, 2, PyLong_FromLong(y)); + PyTuple_SET_ITEM(args, 3, CPyCppyy::Instance_FromVoidPtr(selected, "TObject")); PyObject *result = PyObject_CallObject(fCallable, args); Py_XDECREF(args); @@ -212,9 +212,9 @@ PyObject *TPyDispatcher::Dispatch(Int_t event, Int_t x, Int_t y, TObject *select PyObject *TPyDispatcher::Dispatch(TVirtualPad *pad, TObject *obj, Int_t event) { PyObject *args = PyTuple_New(3); - PyTuple_SET_ITEM(args, 0, CPyCppyy::BindCppObject(pad, Cppyy::GetScope("TVirtualPad"))); - PyTuple_SET_ITEM(args, 1, CPyCppyy::BindCppObject(obj, Cppyy::GetScope("TObject"))); - PyTuple_SET_ITEM(args, 2, PyInt_FromLong(event)); + PyTuple_SET_ITEM(args, 0, CPyCppyy::Instance_FromVoidPtr(pad, "TVirtualPad")); + PyTuple_SET_ITEM(args, 1, CPyCppyy::Instance_FromVoidPtr(obj, "TObject")); + PyTuple_SET_ITEM(args, 2, PyLong_FromLong(event)); PyObject *result = PyObject_CallObject(fCallable, args); Py_XDECREF(args); @@ -232,8 +232,8 @@ PyObject *TPyDispatcher::Dispatch(TVirtualPad *pad, TObject *obj, Int_t event) PyObject *TPyDispatcher::Dispatch(TGListTreeItem *item, TDNDData *data) { PyObject *args = PyTuple_New(2); - PyTuple_SET_ITEM(args, 0, CPyCppyy::BindCppObject(item, Cppyy::GetScope("TGListTreeItem"))); - PyTuple_SET_ITEM(args, 1, CPyCppyy::BindCppObject(data, Cppyy::GetScope("TDNDData"))); + PyTuple_SET_ITEM(args, 0, CPyCppyy::Instance_FromVoidPtr(item, "TGListTreeItem")); + PyTuple_SET_ITEM(args, 1, CPyCppyy::Instance_FromVoidPtr(data, "TDNDData")); PyObject *result = PyObject_CallObject(fCallable, args); Py_XDECREF(args); @@ -252,7 +252,7 @@ PyObject *TPyDispatcher::Dispatch(const char *name, const TList *attr) { PyObject *args = PyTuple_New(2); PyTuple_SET_ITEM(args, 0, PyBytes_FromString(name)); - PyTuple_SET_ITEM(args, 1, CPyCppyy::BindCppObject((void *)attr, Cppyy::GetScope("TList"))); + PyTuple_SET_ITEM(args, 1, CPyCppyy::Instance_FromVoidPtr((void *)attr, "TList")); PyObject *result = PyObject_CallObject(fCallable, args); Py_XDECREF(args); @@ -270,8 +270,8 @@ PyObject *TPyDispatcher::Dispatch(const char *name, const TList *attr) PyObject *TPyDispatcher::Dispatch(TSlave *slave, TProofProgressInfo *pi) { PyObject *args = PyTuple_New(2); - PyTuple_SET_ITEM(args, 0, CPyCppyy::BindCppObject(slave, Cppyy::GetScope("TSlave"))); - PyTuple_SET_ITEM(args, 1, CPyCppyy::BindCppObject(pi, Cppyy::GetScope("TProofProgressInfo"))); + PyTuple_SET_ITEM(args, 0, CPyCppyy::Instance_FromVoidPtr(slave, "TSlave")); + PyTuple_SET_ITEM(args, 1, CPyCppyy::Instance_FromVoidPtr(pi, "TProofProgressInfo")); PyObject *result = PyObject_CallObject(fCallable, args); Py_XDECREF(args); diff --git a/bindings/pyroot/pythonizations/src/TTreePyz.cxx b/bindings/pyroot/pythonizations/src/TTreePyz.cxx index cc6c94afecbfc..86b8888bc8ec1 100644 --- a/bindings/pyroot/pythonizations/src/TTreePyz.cxx +++ b/bindings/pyroot/pythonizations/src/TTreePyz.cxx @@ -10,12 +10,20 @@ *************************************************************************/ // Bindings -#include "CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPyCppyy.h" +#include "../../cppyy/CPyCppyy/src/CPPInstance.h" +#include "../../cppyy/CPyCppyy/src/ProxyWrappers.h" +#include "../../cppyy/CPyCppyy/src/Utility.h" + +// Th API changed a bit with the new CPyCppyy, which we can detect by checking +// if CPYCPPYY_VERSION_HEX is defined. +#ifdef CPYCPPYY_VERSION_HEX +#include "../../cppyy/CPyCppyy/src/Dimensions.h" +#endif + +#include "CPyCppyy/API.h" + #include "PyROOTPythonize.h" -#include "CPPInstance.h" -#include "ProxyWrappers.h" -#include "Converters.h" -#include "Utility.h" #include "PyzCppHelpers.hxx" // ROOT @@ -65,7 +73,7 @@ static PyObject *BindBranchToProxy(TTree *tree, const char *name, TBranch *branc TBranchElement *be = (TBranchElement *)branch; if (be->GetCurrentClass() && (be->GetCurrentClass() != be->GetTargetClass()) && (0 <= be->GetID())) { Long_t offset = ((TStreamerElement *)be->GetInfo()->GetElements()->At(be->GetID()))->GetOffset(); - return BindCppObjectNoCast(be->GetObject() + offset, Cppyy::GetScope(be->GetCurrentClass()->GetName())); + return CPyCppyy::Instance_FromVoidPtr(be->GetObject() + offset, be->GetCurrentClass()->GetName()); } } @@ -73,12 +81,12 @@ static PyObject *BindBranchToProxy(TTree *tree, const char *name, TBranch *branc if (branch->IsA() == TBranchElement::Class() || branch->IsA() == TBranchObject::Class()) { TClass *klass = TClass::GetClass(branch->GetClassName()); if (klass && branch->GetAddress()) - return BindCppObjectNoCast(*(void **)branch->GetAddress(), Cppyy::GetScope(branch->GetClassName())); + return CPyCppyy::Instance_FromVoidPtr(*(void **)branch->GetAddress(), branch->GetClassName()); // try leaf, otherwise indicate failure by returning a typed null-object TObjArray *leaves = branch->GetListOfLeaves(); if (klass && !tree->GetLeaf(name) && !(leaves->GetSize() && (leaves->First() == leaves->Last()))) - return BindCppObjectNoCast(nullptr, Cppyy::GetScope(branch->GetClassName())); + return CPyCppyy::Instance_FromVoidPtr(nullptr, branch->GetClassName()); } return nullptr; @@ -88,8 +96,13 @@ static PyObject *WrapLeaf(TLeaf *leaf) { if (1 < leaf->GetLenStatic() || leaf->GetLeafCount()) { // array types - dim_t dims[] = { 1, leaf->GetNdata() }; // first entry is the number of dims std::string typeName = leaf->GetTypeName(); +#ifdef CPYCPPYY_VERSION_HEX + dim_t dimsArr[]{ leaf->GetNdata() }; + CPyCppyy::Dimensions dims{1, dimsArr}; +#else + dim_t dims[]{ 1, leaf->GetNdata() }; // first entry is the number of dims +#endif Converter *pcnv = CreateConverter(typeName + '*', dims); void *address = 0; @@ -119,14 +132,14 @@ static PyObject *WrapLeaf(TLeaf *leaf) } // Allow access to branches/leaves as if they were data members -PyObject *GetAttr(CPPInstance *self, PyObject *pyname) +PyObject *GetAttr(PyObject *self, PyObject *pyname) { - const char *name_possibly_alias = CPyCppyy_PyText_AsString(pyname); + const char *name_possibly_alias = PyUnicode_AsUTF8(pyname); if (!name_possibly_alias) return 0; // get hold of actual tree - auto tree = (TTree *)GetTClass(self)->DynamicCast(TTree::Class(), self->GetObject()); + auto tree = (TTree *)GetTClass(self)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(self)); if (!tree) { PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer"); @@ -201,10 +214,9 @@ PyObject *PyROOT::SetBranchAddressPyz(PyObject * /* self */, PyObject *args) // Look for the (const char*, void*) overload auto argParseStr = "OUO:SetBranchAddress"; - if (argc == 3 && PyArg_ParseTuple(args, const_cast(argParseStr), &treeObj, &name, &address)) { + if (argc == 3 && PyArg_ParseTuple(args, argParseStr, &treeObj, &name, &address)) { - auto treeProxy = (CPPInstance *)treeObj; - auto tree = (TTree *)GetTClass(treeProxy)->DynamicCast(TTree::Class(), treeProxy->GetObject()); + auto tree = (TTree *)GetTClass(treeObj)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(treeObj)); if (!tree) { PyErr_SetString(PyExc_TypeError, @@ -212,7 +224,7 @@ PyObject *PyROOT::SetBranchAddressPyz(PyObject * /* self */, PyObject *args) return nullptr; } - auto branchName = CPyCppyy_PyText_AsString(name); + auto branchName = PyUnicode_AsUTF8(name); auto branch = tree->GetBranch(branchName); if (!branch) { PyErr_SetString(PyExc_TypeError, "TTree::SetBranchAddress must be called with a valid branch name"); @@ -222,18 +234,18 @@ PyObject *PyROOT::SetBranchAddressPyz(PyObject * /* self */, PyObject *args) bool isLeafList = branch->IsA() == TBranch::Class(); void *buf = 0; - if (CPPInstance_Check(address)) { + if (CPyCppyy::Instance_Check(address)) { ((CPPInstance *)address)->GetDatamemberCache(); // force creation of cache if (((CPPInstance *)address)->fFlags & CPPInstance::kIsReference || isLeafList) - buf = (void *)((CPPInstance *)address)->GetObject(); + buf = CPyCppyy::Instance_AsVoidPtr(address); else buf = (void *)&(((CPPInstance *)address)->GetObjectRaw()); } else Utility::GetBuffer(address, '*', 1, buf, false); if (buf != nullptr) { - auto res = tree->SetBranchAddress(CPyCppyy_PyText_AsString(name), buf); + auto res = tree->SetBranchAddress(PyUnicode_AsUTF8(name), buf); return PyInt_FromLong(res); } } @@ -252,15 +264,14 @@ PyObject *TryBranchLeafListOverload(int argc, PyObject *args) PyObject *treeObj = nullptr; PyObject *name = nullptr, *address = nullptr, *leaflist = nullptr, *bufsize = nullptr; - if (PyArg_ParseTuple(args, const_cast("OO!OO!|O!:Branch"), + if (PyArg_ParseTuple(args, "OO!OO!|O!:Branch", &treeObj, - &CPyCppyy_PyText_Type, &name, + &PyUnicode_Type, &name, &address, - &CPyCppyy_PyText_Type, &leaflist, + &PyUnicode_Type, &leaflist, &PyInt_Type, &bufsize)) { - auto treeProxy = (CPPInstance *)treeObj; - auto tree = (TTree *)GetTClass(treeProxy)->DynamicCast(TTree::Class(), treeProxy->GetObject()); + auto tree = (TTree *)GetTClass(treeObj)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(treeObj)); if (!tree) { PyErr_SetString(PyExc_TypeError, "TTree::Branch must be called with a TTree instance as first argument"); return nullptr; @@ -268,17 +279,17 @@ PyObject *TryBranchLeafListOverload(int argc, PyObject *args) void *buf = nullptr; if (CPPInstance_Check(address)) - buf = (void *)((CPPInstance *)address)->GetObject(); + buf = CPyCppyy::Instance_AsVoidPtr(address); else Utility::GetBuffer(address, '*', 1, buf, false); if (buf) { TBranch *branch = nullptr; if (argc == 5) { - branch = tree->Branch(CPyCppyy_PyText_AsString(name), buf, CPyCppyy_PyText_AsString(leaflist), + branch = tree->Branch(PyUnicode_AsUTF8(name), buf, PyUnicode_AsUTF8(leaflist), PyInt_AS_LONG(bufsize)); } else { - branch = tree->Branch(CPyCppyy_PyText_AsString(name), buf, CPyCppyy_PyText_AsString(leaflist)); + branch = tree->Branch(PyUnicode_AsUTF8(name), buf, PyUnicode_AsUTF8(leaflist)); } return BindCppObject(branch, Cppyy::GetScope("TBranch")); @@ -302,19 +313,19 @@ PyObject *TryBranchPtrToPtrOverloads(int argc, PyObject *args) PyObject *name = nullptr, *clName = nullptr, *address = nullptr, *bufsize = nullptr, *splitlevel = nullptr; auto bIsMatch = false; - if (PyArg_ParseTuple(args, const_cast("OO!O!O|O!O!:Branch"), + if (PyArg_ParseTuple(args, "OO!O!O|O!O!:Branch", &treeObj, - &CPyCppyy_PyText_Type, &name, - &CPyCppyy_PyText_Type, &clName, + &PyUnicode_Type, &name, + &PyUnicode_Type, &clName, &address, &PyInt_Type, &bufsize, &PyInt_Type, &splitlevel)) { bIsMatch = true; } else { PyErr_Clear(); - if (PyArg_ParseTuple(args, const_cast("OO!O|O!O!"), + if (PyArg_ParseTuple(args, "OO!O|O!O!", &treeObj, - &CPyCppyy_PyText_Type, &name, + &PyUnicode_Type, &name, &address, &PyInt_Type, &bufsize, &PyInt_Type, &splitlevel)) { @@ -325,14 +336,13 @@ PyObject *TryBranchPtrToPtrOverloads(int argc, PyObject *args) } if (bIsMatch) { - auto treeProxy = (CPPInstance *)treeObj; - auto tree = (TTree *)GetTClass(treeProxy)->DynamicCast(TTree::Class(), treeProxy->GetObject()); + auto tree = (TTree *)GetTClass(treeObj)->DynamicCast(TTree::Class(), CPyCppyy::Instance_AsVoidPtr(treeObj)); if (!tree) { PyErr_SetString(PyExc_TypeError, "TTree::Branch must be called with a TTree instance as first argument"); return nullptr; } - std::string klName = clName ? CPyCppyy_PyText_AsString(clName) : ""; + std::string klName = clName ? PyUnicode_AsUTF8(clName) : ""; void *buf = nullptr; if (CPPInstance_Check(address)) { @@ -342,7 +352,7 @@ PyObject *TryBranchPtrToPtrOverloads(int argc, PyObject *args) buf = (void *)&((CPPInstance *)address)->fObject; if (!clName) { - klName = GetTClass((CPPInstance *)address)->GetName(); + klName = GetTClass(address)->GetName(); argc += 1; } } else { @@ -352,11 +362,11 @@ PyObject *TryBranchPtrToPtrOverloads(int argc, PyObject *args) if (buf && !klName.empty()) { TBranch *branch = nullptr; if (argc == 4) { - branch = tree->Branch(CPyCppyy_PyText_AsString(name), klName.c_str(), buf); + branch = tree->Branch(PyUnicode_AsUTF8(name), klName.c_str(), buf); } else if (argc == 5) { - branch = tree->Branch(CPyCppyy_PyText_AsString(name), klName.c_str(), buf, PyInt_AS_LONG(bufsize)); + branch = tree->Branch(PyUnicode_AsUTF8(name), klName.c_str(), buf, PyInt_AS_LONG(bufsize)); } else if (argc == 6) { - branch = tree->Branch(CPyCppyy_PyText_AsString(name), klName.c_str(), buf, PyInt_AS_LONG(bufsize), + branch = tree->Branch(PyUnicode_AsUTF8(name), klName.c_str(), buf, PyInt_AS_LONG(bufsize), PyInt_AS_LONG(splitlevel)); } diff --git a/bindings/pyroot/pythonizations/test/CMakeLists.txt b/bindings/pyroot/pythonizations/test/CMakeLists.txt index 4c5c0c4c78069..603e219f62224 100644 --- a/bindings/pyroot/pythonizations/test/CMakeLists.txt +++ b/bindings/pyroot/pythonizations/test/CMakeLists.txt @@ -26,8 +26,11 @@ ROOT_ADD_PYUNITTEST(pyroot_pyz_decorator pythonization_decorator.py) ROOT_ADD_PYUNITTEST(pyroot_pyz_pretty_printing pretty_printing.py) ROOT_ADD_PYUNITTEST(pyroot_pyz_array_interface array_interface.py PYTHON_DEPS numpy) -# STL vector pythonizations +# STL containers pythonizations ROOT_ADD_PYUNITTEST(pyroot_pyz_stl_vector stl_vector.py) +if(NOT MSVC OR win_broken_tests) + ROOT_ADD_PYUNITTEST(pyroot_pyz_stl_set stl_set.py) +endif() # TObject and subclasses pythonisations ROOT_ADD_PYUNITTEST(pyroot_pyz_tobject_contains tobject_contains.py) diff --git a/bindings/pyroot/pythonizations/test/stl_set.py b/bindings/pyroot/pythonizations/test/stl_set.py new file mode 100644 index 0000000000000..fbbf10a3bb443 --- /dev/null +++ b/bindings/pyroot/pythonizations/test/stl_set.py @@ -0,0 +1,80 @@ +import unittest +import ROOT + + +class STL_set(unittest.TestCase): + """ + Tests for the pythonizations of std::set. + """ + + def test_set_char_data(self): + """ + Test that a std::set of char behaves as a Python set. + """ + + elems = ['a', 'b', 'b', 'c'] + s = ROOT.std.set['char'](elems) + self.assertEqual(set(s), set(elems)) + self.assertTrue(s) + + for entry in s: + self.assertTrue(entry in set(elems)) + + def test_set_types(self): + """ + Instantiate std::set with different types. + """ + + for entry_type in ['int', 'float', 'double', 'char', 'const char*', 'std::string']: + ROOT.std.set[entry_type]() + + def test_stl_set_boolean(self): + """ + Test that the boolean conversion of a std::set works as expected. + https://github.com/root-project/root/issues/14573 + """ + + for entry_type in ['int', 'float', 'double']: + s = ROOT.std.set[entry_type]() + self.assertTrue(s.empty()) + self.assertFalse(bool(s)) + + s.insert(1) + self.assertFalse(s.empty()) + self.assertTrue(bool(s)) + + def test_stl_set_tree(self): + """ + Test that a TTree with a std::set branch behaves as expected. + """ + + tree = ROOT.TTree("tree", "Tree with std::vector") + + entries_to_fill = [ + set(), + {1}, + {1, 2}, + ] + + # Create variables to store std::vector elements + entry_root = ROOT.std.set(int)() + + # Create branches in the TTree + tree.Branch("set", entry_root) + + for entry in entries_to_fill: + entry_root.clear() + for element in entry: + entry_root.insert(element) + tree.Fill() + + for i in range(tree.GetEntries()): + tree.GetEntry(i) + entry_python = entries_to_fill[i] + self.assertEqual(entry_python, set(entry_root)) + self.assertEqual(bool(entry_python), bool(entry_root)) + self.assertEqual(len(entry_python), len(entry_root)) + + +if __name__ == '__main__': + unittest.main() diff --git a/bindings/pyroot/pythonizations/test/stl_vector.py b/bindings/pyroot/pythonizations/test/stl_vector.py index eeb01985c41c5..2cca40d22fa26 100644 --- a/bindings/pyroot/pythonizations/test/stl_vector.py +++ b/bindings/pyroot/pythonizations/test/stl_vector.py @@ -1,5 +1,7 @@ import unittest import ROOT +import random +import numpy as np class STL_vector(unittest.TestCase): @@ -8,13 +10,13 @@ class STL_vector(unittest.TestCase): """ def test_vec_char_data(self): - ''' + """ Test that calling std::vector::data() returns a Python string that contains the characters of the vector and no exception is raised. Check also that the iteration over the vector runs normally (#9632). - ''' + """ - elems = ['a','b','c'] + elems = ['a', 'b', 'c'] v = ROOT.std.vector['char'](elems) self.assertEqual(v.data(), ''.join(elems)) @@ -22,12 +24,65 @@ def test_vec_char_data(self): self.assertEqual(elem, elems.pop(0)) def test_vec_const_char_p(self): - ''' + """ Test that creating a std::vector does not raise any exception (#11581). - ''' + """ ROOT.std.vector['const char*']() + def test_stl_vector_boolean(self): + """ + Test that the boolean conversion of a std::vector works as expected. + https://github.com/root-project/root/issues/14573 + """ + for entry_type in ['int', 'float', 'double']: + vector = ROOT.std.vector[entry_type]() + self.assertTrue(vector.empty()) + self.assertFalse(bool(vector)) + + vector.push_back(1) + self.assertFalse(vector.empty()) + self.assertTrue(bool(vector)) + + def test_tree_with_containers(self): + """ + Test that the boolean conversion of a std::vector works as expected inside a TTree. + Also checks that the contents are correctly filled and read back. + https://github.com/root-project/root/issues/14573 + """ + + # Create a TTree + tree = ROOT.TTree("tree", "Tree with std::vector") + + # list of random arrays with lengths between 0 and 5 (0 is always included) + entries_to_fill = [ + np.array([random.uniform(10, 20) for _ in range(n % 5)]) for n in range(100) + ] + + # Create variables to store std::vector elements + entry_root = ROOT.std.vector(float)() + + # Create branches in the TTree + tree.Branch("vector", entry_root) + + # Fill the TTree with 100 entries + for entry in entries_to_fill: + entry_root.clear() + + for element in entry: + entry_root.push_back(element) + + tree.Fill() + + for i in range(tree.GetEntries()): + tree.GetEntry(i) + entry_numpy = entries_to_fill[i] + entry_python_list = list(entry_root) + + self.assertEqual(len(entry_numpy), len(entry_root)) + self.assertEqual(bool(entry_python_list), bool(entry_root)) # numpy arrays cannot be converted to bool + np.testing.assert_allclose(entry_numpy, np.array(entry_root), rtol=1e-5) + if __name__ == '__main__': unittest.main() diff --git a/bindings/pyroot/pythonizations/test/tdirectory_attrsyntax.py b/bindings/pyroot/pythonizations/test/tdirectory_attrsyntax.py index 34adfe28d78de..80e5bbe450a5e 100644 --- a/bindings/pyroot/pythonizations/test/tdirectory_attrsyntax.py +++ b/bindings/pyroot/pythonizations/test/tdirectory_attrsyntax.py @@ -45,13 +45,9 @@ def test_readHisto_attrsyntax(self): self.checkHisto(self.dir0.dir1.dir2.h2) def test_caching_getattr(self): - # check that __dict__ of self.dir_caching is initially empty - self.assertFalse(self.dir0.__dict__) + # check that object is not cached initially + self.assertFalse("h" in self.dir0.__dict__) self.dir0.h - # check that after call __dict__ is not empty anymore - self.assertTrue(self.dir0.__dict__) - # check that __dict__ has only one entry - self.assertEqual(len(self.dir0.__dict__), 1) # check that the value in __dict__ is actually the object # inside the directory self.assertEqual(self.dir0.__dict__['h'], self.dir0.h) diff --git a/bindings/pyroot/pythonizations/test/tdirectoryfile_attrsyntax_get.py b/bindings/pyroot/pythonizations/test/tdirectoryfile_attrsyntax_get.py index 3c2d9b8681592..3a881c86d35f8 100644 --- a/bindings/pyroot/pythonizations/test/tdirectoryfile_attrsyntax_get.py +++ b/bindings/pyroot/pythonizations/test/tdirectoryfile_attrsyntax_get.py @@ -50,13 +50,9 @@ def test_readHisto(self): self.checkHisto(self.dir0.Get("dir1/dir2/h2")) def test_caching_getattr(self): - # check that __dict__ of self.dir_caching is initially empty - self.assertFalse(self.dir0.__dict__) + # check that object is not cached initially + self.assertFalse("h" in self.dir0.__dict__) self.dir0.h - # check that after call is not empty anymore - self.assertTrue(self.dir0.__dict__) - # check that __dict__ has only one entry - self.assertEqual(len(self.dir0.__dict__), 1) # check that the value in __dict__ is actually the object # inside the directory self.assertEqual(self.dir0.__dict__['h'], self.dir0.h) diff --git a/bindings/pyroot/pythonizations/test/tfile_attrsyntax_get_writeobject_open.py b/bindings/pyroot/pythonizations/test/tfile_attrsyntax_get_writeobject_open.py index 24e7f74734a19..1d7485ebca77e 100644 --- a/bindings/pyroot/pythonizations/test/tfile_attrsyntax_get_writeobject_open.py +++ b/bindings/pyroot/pythonizations/test/tfile_attrsyntax_get_writeobject_open.py @@ -57,13 +57,9 @@ def test_readHisto(self): def test_caching_getattr(self): f = ROOT.TFile.Open(self.filename) - # check that __dict__ of self.dir_caching is initially empty - self.assertFalse(f.__dict__) + # check that object is not cached initially + self.assertFalse("h" in f.__dict__) f.h - # check that after call is not empty anymore - self.assertTrue(f.__dict__) - # check that __dict__ has only one entry - self.assertEqual(len(f.__dict__), 1) # check that the value in __dict__ is actually the object # inside the directory self.assertEqual(f.__dict__['h'], f.h) diff --git a/builtins/nlohmann/CMakeLists.txt b/builtins/nlohmann/CMakeLists.txt index 45cefe5d4fef5..b085def220a32 100644 --- a/builtins/nlohmann/CMakeLists.txt +++ b/builtins/nlohmann/CMakeLists.txt @@ -17,16 +17,10 @@ add_custom_command( COMMENT "Copying nlohmann/json.hpp header to ${CMAKE_BINARY_DIR}/include" DEPENDS ${CMAKE_SOURCE_DIR}/builtins/nlohmann/json.hpp) -add_custom_command( - OUTPUT ${CMAKE_BINARY_DIR}/include/nlohmann/json_fwd.hpp - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/builtins/nlohmann/json_fwd.hpp ${CMAKE_BINARY_DIR}/include/nlohmann/json_fwd.hpp - COMMENT "Copying nlohmann/json_fwd.hpp header to ${CMAKE_BINARY_DIR}/include" - DEPENDS ${CMAKE_SOURCE_DIR}/builtins/nlohmann/json_fwd.hpp) - -add_custom_target(builtin_nlohmann_json_incl DEPENDS ${CMAKE_BINARY_DIR}/include/nlohmann/json.hpp ${CMAKE_BINARY_DIR}/include/nlohmann/json_fwd.hpp) +add_custom_target(builtin_nlohmann_json_incl DEPENDS ${CMAKE_BINARY_DIR}/include/nlohmann/json.hpp) set_property(GLOBAL APPEND PROPERTY ROOT_HEADER_TARGETS builtin_nlohmann_json_incl) -install(FILES ${CMAKE_SOURCE_DIR}/builtins/nlohmann/json.hpp ${CMAKE_SOURCE_DIR}/builtins/nlohmann/json_fwd.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nlohmann/) +install(FILES ${CMAKE_SOURCE_DIR}/builtins/nlohmann/json.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nlohmann/) diff --git a/builtins/nlohmann/json_fwd.hpp b/builtins/nlohmann/json_fwd.hpp deleted file mode 100644 index 2d5ba384bec79..0000000000000 --- a/builtins/nlohmann/json_fwd.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ -#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ - -#include // int64_t, uint64_t -#include // map -#include // allocator -#include // string -#include // vector - -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ -/*! -@brief default JSONSerializer template argument - -This serializer ignores the template arguments and uses ADL -([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) -for serialization. -*/ -template -struct adl_serializer; - -/// a class to store JSON values -/// @sa https://json.nlohmann.me/api/basic_json/ -template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer, - class BinaryType = std::vector> -class basic_json; - -/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document -/// @sa https://json.nlohmann.me/api/json_pointer/ -template -class json_pointer; - -/*! -@brief default specialization -@sa https://json.nlohmann.me/api/json/ -*/ -using json = basic_json<>; - -/// @brief a minimal map-like container that preserves insertion order -/// @sa https://json.nlohmann.me/api/ordered_map/ -template -struct ordered_map; - -/// @brief specialization that maintains the insertion order of object keys -/// @sa https://json.nlohmann.me/api/ordered_json/ -using ordered_json = basic_json; - -} // namespace nlohmann - -#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ diff --git a/cmake/modules/FindGFAL.cmake b/cmake/modules/FindGFAL.cmake deleted file mode 100644 index 74028ac5a7f35..0000000000000 --- a/cmake/modules/FindGFAL.cmake +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. -# All rights reserved. -# -# For the licensing terms see $ROOTSYS/LICENSE. -# For the list of contributors see $ROOTSYS/README/CREDITS. - -# - Locate GFAL library -# Defines: -# -# GFAL_FOUND -# GFAL_INCLUDE_DIR -# GFAL_INCLUDE_DIRS (not cached) -# GFAL_LIBRARIES (not cached) - -find_path(GFAL_INCLUDE_DIR NAMES gfal_api.h - PATH_SUFFIXES . gfal gfal2 - HINTS ${GFAL_DIR}/include $ENV{GFAL_DIR}/include) -find_library(GFAL_LIBRARY NAMES gfal gfal2 - HINTS ${GFAL_DIR}/lib $ENV{GFAL_DIR}/lib) - -set(GFAL_LIBRARIES ${GFAL_LIBRARY}) -set(GFAL_INCLUDE_DIRS ${GFAL_INCLUDE_DIR}) - -if(GFAL_LIBRARY MATCHES gfal2) - # use pkg-config to get the directories for glib and then use these values - find_package(PkgConfig) - pkg_check_modules(GLIB2 REQUIRED glib-2.0) - list(APPEND GFAL_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS}) - set(GFAL_DEP GLIB2_INCLUDE_DIRS) -else() - find_path(SRM_IFCE_INCLUDE_DIR gfal_srm_ifce_types.h - HINTS ${SRM_IFCE_DIR}/include $ENV{SRM_IFCE_DIR}/include) - list(APPEND GFAL_INCLUDE_DIRS ${SRM_IFCE_INCLUDE_DIR}) - set(GFAL_DEP SRM_IFCE_INCLUDE_DIR) -endif() - -# handle the QUIETLY and REQUIRED arguments and set GFAL_FOUND to TRUE if -# all listed variables are TRUE -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GFAL DEFAULT_MSG GFAL_INCLUDE_DIR ${GFAL_DEP} GFAL_LIBRARY) - -mark_as_advanced(GFAL_FOUND GFAL_INCLUDE_DIR GFAL_LIBRARY SRM_IFCE_INCLUDE_DIR GLIB2_INCLUDE_DIRS) diff --git a/cmake/modules/FindOracle.cmake b/cmake/modules/FindOracle.cmake deleted file mode 100644 index 37106f7612937..0000000000000 --- a/cmake/modules/FindOracle.cmake +++ /dev/null @@ -1,90 +0,0 @@ -############################################################################### -# -# CMake module to search for Oracle client library (OCI) -# -# On success, the macro sets the following variables: -# ORACLE_FOUND = if the library found -# ORACLE_LIBRARY = full path to the library -# ORACLE_LIBRARIES = full path to the library -# ORACLE_INCLUDE_DIR = where to find the library headers also defined, -# but not for general use are -# ORACLE_VERSION = version of library which was found, e.g. "1.2.5" -# -# Copyright (c) 2009-2013 Mateusz Loskot -# -# Developed with inspiration from Petr Vanek -# who wrote similar macro for TOra - http://torasql.com/ -# -# Module source: http://github.com/mloskot/workshop/tree/master/cmake/ -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# -############################################################################### - -# First check for CMAKE variable -if(NOT ORACLE_HOME) - # If ORACLE_HOME is not defined check for env var and if exists set from env var - if(EXISTS $ENV{ORACLE_HOME}) - set(ORACLE_HOME $ENV{ORACLE_HOME}) - endif() -endif() - -message(STATUS "ORACLE_HOME=${ORACLE_HOME}") - -find_path(ORACLE_INCLUDE_DIR - NAMES oci.h - PATHS - ${ORACLE_HOME}/rdbms/public - ${ORACLE_HOME}/include - ${ORACLE_HOME}/sdk/include # Oracle SDK - ${ORACLE_HOME}/OCI/include # Oracle XE on Windows - # instant client from rpm - /usr/include/oracle/*/client${LIB_SUFFIX}) - -set(ORACLE_VERSIONS 21 20 19 18 12 11 10) -set(ORACLE_OCI_NAMES clntsh libclntsh oci) # Dirty trick might help on OSX, see issues/89 -set(ORACLE_OCCI_NAMES libocci occi) -set(ORACLE_NNZ_NAMES ociw32) -foreach(loop_var IN LISTS ORACLE_VERSIONS) - set(ORACLE_OCCI_NAMES ${ORACLE_OCCI_NAMES} oraocci${loop_var}) - set(ORACLE_NNZ_NAMES ${ORACLE_NNZ_NAMES} nnz${loop_var} libnnz${loop_var}) -endforeach(loop_var) - -set(ORACLE_LIB_DIR - ${ORACLE_HOME} - ${ORACLE_HOME}/lib - ${ORACLE_HOME}/sdk/lib # Oracle SDK - ${ORACLE_HOME}/sdk/lib/msvc - ${ORACLE_HOME}/OCI/lib/msvc # Oracle XE on Windows - # Instant client from rpm - /usr/lib/oracle/*/client${LIB_SUFFIX}/lib) - -find_library(ORACLE_OCI_LIBRARY - NAMES ${ORACLE_OCI_NAMES} PATHS ${ORACLE_LIB_DIR}) -find_library(ORACLE_OCCI_LIBRARY - NAMES ${ORACLE_OCCI_NAMES} PATHS ${ORACLE_LIB_DIR}) -find_library(ORACLE_NNZ_LIBRARY - NAMES ${ORACLE_NNZ_NAMES} PATHS ${ORACLE_LIB_DIR}) - -set(ORACLE_LIBRARY - ${ORACLE_OCI_LIBRARY} - ${ORACLE_OCCI_LIBRARY} - ${ORACLE_NNZ_LIBRARY}) - -if(NOT WIN32) - set(ORACLE_LIBRARY ${ORACLE_LIBRARY} ${ORACLE_CLNTSH_LIBRARY}) -endif(NOT WIN32) - -set(ORACLE_LIBRARIES ${ORACLE_LIBRARY}) - -# Handle the QUIETLY and REQUIRED arguments and set ORACLE_FOUND to TRUE -# if all listed variables are TRUE -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Oracle DEFAULT_MSG ORACLE_LIBRARY ORACLE_INCLUDE_DIR) - -if(NOT ORACLE_FOUND) - message(STATUS "None of the supported Oracle versions (${ORACLE_VERSIONS}) could be found, consider updating ORACLE_VERSIONS if the version you use is not among them.") -endif() - -mark_as_advanced(ORACLE_INCLUDE_DIR ORACLE_LIBRARY) diff --git a/cmake/modules/FindTBB.cmake b/cmake/modules/FindTBB.cmake index ac29c90c4775b..5bf78d4ff8be7 100644 --- a/cmake/modules/FindTBB.cmake +++ b/cmake/modules/FindTBB.cmake @@ -201,15 +201,17 @@ ENDIF (DEFINED _TBB_MISSING_COMPONENTS AND _TBB_CHECK_COMPONENTS) # Determine library's version -SET (_TBB_VERSION_HEADER ${TBB_INCLUDE_DIR}/tbb/tbb_stddef.h) +IF (EXISTS ${TBB_INCLUDE_DIR}/oneapi) + SET (_TBB_VERSION_HEADER ${TBB_INCLUDE_DIR}/oneapi/tbb/version.h) +ELSE (EXISTS ${TBB_INCLUDE_DIR}/oneapi) + SET (_TBB_VERSION_HEADER ${TBB_INCLUDE_DIR}/tbb/version.h) +ENDIF (EXISTS ${TBB_INCLUDE_DIR}/oneapi) IF (EXISTS ${_TBB_VERSION_HEADER}) FILE (READ ${_TBB_VERSION_HEADER} _TBB_VERSION_CONTENTS) - STRING (REGEX REPLACE ".*#define TBB_VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" - TBB_VERSION_MAJOR "${_TBB_VERSION_CONTENTS}") - STRING (REGEX REPLACE ".*#define TBB_VERSION_MINOR[ \t]+([0-9]+).*" "\\1" - TBB_VERSION_MINOR "${_TBB_VERSION_CONTENTS}") + STRING (REGEX REPLACE ".*#define TBB_VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" TBB_VERSION_MAJOR "${_TBB_VERSION_CONTENTS}") + STRING (REGEX REPLACE ".*#define TBB_VERSION_MINOR[ \t]+([0-9]+).*" "\\1" TBB_VERSION_MINOR "${_TBB_VERSION_CONTENTS}") SET (TBB_VERSION ${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}) SET (TBB_VERSION_COMPONENTS 2) diff --git a/cmake/modules/RootBuildOptions.cmake b/cmake/modules/RootBuildOptions.cmake index 15cef02576ac5..bdb39d13291d6 100644 --- a/cmake/modules/RootBuildOptions.cmake +++ b/cmake/modules/RootBuildOptions.cmake @@ -80,7 +80,7 @@ endfunction() ROOT_BUILD_OPTION(arrow OFF "Enable support for Apache Arrow") ROOT_BUILD_OPTION(asimage ON "Enable support for image processing via libAfterImage") ROOT_BUILD_OPTION(asserts OFF "Enable asserts (defaults to ON for CMAKE_BUILD_TYPE=Debug and/or dev=ON)") -ROOT_BUILD_OPTION(builtin_afterimage OFF "Build bundled copy of libAfterImage") +ROOT_BUILD_OPTION(builtin_afterimage ON "Build bundled copy of libAfterImage (flag is deprecated, this should be always enabled)") ROOT_BUILD_OPTION(builtin_cfitsio OFF "Build CFITSIO internally (requires network)") ROOT_BUILD_OPTION(builtin_clang ON "Build bundled copy of Clang") ROOT_BUILD_OPTION(builtin_cling ON "Build bundled copy of Cling. Only build with an external cling if you know what you are doing: associating ROOT commits with cling commits is tricky.") @@ -127,7 +127,6 @@ ROOT_BUILD_OPTION(test_distrdf_dask OFF "Enable distributed RDataFrame tests tha ROOT_BUILD_OPTION(davix ON "Enable support for Davix (HTTP/WebDAV access)") ROOT_BUILD_OPTION(dcache OFF "Enable support for dCache (requires libdcap from DESY)") ROOT_BUILD_OPTION(dev OFF "Enable recommended developer compilation flags, reduce exposed includes") -ROOT_BUILD_OPTION(exceptions IGNORE "Enable compiler exception handling (flag is deprecated, exceptions are always enabled)") ROOT_BUILD_OPTION(fftw3 OFF "Enable support for FFTW3 [GPL]") ROOT_BUILD_OPTION(fitsio ON "Enable support for reading FITS images") ROOT_BUILD_OPTION(fortran OFF "Build Fortran components of ROOT") @@ -148,9 +147,8 @@ ROOT_BUILD_OPTION(mpi OFF "Enable support for Message Passing Interface (MPI)") ROOT_BUILD_OPTION(mysql ON "Enable support for MySQL databases") ROOT_BUILD_OPTION(odbc OFF "Enable support for ODBC databases (requires libiodbc or libodbc)") ROOT_BUILD_OPTION(opengl ON "Enable support for OpenGL (requires libGL and libGLU)") -ROOT_BUILD_OPTION(oracle OFF "Enable support for Oracle databases (requires Oracle Instant Client) (deprecated)") ROOT_BUILD_OPTION(pgsql ON "Enable support for PostgreSQL") -ROOT_BUILD_OPTION(proof ON "Enable support for PROOF") +ROOT_BUILD_OPTION(proof OFF "Enable support for PROOF") ROOT_BUILD_OPTION(pyroot ON "Enable support for automatic Python bindings (PyROOT)") ROOT_BUILD_OPTION(pythia6_nolink OFF "Delayed linking of Pythia6 library (deprecated)") ROOT_BUILD_OPTION(pythia6 OFF "Enable support for Pythia 6.x [unlicensed] (deprecated)") @@ -218,7 +216,6 @@ endif() #--- The 'all' option switches ON major options--------------------------------------------------- if(all) - set(alien_defvalue ON) set(arrow_defvalue ON) set(asimage_defvalue ON) set(cefweb_defvalue ON) @@ -242,9 +239,8 @@ if(all) set(mysql_defvalue ON) set(odbc_defvalue ON) set(opengl_defvalue ON) - set(oracle_defvalue ON) set(pgsql_defvalue ON) - set(proof_defvalue ON) + set(proof_defvalue OFF) set(pythia6_defvalue ON) set(pythia8_defvalue ON) set(pyroot_defvalue ON) @@ -405,20 +401,28 @@ if(NOT webgui) endif() #---Removed options------------------------------------------------------------ -foreach(opt afdsmgrd afs alien bonjour castor chirp cxx11 cxx14 cxx17 geocad gfal glite globus gsl_shared hdfs ios - jemalloc krb5 ldap memstat monalisa pyroot_legacy qt qtgsi rfio ruby sapdb srp table tcmalloc python vmc xproofd pyroot-python2) +foreach(opt afdsmgrd afs alien bonjour castor chirp cxx11 cxx14 cxx17 + exceptions geocad gfal glite globus gsl_shared hdfs ios jemalloc krb5 + ldap memstat monalisa oracle pyroot-python2 pyroot_legacy python qt + qtgsi rfio ruby sapdb srp table tcmalloc vmc xproofd) if(${opt}) message(FATAL_ERROR ">>> Option '${opt}' is no longer supported in ROOT ${ROOT_VERSION}.") endif() endforeach() #---Deprecated options------------------------------------------------------------------------ -foreach(opt cxxmodules exceptions oracle pythia6 pythia6_nolink minuit2) +foreach(opt cxxmodules pythia6 pythia6_nolink) if(${opt}) message(DEPRECATION ">>> Option '${opt}' is deprecated and will be removed in the next release of ROOT. Please contact root-dev@cern.ch should you still need it.") endif() endforeach() +foreach(opt builtin_afterimage minuit2) + if(NOT ${opt}) + message(DEPRECATION ">>> Option '${opt}' is deprecated: in the future it will always be set to ON. In the next release of ROOT, you will no longer be able to disable this feature. Please contact root-dev@cern.ch should you still need disabling it.") + endif() +endforeach() + foreach(opt minuit2_omp minuit2_mpi) if(${opt}) message(WARNING "The option '${opt}' can only be used to minimise thread-safe functions in Minuit2. It cannot be used for Histogram/Graph fitting and for RooFit. If you want to use Minuit2 with OpenMP or MPI support, it is better to build Minuit2 as a standalone library.") diff --git a/cmake/modules/RootConfiguration.cmake b/cmake/modules/RootConfiguration.cmake index 198cf91fa16fc..51b53a5c15b8c 100644 --- a/cmake/modules/RootConfiguration.cmake +++ b/cmake/modules/RootConfiguration.cmake @@ -215,11 +215,6 @@ set(glewlibdir ${GLEW_LIBRARY_DIR}) set(glewlibs ${GLEW_LIBRARIES}) set(glewincdir ${GLEW_INCLUDE_DIR}) -set(buildgfal ${value${gfal}}) -set(gfallibdir ${GFAL_LIBRARY_DIR}) -set(gfallib ${GFAL_LIBRARY}) -set(gfalincdir ${GFAL_INCLUDE_DIR}) - set(buildarrow ${value${arrow}}) set(arrowlibdir ${ARROW_LIBRARY_DIR}) set(arrowlib ${ARROW_LIBRARY}) @@ -329,6 +324,17 @@ set(perl ${PERL_EXECUTABLE}) find_program(CHROME_EXECUTABLE NAMES chrome.exe chromium chromium-browser chrome chrome-browser google-chrome-stable Google\ Chrome PATH_SUFFIXES "Google/Chrome/Application") if(CHROME_EXECUTABLE) + if(WIN32) + set(chromemajor 100) + message(STATUS "Found CHROME executable ${CHROME_EXECUTABLE}, not testing the version") + else() + execute_process(COMMAND "${CHROME_EXECUTABLE}" --version + OUTPUT_VARIABLE CHROME_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX MATCH "[0-9]+" CHROME_MAJOR_VERSION "${CHROME_VERSION}") + set(chromemajor ${CHROME_MAJOR_VERSION}) + message(STATUS "Found CHROME executable ${CHROME_EXECUTABLE} major version ${CHROME_MAJOR_VERSION}") + endif() set(chromeexe ${CHROME_EXECUTABLE}) endif() diff --git a/cmake/modules/SearchInstalledSoftware.cmake b/cmake/modules/SearchInstalledSoftware.cmake index 6b6e40719521e..c881bec91b3be 100644 --- a/cmake/modules/SearchInstalledSoftware.cmake +++ b/cmake/modules/SearchInstalledSoftware.cmake @@ -81,29 +81,6 @@ if(NOT builtin_nlohmannjson) message(STATUS "nlohmann/json.hpp not found. Switching on builtin_nlohmannjson option") set(builtin_nlohmannjson ON CACHE BOOL "Enabled because nlohmann/json.hpp not found" FORCE) endif() - - # If we found an external nlohmann_json with a version greater than 3.11, - # check that it has the json_fwd.hpp header. - if(nlohmann_json_FOUND AND nlohmann_json_VERSION VERSION_GREATER_EQUAL 3.11) - set(_old_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) - set(_old_CMAKE_REQUIRED_QUIET ${CMAKE_REQUIRED_QUIET}) - - set(CMAKE_REQUIRED_LIBRARIES nlohmann_json::nlohmann_json) - set(CMAKE_REQUIRED_QUIET TRUE) - check_include_file_cxx("nlohmann/json_fwd.hpp" _nlohmann_json_fwd_hpp) - if(NOT _nlohmann_json_fwd_hpp) - set(_msg "Could not find nlohmann/json_fwd.hpp, which is required for versions greater than 3.11.") - if(nlohmann_json_VERSION VERSION_LESS 3.11.2) - set(_msg "${_msg} Please upgrade to at least version 3.11.2.") - else() - set(_msg "${_msg} It is installed by default, so your installation is incomplete!") - endif() - message(FATAL_ERROR "${_msg}") - endif() - - set(CMAKE_REQUIRED_LIBRARIES ${_old_CMAKE_REQUIRED_LIBRARIES}) - set(CMAKE_REQUIRED_QUIET ${_old_CMAKE_REQUIRED_QUIET}) - endif() endif() endif() @@ -749,20 +726,6 @@ if(fcgi) endif() -#---Check for Oracle------------------------------------------------------------------- -if(oracle) - message(STATUS "Looking for Oracle") - find_package(Oracle) - if(NOT ORACLE_FOUND) - if(fail-on-missing) - message(FATAL_ERROR "Oracle libraries not found and they are required (orable option enabled)") - else() - message(STATUS "Oracle not found. Switching off oracle option") - set(oracle OFF CACHE BOOL "Disabled because Oracle not found (${oracle_description})" FORCE) - endif() - endif() -endif() - #---Check for ODBC------------------------------------------------------------------- if(odbc) message(STATUS "Looking for ODBC") @@ -1058,22 +1021,6 @@ if(arrow) endif() -#---Check for gfal------------------------------------------------------------------- -if(gfal) - find_package(GFAL) - if(NOT GFAL_FOUND) - if(fail-on-missing) - message(FATAL_ERROR "Gfal library not found and is required (gfal option enabled)") - else() - message(STATUS "GFAL library not found. Set variable GFAL_DIR to point to your gfal installation - and the variable SRM_IFCE_DIR to the srm_ifce installation") - message(STATUS "For the time being switching OFF 'gfal' option") - set(gfal OFF CACHE BOOL "Disabled because GFAL not found (${gfal_description})" FORCE) - endif() - endif() -endif() - - #---Check for dCache------------------------------------------------------------------- if(dcache) find_package(DCAP) diff --git a/cmake/scripts/ROOTConfig.cmake.in b/cmake/scripts/ROOTConfig.cmake.in index ca615360ddd09..b63b64d201bae 100644 --- a/cmake/scripts/ROOTConfig.cmake.in +++ b/cmake/scripts/ROOTConfig.cmake.in @@ -130,9 +130,15 @@ if(ROOT_minuit2_omp_FOUND) endif() if(ROOT_vdt_FOUND AND NOT TARGET VDT::VDT) if(ROOT_builtin_vdt_FOUND) - set(Vdt_ROOT "${_thisdir}/.." CACHE PATH "Location of (the builtin) VDT") + function(find_builtin_vdt) + # the function is to create a scope (could use block() but requires CMake>=3.25) + set(CMAKE_PREFIX_PATH ${ROOT_INCLUDE_DIRS} ${ROOT_LIBRARY_DIR}) + find_dependency(Vdt) + endfunction() + find_builtin_vdt() + else() + find_dependency(Vdt) endif() - find_dependency(Vdt) endif() #---------------------------------------------------------------------------- diff --git a/config/Makefile.in b/config/Makefile.in index c1fb8d4af7e86..61ce58f9942be 100644 --- a/config/Makefile.in +++ b/config/Makefile.in @@ -196,10 +196,6 @@ ifeq ($(BUILDGLEW), yes) GLEWCPPFLAGS := -DBUILTIN_GLEW endif -BUILDGFAL := @buildgfal@ -GFALLIBDIR := @gfallibdir@ -GFALCLILIB := @gfallib@ -GFALINCDIR := $(filter-out /usr/include, @gfalincdir@) SRMIFCEINCDIR := $(filter-out /usr/include, @srmifceincdir@) GLIB2INCDIR := $(filter-out /usr/include, @glib2incdir@) diff --git a/config/rootrc.in b/config/rootrc.in index 7641c66f31899..9e13249b719ac 100644 --- a/config/rootrc.in +++ b/config/rootrc.in @@ -249,6 +249,8 @@ WebGui.HttpPortMax: 9800 WebGui.HttpBind: # Use only loopback address to bind http server (default - yes) WebGui.HttpLoopback: yes +# Require unique single-time token (key) for connecting with widget (default - yes) +WebGui.OnetimeKey: yes # Use https protocol for the http server (default - no) WebGui.UseHttps: no WebGui.ServerCert: rootserver.pem @@ -256,6 +258,7 @@ WebGui.ServerCert: rootserver.pem WebGui.WaitForTmout: 100.0 # name of executable for firefox and chrome WebGui.Chrome: @chromeexe@ +WebGui.ChromeVersion: @chromemajor@ WebGui.Firefox: @firefoxexe@ # location of OpenUi5 applications for ROOT like canvas, eve, fitpanel, ... #WebGui.RootUi5Path: @openui5dir@ @@ -486,7 +489,7 @@ TS3WebFile.Root.MultiRangeServer: Huawei OBS # In case the file namespace descriptor ends with - the namespace # is not a part of the filename. # Extend in private .rootrc with a +Url.Special line. -Url.Special: file: hpss: gfal: dcache: +Url.Special: file: hpss: dcache: +Url.Special: /alien/- # PROOF XRD client variables diff --git a/core/base/inc/ROOT/TExecutorCRTP.hxx b/core/base/inc/ROOT/TExecutorCRTP.hxx index fc2f0d14c0024..e26c879374b1a 100644 --- a/core/base/inc/ROOT/TExecutorCRTP.hxx +++ b/core/base/inc/ROOT/TExecutorCRTP.hxx @@ -102,49 +102,50 @@ namespace ROOT { template class TExecutorCRTP { +protected: template using InvokeResult_t = ROOT::TypeTraits::InvokeResult_t; + /// type definition used in templated functions for not allowing mapping functions that return references or void. + /// The resulting vector elements must be assignable, references aren't. + template + using validMapReturnCond = + std::enable_if_t>::value && !std::is_void>::value>; + public: TExecutorCRTP() = default; TExecutorCRTP(const TExecutorCRTP &) = delete; TExecutorCRTP &operator=(const TExecutorCRTP &) = delete; - /// type definition in used in templated functions for not allowing mapping functions that return references. - /// The resulting vector elements must be assignable, references aren't. - template - using noReferenceCond = - std::enable_if_t<"Function can't return a reference" && !std::is_reference>::value>; - // Map // These trailing return types allow for a compile time check of compatibility between function signatures and args - template > + template > auto Map(F func, unsigned nTimes) -> std::vector>; - template > + template > auto Map(F func, ROOT::TSeq args) -> std::vector>; - template > + template > auto Map(F func, std::initializer_list args) -> std::vector>; - template > + template > auto Map(F func, std::vector &args) -> std::vector>; - template > + template > auto Map(F func, const std::vector &args) -> std::vector>; // MapReduce // The trailing return types check at compile time that func is compatible with the type of the arguments. // A static_assert check in TExecutorCRTP::Reduce is used to check that redfunc is compatible with the type returned by func - template > + template > auto MapReduce(F func, unsigned nTimes, R redfunc) -> InvokeResult_t; - template > + template > auto MapReduce(F func, ROOT::TSeq args, R redfunc) -> InvokeResult_t; - template > + template > auto MapReduce(F func, std::initializer_list args, R redfunc) -> InvokeResult_t; - template > + template > auto MapReduce(F func, const std::vector &args, R redfunc) -> InvokeResult_t; - template > + template > auto MapReduce(F func, std::vector &args, R redfunc) -> InvokeResult_t; - template> + template> T* MapReduce(F func, std::vector &args); - template> + template> T* MapReduce(F func, const std::vector &args); template T* Reduce(const std::vector &mergeObjs); @@ -158,16 +159,16 @@ private: } /// Implementation of the Map method, left to the derived classes - template > + template > auto MapImpl(F func, unsigned nTimes) -> std::vector> = delete; /// Implementation of the Map method, left to the derived classes - template > + template > auto MapImpl(F func, ROOT::TSeq args) -> std::vector> = delete; /// Implementation of the Map method, left to the derived classes - template > + template > auto MapImpl(F func, std::vector &args) -> std::vector> = delete; /// Implementation of the Map method, left to the derived classes - template > + template > auto MapImpl(F func, const std::vector &args) -> std::vector> = delete; }; diff --git a/core/base/inc/ROOT/TSequentialExecutor.hxx b/core/base/inc/ROOT/TSequentialExecutor.hxx index 55bc7298f3d2c..d3033a2de5e50 100644 --- a/core/base/inc/ROOT/TSequentialExecutor.hxx +++ b/core/base/inc/ROOT/TSequentialExecutor.hxx @@ -14,7 +14,6 @@ #include "ROOT/EExecutionPolicy.hxx" #include "ROOT/TExecutorCRTP.hxx" #include "ROOT/TSeq.hxx" -#include "ROOT/TypeTraits.hxx" // InvokeResult_t #include #include //std::accumulate @@ -26,9 +25,6 @@ namespace ROOT { class TSequentialExecutor: public TExecutorCRTP { friend TExecutorCRTP; - template - using InvokeResult_t = ROOT::TypeTraits::InvokeResult_t; - public: TSequentialExecutor() = default; @@ -71,13 +67,13 @@ namespace ROOT { private: // Implementation of the Map functions declared in the parent class (TExecutorCRTP) // - template> + template> auto MapImpl(F func, unsigned nTimes) -> std::vector>; - template> + template> auto MapImpl(F func, ROOT::TSeq args) -> std::vector>; - template> + template> auto MapImpl(F func, std::vector &args) -> std::vector>; - template> + template> auto MapImpl(F func, const std::vector &args) -> std::vector>; }; diff --git a/core/base/inc/TBuffer.h b/core/base/inc/TBuffer.h index eba98ddc8adf6..3101676088d83 100644 --- a/core/base/inc/TBuffer.h +++ b/core/base/inc/TBuffer.h @@ -247,24 +247,24 @@ class TBuffer : public TObject { virtual void WriteArrayFloat16(const Float_t *f, Int_t n, TStreamerElement *ele = nullptr) = 0; virtual void WriteArrayDouble32(const Double_t *d, Int_t n, TStreamerElement *ele = nullptr) = 0; - virtual void WriteFastArray(const Bool_t *b, Int_t n) = 0; - virtual void WriteFastArray(const Char_t *c, Int_t n) = 0; - virtual void WriteFastArrayString(const Char_t *c, Int_t n) = 0; - virtual void WriteFastArray(const UChar_t *c, Int_t n) = 0; - virtual void WriteFastArray(const Short_t *h, Int_t n) = 0; - virtual void WriteFastArray(const UShort_t *h, Int_t n) = 0; - virtual void WriteFastArray(const Int_t *i, Int_t n) = 0; - virtual void WriteFastArray(const UInt_t *i, Int_t n) = 0; - virtual void WriteFastArray(const Long_t *l, Int_t n) = 0; - virtual void WriteFastArray(const ULong_t *l, Int_t n) = 0; - virtual void WriteFastArray(const Long64_t *l, Int_t n) = 0; - virtual void WriteFastArray(const ULong64_t *l, Int_t n) = 0; - virtual void WriteFastArray(const Float_t *f, Int_t n) = 0; - virtual void WriteFastArray(const Double_t *d, Int_t n) = 0; - virtual void WriteFastArrayFloat16(const Float_t *f, Int_t n, TStreamerElement *ele = nullptr) = 0; - virtual void WriteFastArrayDouble32(const Double_t *d, Int_t n, TStreamerElement *ele = nullptr) = 0; - virtual void WriteFastArray(void *start, const TClass *cl, Int_t n=1, TMemberStreamer *s = nullptr) = 0; - virtual Int_t WriteFastArray(void **startp, const TClass *cl, Int_t n=1, Bool_t isPreAlloc = kFALSE, TMemberStreamer *s = nullptr) = 0; + virtual void WriteFastArray(const Bool_t *b, Long64_t n) = 0; + virtual void WriteFastArray(const Char_t *c, Long64_t n) = 0; + virtual void WriteFastArrayString(const Char_t *c, Long64_t n) = 0; + virtual void WriteFastArray(const UChar_t *c, Long64_t n) = 0; + virtual void WriteFastArray(const Short_t *h, Long64_t n) = 0; + virtual void WriteFastArray(const UShort_t *h, Long64_t n) = 0; + virtual void WriteFastArray(const Int_t *i, Long64_t n) = 0; + virtual void WriteFastArray(const UInt_t *i, Long64_t n) = 0; + virtual void WriteFastArray(const Long_t *l, Long64_t n) = 0; + virtual void WriteFastArray(const ULong_t *l, Long64_t n) = 0; + virtual void WriteFastArray(const Long64_t *l, Long64_t n) = 0; + virtual void WriteFastArray(const ULong64_t *l, Long64_t n) = 0; + virtual void WriteFastArray(const Float_t *f, Long64_t n) = 0; + virtual void WriteFastArray(const Double_t *d, Long64_t n) = 0; + virtual void WriteFastArrayFloat16(const Float_t *f, Long64_t n, TStreamerElement *ele = nullptr) = 0; + virtual void WriteFastArrayDouble32(const Double_t *d, Long64_t n, TStreamerElement *ele = nullptr) = 0; + virtual void WriteFastArray(void *start, const TClass *cl, Long64_t n=1, TMemberStreamer *s = nullptr) = 0; + virtual Int_t WriteFastArray(void **startp, const TClass *cl, Long64_t n=1, Bool_t isPreAlloc = kFALSE, TMemberStreamer *s = nullptr) = 0; virtual void StreamObject(void *obj, const std::type_info &typeinfo, const TClass* onFileClass = nullptr) = 0; virtual void StreamObject(void *obj, const char *className, const TClass* onFileClass = nullptr) = 0; diff --git a/core/base/inc/TString.h b/core/base/inc/TString.h index 8181dc2563187..b0ba5b436ec5c 100644 --- a/core/base/inc/TString.h +++ b/core/base/inc/TString.h @@ -228,7 +228,7 @@ friend std::strong_ordering operator<=>(const TString &s1, const TString &s2) { // Special concatenation constructor TString(const char *a1, Ssiz_t n1, const char *a2, Ssiz_t n2); void AssertElement(Ssiz_t nc) const; // Index in range - void Clobber(Ssiz_t nc); // Remove old contents + Ssiz_t Clobber(Ssiz_t nc); // Remove old contents void InitChar(char c); // Initialize from char enum { kAlignment = 16 }; @@ -278,6 +278,7 @@ friend std::strong_ordering operator<=>(const TString &s1, const TString &s2) { enum EStripType { kLeading = 0x1, kTrailing = 0x2, kBoth = 0x3 }; enum ECaseCompare { kExact, kIgnoreCase }; static constexpr Ssiz_t kNPOS = ::kNPOS; + using size_type = Ssiz_t; TString(); // Null string explicit TString(Ssiz_t ic); // Suggested capacity @@ -633,6 +634,10 @@ inline Bool_t TString::Contains(const TString &pat, ECaseCompare cmp) const inline Bool_t TString::Contains(const char *s, ECaseCompare cmp) const { return Index(s, s ? (Ssiz_t)strlen(s) : 0, (Ssiz_t)0, cmp) != kNPOS; } +//////////////////////////////////////////////////////////////////////////////// +/// \brief Returns whether the string matches the input TRegexp. +/// \warning Matching regular expressions of type ".?" is not supported. Use +/// std::regex instead. inline Bool_t TString::Contains(const TRegexp &pat) const { return Index(pat, (Ssiz_t)0) != kNPOS; } diff --git a/core/base/inc/TSystem.h b/core/base/inc/TSystem.h index 6cbee070f5a11..026301f2193d8 100644 --- a/core/base/inc/TSystem.h +++ b/core/base/inc/TSystem.h @@ -422,7 +422,7 @@ class TSystem : public TNamed { Bool_t cd(const char *path) { return ChangeDirectory(path); } const char *pwd() { return WorkingDirectory(); } virtual const char *TempDirectory() const; - virtual FILE *TempFileName(TString &base, const char *dir = nullptr); + virtual FILE *TempFileName(TString &base, const char *dir = nullptr, const char *suffix = nullptr); //---- Paths & Files virtual const char *BaseName(const char *pathname); diff --git a/core/base/src/Stringio.cxx b/core/base/src/Stringio.cxx index 0135723ef35b6..6a699ffae78ea 100644 --- a/core/base/src/Stringio.cxx +++ b/core/base/src/Stringio.cxx @@ -20,6 +20,7 @@ #include #include "TString.h" +#include "TError.h" //////////////////////////////////////////////////////////////////////////////// @@ -28,15 +29,20 @@ std::istream& TString::ReadFile(std::istream& strm) { // get file size - Ssiz_t end, cur = strm.tellg(); + auto cur = strm.tellg(); strm.seekg(0, std::ios::end); - end = strm.tellg(); + auto end = strm.tellg(); strm.seekg(cur); // any positive number of reasonable size for a file const Ssiz_t incr = 256; - Clobber(end-cur); + Long64_t fileSize = end - cur; + if(fileSize > MaxSize()) { + Error("TString::ReadFile", "file size too large (%lld, max = %d), clamping", fileSize, MaxSize()); + fileSize = MaxSize(); + } + Clobber(fileSize); while(1) { Ssiz_t len = Length(); diff --git a/core/base/src/TApplication.cxx b/core/base/src/TApplication.cxx index d735762bf9001..2222508b9394c 100644 --- a/core/base/src/TApplication.cxx +++ b/core/base/src/TApplication.cxx @@ -375,6 +375,9 @@ void TApplication::GetOptions(Int_t *argc, char **argv) } else if (!strcmp(argv[i], "-config")) { fprintf(stderr, "ROOT ./configure options:\n%s\n", gROOT->GetConfigOptions()); Terminate(0); + } else if (!strcmp(argv[i], "-a")) { + fprintf(stderr, "ROOT splash screen is not visible with root.exe, use root instead."); + Terminate(0); } else if (!strcmp(argv[i], "-b")) { MakeBatch(); argv[i] = null; diff --git a/core/base/src/TAttFill.cxx b/core/base/src/TAttFill.cxx index 24c5cd48fc5bb..11711c698227d 100644 --- a/core/base/src/TAttFill.cxx +++ b/core/base/src/TAttFill.cxx @@ -55,8 +55,8 @@ End_Macro ### Color transparency `SetFillColorAlpha()`, allows to set a transparent color. In the following example the fill color of the histogram `histo` -is set to blue with a transparency of 35%. The color `kBlue` -itself remains fully opaque. +is set to blue with an opacity of 35% (i.e. a transparency of 65%). +(The color `kBlue` itself is internally stored as fully opaque.) ~~~ {.cpp} histo->SetFillColorAlpha(kBlue, 0.35); @@ -67,6 +67,9 @@ The transparency is available on all platforms when the flag with the Cocoa backend. On the file output it is visible with PDF, PNG, Gif, JPEG, SVG, TeX... but not PostScript. +Alternatively, you can call at the top of your script `gSytle->SetCanvasPreferGL();`. +Or if you prefer to activate GL for a single canvas `c`, then use `c->SetSupportGL(true);`. + ### The ROOT Color Wheel. The wheel contains the recommended 216 colors to be used in web applications. The colors in the Color Wheel are created by TColor::CreateColorWheel. @@ -255,8 +258,10 @@ void TAttFill::SetFillAttributes() } //////////////////////////////////////////////////////////////////////////////// -/// Set a transparent fill color. falpha defines the percentage of -/// the color opacity from 0. (fully transparent) to 1. (fully opaque). +/// Set a transparent fill color. +/// \param fcolor defines the fill color +/// \param falpha defines the percentage of opacity from 0. (fully transparent) to 1. (fully opaque). +/// \note falpha is ignored (treated as 1) if the TCanvas has no GL support activated. void TAttFill::SetFillColorAlpha(Color_t fcolor, Float_t falpha) { diff --git a/core/base/src/TAttLine.cxx b/core/base/src/TAttLine.cxx index a148176868cc5..b6dd093a2e785 100644 --- a/core/base/src/TAttLine.cxx +++ b/core/base/src/TAttLine.cxx @@ -57,8 +57,8 @@ End_Macro ### Color transparency `SetLineColorAlpha()`, allows to set a transparent color. In the following example the line color of the histogram `histo` -is set to blue with a transparency of 35%. The color `kBlue` -itself remains fully opaque. +is set to blue with an opacity of 35% (i.e. a transparency of 65%). +(The color `kBlue` itself is internally stored as fully opaque.) ~~~ {.cpp} histo->SetLineColorAlpha(kBlue, 0.35); @@ -68,6 +68,8 @@ The transparency is available on all platforms when the flag `OpenGL.CanvasPrefe in `$ROOTSYS/etc/system.rootrc`, or on Mac with the Cocoa backend. On the file output it is visible with PDF, PNG, Gif, JPEG, SVG, TeX ... but not PostScript. +Alternatively, you can call at the top of your script `gSytle->SetCanvasPreferGL();`. +Or if you prefer to activate GL for a single canvas `c`, then use `c->SetSupportGL(true);`. \anchor ATTLINE2 ## Line Width @@ -295,8 +297,10 @@ void TAttLine::SetLineAttributes() } //////////////////////////////////////////////////////////////////////////////// -/// Set a transparent line color. lalpha defines the percentage of -/// the color opacity from 0. (fully transparent) to 1. (fully opaque). +/// Set a transparent line color. +/// \param lcolor defines the line color +/// \param lalpha defines the percentage of opacity from 0. (fully transparent) to 1. (fully opaque). +/// \note lalpha is ignored (treated as 1) if the TCanvas has no GL support activated. void TAttLine::SetLineColorAlpha(Color_t lcolor, Float_t lalpha) { diff --git a/core/base/src/TAttMarker.cxx b/core/base/src/TAttMarker.cxx index 58225efe2d680..1789a80bc8128 100644 --- a/core/base/src/TAttMarker.cxx +++ b/core/base/src/TAttMarker.cxx @@ -59,8 +59,8 @@ End_Macro `SetMarkerColorAlpha()`, allows to set a transparent color. In the following example the marker color of the histogram `histo` -is set to blue with a transparency of 35%. The color `kBlue` -itself remains fully opaque. +is set to blue with an opacity of 35% (i.e. a transparency of 65%). +(The color `kBlue` itself is internally stored as fully opaque.) ~~~ {.cpp} histo->SetMarkerColorAlpha(kBlue, 0.35); @@ -70,6 +70,9 @@ The transparency is available on all platforms when the flag `OpenGL.CanvasPrefe in `$ROOTSYS/etc/system.rootrc`, or on Mac with the Cocoa backend. On the file output it is visible with PDF, PNG, Gif, JPEG, SVG, TeX ... but not PostScript. +Alternatively, you can call at the top of your script `gSytle->SetCanvasPreferGL();`. +Or if you prefer to activate GL for a single canvas `c`, then use `c->SetSupportGL(true);`. + \anchor ATTMARKER2 ## Marker style @@ -367,8 +370,10 @@ void TAttMarker::SetMarkerAttributes() } //////////////////////////////////////////////////////////////////////////////// -/// Set a transparent marker color. malpha defines the percentage of -/// the color opacity from 0. (fully transparent) to 1. (fully opaque). +/// Set a transparent marker color. +/// \param mcolor defines the marker color +/// \param malpha defines the percentage of opacity from 0. (fully transparent) to 1. (fully opaque). +/// \note malpha is ignored (treated as 1) if the TCanvas has no GL support activated. void TAttMarker::SetMarkerColorAlpha(Color_t mcolor, Float_t malpha) { diff --git a/core/base/src/TAttText.cxx b/core/base/src/TAttText.cxx index 989c2781bb289..9062dcb2da92d 100644 --- a/core/base/src/TAttText.cxx +++ b/core/base/src/TAttText.cxx @@ -127,8 +127,8 @@ End_Macro ### Color transparency `SetTextColorAlpha()`, allows to set a transparent color. In the following example the text color of the text `text` -is set to blue with a transparency of 35%. The color `kBlue` -itself remains fully opaque. +is set to blue with an opacity of 35% (i.e. a transparency of 65%). +(The color `kBlue` itself is internally stored as fully opaque.) ~~~ {.cpp} text->SetTextColorAlpha(kBlue, 0.35); @@ -138,6 +138,9 @@ The transparency is available on all platforms when the flag `OpenGL.CanvasPrefe in `$ROOTSYS/etc/system.rootrc`, or on Mac with the Cocoa backend. On the file output it is visible with PDF, PNG, Gif, JPEG, SVG, TeX ... but not PostScript. +Alternatively, you can call at the top of your script `gSytle->SetCanvasPreferGL();`. +Or if you prefer to activate GL for a single canvas `c`, then use `c->SetSupportGL(true);`. + \anchor ATTTEXT4 ## Text Size @@ -402,8 +405,10 @@ void TAttText::SetTextAttributes() } //////////////////////////////////////////////////////////////////////////////// -/// Set a transparent marker color. talpha defines the percentage of -/// the color opacity from 0. (fully transparent) to 1. (fully opaque). +/// Set a transparent text color. +/// \param tcolor defines the text color +/// \param talpha defines the percentage of opacity from 0. (fully transparent) to 1. (fully opaque). +/// \note talpha is ignored (treated as 1) if the TCanvas has no GL support activated. void TAttText::SetTextColorAlpha(Color_t tcolor, Float_t talpha) { diff --git a/core/base/src/TColor.cxx b/core/base/src/TColor.cxx index 8f2df21162067..c2c7aeefdcfcd 100644 --- a/core/base/src/TColor.cxx +++ b/core/base/src/TColor.cxx @@ -1005,8 +1005,8 @@ in parallelcoordtrans.C. To ease the creation of a transparent color the static method `GetColorTransparent(Int_t color, Float_t a)` is provided. In the following example the `trans_red` color index point to -a red color 30% transparent. The alpha value of the color index -`kRed` is not modified. +a red color 30% opaque (70% transparent). The alpha value of +the color index `kRed` is not modified. ~~~ {.cpp} Int_t trans_red = GetColorTransparent(kRed, 0.3); @@ -1016,8 +1016,8 @@ This function is also used in the methods `SetFillColorAlpha()`, `SetLineColorAlpha()`, `SetMarkerColorAlpha()` and `SetTextColorAlpha()`. In the following example the fill color of the histogram `histo` -is set to blue with a transparency of 35%. The color `kBlue` -itself remains fully opaque. +is set to blue with an opacity of 35% (i.e. a transparency of 65%). +(The color `kBlue` itself is internally stored as fully opaque.) ~~~ {.cpp} histo->SetFillColorAlpha(kBlue, 0.35); @@ -1026,6 +1026,10 @@ itself remains fully opaque. The transparency is available on all platforms when the flag `OpenGL.CanvasPreferGL` is set to `1` in `$ROOTSYS/etc/system.rootrc`, or on Mac with the Cocoa backend. On the file output it is visible with PDF, PNG, Gif, JPEG, SVG, TeX ... but not PostScript. + +Alternatively, you can call at the top of your script `gSytle->SetCanvasPreferGL();`. +Or if you prefer to activate GL for a single canvas `c`, then use `c->SetSupportGL(true);`. + The following macro gives an example of transparency usage: Begin_Macro(source) @@ -2046,7 +2050,7 @@ Int_t TColor::GetColorDark(Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Static function: Returns the transparent color number corresponding to n. -/// The transparency level is given by the alpha value a. If a color with the same +/// The opacity level is given by the alpha value a. If a color with the same /// RGBa values already exists it is returned. Int_t TColor::GetColorTransparent(Int_t n, Float_t a) @@ -2433,7 +2437,7 @@ void TColor::SetGrayscale(Bool_t set /*= kTRUE*/) /// \param fileName: Name of a .txt file (ASCII) containing three floats per /// line, separated by spaces, namely the r g b fractions of the color, each /// value being in the range [0,1]. -/// \param alpha the global transparency for all colors within this palette +/// \param alpha the global opacity for all colors within this palette /// \return a positive value on success and -1 on error. /// \author Fernando Hueso-González Int_t TColor::CreateColorTableFromFile(TString fileName, Float_t alpha) diff --git a/core/base/src/TRegexp.cxx b/core/base/src/TRegexp.cxx index f8ffa4f82ab8b..0a4f35e2a04bc 100644 --- a/core/base/src/TRegexp.cxx +++ b/core/base/src/TRegexp.cxx @@ -30,6 +30,9 @@ parentheses (grouping). Therefore "a|b" does not match "a". Standard classes like [:alnum:], [:alpha:], etc. are not supported, only [a-zA-Z], [^ntf] and so on. + +Warning: The preferred way to use regular expressions is via std::regex. +E.g., Index() functions may return incorrect result. */ #include "TRegexp.h" @@ -205,6 +208,7 @@ const char *TRegexp::MakeWildcard(const char *re) /// Find the first occurrence of the regexp in string and return the /// position, or -1 if there is no match. Len is length of the matched /// string and i is the offset at which the matching should start. +/// Please, see the Warning in the class documentation above. Ssiz_t TRegexp::Index(const TString& string, Ssiz_t* len, Ssiz_t i) const { @@ -246,6 +250,7 @@ TRegexp::EStatVal TRegexp::Status() /// Find the first occurrence of the regexp in string and return the /// position, or -1 if there is no match. Start is the offset at which /// the search should start. +/// Please, see the Warning in the class documentation above. Ssiz_t TString::Index(const TRegexp& r, Ssiz_t start) const { @@ -257,6 +262,7 @@ Ssiz_t TString::Index(const TRegexp& r, Ssiz_t start) const /// Find the first occurrence of the regexp in string and return the /// position, or -1 if there is no match. Extent is length of the matched /// string and start is the offset at which the matching should start. +/// Please, see the Warning in the class documentation above. Ssiz_t TString::Index(const TRegexp& r, Ssiz_t* extent, Ssiz_t start) const { @@ -265,6 +271,7 @@ Ssiz_t TString::Index(const TRegexp& r, Ssiz_t* extent, Ssiz_t start) const //////////////////////////////////////////////////////////////////////////////// /// Return the substring found by applying the regexp starting at start. +/// Please, see the Warning in the class documentation above. TSubString TString::operator()(const TRegexp& r, Ssiz_t start) const { diff --git a/core/base/src/TString.cxx b/core/base/src/TString.cxx index 1b38c3540f742..d22db1fe71ef5 100644 --- a/core/base/src/TString.cxx +++ b/core/base/src/TString.cxx @@ -234,7 +234,12 @@ TString::TString(const char *a1, Ssiz_t n1, const char *a2, Ssiz_t n2) } if (!a1) n1 = 0; if (!a2) n2 = 0; - Ssiz_t tot = n1+n2; + Long64_t tot = static_cast(n1)+n2; // Final string length, use 64-bit long instead of 32-bit int to check for overflows + if (tot > MaxSize()) { + Error("TString::TString", "Too large number of characters!"); + Zero(); + return; + } char *data = Init(tot, tot); if (a1) memcpy(data, a1, n1); if (a2) memcpy(data+n1, a2, n2); @@ -251,6 +256,7 @@ TString::~TString() //////////////////////////////////////////////////////////////////////////////// /// Private member function returning an empty string representation of /// size capacity and containing nchar characters. +/// \warning If nchar > MaxSize(), then Fatal() is raised and only MaxSize() elements are allocated char *TString::Init(Ssiz_t capacity, Ssiz_t nchar) { @@ -267,7 +273,7 @@ char *TString::Init(Ssiz_t capacity, Ssiz_t nchar) nchar = capacity; } if (capacity > MaxSize()) { - Error("TString::Init", "capacity too large (%d, max = %d)", capacity, MaxSize()); + Fatal("TString::Init", "capacity too large (%d, max = %d)", capacity, MaxSize()); capacity = MaxSize(); if (nchar > capacity) nchar = capacity; @@ -386,6 +392,7 @@ TString& TString::operator=(const TSubString &substr) //////////////////////////////////////////////////////////////////////////////// /// Append character c rep times to string. +/// \warning If length+rep exceeds MaxSize(), then Fatal() is raised and only MaxSize()-length elements are added TString& TString::Append(char c, Ssiz_t rep) { @@ -396,10 +403,10 @@ TString& TString::Append(char c, Ssiz_t rep) return *this; } Ssiz_t len = Length(); - Ssiz_t tot = len + rep; // Final string length + Long64_t tot = static_cast(len) + rep; // Final string length, use 64-bit long instead of 32-bit int to check for overflows if (tot > MaxSize()) { - Error("TString::Append", "rep too large (%d, max = %d)", rep, MaxSize()-len); + Fatal("TString::Append", "rep too large (%d, max = %d)", rep, MaxSize()-len); tot = MaxSize(); rep = tot - len; } @@ -965,6 +972,7 @@ Bool_t TString::MaybeWildcard() const //////////////////////////////////////////////////////////////////////////////// /// Prepend character c rep times to string. +/// \warning If length+rep exceeds MaxSize(), then Fatal() is raised and only MaxSize()-length elements are added TString& TString::Prepend(char c, Ssiz_t rep) { @@ -972,10 +980,10 @@ TString& TString::Prepend(char c, Ssiz_t rep) return *this; Ssiz_t len = Length(); - Ssiz_t tot = len + rep; // Final string length + Long64_t tot = static_cast(len) + rep; // Final string length, use 64-bit long instead of 32-bit int to check for overflows if (tot > MaxSize()) { - Error("TString::Prepend", "rep too large (%d, max = %d)", rep, MaxSize()-len); + Fatal("TString::Prepend", "rep too large (%d, max = %d)", rep, MaxSize()-len); tot = MaxSize(); rep = tot - len; } @@ -1028,7 +1036,11 @@ TString &TString::Replace(Ssiz_t pos, Ssiz_t n1, const char *cs, Ssiz_t n2) n1 = TMath::Min(n1, len - pos); if (!cs) n2 = 0; - Ssiz_t tot = len - n1 + n2; // Final string length + Long64_t tot = static_cast(len) - n1 + n2; // Final string length, use 64-bit long instead of 32-bit int to check for overflows + if (tot > MaxSize()) { + Error("TString::Replace", "Too large number of characters!"); + return *this; + } Ssiz_t rem = len - n1 - pos; // Length of remnant at end of string Ssiz_t capac = Capacity(); @@ -1202,12 +1214,14 @@ void TString::AssertElement(Ssiz_t i) const //////////////////////////////////////////////////////////////////////////////// /// Calculate a nice capacity greater than or equal to newCap. +/// \warning Fatal() is raised if newCap > MaxSize() +/// \return Resulting recommended capacity (after clamping, if needed) Ssiz_t TString::AdjustCapacity(Ssiz_t oldCap, Ssiz_t newCap) { Ssiz_t ms = MaxSize(); if (newCap > ms - 1) { - Error("TString::AdjustCapacity", "capacity too large (%d, max = %d)", + Fatal("TString::AdjustCapacity", "capacity too large (%d, max = %d)", newCap, ms); } Ssiz_t cap = oldCap < ms / 2 - kAlignment ? @@ -1225,12 +1239,18 @@ void TString::Clear() //////////////////////////////////////////////////////////////////////////////// /// Clear string and make sure it has a capacity of nc. +/// \warning If nc > MaxSize(), then Fatal() is raised, and only MaxSize() +/// elements are allocated if Fatal does not abort +/// \return Resulting allocated capacity (after clamping, if needed) -void TString::Clobber(Ssiz_t nc) +Ssiz_t TString::Clobber(Ssiz_t nc) { if (nc > MaxSize()) { - Error("TString::Clobber", "capacity too large (%d, max = %d)", nc, MaxSize()); - nc = MaxSize(); + Fatal("TString::Clobber", "capacity too large (%d, max = %d)", nc, MaxSize()); + // In the rare case where Fatal does not abort, we erase, clamp and continue + UnLink(); + Zero(); + nc = MaxSize(); // Clamping after deleting to avoid corruption } if (nc < kMinCap) { @@ -1248,11 +1268,13 @@ void TString::Clobber(Ssiz_t nc) SetLongSize(0); data[0] = 0; } + return nc; } //////////////////////////////////////////////////////////////////////////////// /// Make self a distinct copy with capacity of at least tot, where tot cannot /// be smaller than the current length. Preserve previous contents. +/// \warning If tot > MaxSize(), then Fatal() is raised and only MaxSize() elements are allocated void TString::Clone(Ssiz_t tot) { @@ -1260,7 +1282,7 @@ void TString::Clone(Ssiz_t tot) if (len >= tot) return; if (tot > MaxSize()) { - Error("TString::Clone", "tot too large (%d, max = %d)", tot, MaxSize()); + Fatal("TString::Clone", "tot too large (%d, max = %d)", tot, MaxSize()); tot = MaxSize(); } @@ -2288,7 +2310,7 @@ TObjArray *TString::Tokenize(const TString &delim) const void TString::FormImp(const char *fmt, va_list ap) { Ssiz_t buflen = 20 + 20 * strlen(fmt); // pick a number, any strictly positive number - Clobber(buflen); + buflen = Clobber(buflen); // Update buflen, as Clobber clamps length to MaxSize (if Fatal does not abort) va_list sap; R__VA_COPY(sap, ap); @@ -2303,7 +2325,7 @@ void TString::FormImp(const char *fmt, va_list ap) buflen *= 2; else buflen = n+1; - Clobber(buflen); + buflen = Clobber(buflen); va_end(ap); R__VA_COPY(ap, sap); vc = 1; diff --git a/core/base/src/TStringLong.cxx b/core/base/src/TStringLong.cxx index e646d489d9839..18f02e6d8f5c0 100644 --- a/core/base/src/TStringLong.cxx +++ b/core/base/src/TStringLong.cxx @@ -134,11 +134,11 @@ void TStringLong::Streamer(TBuffer &b) Int_t nwh; if (b.IsReading()) { b >> nwh; - Clobber(nwh); + Ssiz_t nwr = Clobber(nwh); char *data = GetPointer(); - data[nwh] = 0; - SetSize(nwh); - for (int i = 0; i < nwh; i++) b >> data[i]; + data[nwr] = 0; + SetSize(nwr); + for (Ssiz_t i = 0; i < nwr; i++) b >> data[i]; } else { nwh = Length(); b << nwh; diff --git a/core/base/src/TSystem.cxx b/core/base/src/TSystem.cxx index 90d3209209346..6228addcb4cda 100644 --- a/core/base/src/TSystem.cxx +++ b/core/base/src/TSystem.cxx @@ -57,6 +57,7 @@ allows a simple partial implementation for new OS'es. #ifdef WIN32 #include +#include "Windows4Root.h" #endif const char *gRootDir = nullptr; @@ -1476,12 +1477,14 @@ const char *TSystem::TempDirectory() const /// Create a secure temporary file by appending a unique /// 6 letter string to base. The file will be created in /// a standard (system) directory or in the directory -/// provided in dir. The full filename is returned in base +/// provided in dir. Optionally one can provide suffix +/// append to the final name - like extension ".txt" or ".html". +/// The full filename is returned in base /// and a filepointer is returned for safely writing to the file /// (this avoids certain security problems). Returns 0 in case /// of error. -FILE *TSystem::TempFileName(TString &, const char *) +FILE *TSystem::TempFileName(TString &, const char *, const char *) { AbstractMethod("TempFileName"); return nullptr; @@ -2811,6 +2814,12 @@ static void R__WriteDependencyFile(const TString & build_loc, const TString &dep /// /// (the ... have to be replaced by the actual values and are here only to /// shorten this comment). +/// +/// Note that the default behavior is to remove libraries when closing ROOT, +/// ie TSystem::CleanCompiledMacros() is called in the TROOT destructor. +/// The default behavior of .L script.C+ is the opposite one, leaving things +/// after closing, without removing. In other words, .L always passes the 'k' +/// option behind the scenes. int TSystem::CompileMacro(const char *filename, Option_t *opt, const char *library_specified, @@ -3034,11 +3043,12 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt, // Calculate the -I lines TString includes = GetIncludePath(); + includes.ReplaceAll("-I ", "-I"); includes.Prepend(' '); { // I need to replace the -Isomerelativepath by -I../ (or -I..\ on NT) - TRegexp rel_inc(" -I[^\"/\\$%-][^:-]+"); + TRegexp rel_inc(" -I[^\"/\\\\$\\%-][^:\\s]+"); Int_t len,pos; pos = rel_inc.Index(includes,&len); while( len != 0 ) { @@ -3046,15 +3056,16 @@ int TSystem::CompileMacro(const char *filename, Option_t *opt, sub.Remove(0,3); // Remove ' -I' AssignAndDelete( sub, ConcatFileName( WorkingDirectory(), sub ) ); sub.Prepend(" -I\""); - sub.Chop(); // Remove trailing space (i.e between the -Is ... + if (sub.EndsWith(" ")) + sub.Chop(); // Remove trailing space (i.e between the -Is ... sub.Append("\" "); includes.Replace(pos,len,sub); pos = rel_inc.Index(includes,&len); } } { - // I need to replace the -I"somerelativepath" by -I"$cwd/ (or -I"$cwd\ on NT) - TRegexp rel_inc(" -I\"[^/\\$%-][^:-]+"); + // I need to replace the -I"somerelativepath" by -I"$cwd/ (or -I"$cwd\ on NT) + TRegexp rel_inc(" -I\"[^/\\\\$\\%-][^:\\s]+"); Int_t len,pos; pos = rel_inc.Index(includes,&len); while( len != 0 ) { @@ -4340,14 +4351,30 @@ TString TSystem::SplitAclicMode(const char *filename, TString &aclicMode, } //////////////////////////////////////////////////////////////////////////////// -/// Remove the shared libs produced by the CompileMacro() function. +/// Remove the shared libs produced by the CompileMacro() function, together +/// with their rootmaps, linkdefs, and pcms (and some more on Windows). void TSystem::CleanCompiledMacros() { TIter next(fCompiled); TNamed *lib; + const char *extensions[] = {".lib", ".exp", ".d", ".def", ".rootmap", "_ACLiC_linkdef.h", "_ACLiC_dict_rdict.pcm"}; while ((lib = (TNamed*)next())) { - if (lib->TestBit(kMustCleanup)) Unlink(lib->GetTitle()); + if (lib->TestBit(kMustCleanup)) { + TString libname = lib->GetTitle(); +#ifdef WIN32 + // On Windows, we need to unload the dll before deleting it + if (gInterpreter->IsLibraryLoaded(libname)) + ::FreeLibrary(::GetModuleHandle(libname)); +#endif + Unlink(libname); + TString target, soExt = "." + fSoExt; + libname.ReplaceAll(soExt, ""); + for (const char *ext : extensions) { + target = libname + ext; + Unlink(target); + } + } } } diff --git a/core/base/test/CMakeLists.txt b/core/base/test/CMakeLists.txt index 9b52e834058e3..b2e2c60fbfcdd 100644 --- a/core/base/test/CMakeLists.txt +++ b/core/base/test/CMakeLists.txt @@ -25,3 +25,8 @@ ROOT_ADD_GTEST(CoreBaseTests LIBRARIES ${extralibs} RIO Core) ROOT_ADD_GTEST(CoreErrorTests TErrorTests.cxx LIBRARIES Core) + +ROOT_ADD_GTEST(CoreSystemTests TSystemTests.cxx LIBRARIES Core) + +configure_file(Foo.C Foo.C COPYONLY) +ROOT_ADD_GTEST(IncludePathTest IncludePathTest.cxx LIBRARIES Core) diff --git a/core/base/test/Foo.C b/core/base/test/Foo.C new file mode 100644 index 0000000000000..fedf3ef94dd11 --- /dev/null +++ b/core/base/test/Foo.C @@ -0,0 +1,8 @@ +#include + +class Foo { +public: + Foo() { std::cout << "Foo" << std::endl; } + ~Foo() { std::cout << "~Foo" << std::endl; } + void f() { std::cout << "f" << std::endl; } +}; diff --git a/core/base/test/IncludePathTest.cxx b/core/base/test/IncludePathTest.cxx new file mode 100644 index 0000000000000..b5f0fbadb0fd2 --- /dev/null +++ b/core/base/test/IncludePathTest.cxx @@ -0,0 +1,31 @@ +#include "gtest/gtest.h" +#include "TSystem.h" + +#include + +TEST(TSystem, IncludePath) +{ + ASSERT_TRUE(gSystem); + + gSystem->AddIncludePath("-I /some/path/with-xin-it -I ./some/relative-path"); + gSystem->AddIncludePath("-I %ROOTSYS%\\include -I ${ROOTSYS}/include"); +#ifdef WIN32 + gSystem->AddIncludePath( + "-I \"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.38.33130\\include\""); +#endif + + testing::internal::CaptureStderr(); + gSystem->CompileMacro("Foo.C", "f"); + std::cerr.flush(); + std::string errors = testing::internal::GetCapturedStderr(); + + gSystem->Unload("Foo_C"); + gSystem->CleanCompiledMacros(); +#ifdef WIN32 + EXPECT_TRUE((errors.find("cl : Command line warning D9002 : ignoring unknown option ") == std::string::npos)); + gSystem->Exec("del Foo_C*"); +#else + EXPECT_TRUE((errors.find("c++: error: unrecognized command line option ") == std::string::npos)); + gSystem->Exec("rm -f Foo_C*"); +#endif +} diff --git a/core/base/test/TSystemTests.cxx b/core/base/test/TSystemTests.cxx new file mode 100644 index 0000000000000..18fdaf41589ad --- /dev/null +++ b/core/base/test/TSystemTests.cxx @@ -0,0 +1,57 @@ +#include "gtest/gtest.h" + +#include "TSystem.h" +#include "TString.h" + +#include +#include +#include +#include + +TEST(TSystem, TempFile) +{ + TString fname = "root_test_"; + auto ftmp = gSystem->TempFileName(fname); + + EXPECT_TRUE(fname.Length() > 10); + EXPECT_TRUE(ftmp != nullptr); + + std::string content = "test_temp_file_content"; + auto res_write = fwrite(content.data(), 1, content.length(), ftmp); + EXPECT_EQ(res_write, content.length()); + + auto res_close = fclose(ftmp); + EXPECT_EQ(res_close, 0); + + std::ifstream fread(fname.Data()); + std::string str((std::istreambuf_iterator(fread)), std::istreambuf_iterator()); + EXPECT_STREQ(content.c_str(), str.c_str()); + + gSystem->Unlink(fname); +} + +TEST(TSystem, TempFileSuffix) +{ + TString fname = "root_suffix_test_"; + const char *suffix = ".txt"; + auto ftmp = gSystem->TempFileName(fname, nullptr, suffix); + + EXPECT_TRUE(fname.Length() > 16); + EXPECT_TRUE(ftmp != nullptr); + + // check that suffix really at the end of the file name + EXPECT_STREQ(fname(fname.Length() - strlen(suffix), strlen(suffix)).Data(), suffix); + + std::string content = "test_temp_file_content_suffix"; + auto res_write = fwrite(content.data(), 1, content.length(), ftmp); + EXPECT_EQ(res_write, content.length()); + + auto res_close = fclose(ftmp); + EXPECT_EQ(res_close, 0); + + std::ifstream fread(fname.Data()); + std::string str((std::istreambuf_iterator(fread)), std::istreambuf_iterator()); + EXPECT_STREQ(content.c_str(), str.c_str()); + + gSystem->Unlink(fname); +} diff --git a/core/dictgen/src/Scanner.cxx b/core/dictgen/src/Scanner.cxx index 63003ba181b1d..de0d633654d79 100644 --- a/core/dictgen/src/Scanner.cxx +++ b/core/dictgen/src/Scanner.cxx @@ -57,8 +57,6 @@ inline static bool IsElementPresent(const std::vector &v, T *el){ using namespace ROOT; using namespace clang; -extern cling::Interpreter *gInterp; - const char* RScanner::fgClangDeclKey = "ClangDecl"; // property key used for connection with Clang objects const char* RScanner::fgClangFuncKey = "ClangFunc"; // property key for demangled names @@ -1054,6 +1052,8 @@ void RScanner::Scan(const clang::ASTContext &C) std::cout<<"File name detected"<(&fInterpreter)); + if (fScanType == EScanType::kTwoPasses) TraverseDecl(C.getTranslationUnitDecl()); diff --git a/core/foundation/src/RConversionRuleParser.cxx b/core/foundation/src/RConversionRuleParser.cxx index 59fead341afd2..35694ac5c65b4 100644 --- a/core/foundation/src/RConversionRuleParser.cxx +++ b/core/foundation/src/RConversionRuleParser.cxx @@ -195,6 +195,20 @@ namespace ROOT /////////////////////////////////////////////////////////////////////// if( key == "code" ) { + // Cleaning of the input command: + // - Trim whitespaces at the borders + // - Get the inner command (i.e. the part between quotes) + // - Trim whitespaces again + // - Stitch back together + auto clean_command = [](const std::string &c) { + auto first_trim = TSchemaRuleProcessor::Trim(c); + auto inner_command = + first_trim.substr(first_trim.find_first_of('"') + 1, first_trim.find_last_of('"') - 1); + auto second_trim = TSchemaRuleProcessor::Trim(inner_command); + return '"' + second_trim + '"'; + }; + command = clean_command(command); + if( command[1] != '{' ) { error_string = "Parsing error while processing key: code\n"; error_string += "Expected \"{ at the beginning of the value."; diff --git a/core/imt/CMakeLists.txt b/core/imt/CMakeLists.txt index aa55ee1a3472c..edb9a758c4df9 100644 --- a/core/imt/CMakeLists.txt +++ b/core/imt/CMakeLists.txt @@ -27,7 +27,6 @@ target_link_libraries(Imt PRIVATE Thread INTERFACE Core) if(imt) ROOT_GENERATE_DICTIONARY(G__Imt STAGE1 - ROOT/TFuture.hxx ROOT/TTaskGroup.hxx ROOT/RTaskArena.hxx ROOT/RSlotStack.hxx diff --git a/core/imt/inc/ROOT/TExecutor.hxx b/core/imt/inc/ROOT/TExecutor.hxx index 9de3c888aadee..d36fdcc846d17 100644 --- a/core/imt/inc/ROOT/TExecutor.hxx +++ b/core/imt/inc/ROOT/TExecutor.hxx @@ -15,7 +15,6 @@ #include "ROOT/TExecutorCRTP.hxx" #include "ROOT/TSeq.hxx" #include "ROOT/TSequentialExecutor.hxx" -#include "ROOT/TypeTraits.hxx" // InvokeResult_t #ifdef R__USE_IMT #include "ROOT/TThreadExecutor.hxx" #endif @@ -38,9 +37,6 @@ namespace Internal{ class TExecutor: public TExecutorCRTP { friend TExecutorCRTP; - template - using InvokeResult_t = ROOT::TypeTraits::InvokeResult_t; - public: /// \brief Class constructor. Sets the default execution policy and initializes the corresponding executor. @@ -69,15 +65,15 @@ public: // other than checking that func is compatible with the type of arguments. // a static_assert check in TExecutor::Reduce is used to check that redfunc is compatible with the type returned by func using TExecutorCRTP::MapReduce; - template > + template > auto MapReduce(F func, unsigned nTimes, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, ROOT::TSeq args, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, std::initializer_list args, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, std::vector &args, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, const std::vector &args, R redfunc, unsigned nChunks) -> InvokeResult_t; // Reduce @@ -89,13 +85,13 @@ public: private: // Implementation of the Map functions declared in the parent class (TExecutorCRTP) // - template > + template > auto MapImpl(F func, unsigned nTimes) -> std::vector>; - template > + template > auto MapImpl(F func, ROOT::TSeq args) -> std::vector>; - template > + template > auto MapImpl(F func, std::vector &args) -> std::vector>; - template > + template > auto MapImpl(F func, const std::vector &args) -> std::vector>; ROOT::EExecutionPolicy fExecPolicy; @@ -209,12 +205,15 @@ auto TExecutor::MapImpl(F func, const std::vector &args) -> std::vector` where T +/// is the output of `func`. /// \param nChunks Number of chunks to split the input data for processing. /// \return A value result of "reducing" the vector returned by the Map operation into a single object. template auto TExecutor::MapReduce(F func, unsigned nTimes, R redfunc, unsigned nChunks) -> InvokeResult_t { + // check we can apply reduce to objs + static_assert(std::is_invocable_v>>, "redfunc does not have the correct signature"); if (fExecPolicy == ROOT::EExecutionPolicy::kMultiThread) { return fThreadExecutor->MapReduce(func, nTimes, redfunc, nChunks); } @@ -229,12 +228,15 @@ auto TExecutor::MapReduce(F func, unsigned nTimes, R redfunc, unsigned nChunks) /// \param func Function to be executed. Must take an element of the sequence passed assecond argument as a parameter. /// \param args Sequence of indexes to execute `func` on. /// \param redfunc Reduction function to combine the results of the calls to `func` into partial results, and these -/// into a final result. Must return the same type as `func`. +/// into a final result. Must return the same type as `func` and should be callable with `std::vector` where T is the +/// output of `func`. /// \param nChunks Number of chunks to split the input data for processing. -/// \return A value result of "reducing" the vector returned by the Map operation into a single object. +/// \return A value result of "reducing" the vector returned by the Map operation into a single object. template auto TExecutor::MapReduce(F func, ROOT::TSeq args, R redfunc, unsigned nChunks) -> InvokeResult_t { + static_assert(std::is_invocable_v>>, + "redfunc does not have the correct signature"); if (fExecPolicy == ROOT::EExecutionPolicy::kMultiThread) { return fThreadExecutor->MapReduce(func, args, redfunc, nChunks); } @@ -242,19 +244,22 @@ auto TExecutor::MapReduce(F func, ROOT::TSeq args, R redfunc, unsigned } ////////////////////////////////////////////////////////////////////////// -/// \brief Execute a function over the elements of an initializer_list (Map) and accumulate the results into a single value (Reduce). -/// Benefits from partial reduction into `nChunks` intermediate results if the execution policy is multithreaded. -/// Otherwise, it ignores the nChunks argument and performs a normal MapReduce operation. +/// \brief Execute a function over the elements of an initializer_list (Map) and accumulate the results into a single +/// value (Reduce). Benefits from partial reduction into `nChunks` intermediate results if the execution policy is +/// multithreaded. Otherwise, it ignores the nChunks argument and performs a normal MapReduce operation. /// -/// \param func Function to be executed. Must take an element of the sequence passed assecond argument as a parameter. +/// \param func Function to be executed. Must take an element of the sequence passed as second argument as a parameter. /// \param args initializer_list for a vector to apply `func` on. /// \param redfunc Reduction function to combine the results of the calls to `func` into partial results, and these -/// into a final result. Must return the same type as `func`. +/// into a final result. Must return the same type as `func` and should be callable with `const std::vector` where T +/// is the output of `func`. /// \param nChunks Number of chunks to split the input data for processing. /// \return A value result of "reducing" the vector returned by the Map operation into a single object. template auto TExecutor::MapReduce(F func, std::initializer_list args, R redfunc, unsigned nChunks) -> InvokeResult_t { + static_assert(std::is_invocable_v>>, + "redfunc does not have the correct signature"); if (fExecPolicy == ROOT::EExecutionPolicy::kMultiThread) { return fThreadExecutor->MapReduce(func, args, redfunc, nChunks); } @@ -262,19 +267,22 @@ auto TExecutor::MapReduce(F func, std::initializer_list args, R redfunc, unsi } ////////////////////////////////////////////////////////////////////////// -/// \brief Execute a function over the elements of a vector (Map) and accumulate the results into a single value (Reduce). -/// Benefits from partial reduction into `nChunks` intermediate results if the execution policy is multithreaded. -/// Otherwise, it ignores the nChunks argument and performs a normal MapReduce operation. +/// \brief Execute a function over the elements of a vector (Map) and accumulate the results into a single value +/// (Reduce). Benefits from partial reduction into `nChunks` intermediate results if the execution policy is +/// multithreaded. Otherwise, it ignores the nChunks argument and performs a normal MapReduce operation. /// /// \param func Function to be executed. Must take an element of the sequence passed assecond argument as a parameter. /// \param args Vector of elements passed as an argument to `func`. /// \param redfunc Reduction function to combine the results of the calls to `func` into partial results, and these -/// into a final result. Must return the same type as `func`. +/// into a final result. Must return the same type as `func` and should be callable with `const std::vector` where T +/// is the output of `func`. /// \param nChunks Number of chunks to split the input data for processing. /// \return A value result of "reducing" the vector returned by the Map operation into a single object. template auto TExecutor::MapReduce(F func, std::vector &args, R redfunc, unsigned nChunks) -> InvokeResult_t { + static_assert(std::is_invocable_v>>, + "redfunc does not have the correct signature"); if (fExecPolicy == ROOT::EExecutionPolicy::kMultiThread) { return fThreadExecutor->MapReduce(func, args, redfunc, nChunks); } @@ -282,19 +290,22 @@ auto TExecutor::MapReduce(F func, std::vector &args, R redfunc, unsigned nChu } ////////////////////////////////////////////////////////////////////////// -/// \brief Execute a function over the elements of an immutable vector (Map) and accumulate the results into a single value (Reduce). -/// Benefits from partial reduction into `nChunks` intermediate results if the execution policy is multithreaded. -/// Otherwise, it ignores the nChunks argument and performs a normal MapReduce operation. +/// \brief Execute a function over the elements of an immutable vector (Map) and accumulate the results into a single +/// value (Reduce). Benefits from partial reduction into `nChunks` intermediate results if the execution policy is +/// multithreaded. Otherwise, it ignores the nChunks argument and performs a normal MapReduce operation. /// /// \param func Function to be executed. Must take an element of the sequence passed assecond argument as a parameter. /// \param args Immutable vector, whose elements are passed as an argument to `func`. /// \param redfunc Reduction function to combine the results of the calls to `func` into partial results, and these -/// into a final result. Must return the same type as `func`. +/// into a final result. Must return the same type as `func` and should be callable with `const std::vector` where T +/// is the output of `func`. /// \param nChunks Number of chunks to split the input data for processing. /// \return A value result of "reducing" the vector returned by the Map operation into a single object. template auto TExecutor::MapReduce(F func, const std::vector &args, R redfunc, unsigned nChunks) -> InvokeResult_t { + static_assert(std::is_invocable_v>>, + "redfunc does not have the correct signature"); if (fExecPolicy == ROOT::EExecutionPolicy::kMultiThread) { return fThreadExecutor->MapReduce(func, args, redfunc, nChunks); } diff --git a/core/imt/inc/ROOT/TFuture.hxx b/core/imt/inc/ROOT/TFuture.hxx deleted file mode 100644 index 3c0bb0d4c1f74..0000000000000 --- a/core/imt/inc/ROOT/TFuture.hxx +++ /dev/null @@ -1,168 +0,0 @@ -// @(#)root/thread:$Id$ -// Author: Danilo Piparo August 2017 - -/************************************************************************* - * Copyright (C) 1995-2017, Rene Brun and Fons Rademakers. * - * All rights reserved. * - * * - * For the licensing terms see $ROOTSYS/LICENSE. * - * For the list of contributors see $ROOTSYS/README/CREDITS. * - *************************************************************************/ - -#ifndef ROOT_TFuture -#define ROOT_TFuture - -#include "RConfigure.h" - -#include "ROOT/TTaskGroup.hxx" -#include "ROOT/TypeTraits.hxx" - -#include -#include - -// exclude in case ROOT does not have IMT support -#ifndef R__USE_IMT -// No need to error out for dictionaries. -#if !defined(__ROOTCLING__) && !defined(G__DICTIONARY) -#error "Cannot use ROOT::Experimental::Async without defining R__USE_IMT." -#endif -#else - -namespace ROOT { - -// fwd declaration -namespace Experimental { -template -class TFuture; -} - -namespace Detail { -template -class TFutureImpl { - template - friend class Experimental::TFuture; - -protected: - using TTaskGroup = Experimental::TTaskGroup; - std::future fStdFut; - std::unique_ptr fTg{nullptr}; - - TFutureImpl(std::future &&fut, std::unique_ptr &&tg) : fStdFut(std::move(fut)) - { - fTg = std::move(tg); - }; - TFutureImpl(){}; - - TFutureImpl(std::future &&fut) : fStdFut(std::move(fut)) {} - - TFutureImpl(TFutureImpl &&other) : fStdFut(std::move(other.fStdFut)), fTg(std::move(other.fTg)) {} - - TFutureImpl &operator=(std::future &&other) { fStdFut = std::move(other); } - - TFutureImpl &operator=(TFutureImpl &&other) = default; - -public: - TFutureImpl &operator=(TFutureImpl &other) = delete; - - TFutureImpl(const TFutureImpl &other) = delete; - - void wait() - { - if (fTg) - fTg->Wait(); - } - - bool valid() const { return fStdFut.valid(); }; -}; -} - -namespace Experimental { - -//////////////////////////////////////////////////////////////////////////////// -/// A TFuture class. It can wrap an std::future. -template -class TFuture final : public ROOT::Detail::TFutureImpl { - template - friend TFuture< - ROOT::TypeTraits::InvokeResult_t::type, typename std::decay::type...>> - Async(Function &&f, Args &&...args); - -private: - TFuture(std::future &&fut, std::unique_ptr &&tg) - : ROOT::Detail::TFutureImpl(std::forward>(fut), std::move(tg)){}; - -public: - TFuture(std::future &&fut) : ROOT::Detail::TFutureImpl(std::forward>(fut)){}; - - T get() - { - this->wait(); - return this->fStdFut.get(); - } -}; -/// \cond -// Two specialisations, for void and T& as for std::future -template <> -class TFuture final : public ROOT::Detail::TFutureImpl { - template - friend TFuture< - ROOT::TypeTraits::InvokeResult_t::type, typename std::decay::type...>> - Async(Function &&f, Args &&...args); - -private: - TFuture(std::future &&fut, std::unique_ptr &&tg) - : ROOT::Detail::TFutureImpl(std::forward>(fut), std::move(tg)){}; - -public: - TFuture(std::future &&fut) : ROOT::Detail::TFutureImpl(std::forward>(fut)){}; - - void get() - { - this->wait(); - fStdFut.get(); - } -}; - -template -class TFuture final : public ROOT::Detail::TFutureImpl { - template - friend TFuture< - ROOT::TypeTraits::InvokeResult_t::type, typename std::decay::type...>> - Async(Function &&f, Args &&...args); - -private: - TFuture(std::future &&fut, std::unique_ptr &&tg) - : ROOT::Detail::TFutureImpl(std::forward>(fut), std::move(tg)){}; - -public: - TFuture(std::future &&fut) : ROOT::Detail::TFutureImpl(std::forward>(fut)){}; - - T &get() - { - this->wait(); - return this->fStdFut.get(); - } -}; -/// \endcond - -//////////////////////////////////////////////////////////////////////////////// -/// Runs a function asynchronously potentially in a new thread and returns a -/// ROOT TFuture that will hold the result. -template -TFuture::type, typename std::decay::type...>> -Async(Function &&f, Args &&...args) -{ - // The return type according to the standard implementation of std::future - using Ret_t = ROOT::TypeTraits::InvokeResult_t, std::decay_t...>; - - auto thisPt = std::make_shared>(std::bind(f, args...)); - std::unique_ptr tg(new ROOT::Experimental::TTaskGroup()); - tg->Run([thisPt]() { (*thisPt)(); }); - - return ROOT::Experimental::TFuture(thisPt->get_future(), std::move(tg)); -} -} -} - -#endif -#endif diff --git a/core/imt/inc/ROOT/TThreadExecutor.hxx b/core/imt/inc/ROOT/TThreadExecutor.hxx index 3d24ce06e3739..764111f3d84d8 100644 --- a/core/imt/inc/ROOT/TThreadExecutor.hxx +++ b/core/imt/inc/ROOT/TThreadExecutor.hxx @@ -41,9 +41,6 @@ namespace ROOT { class TThreadExecutor: public TExecutorCRTP { friend TExecutorCRTP; - template - using InvokeResult_t = ROOT::TypeTraits::InvokeResult_t; - public: explicit TThreadExecutor(UInt_t nThreads = 0u); @@ -77,21 +74,21 @@ namespace ROOT { // other than checking that func is compatible with the type of arguments. // a static_assert check in TThreadExecutor::Reduce is used to check that redfunc is compatible with the type returned by func using TExecutorCRTP::MapReduce; - template > + template > auto MapReduce(F func, unsigned nTimes, R redfunc) -> InvokeResult_t; - template > + template > auto MapReduce(F func, unsigned nTimes, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, ROOT::TSeq args, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, std::initializer_list args, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, std::vector &args, R redfunc) -> InvokeResult_t; - template > + template > auto MapReduce(F func, const std::vector &args, R redfunc) -> InvokeResult_t; - template > + template > auto MapReduce(F func, std::vector &args, R redfunc, unsigned nChunks) -> InvokeResult_t; - template > + template > auto MapReduce(F func, const std::vector &args, R redfunc, unsigned nChunks) -> InvokeResult_t; using TExecutorCRTP::Reduce; @@ -103,27 +100,27 @@ namespace ROOT { private: // Implementation of the Map functions declared in the parent class (TExecutorCRTP) // - template > + template > auto MapImpl(F func, unsigned nTimes) -> std::vector>; - template > + template > auto MapImpl(F func, ROOT::TSeq args) -> std::vector>; - template > + template > auto MapImpl(F func, std::vector &args) -> std::vector>; - template > + template > auto MapImpl(F func, const std::vector &args) -> std::vector>; // Extension of the Map interfaces with chunking, specific to this class and // only available from a MapReduce call. - template > + template > auto Map(F func, unsigned nTimes, R redfunc, unsigned nChunks) -> std::vector>; - template > + template > auto Map(F func, ROOT::TSeq args, R redfunc, unsigned nChunks) -> std::vector>; - template > + template > auto Map(F func, std::initializer_list args, R redfunc, unsigned nChunks) -> std::vector>; - template > + template > auto Map(F func, std::vector &args, R redfunc, unsigned nChunks) -> std::vector>; - template > + template > auto Map(F func, const std::vector &args, R redfunc, unsigned nChunks) -> std::vector>; // Functions that interface with the parallel library used as a backend diff --git a/core/imt/src/TExecutor.cxx b/core/imt/src/TExecutor.cxx index 983ea4a306dc1..9656a8cfc93d0 100644 --- a/core/imt/src/TExecutor.cxx +++ b/core/imt/src/TExecutor.cxx @@ -54,23 +54,25 @@ /// This set of methods behaves exactly like Map, but takes an additional /// function as a third argument. This function is applied to the set of /// objects returned by the corresponding Map execution to "squash" them -/// into a single object. +/// into a single object. The signature of the reduce function should be `(const std::vector) -> T` /// /// An integer can be passed as the fourth argument indicating the number of chunks we want to divide our work in. -/// (Note: Please be aware that chunking is only available when the policy is kMultiThread, ignoring this argument in other cases) -/// This may be useful to avoid the overhead introduced when running really short tasks. In this case, the reduction -/// function should be independent of the size of the vector returned by Map due to optimization of the number of -/// chunks. +/// (Note: Please be aware that chunking is only available when the policy is kMultiThread, ignoring this argument in +/// other cases) This may be useful to avoid the overhead introduced when running really short tasks. In this case, +/// the reduction function should be independent of the size of the vector returned by Map due to optimization of the +/// number of chunks. /// /// #### Examples: /// ~~~{.cpp} -/// root[] ROOT::Internal::TExecutor pool; auto ten = pool.MapReduce([]() { return 1; }, 10, [](const std::vector &v) { return std::accumulate(v.begin(), v.end(), 0); }) -/// root[] ROOT::Internal::TExecutor pool(ROOT::EExecutionPolicy::kMultiProcess); auto hist = pool.MapReduce(CreateAndFillHists, 10, PoolUtils::ReduceObjects); +/// root[] ROOT::Internal::TExecutor pool; auto ten = pool.MapReduce([]() { return 1; }, 10, [](const std::vector +/// &v) { return std::accumulate(v.begin(), v.end(), 0); }) +/// root[] ROOT::Internal::TExecutor +/// pool(ROOT::EExecutionPolicy::kMultiProcess); auto hist = pool.MapReduce(CreateAndFillHists, 10, +/// PoolUtils::ReduceObjects); /// ~~~ /// ////////////////////////////////////////////////////////////////////////// - namespace ROOT { namespace Internal { TExecutor::TExecutor(ROOT::EExecutionPolicy execPolicy, unsigned nWorkers): fExecPolicy(execPolicy) { diff --git a/core/imt/test/CMakeLists.txt b/core/imt/test/CMakeLists.txt index 2deaaf659ab67..9cfaceffbc1c6 100644 --- a/core/imt/test/CMakeLists.txt +++ b/core/imt/test/CMakeLists.txt @@ -4,6 +4,5 @@ # For the licensing terms see $ROOTSYS/LICENSE. # For the list of contributors see $ROOTSYS/README/CREDITS. -ROOT_ADD_GTEST(testImt testTFuture.cxx testTTaskGroup.cxx LIBRARIES Imt) ROOT_ADD_GTEST(testTaskArena testRTaskArena.cxx LIBRARIES Imt ${TBB_LIBRARIES} FAILREGEX "") ROOT_ADD_GTEST(testTBBGlobalControl testTBBGlobalControl.cxx LIBRARIES Imt ${TBB_LIBRARIES}) diff --git a/core/imt/test/testTFuture.cxx b/core/imt/test/testTFuture.cxx deleted file mode 100644 index 41976e0131062..0000000000000 --- a/core/imt/test/testTFuture.cxx +++ /dev/null @@ -1,53 +0,0 @@ -#include "TROOT.h" -#include "ROOT/TFuture.hxx" - -#include - -#include "gtest/gtest.h" - -#ifdef R__USE_IMT - -using namespace ROOT::Experimental; - -TEST(TFuture, BuildFromSTLFuture) -{ - TFuture f = std::async([]() { return 1; }); - ASSERT_EQ(1, f.get()); -} - -TEST(TFuture, BuildFromSTLFuture_ref) -{ - int a(0); - TFuture f = std::async([&a]() -> int & { return a; }); - auto &r = f.get(); - ASSERT_EQ(&a, &(r)); -} - -TEST(TFuture, BuildFromSTLFuture_void) -{ - TFuture f = std::async([]() {}); - f.get(); -} - -TEST(TFuture, BuildFromAsync) -{ - ROOT::EnableImplicitMT(); - auto f = Async([]() { return 1; }); - ASSERT_EQ(1, f.get()); -} - -TEST(TFuture, BuildFromAsync_ref) -{ - int a(0); - auto f = Async([&a]() -> int & { return a; }); - auto &&r = f.get(); - ASSERT_EQ(&a, &(r)); -} - -TEST(TFuture, BuildFromAsync_void) -{ - auto f = Async([]() {}); - f.get(); -} - -#endif diff --git a/core/lzma/src/ZipLZMA.c b/core/lzma/src/ZipLZMA.c index d3d48b0fe748f..eae42fc6ae243 100644 --- a/core/lzma/src/ZipLZMA.c +++ b/core/lzma/src/ZipLZMA.c @@ -68,7 +68,7 @@ void R__zipLZMA(int cxlevel, int *srcsize, char *src, int *tgtsize, char *tgt, i stream.avail_in = (size_t)(*srcsize); stream.next_out = (uint8_t *)(&tgt[kHeaderSize]); - stream.avail_out = (size_t)(*tgtsize); + stream.avail_out = (size_t)(*tgtsize) - kHeaderSize; returnStatus = lzma_code(&stream, LZMA_FINISH); if (returnStatus != LZMA_STREAM_END) { @@ -117,7 +117,7 @@ void R__unzipLZMA(int *srcsize, unsigned char *src, int *tgtsize, unsigned char } stream.next_in = (const uint8_t *)(&src[kHeaderSize]); - stream.avail_in = (size_t)(*srcsize); + stream.avail_in = (size_t)(*srcsize) - kHeaderSize; stream.next_out = (uint8_t *)tgt; stream.avail_out = (size_t)(*tgtsize); diff --git a/core/meta/inc/TClass.h b/core/meta/inc/TClass.h index ec1842e891d76..2c94343377ef9 100644 --- a/core/meta/inc/TClass.h +++ b/core/meta/inc/TClass.h @@ -548,7 +548,7 @@ friend class TStreamerInfo; void SetContextMenuTitle(const char *title); void SetCurrentStreamerInfo(TVirtualStreamerInfo *info); void SetGlobalIsA(IsAGlobalFunc_t); - void SetDeclFile(const char *name, int line) { fDeclFileName = name; fDeclFileLine = line; } + void SetDeclFile(const char *name, Short_t line) { fDeclFileName = name; fDeclFileLine = line; } void SetDelete(ROOT::DelFunc_t deleteFunc); void SetDeleteArray(ROOT::DelArrFunc_t deleteArrayFunc); void SetDirectoryAutoAdd(ROOT::DirAutoAdd_t dirAutoAddFunc); diff --git a/core/meta/inc/TDictionary.h b/core/meta/inc/TDictionary.h index 04546c4ac37d4..facf5c31a6513 100644 --- a/core/meta/inc/TDictionary.h +++ b/core/meta/inc/TDictionary.h @@ -143,7 +143,8 @@ enum EClassProperty { kClassHasImplicitDtor = 0x00000200, kClassHasDtor = 0x00000300, kClassHasVirtual = 0x00001000, - kClassIsAbstract = 0x00002000 + kClassIsAbstract = 0x00002000, + kClassIsAggregate = 0x00004000 }; enum ERefTypeValues { diff --git a/core/metacling/src/CMakeLists.txt b/core/metacling/src/CMakeLists.txt index feef0c08d6cd5..2f0bb8a8d5d4c 100644 --- a/core/metacling/src/CMakeLists.txt +++ b/core/metacling/src/CMakeLists.txt @@ -135,6 +135,12 @@ if(MSVC) ) endif() + if(MSVC_VERSION GREATER_EQUAL 1939) + set(cling_exports ${cling_exports} + _Thrd_sleep_for + ) + endif() + set(cling_exports ${cling_exports} _Smtx_lock_shared _Smtx_unlock_shared diff --git a/core/metacling/src/TClingClassInfo.cxx b/core/metacling/src/TClingClassInfo.cxx index c45ea6322881a..66fdd3c3ab48d 100644 --- a/core/metacling/src/TClingClassInfo.cxx +++ b/core/metacling/src/TClingClassInfo.cxx @@ -194,6 +194,10 @@ long TClingClassInfo::ClassProperty() const if (CRD->isPolymorphic()) { property |= kClassHasVirtual; } + if (CRD->isAggregate() || CRD->isPOD()) { + // according to the C++ standard, being a POD implies being an aggregate + property |= kClassIsAggregate; + } return property; } diff --git a/core/multiproc/inc/ROOT/TProcessExecutor.hxx b/core/multiproc/inc/ROOT/TProcessExecutor.hxx index b2544ec6c2882..3dca653c9c089 100644 --- a/core/multiproc/inc/ROOT/TProcessExecutor.hxx +++ b/core/multiproc/inc/ROOT/TProcessExecutor.hxx @@ -37,9 +37,6 @@ namespace ROOT { class TProcessExecutor : public TExecutorCRTP, private TMPClient { friend TExecutorCRTP; - template - using InvokeResult_t = ROOT::TypeTraits::InvokeResult_t; - public: explicit TProcessExecutor(unsigned nWorkers = 0); //default number of workers is the number of processors ~TProcessExecutor() = default; @@ -55,11 +52,11 @@ public: // Redefinition of the MapReduce classes of the base class, to adapt them to // TProcessExecutor's logic using TExecutorCRTP::MapReduce; - template> + template> auto MapReduce(F func, unsigned nTimes, R redfunc) -> InvokeResult_t; - template> + template> auto MapReduce(F func, std::vector &args, R redfunc) -> InvokeResult_t; - template> + template> auto MapReduce(F func, const std::vector &args, R redfunc) -> InvokeResult_t; // Reduce @@ -77,13 +74,13 @@ public: private: // Implementation of the Map functions declared in the parent class (TExecutorCRTP) // - template> + template> auto MapImpl(F func, unsigned nTimes) -> std::vector>; - template> + template> auto MapImpl(F func, ROOT::TSeq args) -> std::vector>; - template> + template> auto MapImpl(F func, std::vector &args) -> std::vector>; - template> + template> auto MapImpl(F func, const std::vector &args) -> std::vector>; template void Collect(std::vector &reslist); diff --git a/core/rint/test/TTabComTests.cxx b/core/rint/test/TTabComTests.cxx index b6986af8d650e..180db11e8db8b 100644 --- a/core/rint/test/TTabComTests.cxx +++ b/core/rint/test/TTabComTests.cxx @@ -68,7 +68,7 @@ TEST(TTabComTests, CompleteTH1) // FIXME: See ROOT-10989 " TH1DModel" #endif - " TH1Editor TH1F TH1I TH1K TH1S"; + " TH1Editor TH1F TH1I TH1K TH1L TH1S"; ASSERT_STREQ(expected.c_str(), GetCompletions("TH1").c_str()); } diff --git a/core/unix/inc/TUnixSystem.h b/core/unix/inc/TUnixSystem.h index a556ba18cd207..44231d98b8a4d 100644 --- a/core/unix/inc/TUnixSystem.h +++ b/core/unix/inc/TUnixSystem.h @@ -132,7 +132,7 @@ class TUnixSystem : public TSystem { const char *HomeDirectory(const char *userName = nullptr) override; std::string GetHomeDirectory(const char *userName = nullptr) const override; const char *TempDirectory() const override; - FILE *TempFileName(TString &base, const char *dir = nullptr) override; + FILE *TempFileName(TString &base, const char *dir = nullptr, const char *suffix = nullptr) override; //---- Paths & Files ---------------------------------------- const char *PrependPathName(const char *dir, TString& name) override; diff --git a/core/unix/src/TUnixSystem.cxx b/core/unix/src/TUnixSystem.cxx index eeade1dd6f4df..b972ded808e25 100644 --- a/core/unix/src/TUnixSystem.cxx +++ b/core/unix/src/TUnixSystem.cxx @@ -1481,20 +1481,25 @@ const char *TUnixSystem::TempDirectory() const /// Create a secure temporary file by appending a unique /// 6 letter string to base. The file will be created in /// a standard (system) directory or in the directory -/// provided in dir. The full filename is returned in base +/// provided in dir. Optionally one can provide suffix +/// append to the final name - like extension ".txt" or ".html". +/// The full filename is returned in base /// and a filepointer is returned for safely writing to the file /// (this avoids certain security problems). Returns 0 in case /// of error. -FILE *TUnixSystem::TempFileName(TString &base, const char *dir) +FILE *TUnixSystem::TempFileName(TString &base, const char *dir, const char *suffix) { char *b = ConcatFileName(dir ? dir : TempDirectory(), base); base = b; base += "XXXXXX"; + const bool hasSuffix = suffix && *suffix; + if (hasSuffix) + base.Append(suffix); delete [] b; char *arg = StrDup(base); - int fd = mkstemp(arg); + int fd = hasSuffix ? mkstemps(arg, strlen(suffix)) : mkstemp(arg); base = arg; delete [] arg; diff --git a/core/winnt/inc/TWinNTSystem.h b/core/winnt/inc/TWinNTSystem.h index 3d3d4a3b838c6..f5bea78e25f3e 100644 --- a/core/winnt/inc/TWinNTSystem.h +++ b/core/winnt/inc/TWinNTSystem.h @@ -152,7 +152,7 @@ class TWinNTSystem : public TSystem { FILE *OpenPipe(const char *shellcmd, const char *mode) override; int ClosePipe(FILE *pipe) override; int GetPid() override; - + [[ noreturn ]] void Exit (int code, Bool_t mode = kTRUE) override; [[ noreturn ]] void Abort (int code = 0) override; @@ -175,7 +175,7 @@ class TWinNTSystem : public TSystem { const char *HomeDirectory(const char *userName=0) override; std::string GetHomeDirectory(const char *userName = nullptr) const override; const char *TempDirectory() const override; - FILE *TempFileName(TString &base, const char *dir = nullptr) override; + FILE *TempFileName(TString &base, const char *dir = nullptr, const char *suffix = nullptr) override; //---- Users & Groups --------------------------------------- Int_t GetUid(const char *user = nullptr) override; diff --git a/core/winnt/src/TWinNTSystem.cxx b/core/winnt/src/TWinNTSystem.cxx index 52adcfba1f3a5..86ea79f6ef4e0 100644 --- a/core/winnt/src/TWinNTSystem.cxx +++ b/core/winnt/src/TWinNTSystem.cxx @@ -2256,20 +2256,44 @@ const char *TWinNTSystem::TempDirectory() const /// Create a secure temporary file by appending a unique /// 6 letter string to base. The file will be created in /// a standard (system) directory or in the directory -/// provided in dir. The full filename is returned in base +/// provided in dir. Optionally one can provide suffix +/// append to the final name - like extension ".txt" or ".html". +/// The full filename is returned in base /// and a filepointer is returned for safely writing to the file /// (this avoids certain security problems). Returns 0 in case /// of error. -FILE *TWinNTSystem::TempFileName(TString &base, const char *dir) +FILE *TWinNTSystem::TempFileName(TString &base, const char *dir, const char *suffix) { char tmpName[MAX_PATH]; - ::GetTempFileName(dir ? dir : TempDirectory(), base.Data(), 0, tmpName); + auto res = ::GetTempFileName(dir ? dir : TempDirectory(), base.Data(), 0, tmpName); + if (res == 0) { + ::SysError("TempFileName", "Fail to generate temporary file name"); + return nullptr; + } + base = tmpName; - FILE *fp = fopen(tmpName, "w+"); + if (suffix && *suffix) { + base.Append(suffix); + + if (!AccessPathName(base, kFileExists)) { + ::SysError("TempFileName", "Temporary file %s already exists", base.Data()); + Unlink(tmpName); + return nullptr; + } + + auto res2 = Rename(tmpName, base.Data()); + if (res2 != 0) { + ::SysError("TempFileName", "Fail to rename temporary file to %s", base.Data()); + Unlink(tmpName); + return nullptr; + } + } + + FILE *fp = fopen(base.Data(), "w+"); - if (!fp) ::SysError("TempFileName", "error opening %s", tmpName); + if (!fp) ::SysError("TempFileName", "error opening %s", base.Data()); return fp; } diff --git a/core/zip/CMakeLists.txt b/core/zip/CMakeLists.txt index 0be3d6c0c5154..72d1bfb756dd8 100644 --- a/core/zip/CMakeLists.txt +++ b/core/zip/CMakeLists.txt @@ -22,3 +22,5 @@ target_include_directories(Core PUBLIC ) ROOT_INSTALL_HEADERS() + +ROOT_ADD_TEST_SUBDIRECTORY(test) diff --git a/core/zip/src/RZip.cxx b/core/zip/src/RZip.cxx index bdd285494095b..471121f3624d4 100644 --- a/core/zip/src/RZip.cxx +++ b/core/zip/src/RZip.cxx @@ -78,14 +78,18 @@ unsigned long R__crc32(unsigned long crc, const unsigned char* buf, unsigned int /* 3 = old */ void R__zipMultipleAlgorithm(int cxlevel, int *srcsize, char *src, int *tgtsize, char *tgt, int *irep, ROOT::RCompressionSetting::EAlgorithm::EValues compressionAlgorithm) { + *irep = 0; + // Performance optimization: avoid compressing tiny source buffers. if (*srcsize < 1 + HDRSIZE + 1) { - *irep = 0; + return; + } + // Correctness check: we need at least enough bytes to prepend the header! + if (*tgtsize <= HDRSIZE) { return; } if (cxlevel <= 0) { - *irep = 0; return; } @@ -197,7 +201,7 @@ static void R__zipZLIB(int cxlevel, int *srcsize, char *src, int *tgtsize, char stream.avail_in = (uInt)(*srcsize); stream.next_out = (Bytef*)(&tgt[HDRSIZE]); - stream.avail_out = (uInt)(*tgtsize); + stream.avail_out = (uInt)(*tgtsize) - HDRSIZE; stream.zalloc = (alloc_func)0; stream.zfree = (free_func)0; diff --git a/core/zip/test/CMakeLists.txt b/core/zip/test/CMakeLists.txt new file mode 100644 index 0000000000000..61b10d7ef40c7 --- /dev/null +++ b/core/zip/test/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 1995-2024, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +ROOT_ADD_GTEST(ZipTest ZipTest.cxx LIBRARIES Core) diff --git a/core/zip/test/ZipTest.cxx b/core/zip/test/ZipTest.cxx new file mode 100644 index 0000000000000..f1b40f7853950 --- /dev/null +++ b/core/zip/test/ZipTest.cxx @@ -0,0 +1,70 @@ +#include +#include + +#include + +#include + +static void testZipBufferSizes(ROOT::RCompressionSetting::EAlgorithm::EValues compressionAlgorithm) +{ + static constexpr size_t BufferSize = 256; + static constexpr size_t MaxBytes = 128; + static_assert(MaxBytes <= BufferSize, "MaxBytes must be smaller than BufferSize"); + static constexpr size_t StartOffset = (BufferSize - MaxBytes) / 2; + // For extra "safety", allocate the buffers on the heap to avoid corrupting the stack should anything go wrong. + std::unique_ptr source(new char[BufferSize]); + std::unique_ptr target(new char[BufferSize]); + + // Fill the buffers with monotonically increasing numbers. This is easy to compress, but that's fine because we scan + // through all possible sizes. + for (size_t i = 0; i < BufferSize; i++) { + source[i] = static_cast(i); + target[i] = static_cast(i); + } + + // Now test all possible combinations of target and source sizes. The outer loop is for the target sizes because that + // allows us to check that nothing got overwritten. + for (size_t targetSize = 1; targetSize <= MaxBytes; targetSize++) { + for (size_t sourceSize = 1; sourceSize <= MaxBytes; sourceSize++) { + for (int cxlevel = 1; cxlevel <= 9; cxlevel++) { + int srcsize = static_cast(sourceSize); + int tgtsize = static_cast(targetSize); + int irep = -1; + R__zipMultipleAlgorithm(cxlevel, &srcsize, source.get(), &tgtsize, target.get() + StartOffset, &irep, + compressionAlgorithm); + + for (size_t i = 0; i < StartOffset; i++) { + EXPECT_EQ(target[i], static_cast(i)); + } + for (size_t i = StartOffset + targetSize + 1; i < BufferSize; i++) { + EXPECT_EQ(target[i], static_cast(i)); + } + } + } + } +} + +TEST(RZip, ZipBufferSizesOld) +{ + testZipBufferSizes(ROOT::RCompressionSetting::EAlgorithm::kOldCompressionAlgo); +} + +TEST(RZip, ZipBufferSizesZLIB) +{ + testZipBufferSizes(ROOT::RCompressionSetting::EAlgorithm::kZLIB); +} + +TEST(RZip, ZipBufferSizesLZMA) +{ + testZipBufferSizes(ROOT::RCompressionSetting::EAlgorithm::kLZMA); +} + +TEST(RZip, ZipBufferSizesLZ4) +{ + testZipBufferSizes(ROOT::RCompressionSetting::EAlgorithm::kLZ4); +} + +TEST(RZip, ZipBufferSizesZSTD) +{ + testZipBufferSizes(ROOT::RCompressionSetting::EAlgorithm::kZSTD); +} diff --git a/documentation/doxygen/makeinput.sh b/documentation/doxygen/makeinput.sh index 120e096904e03..ed2519accd21a 100755 --- a/documentation/doxygen/makeinput.sh +++ b/documentation/doxygen/makeinput.sh @@ -48,7 +48,6 @@ echo " ../../hist/ \\" >> Doxyfile_INPUT echo " ../../html/ \\" >> Doxyfile_INPUT echo " ../../io/doc/TFile \\" >> Doxyfile_INPUT echo " ../../io/dcache/ \\" >> Doxyfile_INPUT -echo " ../../io/gfal/ \\" >> Doxyfile_INPUT echo " ../../io/io/ \\" >> Doxyfile_INPUT echo " ../../io/sql/ \\" >> Doxyfile_INPUT echo " ../../io/xml/ \\" >> Doxyfile_INPUT diff --git a/documentation/primer/histograms.md b/documentation/primer/histograms.md index 61ac3185a1ac5..fedf6083e2931 100644 --- a/documentation/primer/histograms.md +++ b/documentation/primer/histograms.md @@ -149,4 +149,4 @@ macros shows how it looks for 2D histograms: ![Two 2D histograms stack on top of each other.\label{f56}][f56] -[^4]: To optimise the memory usage you might go for one byte (TH1C), short (TH1S), integer (TH1I) or double-precision (TH1D) bin-content. +[^4]: To optimise the memory usage you might go for one byte (TH1C), short (TH1S), integer (TH1I), long64 (TH1L) or double-precision (TH1D) bin-content. diff --git a/documentation/users-guide/Histograms.md b/documentation/users-guide/Histograms.md index 7d431d6cca3d9..89e0de9552d54 100644 --- a/documentation/users-guide/Histograms.md +++ b/documentation/users-guide/Histograms.md @@ -18,20 +18,23 @@ classes are provided for one-dimensional, two-dimensional and three-dimensional classes. The histogram classes are split into further categories, depending on the set of possible bin values: -- **`TH1C, TH2C and TH3C`** contain one byte per bin (maximum bin - content = 255) +- **`TH1C, TH2C and TH3C`** contain one char (one byte) per bin + (maximum bin content = 255) -- **`TH1S, TH2S and TH3S`** contain one short per bin (maximum bin - content = 65 535). +- **`TH1S, TH2S and TH3S`** contain one short (two bytes) per bin + (maximum bin content = 65 535). -- **`TH1I, TH2I and TH3I`** contain one integer per bin (maximum bin - content = 2 147 483 647). +- **`TH1I, TH2I and TH3I`** contain one integer (four bytes) per bin + (maximum bin content = 2 147 483 647). + +- **`TH1L, TH2L and TH3L`** contain one long64 (eight bytes) per bin + (maximum bin content = 9 223 372 036 854 775 807). -- **`TH1F, TH2F and TH3F`** contain one float per bin (maximum - precision = 7 digits). +- **`TH1F, TH2F and TH3F`** contain one float (four bytes) per bin + (maximum precision = 7 digits). -- **`TH1D, TH2D and TH3D`** contain one double per bin (maximum - precision = 14 digits). +- **`TH1D, TH2D and TH3D`** contain one double (eight bytes) per bin + (maximum precision = 14 digits). ROOT also supports profile histograms, which constitute an elegant replacement of two-dimensional histograms in many cases. The diff --git a/documentation/users-guide/InstallandBuild.md b/documentation/users-guide/InstallandBuild.md index 5793e9209c645..03ec02b0042f8 100644 --- a/documentation/users-guide/InstallandBuild.md +++ b/documentation/users-guide/InstallandBuild.md @@ -650,7 +650,7 @@ namespace descriptor ends with - the namespace is not a part of the filename. Extend in private .rootrc with a +Url.Special line. ``` {.cpp} -Url.Special: file: rfio: hpss: castor: gfal: dcache: +Url.Special: file: rfio: hpss: castor: dcache: +Url.Special: /alien/- /castor/ ``` diff --git a/etc/plugins/TFile/P050_TGFALFile.C b/etc/plugins/TFile/P050_TGFALFile.C deleted file mode 100644 index 796a752589578..0000000000000 --- a/etc/plugins/TFile/P050_TGFALFile.C +++ /dev/null @@ -1,5 +0,0 @@ -void P050_TGFALFile() -{ - gPluginMgr->AddHandler("TFile", "^gfal:", "TGFALFile", - "GFAL", "TGFALFile(const char*,Option_t*,const char*,Int_t)"); -} diff --git a/geom/geom/inc/TGeoManager.h b/geom/geom/inc/TGeoManager.h index 1a9a7950aae23..f34ce7802aa66 100644 --- a/geom/geom/inc/TGeoManager.h +++ b/geom/geom/inc/TGeoManager.h @@ -252,7 +252,7 @@ class TGeoManager : public TNamed { Option_t *option = "ob"); // *MENU* void CheckGeometry(Option_t *option = ""); void CheckOverlaps(Double_t ovlp = 0.1, Option_t *option = ""); // *MENU* - void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = ""); // *MENU* + void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = "", Double_t safety = 0.); // *MENU* void CheckShape(TGeoShape *shape, Int_t testNo, Int_t nsamples, Option_t *option); void ConvertReflections(); void DrawCurrentPoint(Int_t color = 2); // *MENU* diff --git a/geom/geom/inc/TGeoMaterial.h b/geom/geom/inc/TGeoMaterial.h index 63d4bd61f7aa3..220a235439a04 100644 --- a/geom/geom/inc/TGeoMaterial.h +++ b/geom/geom/inc/TGeoMaterial.h @@ -80,7 +80,10 @@ class TGeoMaterial : public TNamed, public TAttFill { Int_t GetNproperties() const { return fProperties.GetSize(); } Int_t GetNconstProperties() const { return fConstProperties.GetSize(); } const char *GetPropertyRef(const char *property) const; - const char *GetPropertyRef(Int_t i) const { return (fProperties.At(i) ? fProperties.At(i)->GetTitle() : nullptr); } + const char *GetPropertyRef(Int_t i) const + { + return (fProperties.At(i) ? fProperties.At(i)->GetTitle() : nullptr); + } Double_t GetConstProperty(const char *property, Bool_t *error = nullptr) const; Double_t GetConstProperty(Int_t i, Bool_t *error = nullptr) const; const char *GetConstPropertyRef(const char *property) const; diff --git a/geom/geom/inc/TGeoOpticalSurface.h b/geom/geom/inc/TGeoOpticalSurface.h index 823c06a68c4d1..0f9c43192187c 100644 --- a/geom/geom/inc/TGeoOpticalSurface.h +++ b/geom/geom/inc/TGeoOpticalSurface.h @@ -102,7 +102,8 @@ class TGeoOpticalSurface : public TNamed { Double_t fSigmaAlpha = 0.0; // The sigma of micro-facet polar angle Double_t fPolish = 0.0; // Polish parameter in glisur model - TList fProperties; // List of surface properties + TList fProperties; // List of surface properties + TList fConstProperties; // user-defined constant properties // No copy TGeoOpticalSurface(const TGeoOpticalSurface &) = delete; @@ -119,11 +120,25 @@ class TGeoOpticalSurface : public TNamed { // Accessors bool AddProperty(const char *property, const char *ref); + bool AddConstProperty(const char *property, const char *ref); const char *GetPropertyRef(const char *property); + const char *GetPropertyRef(Int_t i) const + { + return (fProperties.At(i) ? fProperties.At(i)->GetTitle() : nullptr); + } + const char *GetConstPropertyRef(const char *property) const; + const char *GetConstPropertyRef(Int_t i) const + { + return (fConstProperties.At(i) ? fConstProperties.At(i)->GetTitle() : nullptr); + } TList const &GetProperties() const { return fProperties; } + TList const &GetConstProperties() const { return fConstProperties; } Int_t GetNproperties() const { return fProperties.GetSize(); } + Int_t GetNconstProperties() const { return fConstProperties.GetSize(); } TGDMLMatrix *GetProperty(const char *name) const; TGDMLMatrix *GetProperty(Int_t i) const; + Double_t GetConstProperty(const char *property, Bool_t *error = nullptr) const; + Double_t GetConstProperty(Int_t i, Bool_t *error = nullptr) const; ESurfaceType GetType() const { return fType; } ESurfaceModel GetModel() const { return fModel; } ESurfaceFinish GetFinish() const { return fFinish; } @@ -147,7 +162,7 @@ class TGeoOpticalSurface : public TNamed { static ESurfaceFinish StringToFinish(const char *finish); static const char *FinishToString(ESurfaceFinish finish); - ClassDefOverride(TGeoOpticalSurface, 1) // Class representing an optical surface + ClassDefOverride(TGeoOpticalSurface, 2) // Class representing an optical surface }; //////////////////////////////////////////////////////////////////////////// diff --git a/geom/geom/inc/TVirtualGeoPainter.h b/geom/geom/inc/TVirtualGeoPainter.h index 9f27a73fcfd72..e1ef2602edb69 100644 --- a/geom/geom/inc/TVirtualGeoPainter.h +++ b/geom/geom/inc/TVirtualGeoPainter.h @@ -58,7 +58,7 @@ class TVirtualGeoPainter : public TObject { virtual TVirtualGeoTrack *AddTrack(Int_t id, Int_t pdgcode, TObject *particle) = 0; virtual void AddTrackPoint(Double_t *point, Double_t *box, Bool_t reset = kFALSE) = 0; virtual void BombTranslation(const Double_t *tr, Double_t *bombtr) = 0; - virtual void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = "") = 0; + virtual void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = "", Double_t safety = 0.) = 0; virtual void CheckShape(TGeoShape *shape, Int_t testNo, Int_t nsamples, Option_t *option) = 0; virtual void CheckBoundaryErrors(Int_t ntracks = 1000000, Double_t radius = -1.) = 0; virtual void CheckBoundaryReference(Int_t icheck = -1) = 0; diff --git a/geom/geom/src/TGeoManager.cxx b/geom/geom/src/TGeoManager.cxx index 9958482b9efb3..4ac4dce8a04f6 100644 --- a/geom/geom/src/TGeoManager.cxx +++ b/geom/geom/src/TGeoManager.cxx @@ -210,7 +210,9 @@ from the painting package. This method is drawing the daughters of the volume containing the point one level down, printing the path to the deepest physical node holding this point. It also computes the closest distance to any boundary. The point will be drawn -in red. +in red, as well as a sphere having this closest distance as radius. In case a +non-zero distance is given by the user as fifth argument of CheckPoint, this +distance will be used as radius of the safety sphere. \image html geom_checkpoint.jpg @@ -3765,9 +3767,9 @@ void TGeoManager::CheckBoundaryReference(Int_t icheck) //////////////////////////////////////////////////////////////////////////////// /// Classify a given point. See TGeoChecker::CheckPoint(). -void TGeoManager::CheckPoint(Double_t x, Double_t y, Double_t z, Option_t *option) +void TGeoManager::CheckPoint(Double_t x, Double_t y, Double_t z, Option_t *option, Double_t safety) { - GetGeomPainter()->CheckPoint(x, y, z, option); + GetGeomPainter()->CheckPoint(x, y, z, option, safety); } //////////////////////////////////////////////////////////////////////////////// diff --git a/geom/geom/src/TGeoOpticalSurface.cxx b/geom/geom/src/TGeoOpticalSurface.cxx index 59f0545a6d281..1913dd15db037 100644 --- a/geom/geom/src/TGeoOpticalSurface.cxx +++ b/geom/geom/src/TGeoOpticalSurface.cxx @@ -268,6 +268,40 @@ TGDMLMatrix *TGeoOpticalSurface::GetProperty(Int_t i) const return gGeoManager->GetGDMLMatrix(prop->GetTitle()); } +//_____________________________________________________________________________ +const char *TGeoOpticalSurface::GetConstPropertyRef(const char *property) const +{ + // Find reference for a given constant property + TNamed *prop = (TNamed *)fConstProperties.FindObject(property); + return (prop) ? prop->GetTitle() : nullptr; +} + +//_____________________________________________________________________________ +Double_t TGeoOpticalSurface::GetConstProperty(const char *property, Bool_t *err) const +{ + // Find reference for a given constant property + TNamed *prop = (TNamed *)fConstProperties.FindObject(property); + if (!prop) { + if (err) + *err = kTRUE; + return 0.; + } + return gGeoManager->GetProperty(prop->GetTitle(), err); +} + +//_____________________________________________________________________________ +Double_t TGeoOpticalSurface::GetConstProperty(Int_t i, Bool_t *err) const +{ + // Find reference for a given constant property + TNamed *prop = (TNamed *)fConstProperties.At(i); + if (!prop) { + if (err) + *err = kTRUE; + return 0.; + } + return gGeoManager->GetProperty(prop->GetTitle(), err); +} + //_____________________________________________________________________________ bool TGeoOpticalSurface::AddProperty(const char *property, const char *ref) { @@ -280,6 +314,18 @@ bool TGeoOpticalSurface::AddProperty(const char *property, const char *ref) return true; } +//_____________________________________________________________________________ +bool TGeoOpticalSurface::AddConstProperty(const char *property, const char *ref) +{ + fConstProperties.SetOwner(); + if (GetConstPropertyRef(property)) { + Error("AddConstProperty", "Constant property %s already added to material %s", property, GetName()); + return false; + } + fConstProperties.Add(new TNamed(property, ref)); + return true; +} + //_____________________________________________________________________________ void TGeoOpticalSurface::Print(Option_t *) const { @@ -293,6 +339,16 @@ void TGeoOpticalSurface::Print(Option_t *) const while ((property = (TNamed *)next())) printf(" property: %s ref: %s\n", property->GetName(), property->GetTitle()); } + if (fConstProperties.GetSize()) { + TIter next(&fConstProperties); + TNamed *property; + Bool_t err; + while ((property = (TNamed *)next())) { + Double_t value = this->GetConstProperty(property->GetName(), &err); + printf(" constant: %s ref: %s value: %g\n", + property->GetName(), property->GetTitle(), value); + } + } } ClassImp(TGeoSkinSurface); diff --git a/geom/geompainter/inc/TGeoChecker.h b/geom/geompainter/inc/TGeoChecker.h index 16e17e4734dec..b88cf78271678 100644 --- a/geom/geompainter/inc/TGeoChecker.h +++ b/geom/geompainter/inc/TGeoChecker.h @@ -70,7 +70,7 @@ class TGeoChecker : public TObject { void CheckGeometry(Int_t nrays, Double_t startx, Double_t starty, Double_t startz) const; void CheckOverlaps(const TGeoVolume *vol, Double_t ovlp = 0.1, Option_t *option = ""); void CheckOverlapsBySampling(TGeoVolume *vol, Double_t ovlp = 0.1, Int_t npoints = 1000000) const; - void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = ""); + void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = "", Double_t safety = 0.); void CheckShape(TGeoShape *shape, Int_t testNo, Int_t nsamples, Option_t *option); Double_t CheckVoxels(TGeoVolume *vol, TGeoVoxelFinder *voxels, Double_t *xyz, Int_t npoints); TH2F *LegoPlot(Int_t ntheta = 60, Double_t themin = 0., Double_t themax = 180., Int_t nphi = 90, diff --git a/geom/geompainter/inc/TGeoPainter.h b/geom/geompainter/inc/TGeoPainter.h index 846630255af18..13a00b472d5db 100644 --- a/geom/geompainter/inc/TGeoPainter.h +++ b/geom/geompainter/inc/TGeoPainter.h @@ -89,7 +89,7 @@ class TGeoPainter : public TVirtualGeoPainter { const Double_t *vertex = nullptr) override; void CheckGeometry(Int_t nrays, Double_t startx, Double_t starty, Double_t startz) const override; void CheckEdit(); - void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = "") override; + void CheckPoint(Double_t x = 0, Double_t y = 0, Double_t z = 0, Option_t *option = "", Double_t safety = 0.) override; void CheckShape(TGeoShape *shape, Int_t testNo, Int_t nsamples, Option_t *option) override; void CheckOverlaps(const TGeoVolume *vol, Double_t ovlp = 0.1, Option_t *option = "") const override; Int_t CountNodes(TGeoVolume *vol, Int_t level) const; diff --git a/geom/geompainter/src/TGeoChecker.cxx b/geom/geompainter/src/TGeoChecker.cxx index fc93cc8b82682..68c1b5638570d 100644 --- a/geom/geompainter/src/TGeoChecker.cxx +++ b/geom/geompainter/src/TGeoChecker.cxx @@ -1193,7 +1193,7 @@ TGeoOverlap *TGeoChecker::MakeCheckOverlap(const char *name, TGeoVolume *vol1, T /// Check illegal overlaps for volume VOL within a limit OVLP by sampling npoints /// inside the volume shape. -void TGeoChecker::CheckOverlapsBySampling(TGeoVolume *vol, Double_t /* ovlp */, Int_t npoints) const +void TGeoChecker::CheckOverlapsBySampling(TGeoVolume *vol, Double_t ovlp, Int_t npoints) const { Int_t nd = vol->GetNdaughters(); if (nd < 2) @@ -1268,7 +1268,7 @@ void TGeoChecker::CheckOverlapsBySampling(TGeoVolume *vol, Double_t /* ovlp */, } // The point is inside 2 or more daughters, check safety safe = shape->Safety(local, kTRUE); - // if (safe < ovlp) continue; + if (safe < ovlp) continue; // We really have found an overlap -> store the point in a container iovlp++; if (!novlps) { @@ -1689,7 +1689,7 @@ void TGeoChecker::PrintOverlaps() const /// Generates a report regarding the path to the node containing this point and the distance to /// the closest boundary. -void TGeoChecker::CheckPoint(Double_t x, Double_t y, Double_t z, Option_t *) +void TGeoChecker::CheckPoint(Double_t x, Double_t y, Double_t z, Option_t *, Double_t safety) { Double_t point[3]; Double_t local[3]; @@ -1712,7 +1712,7 @@ void TGeoChecker::CheckPoint(Double_t x, Double_t y, Double_t z, Option_t *) if (node) vol = node->GetVolume(); // compute safety distance (distance to boundary ignored) - Double_t close = fGeoManager->Safety(); + Double_t close = (safety > 0.) ? safety : fGeoManager->Safety(); printf("Safety radius : %f\n", close); if (close > 1E-4) { TGeoVolume *sph = fGeoManager->MakeSphere("SAFETY", vol->GetMedium(), 0, close, 0, 180, 0, 360); diff --git a/geom/geompainter/src/TGeoPainter.cxx b/geom/geompainter/src/TGeoPainter.cxx index 2861d15d12789..09338de2b774a 100644 --- a/geom/geompainter/src/TGeoPainter.cxx +++ b/geom/geompainter/src/TGeoPainter.cxx @@ -239,9 +239,9 @@ void TGeoPainter::CheckOverlaps(const TGeoVolume *vol, Double_t ovlp, Option_t * //////////////////////////////////////////////////////////////////////////////// /// Check current point in the geometry. -void TGeoPainter::CheckPoint(Double_t x, Double_t y, Double_t z, Option_t *option) +void TGeoPainter::CheckPoint(Double_t x, Double_t y, Double_t z, Option_t *option, Double_t safety) { - fChecker->CheckPoint(x, y, z, option); + fChecker->CheckPoint(x, y, z, option, safety); } //////////////////////////////////////////////////////////////////////////////// diff --git a/geom/webviewer/inc/ROOT/RGeoPainter.hxx b/geom/webviewer/inc/ROOT/RGeoPainter.hxx index b799d1d3393a3..904f6e39a7c1b 100644 --- a/geom/webviewer/inc/ROOT/RGeoPainter.hxx +++ b/geom/webviewer/inc/ROOT/RGeoPainter.hxx @@ -32,7 +32,7 @@ public: TVirtualGeoTrack *AddTrack(Int_t, Int_t, TObject *) override { return nullptr; } void AddTrackPoint(Double_t *, Double_t *, Bool_t =kFALSE) override {} void BombTranslation(const Double_t *, Double_t *) override {} - void CheckPoint(Double_t =0, Double_t =0, Double_t =0, Option_t * ="") override {} + void CheckPoint(Double_t =0, Double_t =0, Double_t =0, Option_t * ="", Double_t = 0.) override {} void CheckShape(TGeoShape *, Int_t, Int_t, Option_t *) override {} void CheckBoundaryErrors(Int_t =1000000, Double_t =-1.) override {} void CheckBoundaryReference(Int_t =-1) override {} diff --git a/geom/webviewer/inc/ROOT/RGeomViewer.hxx b/geom/webviewer/inc/ROOT/RGeomViewer.hxx index 67a7073b8611e..afb0e709c8969 100644 --- a/geom/webviewer/inc/ROOT/RGeomViewer.hxx +++ b/geom/webviewer/inc/ROOT/RGeomViewer.hxx @@ -60,6 +60,8 @@ public: std::string GetWindowAddr() const; + std::string GetWindowUrl(bool remote); + void SetGeometry(TGeoManager *mgr, const std::string &volname = ""); void SelectVolume(const std::string &volname); diff --git a/geom/webviewer/src/RGeomViewer.cxx b/geom/webviewer/src/RGeomViewer.cxx index ca90356659c27..ead7feaa4fe06 100644 --- a/geom/webviewer/src/RGeomViewer.cxx +++ b/geom/webviewer/src/RGeomViewer.cxx @@ -128,13 +128,22 @@ void RGeomViewer::Show(const RWebDisplayArgs &args, bool always_start_new_browse } ////////////////////////////////////////////////////////////////////////////////////////////// -/// Return URL address of web window used for geometry viewer +/// Return web window address (name) used for geometry viewer std::string RGeomViewer::GetWindowAddr() const { return fWebWindow ? fWebWindow->GetAddr() : ""s; } +////////////////////////////////////////////////////////////////////////////////////////////// +/// Return web window URL which can be used for connection +/// See \ref ROOT::RWebWindow::GetUrl docu for more details + +std::string RGeomViewer::GetWindowUrl(bool remote) +{ + return fWebWindow ? fWebWindow->GetUrl(remote) : ""s; +} + ////////////////////////////////////////////////////////////////////////////////////////////// /// Update geometry drawings in all web displays diff --git a/graf2d/gpad/inc/TPad.h b/graf2d/gpad/inc/TPad.h index 975881f1ca75f..2afee106c7357 100644 --- a/graf2d/gpad/inc/TPad.h +++ b/graf2d/gpad/inc/TPad.h @@ -325,7 +325,7 @@ friend class TWebCanvas; void SetCrosshair(Int_t crhair=1) override; // *TOGGLE* void SetCursor(ECursor cursor) override; void SetDoubleBuffer(Int_t mode=1) override; - void SetDrawOption(Option_t *option="") override; + void SetDrawOption(Option_t * = "") override {} void SetEditable(Bool_t mode=kTRUE) override; // *TOGGLE* void SetFixedAspectRatio(Bool_t fixed = kTRUE) override; // *TOGGLE* void SetGrid(Int_t valuex = 1, Int_t valuey = 1) override { fGridx = valuex; fGridy = valuey; Modified(); } @@ -366,8 +366,8 @@ friend class TWebCanvas; void Update() override; void UpdateAsync() override; - Int_t UtoAbsPixel(Double_t u) const override { return Int_t(fUtoAbsPixelk + u*fUtoPixel); } - Int_t VtoAbsPixel(Double_t v) const override { return Int_t(fVtoAbsPixelk + v*fVtoPixel); } + Int_t UtoAbsPixel(Double_t u) const override; + Int_t VtoAbsPixel(Double_t v) const override; Int_t UtoPixel(Double_t u) const override; Int_t VtoPixel(Double_t v) const override; TObject *WaitPrimitive(const char *pname="", const char *emode="") override; @@ -415,134 +415,5 @@ friend class TWebCanvas; ClassDefOverride(TPad,13) //A Graphics pad }; - -//______________________________________________________________________________ -inline void TPad::Modified(Bool_t flag) -{ - if (!fModified && flag) Emit("Modified()"); - fModified = flag; -} - - -//______________________________________________________________________________ -inline void TPad::AbsPixeltoXY(Int_t xpixel, Int_t ypixel, Double_t &x, Double_t &y) -{ - x = AbsPixeltoX(xpixel); - y = AbsPixeltoY(ypixel); -} - - -//______________________________________________________________________________ -inline Double_t TPad::PixeltoX(Int_t px) -{ - if (fAbsCoord) return fAbsPixeltoXk + px*fPixeltoX; - else return fPixeltoXk + px*fPixeltoX; -} - - -//______________________________________________________________________________ -inline Double_t TPad::PixeltoY(Int_t py) -{ - if (fAbsCoord) return fAbsPixeltoYk + py*fPixeltoY; - else return fPixeltoYk + py*fPixeltoY; -} - - -//______________________________________________________________________________ -inline void TPad::PixeltoXY(Int_t xpixel, Int_t ypixel, Double_t &x, Double_t &y) -{ - x = PixeltoX(xpixel); - y = PixeltoY(ypixel); -} - - -//______________________________________________________________________________ -inline Int_t TPad::UtoPixel(Double_t u) const -{ - Double_t val; - if (fAbsCoord) val = fUtoAbsPixelk + u*fUtoPixel; - else val = u*fUtoPixel; - if (val < -kMaxPixel) return -kMaxPixel; - if (val > kMaxPixel) return kMaxPixel; - return Int_t(val); -} - - -//______________________________________________________________________________ -inline Int_t TPad::VtoPixel(Double_t v) const -{ - Double_t val; - if (fAbsCoord) val = fVtoAbsPixelk + v*fVtoPixel; - else val = fVtoPixelk + v*fVtoPixel; - if (val < -kMaxPixel) return -kMaxPixel; - if (val > kMaxPixel) return kMaxPixel; - return Int_t(val); -} - - -//______________________________________________________________________________ -inline Int_t TPad::XtoAbsPixel(Double_t x) const -{ - Double_t val = fXtoAbsPixelk + x*fXtoPixel; - if (val < -kMaxPixel) return -kMaxPixel; - if (val > kMaxPixel) return kMaxPixel; - return Int_t(val); -} - - -//______________________________________________________________________________ -inline Int_t TPad::XtoPixel(Double_t x) const -{ - Double_t val; - if (fAbsCoord) val = fXtoAbsPixelk + x*fXtoPixel; - else val = fXtoPixelk + x*fXtoPixel; - if (val < -kMaxPixel) return -kMaxPixel; - if (val > kMaxPixel) return kMaxPixel; - return Int_t(val); -} - - -//______________________________________________________________________________ -inline Int_t TPad::YtoAbsPixel(Double_t y) const -{ - Double_t val = fYtoAbsPixelk + y*fYtoPixel; - if (val < -kMaxPixel) return -kMaxPixel; - if (val > kMaxPixel) return kMaxPixel; - return Int_t(val); -} - - -//______________________________________________________________________________ -inline Int_t TPad::YtoPixel(Double_t y) const -{ - Double_t val; - if (fAbsCoord) val = fYtoAbsPixelk + y*fYtoPixel; - else val = fYtoPixelk + y*fYtoPixel; - if (val < -kMaxPixel) return -kMaxPixel; - if (val > kMaxPixel) return kMaxPixel; - return Int_t(val); -} - - -//______________________________________________________________________________ -inline void TPad::XYtoAbsPixel(Double_t x, Double_t y, Int_t &xpixel, Int_t &ypixel) const -{ - xpixel = XtoAbsPixel(x); - ypixel = YtoAbsPixel(y); -} - - -//______________________________________________________________________________ -inline void TPad::XYtoPixel(Double_t x, Double_t y, Int_t &xpixel, Int_t &ypixel) const -{ - xpixel = XtoPixel(x); - ypixel = YtoPixel(y); -} - - -//______________________________________________________________________________ -inline void TPad::SetDrawOption(Option_t *) -{ } - #endif diff --git a/graf2d/gpad/src/TPad.cxx b/graf2d/gpad/src/TPad.cxx index 87e325e1757e5..a8ade809d661c 100644 --- a/graf2d/gpad/src/TPad.cxx +++ b/graf2d/gpad/src/TPad.cxx @@ -5538,7 +5538,7 @@ void TPad::ResizePad(Option_t *option) Double_t pyrange = -fAbsHNDC*wh; // Linear X axis - Double_t rounding = 0.00005; + Double_t rounding = 0.; // was used before to adjust somehow wrong int trunctation by coordiantes transformation Double_t xrange = fX2 - fX1; fXtoAbsPixelk = rounding + pxlow - pxrange*fX1/xrange; //origin at left fXtoPixelk = rounding + -pxrange*fX1/xrange; @@ -7235,3 +7235,158 @@ void TPad::SetBBoxY2(const Int_t y) fHNDC = fYUpNDC - fYlowNDC; ResizePad(); } + +//////////////////////////////////////////////////////////////////////////////// +/// Mark pad modified +/// Will be repainted when TCanvas::Update() will be called next time + +void TPad::Modified(Bool_t flag) +{ + if (!fModified && flag) Emit("Modified()"); + fModified = flag; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert absolute pixel into X/Y coordinates + +void TPad::AbsPixeltoXY(Int_t xpixel, Int_t ypixel, Double_t &x, Double_t &y) +{ + x = AbsPixeltoX(xpixel); + y = AbsPixeltoY(ypixel); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Convert pixel to X coordinate + +Double_t TPad::PixeltoX(Int_t px) +{ + if (fAbsCoord) return fAbsPixeltoXk + px*fPixeltoX; + else return fPixeltoXk + px*fPixeltoX; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert pixel to Y coordinate + +Double_t TPad::PixeltoY(Int_t py) +{ + if (fAbsCoord) return fAbsPixeltoYk + py*fPixeltoY; + else return fPixeltoYk + py*fPixeltoY; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert pixel to X/Y coordinates + +void TPad::PixeltoXY(Int_t xpixel, Int_t ypixel, Double_t &x, Double_t &y) +{ + x = PixeltoX(xpixel); + y = PixeltoY(ypixel); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert X/Y into absolute pixel coordinates + +void TPad::XYtoAbsPixel(Double_t x, Double_t y, Int_t &xpixel, Int_t &ypixel) const +{ + xpixel = XtoAbsPixel(x); + ypixel = YtoAbsPixel(y); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert X/Y into pixel coordinates + +void TPad::XYtoPixel(Double_t x, Double_t y, Int_t &xpixel, Int_t &ypixel) const +{ + xpixel = XtoPixel(x); + ypixel = YtoPixel(y); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert X NDC to pixel + +Int_t TPad::UtoPixel(Double_t u) const +{ + Double_t val; + if (fAbsCoord) val = fUtoAbsPixelk + u*fUtoPixel; + else val = u*fUtoPixel; + if (val < -kMaxPixel) return -kMaxPixel; + if (val > kMaxPixel) return kMaxPixel; + return TMath::Nint(val); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert Y NDC to pixel + +Int_t TPad::VtoPixel(Double_t v) const +{ + Double_t val; + if (fAbsCoord) val = fVtoAbsPixelk + v*fVtoPixel; + else val = fVtoPixelk + v*fVtoPixel; + if (val < -kMaxPixel) return -kMaxPixel; + if (val > kMaxPixel) return kMaxPixel; + return TMath::Nint(val); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert X NDC to absolute pixel + +Int_t TPad::UtoAbsPixel(Double_t u) const +{ + return TMath::Nint(fUtoAbsPixelk + u*fUtoPixel); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert Y NDC to absolute pixel + +Int_t TPad::VtoAbsPixel(Double_t v) const +{ + return TMath::Nint(fVtoAbsPixelk + v*fVtoPixel); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert X coordinate to absolute pixel + +Int_t TPad::XtoAbsPixel(Double_t x) const +{ + Double_t val = fXtoAbsPixelk + x*fXtoPixel; + if (val < -kMaxPixel) return -kMaxPixel; + if (val > kMaxPixel) return kMaxPixel; + return TMath::Nint(val); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert X coordinate to pixel + +Int_t TPad::XtoPixel(Double_t x) const +{ + Double_t val; + if (fAbsCoord) val = fXtoAbsPixelk + x*fXtoPixel; + else val = fXtoPixelk + x*fXtoPixel; + if (val < -kMaxPixel) return -kMaxPixel; + if (val > kMaxPixel) return kMaxPixel; + return TMath::Nint(val); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert Y coordinate to absolute pixel + +Int_t TPad::YtoAbsPixel(Double_t y) const +{ + Double_t val = fYtoAbsPixelk + y*fYtoPixel; + if (val < -kMaxPixel) return -kMaxPixel; + if (val > kMaxPixel) return kMaxPixel; + return TMath::Nint(val); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Convert Y coordinate to pixel + +Int_t TPad::YtoPixel(Double_t y) const +{ + Double_t val; + if (fAbsCoord) val = fYtoAbsPixelk + y*fYtoPixel; + else val = fYtoPixelk + y*fYtoPixel; + if (val < -kMaxPixel) return -kMaxPixel; + if (val > kMaxPixel) return kMaxPixel; + return TMath::Nint(val); +} diff --git a/graf2d/gpad/src/TRatioPlot.cxx b/graf2d/gpad/src/TRatioPlot.cxx index ef9fb61fa6935..fc2823c3beaae 100644 --- a/graf2d/gpad/src/TRatioPlot.cxx +++ b/graf2d/gpad/src/TRatioPlot.cxx @@ -607,8 +607,15 @@ void TRatioPlot::Draw(Option_t *option) fConfidenceInterval2->SetFillColor(fCi2Color); if (fMode == TRatioPlot::CalculationMode::kFitResidual) { - TF1 *func = dynamic_cast(fH1->GetListOfFunctions()->At(0)); - + // use last function in the list + TF1 * func = nullptr; + for (int i = fH1->GetListOfFunctions()->GetSize()-1; i >= 0; i--) { + auto obj = fH1->GetListOfFunctions()->At(i); + if (obj->InheritsFrom(TF1::Class()) ) { + func = dynamic_cast(obj); + break; + } + } if (!func) { // this is checked in constructor and should thus not occur Error("BuildLowerPlot", "h1 does not have a fit function"); @@ -1014,9 +1021,9 @@ Int_t TRatioPlot::BuildLowerPlot() Double_t x; Double_t val; - for (Int_t i=0; i<=fH1->GetNbinsX();++i) { + for (Int_t i=1; i<=fH1->GetNbinsX();++i) { val = fH1->GetBinContent(i); - x = fH1->GetBinCenter(i+1); + x = fH1->GetBinCenter(i); if (fErrorMode == TRatioPlot::ErrorMode::kErrorAsymmetric) { @@ -1051,9 +1058,9 @@ Int_t TRatioPlot::BuildLowerPlot() ((TGraphAsymmErrors*)fRatioGraph)->SetPointError(ipoint, fH1->GetBinWidth(i)/2., fH1->GetBinWidth(i)/2., 0.5, 0.5); fConfidenceInterval1->SetPoint(ipoint, x, 0); - fConfidenceInterval1->SetPointError(ipoint, x, i < (Int_t)ci1.size() ? ci1[i] / error : 0); + fConfidenceInterval1->SetPointError(ipoint, x, i <= (Int_t)ci1.size() ? ci1[i-1] / error : 0); fConfidenceInterval2->SetPoint(ipoint, x, 0); - fConfidenceInterval2->SetPointError(ipoint, x, i < (Int_t)ci2.size() ? ci2[i] / error : 0); + fConfidenceInterval2->SetPointError(ipoint, x, i <= (Int_t)ci2.size() ? ci2[i-1] / error : 0); ++ipoint; diff --git a/graf2d/gpadv7/inc/ROOT/RCanvas.hxx b/graf2d/gpadv7/inc/ROOT/RCanvas.hxx index fd535f9a9bb21..64b611febf5a0 100644 --- a/graf2d/gpadv7/inc/ROOT/RCanvas.hxx +++ b/graf2d/gpadv7/inc/ROOT/RCanvas.hxx @@ -125,6 +125,9 @@ public: /// Returns window name used to display canvas std::string GetWindowAddr() const; + /// Returns window URL which can be used for connection + std::string GetWindowUrl(bool remote); + /// Hide all canvas displays void Hide(); diff --git a/graf2d/gpadv7/inc/ROOT/RVirtualCanvasPainter.hxx b/graf2d/gpadv7/inc/ROOT/RVirtualCanvasPainter.hxx index 881f1ecf8e632..be2a467280bc9 100644 --- a/graf2d/gpadv7/inc/ROOT/RVirtualCanvasPainter.hxx +++ b/graf2d/gpadv7/inc/ROOT/RVirtualCanvasPainter.hxx @@ -71,6 +71,8 @@ public: virtual std::string GetWindowAddr() const = 0; + virtual std::string GetWindowUrl(bool remote) = 0; + /// run canvas functionality in caller thread, not needed when main thread is used virtual void Run(double tm = 0.) = 0; diff --git a/graf2d/gpadv7/src/RCanvas.cxx b/graf2d/gpadv7/src/RCanvas.cxx index c288f462b23b2..674bdda430e20 100644 --- a/graf2d/gpadv7/src/RCanvas.cxx +++ b/graf2d/gpadv7/src/RCanvas.cxx @@ -152,6 +152,18 @@ std::string ROOT::Experimental::RCanvas::GetWindowAddr() const return ""; } +////////////////////////////////////////////////////////////////////////// +/// Returns window URL which can be used for connection +/// See \ref ROOT::RWebWindow::GetUrl docu for more details + +std::string ROOT::Experimental::RCanvas::GetWindowUrl(bool remote) +{ + if (fPainter) + return fPainter->GetWindowUrl(remote); + + return ""; +} + ////////////////////////////////////////////////////////////////////////// /// Hide all canvas displays diff --git a/graf2d/graf/inc/TPave.h b/graf2d/graf/inc/TPave.h index d12d88a6051b9..435f437eb0672 100644 --- a/graf2d/graf/inc/TPave.h +++ b/graf2d/graf/inc/TPave.h @@ -70,6 +70,10 @@ class TPave : public TBox { Int_t bordersize=4 ,Option_t *option="br"); void Print(Option_t *option="") const override; void SavePrimitive(std::ostream &out, Option_t *option = "") override; + /** + * \brief Sets the border size of the TPave box and shadow + * \param bordersize 0: disable shadow and box, 1: disable only shadow, 2+: tunes the shadow border size + */ virtual void SetBorderSize(Int_t bordersize=4) {fBorderSize = bordersize;} // *MENU* virtual void SetCornerRadius(Double_t rad = 0.2) {fCornerRadius = rad;} // *MENU* virtual void SetName(const char *name="") {fName = name;} // *MENU* diff --git a/graf2d/graf/src/TPaveLabel.cxx b/graf2d/graf/src/TPaveLabel.cxx index c0c898929ae02..cfca2718c39f1 100644 --- a/graf2d/graf/src/TPaveLabel.cxx +++ b/graf2d/graf/src/TPaveLabel.cxx @@ -39,6 +39,7 @@ TPaveLabel::TPaveLabel(): TPave(), TAttText() /// a PaveLabel is a Pave with a label centered in the Pave /// The Pave is by default defined bith bordersize=5 and option ="br". /// The text size is automatically computed as a function of the pave size. +/// To remove the shadow or border of a TPaveLabel, use the function TPave::SetBorderSize TPaveLabel::TPaveLabel(Double_t x1, Double_t y1,Double_t x2, Double_t y2, const char *label, Option_t *option) :TPave(x1,y1,x2,y2,3,option), TAttText(22,0,1,gStyle->GetTextFont(),0.99) diff --git a/graf2d/graf/src/TPaveStats.cxx b/graf2d/graf/src/TPaveStats.cxx index 838ca11159e44..310eb2951699d 100644 --- a/graf2d/graf/src/TPaveStats.cxx +++ b/graf2d/graf/src/TPaveStats.cxx @@ -167,6 +167,8 @@ its position with these lines ("`h`" being the pointer to the histogram): Root > st->SetX2NDC(newx2); //new x end position ~~~ +To remove the border or shadow of the TPaveStats, use the function TPave::SetBorderSize + To change the type of information for an histogram with an existing `TPaveStats` one should do: ~~~ {.cpp} diff --git a/graf2d/graf/src/TPaveText.cxx b/graf2d/graf/src/TPaveText.cxx index 4d138d1d8c2e0..3218195205fa4 100644 --- a/graf2d/graf/src/TPaveText.cxx +++ b/graf2d/graf/src/TPaveText.cxx @@ -90,6 +90,8 @@ TPaveText::TPaveText(): TPave(), TAttText() /// If none of these four above options is specified the default the /// option "BR" will be used to draw the border. To produces a pave /// without any border it is enough to specify the option "NB" (no border). +/// If you want to remove the border or shadow of an already existing TPaveText, +/// then use the function TPave::SetBorderSize. /// /// - option = "NDC" x1,y1,x2,y2 are given in NDC /// - option = "ARC" corners are rounded diff --git a/graf2d/graf/src/TPavesText.cxx b/graf2d/graf/src/TPavesText.cxx index 9f5e62487f479..c94e4114095f4 100644 --- a/graf2d/graf/src/TPavesText.cxx +++ b/graf2d/graf/src/TPavesText.cxx @@ -45,6 +45,7 @@ TPavesText::TPavesText(): TPaveText() /// - option = "L" Left frame /// - option = "NDC" x1,y1,x2,y2 are given in NDC /// - option = "ARC" corners are rounded +/// To add a shadow to the TPavesText, use the function TPave::SetBorderSize TPavesText::TPavesText(Double_t x1, Double_t y1,Double_t x2, Double_t y2, Int_t npaves,Option_t *option) :TPaveText(x1,y1,x2,y2,option) diff --git a/graf2d/graf/src/TTF.cxx b/graf2d/graf/src/TTF.cxx index b8acf771d95c4..f1fed0d5ca78f 100644 --- a/graf2d/graf/src/TTF.cxx +++ b/graf2d/graf/src/TTF.cxx @@ -570,8 +570,10 @@ void TTF::SetTextSize(Float_t textsize) } Int_t tsize = (Int_t)(textsize*kScale+0.5) << 6; - if (FT_Set_Char_Size(fgFace[fgCurFontIdx], tsize, tsize, 72, 72)) - Error("TTF::SetTextSize", "error in FT_Set_Char_Size"); + FT_Error err = FT_Set_Char_Size(fgFace[fgCurFontIdx], tsize, tsize, 72, 72); + if (err) + Error("TTF::SetTextSize", "error in FT_Set_Char_Size: 0x%x (input size %f, calc. size 0x%x)", err, textsize, + tsize); } //////////////////////////////////////////////////////////////////////////////// diff --git a/graf3d/eve7/inc/ROOT/REveElement.hxx b/graf3d/eve7/inc/ROOT/REveElement.hxx index 79b0518cebfc1..4a51f795412f4 100644 --- a/graf3d/eve7/inc/ROOT/REveElement.hxx +++ b/graf3d/eve7/inc/ROOT/REveElement.hxx @@ -22,57 +22,7 @@ #include #include -#if __has_include() -#include -#else -// This forward declaration works for versions before 3.11.0, which introduced -// a "versioned, ABI-tagged inline namespace". The json_fwd.hpp header is -// present in multi-header installations (the default as of version 3.11.0), and -// single-header installations of version 3.11.2+. - -#include // int64_t, uint64_t -#include // map -#include // vector - -namespace nlohmann -{ -template -struct adl_serializer; - -/// a class to store JSON values -/// @sa https://json.nlohmann.me/api/basic_json/ -template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer, - class BinaryType = std::vector> -class basic_json; - -/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document -/// @sa https://json.nlohmann.me/api/json_pointer/ -template -class json_pointer; - -using json = basic_json<>; - -/// @brief a minimal map-like container that preserves insertion order -/// @sa https://json.nlohmann.me/api/ordered_map/ -template -struct ordered_map; - -/// @brief specialization that maintains the insertion order of object keys -/// @sa https://json.nlohmann.me/api/ordered_json/ -using ordered_json = basic_json; - -} // namespace nlohmann - -#endif +#include class TGeoMatrix; diff --git a/graf3d/gl/src/TGLViewer.cxx b/graf3d/gl/src/TGLViewer.cxx index 79c80e4a818c4..e4e0cbd5ab60d 100644 --- a/graf3d/gl/src/TGLViewer.cxx +++ b/graf3d/gl/src/TGLViewer.cxx @@ -1951,8 +1951,8 @@ void TGLViewer::SetOrthoCamera(ECameraType camera, /// - 'dolly' - distance from 'center' /// - 'center' - world position from which dolly/hRotate/vRotate are measured /// camera rotates round this, always facing in (in center of viewport) -/// - 'hRotate' - horizontal rotation from initial configuration in degrees -/// - 'hRotate' - vertical rotation from initial configuration in degrees +/// - 'hRotate' - horizontal rotation from initial configuration in radians +/// - 'vRotate' - vertical rotation from initial configuration in radians void TGLViewer::SetPerspectiveCamera(ECameraType camera, Double_t fov, Double_t dolly, diff --git a/gui/browsable/src/RFieldHolder.hxx b/gui/browsable/src/RFieldHolder.hxx index cdf21f762797c..c3cd107776d04 100644 --- a/gui/browsable/src/RFieldHolder.hxx +++ b/gui/browsable/src/RFieldHolder.hxx @@ -11,7 +11,7 @@ #include -#include +#include #include #include "TClass.h" @@ -25,23 +25,24 @@ class RPageSource; } class RFieldHolder : public ROOT::Browsable::RHolder { - std::shared_ptr fNtplSource; + std::shared_ptr fNtplReader; std::string fParentName; ROOT::Experimental::DescriptorId_t fFieldId; public: - RFieldHolder(std::shared_ptr ntplSource, - const std::string &parent_name, + RFieldHolder(std::shared_ptr ntplReader, const std::string &parent_name, ROOT::Experimental::DescriptorId_t id) - : fNtplSource(ntplSource), fParentName(parent_name), fFieldId(id) {} + : fNtplReader(ntplReader), fParentName(parent_name), fFieldId(id) + { + } const TClass *GetClass() const override { return TClass::GetClass(); } /** Returns direct (temporary) object pointer */ const void *GetObject() const override { return nullptr; } - auto GetNtplSource() const { return fNtplSource; } + auto GetNtplReader() const { return fNtplReader; } auto GetParentName() const { return fParentName; } auto GetId() const { return fFieldId; } }; diff --git a/gui/browsable/src/RFieldProvider.hxx b/gui/browsable/src/RFieldProvider.hxx index 1b1e5bb6b3e1d..c4f9e683d1cf0 100644 --- a/gui/browsable/src/RFieldProvider.hxx +++ b/gui/browsable/src/RFieldProvider.hxx @@ -44,7 +44,7 @@ using RField = ROOT::Experimental::RField; class RFieldProvider : public RProvider { class RDrawVisitor : public ROOT::Experimental::Detail::RFieldVisitor { private: - std::shared_ptr fNtplSource; + std::shared_ptr fNtplReader; std::unique_ptr fHist; /** Test collected entries if it looks like integer values and one can use better binning */ @@ -91,7 +91,7 @@ class RFieldProvider : public RProvider { int cnt = 0; if (bufsize > 10) bufsize-=3; else bufsize = -1; - auto view = ROOT::Experimental::RNTupleView(field.GetOnDiskId(), fNtplSource.get()); + auto view = fNtplReader->GetView(field.GetOnDiskId()); for (auto i : view.GetFieldRange()) { fHist->Fill(view(i)); if (++cnt == bufsize) { @@ -111,7 +111,7 @@ class RFieldProvider : public RProvider { int nentries = 0; - auto view = ROOT::Experimental::RNTupleView(field.GetOnDiskId(), fNtplSource.get()); + auto view = fNtplReader->GetView(field.GetOnDiskId()); for (auto i : view.GetFieldRange()) { std::string v = view(i); nentries++; @@ -139,10 +139,7 @@ class RFieldProvider : public RProvider { } public: - explicit RDrawVisitor(std::shared_ptr ntplSource) - : fNtplSource(ntplSource) - { - } + explicit RDrawVisitor(std::shared_ptr ntplReader) : fNtplReader(ntplReader) {} TH1 *MoveHist() { return fHist.release(); @@ -179,18 +176,16 @@ public: { if (!holder) return nullptr; - auto ntplSource = holder->GetNtplSource(); + auto ntplReader = holder->GetNtplReader(); std::string name = holder->GetParentName(); - std::unique_ptr field; - { - auto descriptorGuard = ntplSource->GetSharedDescriptorGuard(); - field = descriptorGuard->GetFieldDescriptor(holder->GetId()).CreateField(descriptorGuard.GetRef()); - } - name.append(field->GetFieldName()); + const auto fieldName = ntplReader->GetDescriptor().GetFieldDescriptor(holder->GetId()).GetFieldName(); + const auto qualifiedFieldName = ntplReader->GetDescriptor().GetQualifiedFieldName(holder->GetId()); + auto view = ntplReader->GetView(qualifiedFieldName); + name.append(fieldName); - RDrawVisitor drawVisitor(ntplSource); - field->AcceptVisitor(drawVisitor); + RDrawVisitor drawVisitor(ntplReader); + view.GetField().AcceptVisitor(drawVisitor); return drawVisitor.MoveHist(); } }; diff --git a/gui/browsable/src/RNTupleBrowseProvider.cxx b/gui/browsable/src/RNTupleBrowseProvider.cxx index df5543d86e767..9bbb1e8cb8847 100644 --- a/gui/browsable/src/RNTupleBrowseProvider.cxx +++ b/gui/browsable/src/RNTupleBrowseProvider.cxx @@ -11,9 +11,8 @@ #include #include +#include #include -#include -#include #include "TClass.h" #include "RFieldHolder.hxx" @@ -36,32 +35,31 @@ using namespace ROOT::Browsable; class RFieldElement : public RElement { protected: - std::shared_ptr fNtplSource; + std::shared_ptr fNtplReader; std::string fParentName; ROOT::Experimental::DescriptorId_t fFieldId; public: - - RFieldElement(std::shared_ptr ntplSource, - const std::string &parent_name, + RFieldElement(std::shared_ptr ntplReader, const std::string &parent_name, const ROOT::Experimental::DescriptorId_t id) - : RElement(), fNtplSource(ntplSource), fParentName(parent_name), fFieldId(id) {} + : RElement(), fNtplReader(ntplReader), fParentName(parent_name), fFieldId(id) + { + } virtual ~RFieldElement() = default; /** Name of RField */ std::string GetName() const override { - return fNtplSource->GetSharedDescriptorGuard()->GetFieldDescriptor(fFieldId).GetFieldName(); + return fNtplReader->GetDescriptor().GetFieldDescriptor(fFieldId).GetFieldName(); } /** Title of RField */ std::string GetTitle() const override { - auto descriptorGuard = fNtplSource->GetSharedDescriptorGuard(); - auto &fld = descriptorGuard->GetFieldDescriptor(fFieldId); + auto &fld = fNtplReader->GetDescriptor().GetFieldDescriptor(fFieldId); return "RField name "s + fld.GetFieldName() + " type "s + fld.GetTypeName(); } @@ -72,13 +70,12 @@ class RFieldElement : public RElement { std::unique_ptr GetObject() override { - return std::make_unique(fNtplSource, fParentName, fFieldId); + return std::make_unique(fNtplReader, fParentName, fFieldId); } EActionKind GetDefaultAction() const override { - auto descriptorGuard = fNtplSource->GetSharedDescriptorGuard(); - auto range = descriptorGuard->GetFieldIterable(fFieldId); + auto range = fNtplReader->GetDescriptor().GetFieldIterable(fFieldId); if (range.begin() != range.end()) return kActNone; return kActDraw7; } @@ -105,23 +102,21 @@ class RFieldElement : public RElement { class RNTupleElement : public RElement { protected: - std::shared_ptr fNtplSource; + std::shared_ptr fNtplReader; public: RNTupleElement(const std::string &ntplName, const std::string &filename) { - ROOT::Experimental::RNTupleReadOptions options; - fNtplSource = ROOT::Experimental::Detail::RPageSource::Create(ntplName, filename, options); - fNtplSource->Attach(); + fNtplReader = ROOT::Experimental::RNTupleReader::Open(ntplName, filename); } virtual ~RNTupleElement() = default; /** Returns true if no ntuple found */ - bool IsNull() const { return !fNtplSource; } + bool IsNull() const { return !fNtplReader; } /** Name of NTuple */ - std::string GetName() const override { return fNtplSource->GetSharedDescriptorGuard()->GetName(); } + std::string GetName() const override { return fNtplReader->GetDescriptor().GetName(); } /** Title of NTuple */ std::string GetTitle() const override { return "RNTuple title"s; } @@ -157,16 +152,15 @@ class RNTupleElement : public RElement { class RFieldsIterator : public RLevelIter { - std::shared_ptr fNtplSource; + std::shared_ptr fNtplReader; std::vector fFieldIds; std::string fParentName; int fCounter{-1}; public: - RFieldsIterator(std::shared_ptr ntplSource, - std::vector &&ids, - const std::string &parent_name = ""s) - : fNtplSource(ntplSource), fFieldIds(ids), fParentName(parent_name) + RFieldsIterator(std::shared_ptr ntplReader, + std::vector &&ids, const std::string &parent_name = ""s) + : fNtplReader(ntplReader), fFieldIds(ids), fParentName(parent_name) { } @@ -179,46 +173,37 @@ class RFieldsIterator : public RLevelIter { std::string GetItemName() const override { - return fNtplSource->GetSharedDescriptorGuard()->GetFieldDescriptor(fFieldIds[fCounter]).GetFieldName(); + return fNtplReader->GetDescriptor().GetFieldDescriptor(fFieldIds[fCounter]).GetFieldName(); } bool CanItemHaveChilds() const override { - auto descriptorGuard = fNtplSource->GetSharedDescriptorGuard(); - auto subrange = descriptorGuard->GetFieldIterable(fFieldIds[fCounter]); + auto subrange = fNtplReader->GetDescriptor().GetFieldIterable(fFieldIds[fCounter]); return subrange.begin() != subrange.end(); } /** Create element for the browser */ std::unique_ptr CreateItem() override { - int nchilds = 0; - std::string fieldName; - std::string typeName; - - { - auto descriptorGuard = fNtplSource->GetSharedDescriptorGuard(); - for (auto &sub : descriptorGuard->GetFieldIterable(fFieldIds[fCounter])) { - (void)sub; - nchilds++; - } - - auto &field = descriptorGuard->GetFieldDescriptor(fFieldIds[fCounter]); - fieldName = field.GetFieldName(); - typeName = field.GetTypeName(); + for (auto &sub : fNtplReader->GetDescriptor().GetFieldIterable(fFieldIds[fCounter])) { + (void)sub; + nchilds++; } - auto item = std::make_unique(fieldName, nchilds, nchilds > 0 ? "sap-icon://split" : "sap-icon://e-care"); + const auto &field = fNtplReader->GetDescriptor().GetFieldDescriptor(fFieldIds[fCounter]); + + auto item = + std::make_unique(field.GetFieldName(), nchilds, nchilds > 0 ? "sap-icon://split" : "sap-icon://e-care"); - item->SetTitle("RField name "s + fieldName + " type "s + typeName); + item->SetTitle("RField name "s + field.GetFieldName() + " type "s + field.GetTypeName()); return item; } std::shared_ptr GetElement() override { - return std::make_shared(fNtplSource, fParentName, fFieldIds[fCounter]); + return std::make_shared(fNtplReader, fParentName, fFieldIds[fCounter]); } }; @@ -228,36 +213,29 @@ std::unique_ptr RFieldElement::GetChildsIter() std::vector ids; std::string prefix; - { - auto descriptorGuard = fNtplSource->GetSharedDescriptorGuard(); - - for (auto &f : descriptorGuard->GetFieldIterable(fFieldId)) - ids.emplace_back(f.GetId()); + for (auto &f : fNtplReader->GetDescriptor().GetFieldIterable(fFieldId)) + ids.emplace_back(f.GetId()); - if (ids.size() == 0) - return nullptr; + if (ids.size() == 0) + return nullptr; - prefix = fParentName; - auto &fld = descriptorGuard->GetFieldDescriptor(fFieldId); - prefix.append(fld.GetFieldName()); - } + prefix = fParentName; + const auto &fld = fNtplReader->GetDescriptor().GetFieldDescriptor(fFieldId); + prefix.append(fld.GetFieldName()); prefix.append("."); - return std::make_unique(fNtplSource, std::move(ids), prefix); + return std::make_unique(fNtplReader, std::move(ids), prefix); } std::unique_ptr RNTupleElement::GetChildsIter() { std::vector ids; - { - auto descriptorGuard = fNtplSource->GetSharedDescriptorGuard(); - for (auto &f : descriptorGuard->GetTopLevelFields()) - ids.emplace_back(f.GetId()); - } + for (auto &f : fNtplReader->GetDescriptor().GetTopLevelFields()) + ids.emplace_back(f.GetId()); if (ids.size() == 0) return nullptr; - return std::make_unique(fNtplSource, std::move(ids)); + return std::make_unique(fNtplReader, std::move(ids)); } diff --git a/gui/browserv7/inc/ROOT/RBrowser.hxx b/gui/browserv7/inc/ROOT/RBrowser.hxx index 1e8740ddb52ff..dc68ae96be57b 100644 --- a/gui/browserv7/inc/ROOT/RBrowser.hxx +++ b/gui/browserv7/inc/ROOT/RBrowser.hxx @@ -49,7 +49,7 @@ protected: std::vector> fPostponed; /// AddWidget(const std::string &kind); - std::shared_ptr AddCatchedWidget(const std::string &url, const std::string &kind); + std::shared_ptr AddCatchedWidget(RWebWindow *win, const std::string &kind); std::shared_ptr FindWidget(const std::string &name, const std::string &kind = "") const; std::shared_ptr GetActiveWidget() const { return FindWidget(fActiveWidgetName); } @@ -91,6 +91,8 @@ public: /// hide Browser void Hide(); + std::string GetWindowUrl(bool remote); + void SetWorkingPath(const std::string &path); /// Enable/disable catch of RWebWindow::Show calls to embed created widgets, default on diff --git a/gui/browserv7/src/RBrowser.cxx b/gui/browserv7/src/RBrowser.cxx index 253df7da3e691..75547ef28b197 100644 --- a/gui/browserv7/src/RBrowser.cxx +++ b/gui/browserv7/src/RBrowser.cxx @@ -235,20 +235,20 @@ class RBrowserInfoWidget : public RBrowserWidget { class RBrowserCatchedWidget : public RBrowserWidget { public: - std::string fUrl; // url of catched widget + RWebWindow *fWindow{nullptr}; // catched widget, TODO: to be changed to shared_ptr std::string fCatchedKind; // kind of catched widget void Show(const std::string &) override {} std::string GetKind() const override { return "catched"s; } - std::string GetUrl() override { return fUrl; } + std::string GetUrl() override { return fWindow->GetUrl(false); } std::string GetTitle() override { return fCatchedKind; } - RBrowserCatchedWidget(const std::string &name, const std::string &url, const std::string &kind) : + RBrowserCatchedWidget(const std::string &name, RWebWindow *win, const std::string &kind) : RBrowserWidget(name), - fUrl(url), + fWindow(win), fCatchedKind(kind) { } @@ -312,9 +312,7 @@ RBrowser::RBrowser(bool use_rcanvas) if (!fWebWindow || !fCatchWindowShow || kind.empty()) return false; - std::string url = fWebWindow->GetRelativeAddr(win); - - auto widget = AddCatchedWidget(url, kind); + auto widget = AddCatchedWidget(&win, kind); if (widget && fWebWindow && (fWebWindow->NumConnections() > 0)) fWebWindow->Send(0, NewWidgetMsg(widget)); @@ -524,6 +522,18 @@ void RBrowser::Hide() fWebWindow->CloseConnections(); } +/////////////////////////////////////////////////////////////////////////////////////////////////////// +/// Return URL parameter for the window showing ROOT Browser +/// See \ref ROOT::RWebWindow::GetUrl docu for more details + +std::string RBrowser::GetWindowUrl(bool remote) +{ + if (fWebWindow) + return fWebWindow->GetUrl(remote); + + return ""s; +} + ////////////////////////////////////////////////////////////////////////////////////////////// /// Creates new widget @@ -560,13 +570,13 @@ std::shared_ptr RBrowser::AddWidget(const std::string &kind) ////////////////////////////////////////////////////////////////////////////////////////////// /// Add widget catched from external scripts -std::shared_ptr RBrowser::AddCatchedWidget(const std::string &url, const std::string &kind) +std::shared_ptr RBrowser::AddCatchedWidget(RWebWindow *win, const std::string &kind) { - if (url.empty()) return nullptr; + if (!win || kind.empty()) return nullptr; std::string name = "catched"s + std::to_string(++fWidgetCnt); - auto widget = std::make_shared(name, url, kind); + auto widget = std::make_shared(name, win, kind); fWidgets.emplace_back(widget); @@ -667,7 +677,7 @@ void RBrowser::SendInitMsg(unsigned connid) for (auto &widget : fWidgets) { widget->ResetConn(); - reply.emplace_back(std::vector({ widget->GetKind(), widget->GetUrl(), widget->GetName(), widget->GetTitle() })); + reply.emplace_back(std::vector({ widget->GetKind(), ".."s + widget->GetUrl(), widget->GetName(), widget->GetTitle() })); } if (!fActiveWidgetName.empty()) @@ -731,7 +741,7 @@ std::string RBrowser::GetCurrentWorkingDirectory() std::string RBrowser::NewWidgetMsg(std::shared_ptr &widget) { - std::vector arr = { widget->GetKind(), widget->GetUrl(), widget->GetName(), widget->GetTitle(), + std::vector arr = { widget->GetKind(), ".."s + widget->GetUrl(), widget->GetName(), widget->GetTitle(), Browsable::RElement::GetPathAsString(widget->GetPath()) }; return "NEWWIDGET:"s + TBufferJSON::ToJSON(&arr, TBufferJSON::kNoSpaces).Data(); } diff --git a/gui/browserv7/src/RBrowserGeomWidget.cxx b/gui/browserv7/src/RBrowserGeomWidget.cxx index 7859f76b36f42..d370e2aaac8ad 100644 --- a/gui/browserv7/src/RBrowserGeomWidget.cxx +++ b/gui/browserv7/src/RBrowserGeomWidget.cxx @@ -64,7 +64,7 @@ class RBrowserGeomWidget : public RBrowserWidget { void Show(const std::string &arg) override { fViewer.Show(arg); } - std::string GetUrl() override { return "../"s + fViewer.GetWindowAddr() + "/"s; } + std::string GetUrl() override { return fViewer.GetWindowUrl(false); } bool DrawElement(std::shared_ptr &elem, const std::string & = "") override { diff --git a/gui/browserv7/src/RBrowserRCanvasWidget.cxx b/gui/browserv7/src/RBrowserRCanvasWidget.cxx index 2cb0f3701a20b..f9cc3c2d6e8d9 100644 --- a/gui/browserv7/src/RBrowserRCanvasWidget.cxx +++ b/gui/browserv7/src/RBrowserRCanvasWidget.cxx @@ -47,7 +47,7 @@ class RBrowserRCanvasWidget : public ROOT::RBrowserWidget { std::string GetUrl() override { - return "../"s + fCanvas->GetWindowAddr() + "/"s; + return fCanvas->GetWindowUrl(false); } std::string GetTitle() override diff --git a/gui/browserv7/src/RBrowserTCanvasWidget.cxx b/gui/browserv7/src/RBrowserTCanvasWidget.cxx index a76db43e2f92c..3ad0c4253bbf0 100644 --- a/gui/browserv7/src/RBrowserTCanvasWidget.cxx +++ b/gui/browserv7/src/RBrowserTCanvasWidget.cxx @@ -164,7 +164,7 @@ class RBrowserTCanvasWidget : public RBrowserWidget { std::string GetUrl() override { - return "../"s + fWebCanvas->GetWebWindow()->GetAddr() + "/"s; + return fWebCanvas->GetWebWindow()->GetUrl(false); } std::string GetTitle() override diff --git a/gui/browserv7/src/RBrowserTreeWidget.cxx b/gui/browserv7/src/RBrowserTreeWidget.cxx index 621eca30de210..6328a5b1d465d 100644 --- a/gui/browserv7/src/RBrowserTreeWidget.cxx +++ b/gui/browserv7/src/RBrowserTreeWidget.cxx @@ -45,7 +45,7 @@ class RBrowserTreeWidget : public RBrowserWidget { std::string GetKind() const override { return "tree"s; } std::string GetTitle() override { return fTitle; } - std::string GetUrl() override { return "../"s + fViewer.GetWindowAddr() + "/"s; } + std::string GetUrl() override { return fViewer.GetWindowUrl(false); } void Show(const std::string &arg) override { fViewer.Show(arg); } diff --git a/gui/canvaspainter/src/RCanvasPainter.cxx b/gui/canvaspainter/src/RCanvasPainter.cxx index d595dfbd8d784..f0c9a03c6a3f2 100644 --- a/gui/canvaspainter/src/RCanvasPainter.cxx +++ b/gui/canvaspainter/src/RCanvasPainter.cxx @@ -165,6 +165,8 @@ class RCanvasPainter : public Internal::RVirtualCanvasPainter { std::string GetWindowAddr() const final; + std::string GetWindowUrl(bool remote) final; + void Run(double tm = 0.) final; bool AddPanel(std::shared_ptr) final; @@ -686,6 +688,17 @@ std::string RCanvasPainter::GetWindowAddr() const return fWindow->GetAddr(); } +//////////////////////////////////////////////////////////////////////////////// +/// Returns connection URL for web window + +std::string RCanvasPainter::GetWindowUrl(bool remote) +{ + if (!fWindow) return ""; + + return fWindow->GetUrl(remote); +} + + //////////////////////////////////////////////////////////////////////////////// /// Add window as panel inside canvas window diff --git a/gui/webdisplay/inc/ROOT/RWebDisplayHandle.hxx b/gui/webdisplay/inc/ROOT/RWebDisplayHandle.hxx index 28529a7f69863..76344df84fd5c 100644 --- a/gui/webdisplay/inc/ROOT/RWebDisplayHandle.hxx +++ b/gui/webdisplay/inc/ROOT/RWebDisplayHandle.hxx @@ -61,6 +61,7 @@ protected: class ChromeCreator : public BrowserCreator { bool fEdge{false}; std::string fEnvPrefix; // rc parameters prefix + int fChromeVersion{-1}; // major version in chrome browser public: ChromeCreator(bool is_edge = false); ~ChromeCreator() override = default; diff --git a/gui/webdisplay/inc/ROOT/RWebWindow.hxx b/gui/webdisplay/inc/ROOT/RWebWindow.hxx index 234cb8a637686..350635523d146 100644 --- a/gui/webdisplay/inc/ROOT/RWebWindow.hxx +++ b/gui/webdisplay/inc/ROOT/RWebWindow.hxx @@ -71,6 +71,7 @@ private: bool fHeadlessMode{false}; /// fDisplayHandle; /// fHold; /// fQueue; ///> fEmbed; /// fWSHandler; /// FindOrCreateConnection(unsigned wsid, bool make_new, const char *query); - /// Find connection with specified websocket id - std::shared_ptr FindConnection(unsigned wsid) { return FindOrCreateConnection(wsid, false, nullptr); } + std::shared_ptr FindConnection(unsigned wsid); std::shared_ptr RemoveConnection(unsigned wsid); std::shared_ptr _FindConnWithKey(const std::string &key) const; + bool _CanTrustIn(std::shared_ptr &conn, const std::string &key, const std::string &ntry, bool remote, bool test_first_time); + std::string _MakeSendHeader(std::shared_ptr &conn, bool txt, const std::string &data, int chid); void ProvideQueueEntry(unsigned connid, EQueueEntryKind kind, std::string &&arg); @@ -199,6 +205,8 @@ private: bool HasKey(const std::string &key) const; + void RemoveKey(const std::string &key); + std::string GenerateKey() const; void CheckPendingConnections(); @@ -229,6 +237,8 @@ private: static void SetStartDialogFunc(std::function &, unsigned, const std::string &)>); + static std::string HMAC(const std::string &key, const std::string &sessionKey, const char *msg, int msglen); + public: RWebWindow(); @@ -302,6 +312,14 @@ public: /// returns true if only native (own-created) connections are allowed bool IsNativeOnlyConn() const { return fNativeOnlyConn; } + ///////////////////////////////////////////////////////////////////////// + /// Configure if authentication key in connection string is required + void SetRequireAuthKey(bool on) { fRequireAuthKey = on; } + + ///////////////////////////////////////////////////////////////////////// + /// returns true if authentication string is required + bool IsRequireAuthKey() const { return fRequireAuthKey; } + void SetClientVersion(const std::string &vers); std::string GetClientVersion() const; diff --git a/gui/webdisplay/inc/ROOT/RWebWindowsManager.hxx b/gui/webdisplay/inc/ROOT/RWebWindowsManager.hxx index a580c8fba1d8c..6baff9f403e06 100644 --- a/gui/webdisplay/inc/ROOT/RWebWindowsManager.hxx +++ b/gui/webdisplay/inc/ROOT/RWebWindowsManager.hxx @@ -40,6 +40,8 @@ class RWebWindowsManager { private: std::unique_ptr fServer; /// #include +#include #include #include @@ -107,17 +108,19 @@ class RWebBrowserHandle : public RWebDisplayHandle { typedef pid_t browser_process_id; #endif std::string fTmpDir; ///< temporary directory to delete at the end + std::string fTmpFile; ///< temporary file to remove bool fHasPid{false}; browser_process_id fPid; public: - RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &dump) : RWebDisplayHandle(url), fTmpDir(tmpdir) + RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile, const std::string &dump) : + RWebDisplayHandle(url), fTmpDir(tmpdir), fTmpFile(tmpfile) { SetContent(dump); } - RWebBrowserHandle(const std::string &url, const std::string &tmpdir, browser_process_id pid) - : RWebDisplayHandle(url), fTmpDir(tmpdir), fHasPid(true), fPid(pid) + RWebBrowserHandle(const std::string &url, const std::string &tmpdir, const std::string &tmpfile, browser_process_id pid) + : RWebDisplayHandle(url), fTmpDir(tmpdir), fTmpFile(tmpfile), fHasPid(true), fPid(pid) { } @@ -126,16 +129,18 @@ class RWebBrowserHandle : public RWebDisplayHandle { #ifdef _MSC_VER if (fHasPid) gSystem->Exec(("taskkill /F /PID "s + std::to_string(fPid) + " >NUL 2>NUL").c_str()); - std::string rmdir = "rmdir /S /Q "; + std::string rmdir = "rmdir /S /Q ", rmfile = "del /F "; #else if (fHasPid) kill(fPid, SIGKILL); - std::string rmdir = "rm -rf "; + std::string rmdir = "rm -rf ", rmfile = "rm -f "; #endif if (!fTmpDir.empty()) gSystem->Exec((rmdir + fTmpDir).c_str()); + if (!fTmpFile.empty()) + gSystem->Exec((rmfile + fTmpFile).c_str()); } - + }; } // namespace ROOT @@ -216,7 +221,7 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) if(args.GetBrowserKind() == RWebDisplayArgs::kServer) { std::cout << "New web window: " << url << std::endl; - return std::make_unique(url, "", ""); + return std::make_unique(url, "", "", ""); } std::string exec; @@ -241,6 +246,54 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) std::string rmdir = MakeProfile(exec, args.IsBatchMode() || args.IsHeadless()); + std::string tmpfile; + + // these are secret parameters, hide them in temp file + if ((url.find("token=") || url.find("key=")) && !args.IsBatchMode() && !args.IsHeadless()) { + TString filebase = "root_start_"; + + auto f = gSystem->TempFileName(filebase, nullptr, ".html"); + + bool ferr = false; + + if (!f) { + ferr = true; + } else { + std::string content = std::regex_replace( + "\n" + "\n" + "\n" + " \n" + " \n" + " Opening ROOT widget\n" + "\n" + "\n" + "

\n" + " This page should redirect you to a ROOT widget. If it doesn't,\n" + " click here to go to ROOT.\n" + "

\n" + "\n" + "\n", std::regex("\\$url"), url); + + if (fwrite(content.c_str(), 1, content.length(), f) != content.length()) + ferr = true; + + if (fclose(f) != 0) + ferr = true; + + tmpfile = filebase.Data(); + + url = "file://"s + tmpfile; + } + + if (ferr) { + if (!tmpfile.empty()) + gSystem->Unlink(tmpfile.c_str()); + R__LOG_ERROR(WebGUILog()) << "Fail to create temporary HTML file to startup widget"; + return nullptr; + } + } + exec = std::regex_replace(exec, std::regex("\\$rootetcdir"), TROOT::GetEtcDir().Data()); exec = std::regex_replace(exec, std::regex("\\$url"), url); exec = std::regex_replace(exec, std::regex("\\$width"), swidth); @@ -250,6 +303,8 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) if (exec.compare(0,5,"fork:") == 0) { if (fProg.empty()) { + if (!tmpfile.empty()) + gSystem->Unlink(tmpfile.c_str()); R__LOG_ERROR(WebGUILog()) << "Fork instruction without executable"; return nullptr; } @@ -260,6 +315,8 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) std::unique_ptr fargs(TString(exec.c_str()).Tokenize(" ")); if (!fargs || (fargs->GetLast()<=0)) { + if (!tmpfile.empty()) + gSystem->Unlink(tmpfile.c_str()); R__LOG_ERROR(WebGUILog()) << "Fork instruction is empty"; return nullptr; } @@ -275,17 +332,21 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) pid_t pid; int status = posix_spawn(&pid, argv[0], nullptr, nullptr, argv.data(), nullptr); if (status != 0) { + if (!tmpfile.empty()) + gSystem->Unlink(tmpfile.c_str()); R__LOG_ERROR(WebGUILog()) << "Fail to launch " << argv[0]; return nullptr; } // add processid and rm dir - return std::make_unique(url, rmdir, pid); + return std::make_unique(url, rmdir, tmpfile, pid); #else if (fProg.empty()) { + if (!tmpfile.empty()) + gSystem->Unlink(tmpfile.c_str()); R__LOG_ERROR(WebGUILog()) << "No Web browser found"; return nullptr; } @@ -300,12 +361,14 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) ss >> tmp >> c >> pid; if (pid <= 0) { + if (!tmpfile.empty()) + gSystem->Unlink(tmpfile.c_str()); R__LOG_ERROR(WebGUILog()) << "Fail to launch " << fProg; return nullptr; } // add processid and rm dir - return std::make_unique(url, rmdir, pid); + return std::make_unique(url, rmdir, tmpfile, pid); #endif } @@ -332,7 +395,7 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) _spawnv(_P_NOWAIT, gSystem->UnixPathName(fProg.c_str()), argv.data()); - return std::make_unique(url, rmdir, ""s); + return std::make_unique(url, rmdir, tmpfile, ""s); } std::string prog = "\""s + gSystem->UnixPathName(fProg.c_str()) + "\""s; @@ -368,8 +431,7 @@ RWebDisplayHandle::BrowserCreator::Display(const RWebDisplayArgs &args) gSystem->Unlink(redirect.c_str()); } - // add rmdir if required - return std::make_unique(url, rmdir, dump_content); + return std::make_unique(url, rmdir, tmpfile, dump_content); } ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -383,6 +445,9 @@ RWebDisplayHandle::ChromeCreator::ChromeCreator(bool _edge) : BrowserCreator(tru TestProg(gEnv->GetValue(fEnvPrefix.c_str(), "")); + if (!fProg.empty() && !fEdge) + fChromeVersion = gEnv->GetValue("WebGui.ChromeVersion", -1); + #ifdef _MSC_VER if (fEdge) TestProg("\\Microsoft\\Edge\\Application\\msedge.exe", true); @@ -405,8 +470,20 @@ RWebDisplayHandle::ChromeCreator::ChromeCreator(bool _edge) : BrowserCreator(tru fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --disable-gpu $geometry \"$url\" --dump-dom &"); fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=$url &"); // & in windows mean usage of spawn #else - fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url"); - fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry \'$url\' --dump-dom >/dev/null &"); +#ifdef R__MACOSX + bool use_normal = true; // mac does not like new flag +#else + bool use_normal = fChromeVersion < 119; +#endif + if (use_normal) { + // old browser with standard headless mode + fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url"); + fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry \'$url\' --dump-dom >/dev/null &"); + } else { + // newer version with headless=new mode + fBatchExec = gEnv->GetValue((fEnvPrefix + "Batch").c_str(), "$prog --headless=new --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry $url"); + fHeadlessExec = gEnv->GetValue((fEnvPrefix + "Headless").c_str(), "$prog --headless=new --no-sandbox --no-zygote --disable-extensions --disable-gpu --disable-audio-output $geometry \'$url\' &"); + } fExec = gEnv->GetValue((fEnvPrefix + "Interactive").c_str(), "$prog $geometry --new-window --app=\'$url\' &"); #endif } @@ -460,7 +537,7 @@ std::string RWebDisplayHandle::ChromeCreator::MakeProfile(std::string &exec, boo #else char slash = '/'; #endif - if (!profile_arg.empty() && (profile_arg[profile_arg.length()-1] != slash)) + if (!profile_arg.empty() && (profile_arg[profile_arg.length()-1] != slash)) profile_arg += slash; profile_arg += "root_chrome_profile_"s + std::to_string(gRandom->Integer(0x100000)); @@ -522,10 +599,10 @@ void RWebDisplayHandle::FirefoxCreator::ProcessGeometry(std::string &exec, const std::string RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bool batch_mode) { std::string rmdir, profile_arg; - + if (exec.find("$profile") == std::string::npos) return rmdir; - + const char *ff_profile = gEnv->GetValue("WebGui.FirefoxProfile", ""); const char *ff_profilepath = gEnv->GetValue("WebGui.FirefoxProfilePath", ""); Int_t ff_randomprofile = gEnv->GetValue("WebGui.FirefoxRandomProfile", (Int_t) 1); @@ -543,7 +620,7 @@ std::string RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bo #else char slash = '/'; #endif - if (!profile_dir.empty() && (profile_dir[profile_dir.length()-1] != slash)) + if (!profile_dir.empty() && (profile_dir[profile_dir.length()-1] != slash)) profile_dir += slash; profile_dir += "root_ff_profile_"s + std::to_string(gRandom->Integer(0x100000)); @@ -581,9 +658,9 @@ std::string RWebDisplayHandle::FirefoxCreator::MakeProfile(std::string &exec, bo times_json << "}" << std::endl; if (gSystem->mkdir((profile_dir + "/chrome").c_str()) == 0) { std::ofstream style(profile_dir + "/chrome/userChrome.css", std::ios::trunc); - // do not show tabs + // do not show tabs style << "#TabsToolbar { visibility: collapse; }" << std::endl; - // do not show URL + // do not show URL style << "#nav-bar, #urlbar-container, #searchbar { visibility: collapse !important; }" << std::endl; } } diff --git a/gui/webdisplay/src/RWebWindow.cxx b/gui/webdisplay/src/RWebWindow.cxx index 3b6ee82d039f0..f1722828ff239 100644 --- a/gui/webdisplay/src/RWebWindow.cxx +++ b/gui/webdisplay/src/RWebWindow.cxx @@ -20,7 +20,6 @@ #include "TUrl.h" #include "TROOT.h" #include "TSystem.h" -#include "TRandom3.h" #include #include @@ -29,6 +28,9 @@ #include #include +// must be here because of defines +#include "../../../core/foundation/res/ROOT/RSha256.hxx" + using namespace ROOT; using namespace std::string_literals; @@ -46,6 +48,7 @@ RWebWindow::WebConn::~WebConn() } + /** \class ROOT::RWebWindow \ingroup webdisplay @@ -69,7 +72,10 @@ using RWebWindow::Send() method and call-back function assigned via RWebWindow:: /// RWebWindow constructor /// Should be defined here because of std::unique_ptr -RWebWindow::RWebWindow() = default; +RWebWindow::RWebWindow() +{ + fRequireAuthKey = RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey", 1) == 1; // does authentication key really required +} ////////////////////////////////////////////////////////////////////////////////////////// /// RWebWindow destructor @@ -153,8 +159,13 @@ RWebWindow::CreateWSHandler(std::shared_ptr mgr, unsigned id } ////////////////////////////////////////////////////////////////////////////////////////// -/// Return URL string to access web window -/// \param remote if true, real HTTP server will be started automatically +/// Return URL string to connect web window +/// URL typically includes extra parameters required for connection with the window like +/// `http://localhost:9635/win1/?key=#` +/// When \param remote is true, real HTTP server will be started automatically and +/// widget can be connected from the web browser. If \param remote is false, +/// HTTP server will not be started and window can be connected only from ROOT application itself. +/// !!! WARNING - do not invoke this method without real need, each URL consumes resources in widget and in http server std::string RWebWindow::GetUrl(bool remote) { @@ -247,7 +258,7 @@ unsigned RWebWindow::GetDisplayConnection() const ////////////////////////////////////////////////////////////////////////////////////////// /// Find connection with given websocket id -std::shared_ptr RWebWindow::FindOrCreateConnection(unsigned wsid, bool make_new, const char *query) +std::shared_ptr RWebWindow::FindConnection(unsigned wsid) { std::lock_guard grd(fConnMutex); @@ -256,37 +267,6 @@ std::shared_ptr RWebWindow::FindOrCreateConnection(unsigned return conn; } - // put code to create new connection here to stay under same locked mutex - if (make_new) { - // check if key was registered already - - std::shared_ptr key; - std::string keyvalue; - - if (query) { - TUrl url; - url.SetOptions(query); - if (url.HasOption("key")) - keyvalue = url.GetValueFromOptions("key"); - } - - for (size_t n = 0; n < fPendingConn.size(); ++n) - if (fPendingConn[n]->fKey == keyvalue) { - key = std::move(fPendingConn[n]); - fPendingConn.erase(fPendingConn.begin() + n); - break; - } - - if (key) { - key->fWSId = wsid; - key->fActive = true; - key->ResetStamps(); // TODO: probably, can be moved outside locked area - fConn.emplace_back(key); - } else { - fConn.emplace_back(std::make_shared(++fConnCnt, wsid)); - } - } - return nullptr; } @@ -493,13 +473,21 @@ void RWebWindow::InvokeCallbacks(bool force) ////////////////////////////////////////////////////////////////////////////////////////// /// Add display handle and associated key -/// Key is random number generated when starting new window +/// Key is large random string generated when starting new window /// When client is connected, key should be supplied to correctly identify it unsigned RWebWindow::AddDisplayHandle(bool headless_mode, const std::string &key, std::unique_ptr &handle) { std::lock_guard grd(fConnMutex); + for (auto &entry : fPendingConn) { + if (entry->fKey == key) { + entry->fHeadlessMode = headless_mode; + std::swap(entry->fDisplayHandle, handle); + return entry->fConnId; + } + } + auto conn = std::make_shared(++fConnCnt, headless_mode, key); std::swap(conn->fDisplayHandle, handle); @@ -531,6 +519,56 @@ std::shared_ptr RWebWindow::_FindConnWithKey(const std::str return nullptr; } +////////////////////////////////////////////////////////////////////////////////////////// +/// Check if provided hash, ntry parameters from the connection request could be accepted +/// \param hash - provided hash value which should match with HMAC hash for generated before connection key +/// \param ntry - connection attempt number provided together with request, must come in increasing order +/// \param remote - boolean flag indicating if request comming from remote (via real http), +/// for local displays like Qt5 or CEF simpler connection rules are applied +/// \param test_first_time - true if hash/ntry tested for the first time, false appears only with +/// websocket when connection accepted by server + +bool RWebWindow::_CanTrustIn(std::shared_ptr &conn, const std::string &hash, const std::string &ntry, bool remote, bool test_first_time) +{ + if (!conn) + return false; + + int intry = ntry.empty() ? -1 : std::stoi(ntry); + + auto msg = TString::Format("attempt_%s", ntry.c_str()); + auto expected = HMAC(conn->fKey, fMgr->fUseSessionKey && remote ? fMgr->fSessionKey : ""s, msg.Data(), msg.Length()); + + if (!IsRequireAuthKey()) + return (conn->fKey.empty() && hash.empty()) || (hash == conn->fKey) || (hash == expected); + + // for local connection simple key can be used + if (!remote && (hash == conn->fKey)) + return true; + + if (hash == expected) { + if (test_first_time) { + if (conn->fKeyUsed >= intry) { + // this is indication of main in the middle, already checked hashed value was shown again!!! + // client sends id with increasing counter, if previous value is presented it is BAD + R__LOG_ERROR(WebGUILog()) << "Detect connection hash send before, possible replay attack!!!"; + return false; + } + // remember counter, it should prevent trying previous hash values + conn->fKeyUsed = intry; + } else { + if (conn->fKeyUsed != intry) { + // this is rather error condition, should never happen + R__LOG_ERROR(WebGUILog()) << "Connection failure with HMAC signature check"; + return false; + } + } + return true; + } + + return false; +} + + ////////////////////////////////////////////////////////////////////////////////////////// /// Returns true if provided key value already exists (in processes map or in existing connections) @@ -541,25 +579,54 @@ bool RWebWindow::HasKey(const std::string &key) const auto conn = _FindConnWithKey(key); return conn ? true : false; +} - return false; +////////////////////////////////////////////////////////////////////////////////////////// +/// Removes all connections with the key + +void RWebWindow::RemoveKey(const std::string &key) +{ + ConnectionsList_t lst; + + { + std::lock_guard grd(fConnMutex); + + auto pred = [&](std::shared_ptr &e) { + if (e->fKey == key) { + lst.emplace_back(e); + return true; + } + return false; + }; + + fPendingConn.erase(std::remove_if(fPendingConn.begin(), fPendingConn.end(), pred), fPendingConn.end()); + fConn.erase(std::remove_if(fConn.begin(), fConn.end(), pred), fConn.end()); + } + + for (auto &conn : lst) + if (conn->fActive) + ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s); } + ////////////////////////////////////////////////////////////////////////////////////////// /// Generate new unique key for the window std::string RWebWindow::GenerateKey() const { int ntry = 100000; - TRandom3 rnd; - rnd.SetSeed(); + std::string key; do { - key = std::to_string(rnd.Integer(0x100000)); - } while ((--ntry > 0) && HasKey(key)); + key = RWebWindowsManager::GenerateKey(8); + } while ((--ntry > 0) && (HasKey(key) || (key == fMgr->fSessionKey))); - if (ntry <= 0) key.clear(); + + if (ntry <= 0) { + R__LOG_ERROR(WebGUILog()) << "Fail to generate new connection key"; + key.clear(); + } return key; } @@ -585,7 +652,7 @@ void RWebWindow::CheckPendingConnections() std::chrono::duration diff = stamp - e->fSendStamp; if (diff.count() > tmout) { - R__LOG_DEBUG(0, WebGUILog()) << "Halt process after " << diff.count() << " sec"; + R__LOG_DEBUG(0, WebGUILog()) << "Remove pending connection " << e->fKey << " after " << diff.count() << " sec"; selected.emplace_back(e); return true; } @@ -595,7 +662,6 @@ void RWebWindow::CheckPendingConnections() fPendingConn.erase(std::remove_if(fPendingConn.begin(), fPendingConn.end(), pred), fPendingConn.end()); } - } @@ -686,11 +752,17 @@ bool RWebWindow::ProcessWS(THttpCallArg &arg) if (arg.GetWSId() == 0) return true; + bool is_longpoll = arg.GetFileName() && ("root.longpoll"s == arg.GetFileName()), + is_remote = arg.GetTopName() && ("remote"s == arg.GetTopName()); + + // do not allow longpoll requests for loopback device + if (is_longpoll && is_remote && RWebWindowsManager::IsLoopbackMode()) + return false; + if (arg.IsMethod("WS_CONNECT")) { TUrl url; url.SetOptions(arg.GetQuery()); - bool check_key = RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey") == 1; std::lock_guard grd(fConnMutex); @@ -706,40 +778,89 @@ bool RWebWindow::ProcessWS(THttpCallArg &arg) } } - if (check_key) { - if(!url.HasOption("key")) { - R__LOG_DEBUG(0, WebGUILog()) << "key parameter not provided in url"; - return false; - } - - auto key = url.GetValueFromOptions("key"); + if (!IsRequireAuthKey()) + return true; - auto conn = _FindConnWithKey(key); - if (!conn) { - R__LOG_ERROR(WebGUILog()) << "connection with key " << key << " not found "; - return false; - } + if(!url.HasOption("key")) { + R__LOG_DEBUG(0, WebGUILog()) << "key parameter not provided in url"; + return false; + } - if (conn->fKeyUsed > 0) { - R__LOG_ERROR(WebGUILog()) << "key " << key << " was used for establishing connection, call ShowWindow again"; - return false; - } + std::string key, ntry; + key = url.GetValueFromOptions("key"); + if(url.HasOption("ntry")) + ntry = url.GetValueFromOptions("ntry"); - conn->fKeyUsed = 1; - } + for (auto &conn : fPendingConn) + if (_CanTrustIn(conn, key, ntry, is_remote, true /* test_first_time */)) + return true; - return true; + return false; } if (arg.IsMethod("WS_READY")) { - auto conn = FindOrCreateConnection(arg.GetWSId(), true, arg.GetQuery()); - if (conn) { + if (FindConnection(arg.GetWSId())) { R__LOG_ERROR(WebGUILog()) << "WSHandle with given websocket id " << arg.GetWSId() << " already exists"; return false; } - return true; + std::shared_ptr conn; + std::string key, ntry; + + TUrl url; + url.SetOptions(arg.GetQuery()); + if (url.HasOption("key")) + key = url.GetValueFromOptions("key"); + if (url.HasOption("ntry")) + ntry = url.GetValueFromOptions("ntry"); + + std::lock_guard grd(fConnMutex); + + // check if in pending connections exactly this combination was checked + for (size_t n = 0; n < fPendingConn.size(); ++n) + if (_CanTrustIn(fPendingConn[n], key, ntry, is_remote, false /* test_first_time */)) { + conn = std::move(fPendingConn[n]); + fPendingConn.erase(fPendingConn.begin() + n); + break; + } + + if (conn) { + conn->fWSId = arg.GetWSId(); + conn->fActive = true; + conn->fRecvSeq = 0; + conn->fSendSeq = 1; + // preserve key for longpoll or when with session key used for HMAC hash of messages + // conn->fKey.clear(); + conn->ResetStamps(); + fConn.emplace_back(conn); + return true; + } else if (!IsRequireAuthKey() && (!fConnLimit || (fConn.size() < fConnLimit))) { + fConn.emplace_back(std::make_shared(++fConnCnt, arg.GetWSId())); + return true; + } + + // reject connection, should not really happen + return false; + } + + // special sequrity check for the longpoll requests + if(is_longpoll) { + auto conn = FindConnection(arg.GetWSId()); + if (!conn) + return false; + + TUrl url; + url.SetOptions(arg.GetQuery()); + + std::string key, ntry; + if(url.HasOption("key")) + key = url.GetValueFromOptions("key"); + if(url.HasOption("ntry")) + ntry = url.GetValueFromOptions("ntry"); + + if (!_CanTrustIn(conn, key, ntry, is_remote, true /* test_first_time */)) + return false; } if (arg.IsMethod("WS_CLOSE")) { @@ -750,12 +871,15 @@ bool RWebWindow::ProcessWS(THttpCallArg &arg) if (conn) { ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s); bool do_clear_on_close = false; - if (conn->fKeyUsed < 0) { + if (!conn->fNewKey.empty()) { // case when same handle want to be reused by client with new key std::lock_guard grd(fConnMutex); conn->fKeyUsed = 0; + conn->fKey = conn->fNewKey; + conn->fNewKey.clear(); conn->fConnId = ++fConnCnt; // change connection id to avoid confusion conn->ResetData(); + conn->ResetStamps(); // reset stamps, after timeout connection wll be removed fPendingConn.emplace_back(conn); } else { std::lock_guard grd(fConnMutex); @@ -784,13 +908,67 @@ bool RWebWindow::ProcessWS(THttpCallArg &arg) if (arg.GetPostDataLength() <= 0) return true; + // here start testing of HMAC in the begin of the message + + const char *buf0 = (const char *) arg.GetPostData(); + Long_t data_len = arg.GetPostDataLength(); + + const char *buf = strchr(buf0, ':'); + if (!buf) { + R__LOG_ERROR(WebGUILog()) << "missing separator for HMAC checksum"; + return false; + } + + Int_t code_len = buf - buf0; + data_len -= code_len + 1; + buf++; // starting of normal message + + if (data_len < 0) { + R__LOG_ERROR(WebGUILog()) << "no any data after HMAC checksum"; + return false; + } + + bool is_none = strncmp(buf0, "none:", 5) == 0, is_match = false; + + if (!is_none) { + std::string hmac = HMAC(conn->fKey, fMgr->fSessionKey, buf, data_len); + + is_match = strncmp(buf0, hmac.c_str(), code_len) == 0; + } else if (!fMgr->fUseSessionKey) { + // no packet signing without session key + is_match = true; + } + + // IMPORTANT: final place where integrity of input message is checked! + if (!is_match) { + // mismatch of HMAC checksum + if (is_remote && IsRequireAuthKey()) + return false; + if (!is_none) { + R__LOG_ERROR(WebGUILog()) << "wrong HMAC checksum provided"; + return false; + } + } + // here processing of received data should be performed // this is task for the implemented windows - const char *buf = (const char *)arg.GetPostData(); char *str_end = nullptr; - unsigned long ackn_oper = std::strtoul(buf, &str_end, 10); + unsigned long oper_seq = std::strtoul(buf, &str_end, 10); + if (!str_end || *str_end != ':') { + R__LOG_ERROR(WebGUILog()) << "missing operation sequence"; + return false; + } + + if (oper_seq <= conn->fRecvSeq) { + R__LOG_ERROR(WebGUILog()) << "supply same package again - MiM attacker?"; + return false; + } + + conn->fRecvSeq = oper_seq; + + unsigned long ackn_oper = std::strtoul(str_end + 1, &str_end, 10); if (!str_end || *str_end != ':') { R__LOG_ERROR(WebGUILog()) << "missing number of acknowledged operations"; return false; @@ -810,12 +988,12 @@ bool RWebWindow::ProcessWS(THttpCallArg &arg) Long_t processed_len = (str_end + 1 - buf); - if (processed_len > arg.GetPostDataLength()) { + if (processed_len > data_len) { R__LOG_ERROR(WebGUILog()) << "corrupted buffer"; return false; } - std::string cdata(str_end + 1, arg.GetPostDataLength() - processed_len); + std::string cdata(str_end + 1, data_len - processed_len); timestamp_t stamp = std::chrono::system_clock::now(); @@ -885,18 +1063,12 @@ bool RWebWindow::ProcessWS(THttpCallArg &arg) conn->fDisplayHandle->Resize(width, height); } } else if (cdata == "GENERATE_KEY") { - if (fMaster) { R__LOG_ERROR(WebGUILog()) << "Not able to generate new key with master connections"; } else { - auto newkey = GenerateKey(); - if(newkey.empty()) { - R__LOG_ERROR(WebGUILog()) << "Fail to generate new key by GENERATE_KEY request"; - } else { - SubmitData(conn->fConnId, true, "NEW_KEY="s + newkey, -1); - conn->fKey = newkey; - conn->fKeyUsed = -1; - } + conn->fNewKey = GenerateKey(); + if(!conn->fNewKey.empty()) + SubmitData(conn->fConnId, true, "NEW_KEY="s + conn->fNewKey, -1); } } } else if (fPanelName.length() && (conn->fReady < 10)) { @@ -967,6 +1139,8 @@ std::string RWebWindow::_MakeSendHeader(std::shared_ptr &conn, bool txt if (txt) buf.reserve(data.length() + 100); + buf.append(std::to_string(conn->fSendSeq++)); + buf.append(":"); buf.append(std::to_string(conn->fRecvCount)); buf.append(":"); buf.append(std::to_string(conn->fSendCredits)); @@ -983,6 +1157,8 @@ std::string RWebWindow::_MakeSendHeader(std::shared_ptr &conn, bool txt buf.append("$$nullbinary$$"); } else { buf.append("$$binary$$"); + if (!conn->fKey.empty() && !fMgr->fSessionKey.empty() && fMgr->fUseSessionKey) + buf.append(HMAC(conn->fKey, fMgr->fSessionKey, data.data(), data.length())); } return buf; @@ -994,7 +1170,7 @@ std::string RWebWindow::_MakeSendHeader(std::shared_ptr &conn, bool txt bool RWebWindow::CheckDataToSend(std::shared_ptr &conn) { - std::string hdr, data; + std::string hdr, data, prefix; { std::lock_guard grd(conn->fMutex); @@ -1017,6 +1193,16 @@ bool RWebWindow::CheckDataToSend(std::shared_ptr &conn) conn->fDoingSend = true; } + // add HMAC checksum for string send to client + if (!conn->fKey.empty() && !fMgr->fSessionKey.empty() && fMgr->fUseSessionKey) { + prefix = HMAC(conn->fKey, fMgr->fSessionKey, hdr.c_str(), hdr.length()); + } else { + prefix = "none"; + } + + prefix += ":"; + hdr.insert(0, prefix); + int res = 0; if (data.empty()) { @@ -1811,3 +1997,62 @@ bool RWebWindow::EmbedFileDialog(const std::shared_ptr &window, unsi return gStartDialogFunc(window, connid, args); } + +///////////////////////////////////////////////////////////////////////////////////// +/// Calculate HMAC checksum for provided key and message +/// Key combained from connection key and session key + +std::string RWebWindow::HMAC(const std::string &key, const std::string &sessionKey, const char *msg, int msglen) +{ + using namespace ROOT::Internal::SHA256; + + auto get_digest = [](sha256_t &hash, bool as_hex = false) -> std::string { + std::string digest; + digest.resize(32); + + sha256_final(&hash, reinterpret_cast(digest.data())); + + if (!as_hex) return digest; + + static const char* digits = "0123456789abcdef"; + std::string hex; + for (int n = 0; n < 32; n++) { + unsigned char code = (unsigned char) digest[n]; + hex += digits[code / 16]; + hex += digits[code % 16]; + } + return hex; + }; + + // calculate hash of sessionKey + key; + sha256_t hash1; + sha256_init(&hash1); + sha256_update(&hash1, (const unsigned char *) sessionKey.data(), sessionKey.length()); + sha256_update(&hash1, (const unsigned char *) key.data(), key.length()); + std::string kbis = get_digest(hash1); + + kbis.resize(64, 0); // resize to blocksize 64 bytes required by the sha256 + + std::string ki = kbis, ko = kbis; + const int opad = 0x5c; + const int ipad = 0x36; + for (size_t i = 0; i < kbis.length(); ++i) { + ko[i] = kbis[i] ^ opad; + ki[i] = kbis[i] ^ ipad; + } + + // calculate hash for ko + msg; + sha256_t hash2; + sha256_init(&hash2); + sha256_update(&hash2, (const unsigned char *) ki.data(), ki.length()); + sha256_update(&hash2, (const unsigned char *) msg, msglen); + std::string m2digest = get_digest(hash2); + + // calculate hash for ki + m2_digest; + sha256_t hash3; + sha256_init(&hash3); + sha256_update(&hash3, (const unsigned char *) ko.data(), ko.length()); + sha256_update(&hash3, (const unsigned char *) m2digest.data(), m2digest.length()); + + return get_digest(hash3, true); +} diff --git a/gui/webdisplay/src/RWebWindowWSHandler.hxx b/gui/webdisplay/src/RWebWindowWSHandler.hxx index 993d72005fc43..502dd4e81a0e7 100644 --- a/gui/webdisplay/src/RWebWindowWSHandler.hxx +++ b/gui/webdisplay/src/RWebWindowWSHandler.hxx @@ -50,6 +50,18 @@ protected: } } + if (fWindow.IsRequireAuthKey()) { + TUrl url; + url.SetOptions(arg->GetQuery()); + TString key = url.GetValueFromOptions("key"); + if (key.IsNull() || !fWindow.HasKey(key.Data())) { + // refuce loading of default web page without valid key + arg->SetContent("refused"); + arg->Set404(); + return; + } + } + auto version = fWindow.GetClientVersion(); if (!version.empty()) { // replace link to JSROOT modules in import statements emulating new version for browser diff --git a/gui/webdisplay/src/RWebWindowsManager.cxx b/gui/webdisplay/src/RWebWindowsManager.cxx index add1be8be9fce..f3ea7e9d1d837 100644 --- a/gui/webdisplay/src/RWebWindowsManager.cxx +++ b/gui/webdisplay/src/RWebWindowsManager.cxx @@ -25,6 +25,7 @@ #include "TApplication.h" #include "TTimer.h" #include "TRandom.h" +#include "TError.h" #include "TROOT.h" #include "TEnv.h" #include "TExec.h" @@ -90,6 +91,8 @@ std::shared_ptr &RWebWindowsManager::Instance() static std::thread::id gWebWinMainThrd = std::this_thread::get_id(); static bool gWebWinMainThrdSet = true; +static bool gWebWinLoopbackMode = true; +static bool gWebWinUseSessionKey = true; ////////////////////////////////////////////////////////////////////////////////////////// /// Returns true when called from main process @@ -116,12 +119,68 @@ void RWebWindowsManager::AssignMainThrd() gWebWinMainThrd = std::this_thread::get_id(); } + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set loopback mode for THttpServer used for web widgets +/// By default is on. Only local communication via localhost address is possible +/// Disable it only if really necessary - it may open unauthorized access to your application from external nodes!! + +void RWebWindowsManager::SetLoopbackMode(bool on) +{ + gWebWinLoopbackMode = on; + if (!on) { + printf("\nWARNING!\n"); + printf("Disabling loopback mode may leads to security problem.\n"); + printf("See https://root.cern/about/security/ for more information.\n\n"); + if (!gWebWinUseSessionKey) { + printf("Enforce session key to safely work on public network.\n"); + printf("One may call RWebWindowsManager::SetUseSessionKey(false); to disable it.\n"); + gWebWinUseSessionKey = true; + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns true if loopback mode used by THttpServer for web widgets + +bool RWebWindowsManager::IsLoopbackMode() +{ + return gWebWinLoopbackMode; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Enable or disable usage of session key +/// If enabled, each packet send to or from server is signed with special hashsum +/// This protects http server from different attacks to get access to server functionality + +void RWebWindowsManager::SetUseSessionKey(bool on) +{ + gWebWinUseSessionKey = on; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Static method to generate kryptographic key + +std::string RWebWindowsManager::GenerateKey(int keylen) +{ + // try to randomize?? + gRandom->SetSeed(); + std::string key; + for(int n = 0; n < keylen; n++) + key.append(TString::Itoa(gRandom->Integer(0xFFFFFFF), 16).Data()); + return key; +} + + ////////////////////////////////////////////////////////////////////////////////////////// /// window manager constructor /// Required here for correct usage of unique_ptr RWebWindowsManager::RWebWindowsManager() { + fSessionKey = GenerateKey(8); + fUseSessionKey = gWebWinUseSessionKey; + fExternalProcessEvents = RWebWindowWSHandler::GetBoolEnv("WebGui.ExternalProcessEvents") == 1; if (fExternalProcessEvents) RWebWindowsManager::AssignMainThrd(); @@ -331,8 +390,7 @@ bool RWebWindowsManager::CreateServer(bool with_http) int fcgi_thrds = gEnv->GetValue("WebGui.FastCgiThreads", 10); const char *fcgi_serv = gEnv->GetValue("WebGui.FastCgiServer", ""); fLaunchTmout = gEnv->GetValue("WebGui.LaunchTmout", 30.); - // always use loopback - bool assign_loopback = true; // RWebWindowWSHandler::GetBoolEnv("WebGui.HttpLoopback", 1) == 1; + bool assign_loopback = gWebWinLoopbackMode; const char *http_bind = gEnv->GetValue("WebGui.HttpBind", ""); bool use_secure = RWebWindowWSHandler::GetBoolEnv("WebGui.UseHttps", 0) == 1; const char *ssl_cert = gEnv->GetValue("WebGui.ServerCert", "rootserver.pem"); @@ -413,17 +471,17 @@ bool RWebWindowsManager::CreateServer(bool with_http) } } - engine.Append(TString::Format("webgui&thrds=%d&websocket_timeout=%d", http_thrds, http_wstmout)); + engine.Append(TString::Format("webgui&top=remote&thrds=%d&websocket_timeout=%d", http_thrds, http_wstmout)); if (http_maxage >= 0) engine.Append(TString::Format("&max_age=%d", http_maxage)); - if (use_secure) { + if (use_secure && !strchr(ssl_cert,'&')) { engine.Append("&ssl_cert="); engine.Append(ssl_cert); } - if (extra_args && strlen(extra_args) > 0) { + if (!use_unix_socket && !assign_loopback && extra_args && strlen(extra_args) > 0) { engine.Append("&"); engine.Append(extra_args); } @@ -517,7 +575,7 @@ void RWebWindowsManager::Unregister(RWebWindow &win) ////////////////////////////////////////////////////////////////////////// /// Provide URL address to access specified window from inside or from remote -std::string RWebWindowsManager::GetUrl(const RWebWindow &win, bool remote) +std::string RWebWindowsManager::GetUrl(RWebWindow &win, bool remote, std::string *produced_key) { if (!fServer) { R__LOG_ERROR(WebGUILog()) << "Server instance not exists when requesting window URL"; @@ -525,20 +583,47 @@ std::string RWebWindowsManager::GetUrl(const RWebWindow &win, bool remote) } std::string addr = "/"; - addr.append(win.fWSHandler->GetName()); - addr.append("/"); + bool qmark = false; + + std::string key; + + if (win.IsRequireAuthKey() || produced_key) { + key = win.GenerateKey(); + R__ASSERT(!key.empty()); + addr.append("?key="); + addr.append(key); + qmark = true; + std::unique_ptr dummy; + win.AddDisplayHandle(false, key, dummy); + } + + auto token = win.GetConnToken(); + if (!token.empty()) { + if (!qmark) addr.append("?"); + addr.append("token="); + addr.append(token); + } + if (remote) { - if (!CreateServer(true)) { + if (!CreateServer(true) || fAddr.empty()) { R__LOG_ERROR(WebGUILog()) << "Fail to start real HTTP server when requesting URL"; + if (!key.empty()) + win.RemoveKey(key); return ""; } addr = fAddr + addr; + + if (!key.empty() && !fSessionKey.empty() && fUseSessionKey && win.IsRequireAuthKey()) + addr += "#"s + fSessionKey; } + if (produced_key) + *produced_key = key; + return addr; } @@ -602,23 +687,11 @@ unsigned RWebWindowsManager::ShowWindow(RWebWindow &win, const RWebDisplayArgs & return 0; } - // place here while involves conn mutex - auto token = win.GetConnToken(); - - // we book manager mutex for a longer operation, - std::lock_guard grd(fMutex); - if (!fServer) { R__LOG_ERROR(WebGUILog()) << "Server instance not exists to show window"; return 0; } - std::string key = win.GenerateKey(); - if (key.empty()) { - R__LOG_ERROR(WebGUILog()) << "Fail to create unique key for the window"; - return 0; - } - RWebDisplayArgs args(user_args); if (args.IsHeadless() && !args.IsSupportHeadless()) { @@ -626,6 +699,22 @@ unsigned RWebWindowsManager::ShowWindow(RWebWindow &win, const RWebDisplayArgs & return 0; } + bool normal_http = !args.IsLocalDisplay(); + if (!normal_http && (gEnv->GetValue("WebGui.ForceHttp", 0) == 1)) + normal_http = true; + + std::string key; + + std::string url = GetUrl(win, normal_http, &key); + // empty url indicates failure, which already pinted by GetUrl method + if (url.empty()) + return 0; + + // we book manager mutex for a longer operation, + std::lock_guard grd(fMutex); + + args.SetUrl(url); + if (args.GetWidth() <= 0) args.SetWidth(win.GetWidth()); if (args.GetHeight() <= 0) @@ -635,27 +724,8 @@ unsigned RWebWindowsManager::ShowWindow(RWebWindow &win, const RWebDisplayArgs & if (args.GetY() < 0) args.SetY(win.GetY()); - bool normal_http = !args.IsLocalDisplay(); - if (!normal_http && (gEnv->GetValue("WebGui.ForceHttp", 0) == 1)) - normal_http = true; - - std::string url = GetUrl(win, normal_http); - if (url.empty()) { - R__LOG_ERROR(WebGUILog()) << "Cannot create URL for the window"; - return 0; - } - if (normal_http && fAddr.empty()) { - R__LOG_WARNING(WebGUILog()) << "Full URL cannot be produced for window " << url << " to start web browser"; - return 0; - } - - args.SetUrl(url); - - args.AppendUrlOpt(std::string("key=") + key); if (args.IsHeadless()) args.AppendUrlOpt("headless"); // used to create holder request - if (!token.empty()) - args.AppendUrlOpt(std::string("token=") + token); if (!args.IsHeadless() && normal_http) { auto winurl = args.GetUrl(); @@ -697,6 +767,8 @@ unsigned RWebWindowsManager::ShowWindow(RWebWindow &win, const RWebDisplayArgs & if (!handle) { R__LOG_ERROR(WebGUILog()) << "Cannot display window in " << args.GetBrowserName(); + if (!key.empty()) + win.RemoveKey(key); return 0; } diff --git a/hist/doc/Hist.md b/hist/doc/Hist.md index cd7cc3031c1a8..d8065623926aa 100644 --- a/hist/doc/Hist.md +++ b/hist/doc/Hist.md @@ -7,18 +7,21 @@ ROOT supports the following histogram types: * TH1C : histograms with one byte per channel. Maximum bin content = 127 * TH1S : histograms with one short per channel. Maximum bin content = 32767 * TH1I : histograms with one int per channel. Maximum bin content = 2147483647 + * TH1L : histograms with one long64 per channel. Maximum bin content = 9223372036854775807 * TH1F : histograms with one float per channel. Maximum precision 7 digits * TH1D : histograms with one double per channel. Maximum precision 14 digits * 2-D histograms: * TH2C : histograms with one byte per channel. Maximum bin content = 127 * TH2S : histograms with one short per channel. Maximum bin content = 32767 * TH2I : histograms with one int per channel. Maximum bin content = 2147483647 + * TH2L : histograms with one long64 per channel. Maximum bin content = 9223372036854775807 * TH2F : histograms with one float per channel. Maximum precision 7 digits * TH2D : histograms with one double per channel. Maximum precision 14 digits * 3-D histograms: * TH3C : histograms with one byte per channel. Maximum bin content = 127 * TH3S : histograms with one short per channel. Maximum bin content = 32767 * TH3I : histograms with one int per channel. Maximum bin content = 2147483647 + * TH3L : histograms with one long64 per channel. Maximum bin content = 9223372036854775807 * TH3F : histograms with one float per channel. Maximum precision 7 digits * TH3D : histograms with one double per channel. Maximum precision 14 digits diff --git a/hist/hist/CMakeLists.txt b/hist/hist/CMakeLists.txt index 58a17430efe25..cd75f69c397d7 100644 --- a/hist/hist/CMakeLists.txt +++ b/hist/hist/CMakeLists.txt @@ -49,12 +49,14 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Hist TH1.h TH1I.h TH1K.h + TH1L.h TH1S.h TH2C.h TH2D.h TH2F.h TH2.h TH2I.h + TH2L.h TH2Poly.h TH2S.h TH3C.h @@ -62,6 +64,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Hist TH3F.h TH3.h TH3I.h + TH3L.h TH3S.h THLimitsFinder.h THnBase.h diff --git a/hist/hist/inc/LinkDef.h b/hist/hist/inc/LinkDef.h index 98102b2bb9038..b09e030299824 100644 --- a/hist/hist/inc/LinkDef.h +++ b/hist/hist/inc/LinkDef.h @@ -63,6 +63,7 @@ #pragma link C++ class TH1S+; #pragma link C++ class TH1I+; #pragma link C++ class TH1K+; +#pragma link C++ class TH1L+; #pragma link C++ class TH2-; #pragma link C++ class TH2C-; #pragma link C++ class TH2D-; @@ -75,12 +76,14 @@ #pragma link C++ class TProfile2PolyBin+; #pragma link C++ class TH2S-; #pragma link C++ class TH2I+; +#pragma link C++ class TH2L+; #pragma link C++ class TH3-; #pragma link C++ class TH3C-; #pragma link C++ class TH3D-; #pragma link C++ class TH3F-; #pragma link C++ class TH3S-; #pragma link C++ class TH3I+; +#pragma link C++ class TH3L+; #pragma link C++ class THLimitsFinder+; #pragma link C++ class THnBase+; #pragma link C++ class THnIter+; @@ -144,7 +147,7 @@ #pragma link C++ class THnSparse+; #pragma link C++ class THnSparseT+; #pragma link C++ class THnSparseT+; -#pragma link C++ class THnSparseT+; +#pragma link C++ class THnSparseT+; #pragma link C++ class THnSparseT+; #pragma link C++ class THnSparseT+; #pragma link C++ class THnSparseT+; @@ -231,6 +234,13 @@ #pragma link C++ function operator*(TH1I&, TH1I&); #pragma link C++ function operator/(TH1I&, TH1I&); +#pragma link C++ function operator*(Float_t,TH1L&); +#pragma link C++ function operator*(TH1L&, Float_t); +#pragma link C++ function operator+(TH1L&, TH1L&); +#pragma link C++ function operator-(TH1L&, TH1L&); +#pragma link C++ function operator*(TH1L&, TH1L&); +#pragma link C++ function operator/(TH1L&, TH1L&); + #pragma link C++ function operator*(Float_t,TH1F&); #pragma link C++ function operator*(TH1F&, Float_t); #pragma link C++ function operator+(TH1F&, TH1F&); @@ -266,6 +276,13 @@ #pragma link C++ function operator*(TH2I&, TH2I&); #pragma link C++ function operator/(TH2I&, TH2I&); +#pragma link C++ function operator*(Float_t,TH2L&); +#pragma link C++ function operator*(TH2L&, Float_t); +#pragma link C++ function operator+(TH2L&, TH2L&); +#pragma link C++ function operator-(TH2L&, TH2L&); +#pragma link C++ function operator*(TH2L&, TH2L&); +#pragma link C++ function operator/(TH2L&, TH2L&); + #pragma link C++ function operator*(Float_t,TH2F&); #pragma link C++ function operator*(TH2F&, Float_t); #pragma link C++ function operator+(TH2F&, TH2F&); @@ -301,6 +318,13 @@ #pragma link C++ function operator*(TH3I&, TH3I&); #pragma link C++ function operator/(TH3I&, TH3I&); +#pragma link C++ function operator*(Float_t,TH3L&); +#pragma link C++ function operator*(TH3L&, Float_t); +#pragma link C++ function operator+(TH3L&, TH3L&); +#pragma link C++ function operator-(TH3L&, TH3L&); +#pragma link C++ function operator*(TH3L&, TH3L&); +#pragma link C++ function operator/(TH3L&, TH3L&); + #pragma link C++ function operator*(Float_t,TH3F&); #pragma link C++ function operator*(TH3F&, Float_t); #pragma link C++ function operator+(TH3F&, TH3F&); diff --git a/hist/hist/inc/TEfficiency.h b/hist/hist/inc/TEfficiency.h index 686821075c80c..81a64d40be0a0 100644 --- a/hist/hist/inc/TEfficiency.h +++ b/hist/hist/inc/TEfficiency.h @@ -154,7 +154,7 @@ class TEfficiency: public TNamed, public TAttLine, public TAttFill, public TAttM const Double_t *zBins); void SetTitle(const char* title) override; - Bool_t SetTotalEvents(Int_t bin,Int_t events); + Bool_t SetTotalEvents(Int_t bin, Double_t events); Bool_t SetTotalHistogram(const TH1& rTotal,Option_t* opt); void SetUseWeightedEvents(Bool_t on = kTRUE); void SetWeight(Double_t weight); diff --git a/hist/hist/inc/TF1.h b/hist/hist/inc/TF1.h index 5d14b70a41183..877709aca62a9 100644 --- a/hist/hist/inc/TF1.h +++ b/hist/hist/inc/TF1.h @@ -466,6 +466,7 @@ class TF1 : public TNamed, public TAttLine, public TAttFill, public TAttMarker { { return (fType == EFType::kTemplVec) || (fType == EFType::kFormula && fFormula && fFormula->IsVectorized()); } + /// Return the Chisquare after fitting. See ROOT::Fit::FitResult::Chi2() Double_t GetChisquare() const { return fChisquare; diff --git a/hist/hist/inc/TGraph.h b/hist/hist/inc/TGraph.h index 9c7249a4d25bb..167a6d57c5450 100644 --- a/hist/hist/inc/TGraph.h +++ b/hist/hist/inc/TGraph.h @@ -50,6 +50,7 @@ class TGraph : public TNamed, public TAttLine, public TAttFill, public TAttMarke TH1F *fHistogram; ///< Pointer to histogram used for drawing axis Double_t fMinimum; ///< Minimum value for plotting along y Double_t fMaximum; ///< Maximum value for plotting along y + TString fOption; ///< Options used for drawing the graph static void SwapValues(Double_t* arr, Int_t pos1, Int_t pos2); virtual void SwapPoints(Int_t pos1, Int_t pos2); @@ -185,6 +186,7 @@ class TGraph : public TNamed, public TAttLine, public TAttFill, public TAttMarke virtual void SetMaximum(Double_t maximum=-1111); // *MENU* virtual void SetMinimum(Double_t minimum=-1111); // *MENU* virtual void Set(Int_t n); + virtual void SetOption(Option_t *option = " ") { fOption = option; } virtual void SetPoint(Int_t i, Double_t x, Double_t y); virtual void SetPointX(Int_t i, Double_t x); virtual void SetPointY(Int_t i, Double_t y); @@ -197,7 +199,7 @@ class TGraph : public TNamed, public TAttLine, public TAttFill, public TAttMarke void UseCurrentStyle() override; void Zero(Int_t &k,Double_t AZ,Double_t BZ,Double_t E2,Double_t &X,Double_t &Y,Int_t maxiterations); - ClassDefOverride(TGraph,4) //Graph graphics class + ClassDefOverride(TGraph, 5) // Graph graphics class }; #endif diff --git a/hist/hist/inc/TH1.h b/hist/hist/inc/TH1.h index 09ae2e18c971f..c8fe9c539780c 100644 --- a/hist/hist/inc/TH1.h +++ b/hist/hist/inc/TH1.h @@ -32,6 +32,7 @@ #include "TArrayC.h" #include "TArrayS.h" #include "TArrayI.h" +#include "TArrayL64.h" #include "TArrayF.h" #include "TArrayD.h" #include "Foption.h" @@ -355,6 +356,7 @@ class TH1 : public TNamed, public TAttLine, public TAttFill, public TAttMarker { void RecursiveRemove(TObject *obj) override; virtual void Reset(Option_t *option = ""); virtual void ResetStats(); + void SaveAs(const char *filename, Option_t *option) const override; // *MENU* void SavePrimitive(std::ostream &out, Option_t *option = "") override; virtual void Scale(Double_t c1=1, Option_t *option=""); virtual void SetAxisColor(Color_t color=1, Option_t *axis="X"); @@ -551,7 +553,7 @@ class TH1I: public TH1, public TArrayI { void Reset(Option_t *option="") override; void SetBinsLength(Int_t n=-1) override; - ClassDefOverride(TH1I,3) //1-Dim histograms (one 32 bits integer per channel) + ClassDefOverride(TH1I,3) //1-Dim histograms (one 32 bit integer per channel) friend TH1I operator*(Double_t c1, const TH1I &h1); friend TH1I operator*(const TH1I &h1, Double_t c1); @@ -575,6 +577,47 @@ TH1I operator/(const TH1I &h1, const TH1I &h2); //________________________________________________________________________ +class TH1L: public TH1, public TArrayL64 { + +public: + TH1L(); + TH1L(const char *name,const char *title,Int_t nbinsx,Double_t xlow,Double_t xup); + TH1L(const char *name,const char *title,Int_t nbinsx,const Float_t *xbins); + TH1L(const char *name,const char *title,Int_t nbinsx,const Double_t *xbins); + TH1L(const TH1L &h1l); + TH1L& operator=(const TH1L &h1); + ~TH1L() override; + + void AddBinContent(Int_t bin) override; + void AddBinContent(Int_t bin, Double_t w) override; + void Copy(TObject &hnew) const override; + void Reset(Option_t *option="") override; + void SetBinsLength(Int_t n=-1) override; + + ClassDefOverride(TH1L,0) //1-Dim histograms (one 64 bit integer per channel) + + friend TH1L operator*(Double_t c1, const TH1L &h1); + friend TH1L operator*(const TH1L &h1, Double_t c1); + friend TH1L operator+(const TH1L &h1, const TH1L &h2); + friend TH1L operator-(const TH1L &h1, const TH1L &h2); + friend TH1L operator*(const TH1L &h1, const TH1L &h2); + friend TH1L operator/(const TH1L &h1, const TH1L &h2); + +protected: + Double_t RetrieveBinContent(Int_t bin) const override { return Double_t (fArray[bin]); } + void UpdateBinContent(Int_t bin, Double_t content) override { fArray[bin] = Int_t (content); } +}; + +TH1L operator*(Double_t c1, const TH1L &h1); +inline +TH1L operator*(const TH1L &h1, Double_t c1) {return operator*(c1,h1);} +TH1L operator+(const TH1L &h1, const TH1L &h2); +TH1L operator-(const TH1L &h1, const TH1L &h2); +TH1L operator*(const TH1L &h1, const TH1L &h2); +TH1L operator/(const TH1L &h1, const TH1L &h2); + +//________________________________________________________________________ + class TH1F : public TH1, public TArrayF { public: diff --git a/hist/hist/inc/TH1L.h b/hist/hist/inc/TH1L.h new file mode 100644 index 0000000000000..2dfa6f0dfcb2f --- /dev/null +++ b/hist/hist/inc/TH1L.h @@ -0,0 +1,26 @@ +// @(#)root/hist:$Id$ +// Author: Rene Brun 08/09/2003 + +/************************************************************************* + * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_TH1L +#define ROOT_TH1L + + +////////////////////////////////////////////////////////////////////////// +// // +// TH1L // +// // +// 1-Dim histogram with a 64 bit integer per channel // +// // +////////////////////////////////////////////////////////////////////////// + +#include "TH1.h" + +#endif diff --git a/hist/hist/inc/TH2.h b/hist/hist/inc/TH2.h index 9110a9ea01842..73e6fb3addc34 100644 --- a/hist/hist/inc/TH2.h +++ b/hist/hist/inc/TH2.h @@ -249,9 +249,46 @@ class TH2I : public TH2, public TArrayI { Double_t RetrieveBinContent(Int_t bin) const override { return Double_t (fArray[bin]); } void UpdateBinContent(Int_t bin, Double_t content) override { fArray[bin] = Int_t (content); } - ClassDefOverride(TH2I,4) //2-Dim histograms (one 32 bits integer per channel) + ClassDefOverride(TH2I,4) //2-Dim histograms (one 32 bit integer per channel) }; +//______________________________________________________________________________ + +class TH2L : public TH2, public TArrayL64 { + +public: + TH2L(); + TH2L(const char *name,const char *title,Int_t nbinsx,Double_t xlow,Double_t xup + ,Int_t nbinsy,Double_t ylow,Double_t yup); + TH2L(const char *name,const char *title,Int_t nbinsx,const Double_t *xbins + ,Int_t nbinsy,Double_t ylow,Double_t yup); + TH2L(const char *name,const char *title,Int_t nbinsx,Double_t xlow,Double_t xup + ,Int_t nbinsy,const Double_t *ybins); + TH2L(const char *name,const char *title,Int_t nbinsx,const Double_t *xbins + ,Int_t nbinsy,const Double_t *ybins); + TH2L(const char *name,const char *title,Int_t nbinsx,const Float_t *xbins + ,Int_t nbinsy,const Float_t *ybins); + TH2L(const TH2L &h2l); + ~TH2L() override; + void AddBinContent(Int_t bin) override; + void AddBinContent(Int_t bin, Double_t w) override; + void Copy(TObject &hnew) const override; + void Reset(Option_t *option="") override; + void SetBinsLength(Int_t n=-1) override; + TH2L& operator=(const TH2L &h1); + friend TH2L operator*(Float_t c1, TH2L &h1); + friend TH2L operator*(TH2L &h1, Float_t c1) {return operator*(c1,h1);} + friend TH2L operator+(TH2L &h1, TH2L &h2); + friend TH2L operator-(TH2L &h1, TH2L &h2); + friend TH2L operator*(TH2L &h1, TH2L &h2); + friend TH2L operator/(TH2L &h1, TH2L &h2); + +protected: + Double_t RetrieveBinContent(Int_t bin) const override { return Double_t (fArray[bin]); } + void UpdateBinContent(Int_t bin, Double_t content) override { fArray[bin] = Int_t (content); } + + ClassDefOverride(TH2L,0) //2-Dim histograms (one 64 bit integer per channel) +}; //______________________________________________________________________________ diff --git a/hist/hist/inc/TH2L.h b/hist/hist/inc/TH2L.h new file mode 100644 index 0000000000000..2e2dbc0e89fa4 --- /dev/null +++ b/hist/hist/inc/TH2L.h @@ -0,0 +1,26 @@ +// @(#)root/hist:$Id$ +// Author: Rene Brun 18/05/2002 + +/************************************************************************* + * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_TH2L +#define ROOT_TH2L + + +////////////////////////////////////////////////////////////////////////// +// // +// TH2L // +// // +// 2-Dim histogram with a 64 bit integer per channel // +// // +////////////////////////////////////////////////////////////////////////// + +#include "TH2.h" + +#endif diff --git a/hist/hist/inc/TH2Poly.h b/hist/hist/inc/TH2Poly.h index 8e88b076db92e..d35c0e00686ef 100644 --- a/hist/hist/inc/TH2Poly.h +++ b/hist/hist/inc/TH2Poly.h @@ -70,6 +70,8 @@ class TH2Poly : public TH2 { TH2Poly(const char *name,const char *title, Double_t xlow, Double_t xup, Double_t ylow, Double_t yup); TH2Poly(const char *name,const char *title, Int_t nX, Double_t xlow, Double_t xup, Int_t nY, Double_t ylow, Double_t yup); ~TH2Poly() override; + TH2Poly(const TH2Poly & rhs); + TH2Poly & operator=(const TH2Poly & rhs); virtual TH2PolyBin *CreateBin(TObject *poly); virtual Int_t AddBin(TObject *poly); @@ -80,6 +82,7 @@ class TH2Poly : public TH2 { Bool_t Add(TF1 *h1, Double_t c1=1, Option_t *option="") override; void ClearBinContents(); // Clears the content of all bins TObject *Clone(const char* newname = "") const override; + void Copy(TObject & newth2p) const override; void ChangePartition(Int_t n, Int_t m); // Sets the number of partition cells to another value using TH2::Multiply; using TH2::Divide; @@ -107,7 +110,7 @@ class TH2Poly : public TH2 { Double_t GetMinimum() const; Double_t GetMinimum(Double_t minval) const override; Bool_t GetNewBinAdded() const{return fNewBinAdded;} - Int_t GetNumberOfBins() const{return fNcells-kNOverflow;} + Int_t GetNumberOfBins() const; void Honeycomb(Double_t xstart, Double_t ystart, Double_t a, Int_t k, Int_t s, Option_t* option = "v"); Double_t Integral(Option_t* option = "") const override; Long64_t Merge(TCollection *) override; diff --git a/hist/hist/inc/TH3.h b/hist/hist/inc/TH3.h index ec479eb96fc92..bfdf893656769 100644 --- a/hist/hist/inc/TH3.h +++ b/hist/hist/inc/TH3.h @@ -259,7 +259,44 @@ class TH3I : public TH3, public TArrayI { Double_t RetrieveBinContent(Int_t bin) const override { return Double_t (fArray[bin]); } void UpdateBinContent(Int_t bin, Double_t content) override { fArray[bin] = Int_t (content); } - ClassDefOverride(TH3I,4) //3-Dim histograms (one 32 bits integer per channel) + ClassDefOverride(TH3I,4) //3-Dim histograms (one 32 bit integer per channel) +}; + + +//________________________________________________________________________ + +class TH3L : public TH3, public TArrayL64 { +public: + TH3L(); + TH3L(const char *name,const char *title,Int_t nbinsx,Double_t xlow,Double_t xup + ,Int_t nbinsy,Double_t ylow,Double_t yup + ,Int_t nbinsz,Double_t zlow,Double_t zup); + TH3L(const char *name,const char *title,Int_t nbinsx,const Float_t *xbins + ,Int_t nbinsy,const Float_t *ybins + ,Int_t nbinsz,const Float_t *zbins); + TH3L(const char *name,const char *title,Int_t nbinsx,const Double_t *xbins + ,Int_t nbinsy,const Double_t *ybins + ,Int_t nbinsz,const Double_t *zbins); + TH3L(const TH3L &h3l); + ~TH3L() override; + void AddBinContent(Int_t bin) override; + void AddBinContent(Int_t bin, Double_t w) override; + void Copy(TObject &hnew) const override; + void Reset(Option_t *option="") override; + void SetBinsLength(Int_t n=-1) override; + TH3L& operator=(const TH3L &h1); + friend TH3L operator*(Float_t c1, TH3L &h1); + friend TH3L operator*(TH3L &h1, Float_t c1) {return operator*(c1,h1);} + friend TH3L operator+(TH3L &h1, TH3L &h2); + friend TH3L operator-(TH3L &h1, TH3L &h2); + friend TH3L operator*(TH3L &h1, TH3L &h2); + friend TH3L operator/(TH3L &h1, TH3L &h2); + +protected: + Double_t RetrieveBinContent(Int_t bin) const override { return Double_t (fArray[bin]); } + void UpdateBinContent(Int_t bin, Double_t content) override { fArray[bin] = Int_t (content); } + + ClassDefOverride(TH3L,0) //3-Dim histograms (one 64 bit integer per channel) }; diff --git a/hist/hist/inc/TH3L.h b/hist/hist/inc/TH3L.h new file mode 100644 index 0000000000000..613ba5d8fe9f9 --- /dev/null +++ b/hist/hist/inc/TH3L.h @@ -0,0 +1,26 @@ +// @(#)root/hist:$Id$ +// Author: Rene Brun 18/05/2002 + +/************************************************************************* + * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_TH3L +#define ROOT_TH3L + + +////////////////////////////////////////////////////////////////////////// +// // +// TH3L // +// // +// 3-Dim histogram with a 64 bit integer per channel // +// // +////////////////////////////////////////////////////////////////////////// + +#include "TH3.h" + +#endif diff --git a/hist/hist/inc/THStack.h b/hist/hist/inc/THStack.h index b148144c5f4b7..fe747d13a9c58 100644 --- a/hist/hist/inc/THStack.h +++ b/hist/hist/inc/THStack.h @@ -32,6 +32,8 @@ class TAxis; #include "TH1.h" #endif +#include + class TBrowser; class TFileMergeInfo; @@ -72,8 +74,8 @@ class THStack : public TNamed { TIter end() const { return TIter::End(); } Int_t GetNhists() const; TObjArray *GetStack(); - virtual Double_t GetMaximum(Option_t *option=""); - virtual Double_t GetMinimum(Option_t *option=""); + virtual Double_t GetMaximum(Option_t *option = "", Double_t maxval = std::numeric_limits::max()); + virtual Double_t GetMinimum(Option_t *option = "", Double_t minval = -std::numeric_limits::max()); TAxis *GetXaxis() const; TAxis *GetYaxis() const; TAxis *GetZaxis() const; diff --git a/hist/hist/inc/THn.h b/hist/hist/inc/THn.h index a53634b254ea6..9afaf14122efb 100644 --- a/hist/hist/inc/THn.h +++ b/hist/hist/inc/THn.h @@ -201,7 +201,7 @@ class THn: public THnBase { THnT | THnC | Char_t THnT | THnS | Short_t THnT | THnI | Int_t - THnT | THnL | Long_t + THnT | THnL | Long64_t THnT | THnF | Float_t THnT | THnD | Double_t @@ -245,7 +245,7 @@ typedef THnT THnD; typedef THnT THnC; typedef THnT THnS; typedef THnT THnI; -typedef THnT THnL; +typedef THnT THnL; typedef THnT THnL64; #endif // ROOT_THN diff --git a/hist/hist/inc/THnSparse.h b/hist/hist/inc/THnSparse.h index 8bc4dd0b2f384..25d4b7eb4e88d 100644 --- a/hist/hist/inc/THnSparse.h +++ b/hist/hist/inc/THnSparse.h @@ -27,6 +27,7 @@ // needed only for template instantiations of THnSparseT: #include "TArrayF.h" #include "TArrayL.h" +#include "TArrayL64.h" #include "TArrayI.h" #include "TArrayS.h" #include "TArrayC.h" @@ -107,7 +108,7 @@ class THnSparse: public THnBase { void SetBinContent(Long64_t bin, Double_t v) override; void SetBinError2(Long64_t bin, Double_t e2) override; - /// Forwards to THnBase::SetBinContent(). + /// Forwards to THnBase::AddBinContent(). /// Non-virtual, CINT-compatible replacement of a using declaration. void AddBinContent(const Int_t* idx, Double_t v = 1.) { THnBase::AddBinContent(idx, v); @@ -182,14 +183,14 @@ class THnSparse: public THnBase { Typedefs exist for template parameters with ROOT's generic types: - Templated name | Typedef | Bin content type - --------------------|--------------|-------------------- - THnSparseT | THnSparseC | Char_t - THnSparseT | THnSparseS | Short_t - THnSparseT | THnSparseI | Int_t - THnSparseT | THnSparseL | Long_t - THnSparseT | THnSparseF | Float_t - THnSparseT | THnSparseD | Double_t + Templated name | Typedef | Bin content type + ----------------------|---------------|-------------------- + THnSparseT | THnSparseC | Char_t + THnSparseT | THnSparseS | Short_t + THnSparseT | THnSparseI | Int_t + THnSparseT | THnSparseL | Long64_t + THnSparseT | THnSparseF | Float_t + THnSparseT | THnSparseD | Double_t We recommend to use THnSparseC wherever possible, and to map its value space of 256 possible values to e.g. float values outside the class. This saves an @@ -218,7 +219,7 @@ class THnSparseT: public THnSparse { typedef THnSparseT THnSparseD; typedef THnSparseT THnSparseF; -typedef THnSparseT THnSparseL; +typedef THnSparseT THnSparseL; typedef THnSparseT THnSparseI; typedef THnSparseT THnSparseS; typedef THnSparseT THnSparseC; diff --git a/hist/hist/src/HFitImpl.cxx b/hist/hist/src/HFitImpl.cxx index 0e2bbcc388b05..6f3f2a2e8e8cc 100644 --- a/hist/hist/src/HFitImpl.cxx +++ b/hist/hist/src/HFitImpl.cxx @@ -352,8 +352,8 @@ TFitResultPtr HFit::Fit(FitObject * h1, TF1 *f1 , Foption_t & fitOption , const // check if can use option user - //typedef void (* MinuitFCN_t )(int &npar, double *gin, double &f, double *u, int flag); TVirtualFitter::FCNFunc_t userFcn = nullptr; + // option user is enabled only when running in serial mode if (fitOption.User && TVirtualFitter::GetFitter() ) { userFcn = (TVirtualFitter::GetFitter())->GetFCN(); (TVirtualFitter::GetFitter())->SetUserFunc(f1); @@ -407,6 +407,7 @@ TFitResultPtr HFit::Fit(FitObject * h1, TF1 *f1 , Foption_t & fitOption , const // if using Fitter class must be done here // use old style Minuit for TMinuit and if no corrections have been applied if (!fitOption.Quiet) { + R__LOCKGUARD(gROOTMutex); if (fitter->GetMinimizer() && fitConfig.MinimizerType() == "Minuit" && !fitConfig.NormalizeErrors() && fitOption.Like <= 1) { fitter->GetMinimizer()->PrintResults(); // use old style Minuit @@ -418,10 +419,9 @@ TFitResultPtr HFit::Fit(FitObject * h1, TF1 *f1 , Foption_t & fitOption , const } } - - // store result in the backward compatible VirtualFitter - // in case multi-thread is not enabled - if (!gGlobalMutex) { + // store result in the backward compatible TVirtualFitter + { + R__LOCKGUARD(gROOTMutex); TVirtualFitter * lastFitter = TVirtualFitter::GetFitter(); TBackCompFitter * bcfitter = new TBackCompFitter(fitter, fitdata); bcfitter->SetFitOption(fitOption); @@ -444,10 +444,6 @@ TFitResultPtr HFit::Fit(FitObject * h1, TF1 *f1 , Foption_t & fitOption , const TVirtualFitter::SetFitter( bcfitter ); } - // use old-style for printing the results - // if (fitOption.Verbose) bcfitter->PrintResults(2,0.); - // else if (!fitOption.Quiet) bcfitter->PrintResults(1,0.); - if (fitOption.StoreResult) { TString name = "TFitResult-"; @@ -781,7 +777,15 @@ void ROOT::Fit::FitOptionsMake(EFitObjectType type, const char *option, Foption_ if (opt.Contains("W")) fitOption.W1 = 1; // ignorer all point errors when fitting } - if (opt.Contains("U")) fitOption.User = 1; + if (opt.Contains("U")) { + // user option can work only when not running in multiple threads + if (gGlobalMutex || !ROOT::IsImplicitMTEnabled()) { + fitOption.User = 1; + } else { + Warning("FitOptionsMake","Cannot use User (U) fit option when running in multi-thread mode. The option is ignored"); + fitOption.User = 0; + } + } if (opt.Contains("Q")) fitOption.Quiet = 1; if (opt.Contains("V")) {fitOption.Verbose = 1; fitOption.Quiet = 0;} @@ -928,29 +932,25 @@ TFitResultPtr ROOT::Fit::UnBinFit(ROOT::Fit::UnBinData * data, TF1 * fitfunc, Fo } - // store result in the backward compatible VirtualFitter - // in case not running in a multi-thread enabled mode - if (gGlobalMutex) { + // store fitting result in the backward compatible TVirtualFitter object + // lock in case running in a multi-thread enabled mode + { + R__LOCKGUARD(gROOTMutex); TVirtualFitter * lastFitter = TVirtualFitter::GetFitter(); - // pass ownership of Fitter and Fitdata to TBackCompFitter (fitter pointer cannot be used afterwards) TBackCompFitter * bcfitter = new TBackCompFitter(fitter, fitdata); // cannot use anymore now fitdata (given away ownership) fitdata = nullptr; bcfitter->SetFitOption(fitOption); - //bcfitter->SetObjectFit(fTree); bcfitter->SetUserFunc(fitfunc); - + // delete previous fitter and replace with the new one if (lastFitter) delete lastFitter; TVirtualFitter::SetFitter( bcfitter ); - // use old-style for printing the results - // if (fitOption.Verbose) bcfitter->PrintResults(2,0.); - // else if (!fitOption.Quiet) bcfitter->PrintResults(1,0.); - + // print results + if (fitOption.Verbose) fitResult.PrintCovMatrix(std::cout); + else if (!fitOption.Quiet) fitResult.Print(std::cout); } - // print results - if (fitOption.Verbose) fitResult.PrintCovMatrix(std::cout); - else if (!fitOption.Quiet) fitResult.Print(std::cout); + if (fitOption.StoreResult) { diff --git a/hist/hist/src/TAxis.cxx b/hist/hist/src/TAxis.cxx index 2cdc63c5d6487..84dc516d71bad 100644 --- a/hist/hist/src/TAxis.cxx +++ b/hist/hist/src/TAxis.cxx @@ -1062,7 +1062,9 @@ void TAxis::SetRange(Int_t first, Int_t last) fLast = fNbins; SetBit(kAxisRange, false); } else { + if (first<0) Warning("TAxis::SetRange","first < 0, 0 is used"); fFirst = std::max(first, 0); + if (last>nCells) Warning("TAxis::SetRange","last > fNbins+1, fNbins+1 is used"); fLast = std::min(last, nCells); SetBit(kAxisRange, true); } @@ -1087,6 +1089,8 @@ void TAxis::SetRangeUser(Double_t ufirst, Double_t ulast) return; } } + if (ufirstfXmax) Warning("TAxis::SetRangeUser","ulast > fXmax, fXmax is used"); Int_t ifirst = FindFixBin(ufirst); Int_t ilast = FindFixBin(ulast); // fixes for numerical error and for https://savannah.cern.ch/bugs/index.php?99777 diff --git a/hist/hist/src/TEfficiency.cxx b/hist/hist/src/TEfficiency.cxx index c64dace9bbe17..35ea41706ac10 100644 --- a/hist/hist/src/TEfficiency.cxx +++ b/hist/hist/src/TEfficiency.cxx @@ -66,6 +66,7 @@ ClassImp(TEfficiency); - [VI.1 Information about the internal histograms](\ref EFF061) - [VI.2 Fitting](\ref EFF062) - [VI.3 Draw a TEfficiency object](\ref EFF063) + - [VI.4 TEfficiency object's axis customisation](\ref EFF064) \anchor EFF01 ## I. Overview @@ -634,6 +635,52 @@ In the 1-dimensional case, you can use the same options as for the TGraphAsymmEr method. For 2-dimensional TEfficiency objects, you can pass the same options as for a TH2::Draw object. +\anchor EFF064 +### VI.4 TEfficiency object's axis customisation +The axes of a TEfficiency object can be accessed and customised by calling the +GetPaintedGraph method and then GetXaxis() or GetYaxis() and the corresponding TAxis +methods. +Note that in order to access the painted graph via GetPaintedGraph(), one should either +call Paint or, better, gPad->Update(). + +Begin_Macro(source) +{ + //canvas only needed for this documentation + TCanvas* c1 = new TCanvas("example","",600,400); + c1->SetFillStyle(1001); + c1->SetFillColor(kWhite); + c1->Divide(2,1); + + //create one-dimensional TEfficiency object with fixed bin size + TEfficiency* pEff = new TEfficiency("eff","my efficiency;x;#epsilon",20,0,10); + TRandom3 rand3; + + bool bPassed; + double x; + for(int i=0; i<10000; ++i) + { + //simulate events with variable under investigation + x = rand3.Uniform(10); + //check selection: bPassed = DoesEventPassSelection(x) + bPassed = rand3.Rndm() < TMath::Gaus(x,5,4); + pEff->Fill(bPassed,x); + } + c1->cd(1); + pEff->Draw("AP"); + c1->cd(2); + pEff->Draw("AP"); + gPad->Update(); + pEff->GetPaintedGraph()->GetXaxis()->SetTitleSize(0.05); + pEff->GetPaintedGraph()->GetXaxis()->SetLabelFont(42); + pEff->GetPaintedGraph()->GetXaxis()->SetLabelSize(0.05); + pEff->GetPaintedGraph()->GetYaxis()->SetTitleOffset(0.85); + pEff->GetPaintedGraph()->GetYaxis()->SetTitleSize(0.05); + pEff->GetPaintedGraph()->GetYaxis()->SetLabelFont(42); + pEff->GetPaintedGraph()->GetYaxis()->SetLabelSize(0.05); + pEff->GetPaintedGraph()->GetXaxis()->SetRangeUser(3,7); +} +End_Macro + */ //////////////////////////////////////////////////////////////////////////////// @@ -3216,7 +3263,7 @@ void TEfficiency::SavePrimitive(std::ostream& out,Option_t* opt) << std::endl; out << indent << name << "->SetBetaBeta(" << fBeta_beta << ");" << std::endl; out << indent << name << "->SetWeight(" << fWeight << ");" << std::endl; - out << indent << name << "->SetStatisticOption(" << fStatisticOption << ");" + out << indent << name << "->SetStatisticOption(static_cast(" << fStatisticOption << "));" << std::endl; out << indent << name << "->SetPosteriorMode(" << TestBit(kPosteriorMode) << ");" << std::endl; out << indent << name << "->SetShortestInterval(" << TestBit(kShortestInterval) << ");" << std::endl; @@ -3710,14 +3757,14 @@ void TEfficiency::SetTitle(const char* title) /// /// Note: - requires: fPassedHistogram->GetBinContent(bin) <= events -Bool_t TEfficiency::SetTotalEvents(Int_t bin,Int_t events) +Bool_t TEfficiency::SetTotalEvents(Int_t bin, Double_t events) { if(events >= fPassedHistogram->GetBinContent(bin)) { fTotalHistogram->SetBinContent(bin,events); return true; } else { - Error("SetTotalEvents(Int_t,Int_t)","passed number of events (%.1lf) in bin %i is bigger than given number of total events %i",fPassedHistogram->GetBinContent(bin),bin,events); + Error("SetTotalEvents(Int_t,Double_t)","passed number of events (%.1lf) in bin %i is bigger than given number of total events %.1lf",fPassedHistogram->GetBinContent(bin),bin,events); return false; } } diff --git a/hist/hist/src/TGraph.cxx b/hist/hist/src/TGraph.cxx index 77309fd48d1e5..e05ecdbd4216b 100644 --- a/hist/hist/src/TGraph.cxx +++ b/hist/hist/src/TGraph.cxx @@ -823,13 +823,17 @@ void TGraph::Draw(Option_t *option) opt.Replace(pos, 1, "p"); } - // If no option is specified, it is defined as "alp" in case there - // no current pad or if the current pad as no axis defined. - if (!option || !strlen(option)) { + // If no option is specified, it is defined as "alp" in case there is + // no current pad or if the current pad has no axis defined and if there is + // no default option set using TGraph::SetOption. If fOption is set using + // TGraph::SetOption, it is used as default option. + if ((!option || !strlen(option))) { + Option_t *topt = (!fOption.IsNull()) ? fOption.Data() : "alp"; if (gPad) { - if (!gPad->GetListOfPrimitives()->FindObject("TFrame")) opt = "alp"; + if (!gPad->GetListOfPrimitives()->FindObject("TFrame")) + opt = topt; } else { - opt = "alp"; + opt = topt; } } diff --git a/hist/hist/src/TH1.cxx b/hist/hist/src/TH1.cxx index ce8445fb81f75..17ba102708cc4 100644 --- a/hist/hist/src/TH1.cxx +++ b/hist/hist/src/TH1.cxx @@ -18,6 +18,7 @@ #include #include #include +#include #include "TROOT.h" #include "TBuffer.h" @@ -61,11 +62,13 @@ \class TH1S \brief 1-D histogram with a short per channel (see TH1 documentation) \class TH1I -\brief 1-D histogram with an int per channel (see TH1 documentation)} +\brief 1-D histogram with an int per channel (see TH1 documentation) +\class TH1L +\brief 1-D histogram with a long64 per channel (see TH1 documentation) \class TH1F -\brief 1-D histogram with a float per channel (see TH1 documentation)} +\brief 1-D histogram with a float per channel (see TH1 documentation) \class TH1D -\brief 1-D histogram with a double per channel (see TH1 documentation)} +\brief 1-D histogram with a double per channel (see TH1 documentation) @} */ @@ -106,18 +109,21 @@ ROOT supports the following histogram types: - TH1C : histograms with one byte per channel. Maximum bin content = 127 - TH1S : histograms with one short per channel. Maximum bin content = 32767 - TH1I : histograms with one int per channel. Maximum bin content = INT_MAX (\ref intmax "*") + - TH1L : histograms with one long64 per channel. Maximum bin content = LLONG_MAX (\ref llongmax "*") - TH1F : histograms with one float per channel. Maximum precision 7 digits - TH1D : histograms with one double per channel. Maximum precision 14 digits - 2-D histograms: - TH2C : histograms with one byte per channel. Maximum bin content = 127 - TH2S : histograms with one short per channel. Maximum bin content = 32767 - TH2I : histograms with one int per channel. Maximum bin content = INT_MAX (\ref intmax "*") + - TH2L : histograms with one long64 per channel. Maximum bin content = LLONG_MAX (\ref llongmax "*") - TH2F : histograms with one float per channel. Maximum precision 7 digits - TH2D : histograms with one double per channel. Maximum precision 14 digits - 3-D histograms: - TH3C : histograms with one byte per channel. Maximum bin content = 127 - TH3S : histograms with one short per channel. Maximum bin content = 32767 - TH3I : histograms with one int per channel. Maximum bin content = INT_MAX (\ref intmax "*") + - TH3L : histograms with one long64 per channel. Maximum bin content = LLONG_MAX (\ref llongmax "*") - TH3F : histograms with one float per channel. Maximum precision 7 digits - TH3D : histograms with one double per channel. Maximum precision 14 digits - Profile histograms: See classes TProfile, TProfile2D and TProfile3D. @@ -131,6 +137,7 @@ ROOT supports the following histogram types: \anchor intmax (*) INT_MAX = 2147483647 is the [maximum value for a variable of type int.](https://docs.microsoft.com/en-us/cpp/c-language/cpp-integer-limits) +\anchor llongmax (*) LLONG_MAX = 9223372036854775807 is the [maximum value for a variable of type long64.](https://docs.microsoft.com/en-us/cpp/c-language/cpp-integer-limits) The inheritance hierarchy looks as follows: @@ -336,7 +343,8 @@ When using the options 2 or 3 above, the labels are automatically If TH1::Sumw2 has been called before filling, the sum of squares of weights is also stored. One can also increment directly a bin number via TH1::AddBinContent - or replace the existing content via TH1::SetBinContent. + or replace the existing content via TH1::SetBinContent. Passing an + out-of-range bin to TH1::AddBinContent leads to undefined behavior. To access the bin content of a given bin, do: ~~~ {.cpp} Double_t binContent = h->GetBinContent(bin); @@ -1253,6 +1261,7 @@ Bool_t TH1::Add(const TH1 *h1, const TH1 *h2, Double_t c1, Double_t c2) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH1::AddBinContent(Int_t) { @@ -1261,6 +1270,7 @@ void TH1::AddBinContent(Int_t) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by a weight w. +/// Passing an out-of-range bin leads to undefined behavior void TH1::AddBinContent(Int_t, Double_t) { @@ -1709,8 +1719,9 @@ int TH1::CheckConsistency(const TH1* h1, const TH1* h2) } //////////////////////////////////////////////////////////////////////////////// -/// \f$ \chi^{2} \f$ test for comparing weighted and unweighted histograms +/// \f$ \chi^{2} \f$ test for comparing weighted and unweighted histograms. /// +/// Compares the histograms' adjusted (normalized) residuals. /// Function: Returns p-value. Other return values are specified by the 3rd parameter /// /// \param[in] h2 the second histogram @@ -2686,10 +2697,14 @@ void TH1::Copy(TObject &obj) const ((TH1&)obj).fBuffer = buf; } - + // copy bin contents (this should be done by the derived classes, since TH1 does not store the bin content) + // Do this in case derived from TArray TArray* a = dynamic_cast(&obj); - if (a) a->Set(fNcells); - for (Int_t i = 0; i < fNcells; i++) ((TH1&)obj).UpdateBinContent(i, RetrieveBinContent(i)); + if (a) { + a->Set(fNcells); + for (Int_t i = 0; i < fNcells; i++) + ((TH1&)obj).UpdateBinContent(i, RetrieveBinContent(i)); + } ((TH1&)obj).fEntries = fEntries; @@ -7104,6 +7119,85 @@ void TH1::Reset(Option_t *option) fContour.Set(0); } +//////////////////////////////////////////////////////////////////////////////// +/// Save the histogram as .csv, .tsv or .txt. In case of any other extension, fall +/// back to TObject::SaveAs, which saves as a .C macro (but with the file name +/// extension specified by the user) +/// +/// The Under/Overflow bins are also exported (as first and last lines) +/// The fist 2 columns are the lower and upper edges of the bins +/// Column 3 contains the bin contents +/// The last column contains the error in y. If errors are not present, the column +/// is left empty +/// +/// The result can be immediately imported into Excel, gnuplot, Python or whatever, +/// without the needing to install pyroot, etc. +/// +/// \param filename the name of the file where to store the histogram +/// \param option some tuning options +/// +/// The file extension defines the delimiter used: +/// - `.csv` : comma +/// - `.tsv` : tab +/// - `.txt` : space +/// +/// If option = "title" a title line is generated. If the y-axis has a title, +/// this title is displayed as column 3 name, otherwise, it shows "BinContent" + +void TH1::SaveAs(const char *filename, Option_t *option) const +{ + char del = '\0'; + TString ext = ""; + TString fname = filename; + TString opt = option; + + if (filename) { + if (fname.EndsWith(".csv")) { + del = ','; + ext = "csv"; + } else if (fname.EndsWith(".tsv")) { + del = '\t'; + ext = "tsv"; + } else if (fname.EndsWith(".txt")) { + del = ' '; + ext = "txt"; + } + } + if (!del) { + Info("SaveAs", "The file extension is not any of '.csv', '.tsv', '.txt'. Falling back to TObject::SaveAs"); + TObject::SaveAs(filename, option); + return; + } + std::ofstream out; + out.open(filename, std::ios::out); + if (!out.good()) { + Error("SaveAs", "cannot open file: %s", filename); + return; + } + if (opt.Contains("title")) { + if (std::strcmp(GetYaxis()->GetTitle(), "") == 0) { + out << "#\tBinLowEdge\tBinUpEdge\t" + << "BinContent" + << "\tey" << std::endl; + } else { + out << "#\tBinLowEdge\tBinUpEdge\t" << GetYaxis()->GetTitle() << "\tey" << std::endl; + } + } + if (fSumw2.fN) { + for (Int_t i = 0; i < fNcells; ++i) { // loop on cells (bins including underflow / overflow) + out << GetXaxis()->GetBinLowEdge(i) << del << GetXaxis()->GetBinUpEdge(i) << del << GetBinContent(i) << del + << GetBinError(i) << std::endl; + } + } else { + for (Int_t i = 0; i < fNcells; ++i) { // loop on cells (bins including underflow / overflow) + out << GetXaxis()->GetBinLowEdge(i) << del << GetXaxis()->GetBinUpEdge(i) << del << GetBinContent(i) << del + << std::endl; + } + } + out.close(); + Info("SaveAs", "%s file: %s has been generated", ext.Data(), filename); +} + //////////////////////////////////////////////////////////////////////////////// /// Save primitive as a C++ statement(s) on output stream out @@ -9406,6 +9500,7 @@ TH1C::TH1C(const TH1C &h1c) : TH1(), TArrayC() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH1C::AddBinContent(Int_t bin) { @@ -9414,6 +9509,7 @@ void TH1C::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior void TH1C::AddBinContent(Int_t bin, Double_t w) { @@ -9589,6 +9685,7 @@ TH1S::TH1S(const TH1S &h1s) : TH1(), TArrayS() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH1S::AddBinContent(Int_t bin) { @@ -9597,6 +9694,7 @@ void TH1S::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w +/// Passing an out-of-range bin leads to undefined behavior void TH1S::AddBinContent(Int_t bin, Double_t w) { @@ -9773,6 +9871,7 @@ TH1I::TH1I(const TH1I &h1i) : TH1(), TArrayI() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH1I::AddBinContent(Int_t bin) { @@ -9781,6 +9880,7 @@ void TH1I::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w +/// Passing an out-of-range bin leads to undefined behavior void TH1I::AddBinContent(Int_t bin, Double_t w) { @@ -9884,6 +9984,193 @@ TH1I operator/(const TH1I &h1, const TH1I &h2) return hnew; } +//______________________________________________________________________________ +// TH1L methods +// TH1L : histograms with one long64 per channel. Maximum bin content = 9223372036854775807 +// 9223372036854775807 = LLONG_MAX +//______________________________________________________________________________ + +ClassImp(TH1L); + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor. + +TH1L::TH1L(): TH1(), TArrayL64() +{ + fDimension = 1; + SetBinsLength(3); + if (fgDefaultSumw2) Sumw2(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a 1-Dim histogram with fix bins of type long64 +/// (see TH1::TH1 for explanation of parameters) + +TH1L::TH1L(const char *name,const char *title,Int_t nbins,Double_t xlow,Double_t xup) +: TH1(name,title,nbins,xlow,xup) +{ + fDimension = 1; + TArrayL64::Set(fNcells); + + if (xlow >= xup) SetBuffer(fgBufferSize); + if (fgDefaultSumw2) Sumw2(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a 1-Dim histogram with variable bins of type long64 +/// (see TH1::TH1 for explanation of parameters) + +TH1L::TH1L(const char *name,const char *title,Int_t nbins,const Float_t *xbins) +: TH1(name,title,nbins,xbins) +{ + fDimension = 1; + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a 1-Dim histogram with variable bins of type long64 +/// (see TH1::TH1 for explanation of parameters) + +TH1L::TH1L(const char *name,const char *title,Int_t nbins,const Double_t *xbins) +: TH1(name,title,nbins,xbins) +{ + fDimension = 1; + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Destructor. + +TH1L::~TH1L() +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Copy constructor. +/// The list of functions is not copied. (Use Clone() if needed) + +TH1L::TH1L(const TH1L &h1l) : TH1(), TArrayL64() +{ + h1l.TH1L::Copy(*this); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior + +void TH1L::AddBinContent(Int_t bin) +{ + if (fArray[bin] < LLONG_MAX) fArray[bin]++; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Increment bin content by w +/// Passing an out-of-range bin leads to undefined behavior + +void TH1L::AddBinContent(Int_t bin, Double_t w) +{ + Long64_t newval = fArray[bin] + Long64_t(w); + if (newval > -LLONG_MAX && newval < LLONG_MAX) {fArray[bin] = newval; return;} + if (newval < -LLONG_MAX) fArray[bin] = -LLONG_MAX; + if (newval > LLONG_MAX) fArray[bin] = LLONG_MAX; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Copy this to newth1 + +void TH1L::Copy(TObject &newth1) const +{ + TH1::Copy(newth1); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Reset. + +void TH1L::Reset(Option_t *option) +{ + TH1::Reset(option); + TArrayL64::Reset(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Set total number of bins including under/overflow +/// Reallocate bin contents array + +void TH1L::SetBinsLength(Int_t n) +{ + if (n < 0) n = fXaxis.GetNbins() + 2; + fNcells = n; + TArrayL64::Set(n); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Operator = + +TH1L& TH1L::operator=(const TH1L &h1) +{ + if (this != &h1) + h1.TH1L::Copy(*this); + return *this; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator * + +TH1L operator*(Double_t c1, const TH1L &h1) +{ + TH1L hnew = h1; + hnew.Scale(c1); + hnew.SetDirectory(nullptr); + return hnew; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Operator + + +TH1L operator+(const TH1L &h1, const TH1L &h2) +{ + TH1L hnew = h1; + hnew.Add(&h2,1); + hnew.SetDirectory(nullptr); + return hnew; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Operator - + +TH1L operator-(const TH1L &h1, const TH1L &h2) +{ + TH1L hnew = h1; + hnew.Add(&h2,-1); + hnew.SetDirectory(nullptr); + return hnew; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Operator * + +TH1L operator*(const TH1L &h1, const TH1L &h2) +{ + TH1L hnew = h1; + hnew.Multiply(&h2); + hnew.SetDirectory(nullptr); + return hnew; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Operator / + +TH1L operator/(const TH1L &h1, const TH1L &h2) +{ + TH1L hnew = h1; + hnew.Divide(&h2); + hnew.SetDirectory(nullptr); + return hnew; +} + //______________________________________________________________________________ // TH1F methods // TH1F : histograms with one float per channel. Maximum precision 7 digits diff --git a/hist/hist/src/TH2.cxx b/hist/hist/src/TH2.cxx index 119f57ed4b2f6..8d0bf4e15f723 100644 --- a/hist/hist/src/TH2.cxx +++ b/hist/hist/src/TH2.cxx @@ -37,11 +37,13 @@ ClassImp(TH2); \class TH2S \brief 2-D histogram with a short per channel (see TH1 documentation) \class TH2I -\brief 2-D histogram with an int per channel (see TH1 documentation)} +\brief 2-D histogram with an int per channel (see TH1 documentation) +\class TH2L +\brief 2-D histogram with a long64 per channel (see TH1 documentation) \class TH2F -\brief 2-D histogram with a float per channel (see TH1 documentation)} +\brief 2-D histogram with a float per channel (see TH1 documentation) \class TH2D -\brief 2-D histogram with a double per channel (see TH1 documentation)} +\brief 2-D histogram with a double per channel (see TH1 documentation) @} */ @@ -50,7 +52,8 @@ ClassImp(TH2); - TH2C a 2-D histogram with one byte per cell (char) - TH2S a 2-D histogram with two bytes per cell (short integer) -- TH2I a 2-D histogram with four bytes per cell (32 bits integer) +- TH2I a 2-D histogram with four bytes per cell (32 bit integer) +- TH2L a 2-D histogram with eight bytes per cell (64 bit integer) - TH2F a 2-D histogram with four bytes per cell (float) - TH2D a 2-D histogram with eight bytes per cell (double) */ @@ -1933,6 +1936,17 @@ TProfile *TH2::DoProfile(bool onX, const char *name, Int_t firstbin, Int_t lastb // Copy attributes h1->GetXaxis()->ImportAttributes( &outAxis); + THashList* labels=outAxis.GetLabels(); + if (labels) { + TIter iL(labels); + TObjString* lb; + Int_t i = 1; + while ((lb=(TObjString*)iL())) { + h1->GetXaxis()->SetBinLabel(i,lb->String().Data()); + i++; + } + } + h1->SetLineColor(this->GetLineColor()); h1->SetFillColor(this->GetFillColor()); h1->SetMarkerColor(this->GetMarkerColor()); @@ -2250,6 +2264,9 @@ TH1D *TH2::DoProjection(bool onX, const char *name, Int_t firstbin, Int_t lastbi // implement filling of projected histogram // outbin is bin number of outAxis (the projected axis). Loop is done on all bin of TH2 histograms // inbin is the axis being integrated. Loop is done only on the selected bins + // if the out axis has labels and is extendable, temporary make it non-extendable to avoid adding extra bins + Bool_t extendable = outAxis->CanExtend(); + if ( labels && extendable ) h1->GetXaxis()->SetCanExtend(kFALSE); for ( Int_t outbin = 0; outbin <= outAxis->GetNbins() + 1; ++outbin) { err2 = 0; cont = 0; @@ -2277,6 +2294,7 @@ TH1D *TH2::DoProjection(bool onX, const char *name, Int_t firstbin, Int_t lastbi // sum all content totcont += cont; } + if ( labels ) h1->GetXaxis()->SetCanExtend(extendable); // check if we can re-use the original statistics from the previous histogram bool reuseStats = false; @@ -2894,6 +2912,7 @@ TH2C::TH2C(const TH2C &h2c) : TH2(), TArrayC() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH2C::AddBinContent(Int_t bin) { @@ -2903,6 +2922,7 @@ void TH2C::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior void TH2C::AddBinContent(Int_t bin, Double_t w) { @@ -3155,6 +3175,7 @@ TH2S::TH2S(const TH2S &h2s) : TH2(), TArrayS() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH2S::AddBinContent(Int_t bin) { @@ -3164,6 +3185,7 @@ void TH2S::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior void TH2S::AddBinContent(Int_t bin, Double_t w) { @@ -3313,7 +3335,7 @@ TH2S operator/(TH2S &h1, TH2S &h2) //______________________________________________________________________________ // TH2I methods -// TH2I a 2-D histogram with four bytes per cell (32 bits integer) +// TH2I a 2-D histogram with four bytes per cell (32 bit integer) //______________________________________________________________________________ ClassImp(TH2I); @@ -3416,6 +3438,7 @@ TH2I::TH2I(const TH2I &h2i) : TH2(), TArrayI() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH2I::AddBinContent(Int_t bin) { @@ -3425,6 +3448,7 @@ void TH2I::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior void TH2I::AddBinContent(Int_t bin, Double_t w) { @@ -3537,6 +3561,232 @@ TH2I operator/(TH2I &h1, TH2I &h2) } +//______________________________________________________________________________ +// TH2L methods +// TH2L a 2-D histogram with eight bytes per cell (64 bit integer) +//______________________________________________________________________________ + +ClassImp(TH2L); + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor. + +TH2L::TH2L(): TH2(), TArrayL64() +{ + SetBinsLength(9); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Destructor. + +TH2L::~TH2L() +{ +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor +/// (see TH2::TH2 for explanation of parameters) + +TH2L::TH2L(const char *name,const char *title,Int_t nbinsx,Double_t xlow,Double_t xup + ,Int_t nbinsy,Double_t ylow,Double_t yup) + :TH2(name,title,nbinsx,xlow,xup,nbinsy,ylow,yup) +{ + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); + + if (xlow >= xup || ylow >= yup) SetBuffer(fgBufferSize); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor +/// (see TH2::TH2 for explanation of parameters) + +TH2L::TH2L(const char *name,const char *title,Int_t nbinsx,const Double_t *xbins + ,Int_t nbinsy,Double_t ylow,Double_t yup) + :TH2(name,title,nbinsx,xbins,nbinsy,ylow,yup) +{ + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor +/// (see TH2::TH2 for explanation of parameters) + +TH2L::TH2L(const char *name,const char *title,Int_t nbinsx,Double_t xlow,Double_t xup + ,Int_t nbinsy,const Double_t *ybins) + :TH2(name,title,nbinsx,xlow,xup,nbinsy,ybins) +{ + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor +/// (see TH2::TH2 for explanation of parameters) + +TH2L::TH2L(const char *name,const char *title,Int_t nbinsx,const Double_t *xbins + ,Int_t nbinsy,const Double_t *ybins) + :TH2(name,title,nbinsx,xbins,nbinsy,ybins) +{ + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor +/// (see TH2::TH2 for explanation of parameters) + +TH2L::TH2L(const char *name,const char *title,Int_t nbinsx,const Float_t *xbins + ,Int_t nbinsy,const Float_t *ybins) + :TH2(name,title,nbinsx,xbins,nbinsy,ybins) +{ + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Copy constructor. +/// The list of functions is not copied. (Use Clone() if needed) + +TH2L::TH2L(const TH2L &h2l) : TH2(), TArrayL64() +{ + h2l.TH2L::Copy(*this); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Increment bin content by 1. + +void TH2L::AddBinContent(Int_t bin) +{ + if (fArray[bin] < LLONG_MAX) fArray[bin]++; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Increment bin content by w. + +void TH2L::AddBinContent(Int_t bin, Double_t w) +{ + Long64_t newval = fArray[bin] + Long64_t(w); + if (newval > -LLONG_MAX && newval < LLONG_MAX) {fArray[bin] = Int_t(newval); return;} + if (newval < -LLONG_MAX) fArray[bin] = -LLONG_MAX; + if (newval > LLONG_MAX) fArray[bin] = LLONG_MAX; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Copy. + +void TH2L::Copy(TObject &newth2) const +{ + TH2::Copy(newth2); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Reset this histogram: contents, errors, etc. + +void TH2L::Reset(Option_t *option) +{ + TH2::Reset(option); + TArrayL64::Reset(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Set total number of bins including under/overflow +/// Reallocate bin contents array + +void TH2L::SetBinsLength(Int_t n) +{ + if (n < 0) n = (fXaxis.GetNbins()+2)*(fYaxis.GetNbins()+2); + fNcells = n; + TArrayL64::Set(n); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator = + +TH2L& TH2L::operator=(const TH2L &h2l) +{ + if (this != &h2l) + h2l.TH2L::Copy(*this); + return *this; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator * + +TH2L operator*(Float_t c1, TH2L &h1) +{ + TH2L hnew = h1; + hnew.Scale(c1); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator + + +TH2L operator+(TH2L &h1, TH2L &h2) +{ + TH2L hnew = h1; + hnew.Add(&h2,1); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator - + +TH2L operator-(TH2L &h1, TH2L &h2) +{ + TH2L hnew = h1; + hnew.Add(&h2,-1); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator * + +TH2L operator*(TH2L &h1, TH2L &h2) +{ + TH2L hnew = h1; + hnew.Multiply(&h2); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator / + +TH2L operator/(TH2L &h1, TH2L &h2) +{ + TH2L hnew = h1; + hnew.Divide(&h2); + hnew.SetDirectory(nullptr); + return hnew; +} + + //______________________________________________________________________________ // TH2F methods // TH2F a 2-D histogram with four bytes per cell (float) diff --git a/hist/hist/src/TH2Poly.cxx b/hist/hist/src/TH2Poly.cxx index a2466ff151f90..85d8b6002a08c 100644 --- a/hist/hist/src/TH2Poly.cxx +++ b/hist/hist/src/TH2Poly.cxx @@ -178,6 +178,12 @@ TH2Poly::TH2Poly(const char *name,const char *title, SetFloat(kFALSE); } +///////////////////////////////////////////////////////////////////////////////// +/// Copy constructor +TH2Poly::TH2Poly(const TH2Poly & rhs) : TH2() { + rhs.Copy(*this); +} + //////////////////////////////////////////////////////////////////////////////// /// Destructor. @@ -190,6 +196,63 @@ TH2Poly::~TH2Poly() delete fBins; } +///////////////////////////////////////////////////////////////////////////////// +/// Assignment operator +TH2Poly & TH2Poly::operator=(const TH2Poly & rhs) { + if (this != &rhs) + rhs.Copy(*this); + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Copy function for TH2Poly + +void TH2Poly::Copy(TObject &newobj) const +{ + // copy first TH2 information + TH2::Copy(newobj); + auto & newth2p = dynamic_cast(newobj); + newth2p.SetName(GetName()); + newth2p.SetTitle(GetTitle()); + + newth2p.fCellX = fCellX; + newth2p.fCellY = fCellY; + newth2p.fNCells = fNCells; + newth2p.fStepX = fStepX; + newth2p.fStepY = fStepY; + + // allocate arrays + newth2p.fCells = new TList [fNCells]; + newth2p.fIsEmpty = new Bool_t [fNCells]; // Empty partition + newth2p.fCompletelyInside = new Bool_t [fNCells]; // Cell is completely inside bin + // Initializes the flags + for (int i = 0; i(fBins->Clone()); + if (!newth2p.fBins) + Error("Copy","Error cloning the TH2Poly bin list"); + else { + // add bins in the fCells partition. We need to add the TH2PolyBin objects + // of the new copied histograms. For this we call AddBinToPartition + // we could probably optimize this by implementing a copy of the partition + for (auto bin : *(newth2p.fBins)) { + newth2p.AddBinToPartition(dynamic_cast(bin)); + } + } + // copy overflow contents + for(int i = 0; i < kNOverflow; i++ ) { + newth2p.fOverflow[i] = fOverflow[i]; + } + // copy other data members + newth2p.fFloat = fFloat; + newth2p.fNewBinAdded = fNewBinAdded; + newth2p.fBinContentChanged = fBinContentChanged; +} + + //////////////////////////////////////////////////////////////////////////////// /// Create appropriate histogram bin. /// e.g. TH2Poly creates TH2PolyBin, @@ -752,7 +815,8 @@ Double_t TH2Poly::Integral(Option_t* option) const //////////////////////////////////////////////////////////////////////////////// /// Returns the content of the input bin -/// For the overflow/underflow/sea bins: +/// Bin numbers are from [1,nbins] and +/// for the overflow/underflow/sea bins the range is [-9,-1]: ///~~~ {.cpp} /// -1 | -2 | -3 /// ---+----+---- @@ -795,6 +859,16 @@ Double_t TH2Poly::GetBinError(Int_t bin) const return TMath::Sqrt(error2); } +//////////////////////////////////////////////////////////////////////////////// +/// Return the number of bins : +/// it should be the size of the bin list +Int_t TH2Poly::GetNumberOfBins() const { + Int_t nbins = fNcells-kNOverflow; + if (nbins != fBins->GetSize()) + Fatal("GetNumberOfBins","Object has an invalid number of bins"); + return nbins; +} + //////////////////////////////////////////////////////////////////////////////// /// Set the bin Error. /// Re-implementation for TH2Poly given the different bin indexing in the diff --git a/hist/hist/src/TH3.cxx b/hist/hist/src/TH3.cxx index 3aa657be76042..89ad78f4a918f 100644 --- a/hist/hist/src/TH3.cxx +++ b/hist/hist/src/TH3.cxx @@ -34,11 +34,13 @@ ClassImp(TH3); \class TH3S \brief 3-D histogram with a short per channel (see TH1 documentation) \class TH3I -\brief 3-D histogram with an int per channel (see TH1 documentation)} +\brief 3-D histogram with an int per channel (see TH1 documentation) +\class TH3L +\brief 3-D histogram with a long64 per channel (see TH1 documentation) \class TH3F -\brief 3-D histogram with a float per channel (see TH1 documentation)} +\brief 3-D histogram with a float per channel (see TH1 documentation) \class TH3D -\brief 3-D histogram with a double per channel (see TH1 documentation)} +\brief 3-D histogram with a double per channel (see TH1 documentation) @} */ @@ -52,7 +54,8 @@ cell content. - TH3C a 3-D histogram with one byte per cell (char) - TH3S a 3-D histogram with two bytes per cell (short integer) -- TH3I a 3-D histogram with four bytes per cell (32 bits integer) +- TH3I a 3-D histogram with four bytes per cell (32 bit integer) +- TH3L a 3-D histogram with eight bytes per cell (64 bit integer) - TH3F a 3-D histogram with four bytes per cell (float) - TH3D a 3-D histogram with eight bytes per cell (double) */ @@ -1975,6 +1978,9 @@ TH1D *TH3::DoProject1D(const char* name, const char * title, const TAxis* projX, if (useUF && !out2->TestBit(TAxis::kAxisRange) ) out2min -= 1; if (useOF && !out2->TestBit(TAxis::kAxisRange) ) out2max += 1; + // if the out axis has labels and is extendable, temporary make it non-extendable to avoid adding extra bins + Bool_t extendable = projX->CanExtend(); + if ( labels && extendable ) h1->GetXaxis()->SetCanExtend(kFALSE); for (ixbin=0;ixbin<=1+projX->GetNbins();ixbin++) { if ( projX->TestBit(TAxis::kAxisRange) && ( ixbin < ixmin || ixbin > ixmax )) continue; @@ -2002,6 +2008,7 @@ TH1D *TH3::DoProject1D(const char* name, const char * title, const TAxis* projX, totcont += cont; } + if ( labels ) h1->GetXaxis()->SetCanExtend(extendable); // since we use a combination of fill and SetBinError we need to reset and recalculate the statistics // for weighted histograms otherwise sumw2 will be wrong. @@ -2605,6 +2612,30 @@ TProfile2D *TH3::DoProjectProfile2D(const char* name, const char * title, const } } + // Copy the axis attributes and the axis labels if needed + p2->GetXaxis()->ImportAttributes(projY); + p2->GetYaxis()->ImportAttributes(projX); + THashList* labelsX = projY->GetLabels(); + if (labelsX) { + TIter iL(labelsX); + TObjString* lb; + Int_t i = 1; + while ((lb=(TObjString*)iL())) { + p2->GetXaxis()->SetBinLabel(i,lb->String().Data()); + ++i; + } + } + THashList* labelsY = projX->GetLabels(); + if (labelsY) { + TIter iL(labelsY); + TObjString* lb; + Int_t i = 1; + while ((lb=(TObjString*)iL())) { + p2->GetYaxis()->SetBinLabel(i,lb->String().Data()); + ++i; + } + } + // Set references to the axis, so that the loop has no branches. const TAxis* outAxis = nullptr; if ( projX != GetXaxis() && projY != GetXaxis() ) { @@ -3528,6 +3559,7 @@ TH3C::TH3C(const TH3C &h3c) : TH3(), TArrayC() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH3C::AddBinContent(Int_t bin) { @@ -3537,6 +3569,7 @@ void TH3C::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior void TH3C::AddBinContent(Int_t bin, Double_t w) { @@ -3794,6 +3827,7 @@ TH3S::TH3S(const TH3S &h3s) : TH3(), TArrayS() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH3S::AddBinContent(Int_t bin) { @@ -3803,6 +3837,7 @@ void TH3S::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior void TH3S::AddBinContent(Int_t bin, Double_t w) { @@ -3951,7 +3986,7 @@ TH3S operator/(TH3S &h1, TH3S &h2) //______________________________________________________________________________ // TH3I methods -// TH3I a 3-D histogram with four bytes per cell (32 bits integer) +// TH3I a 3-D histogram with four bytes per cell (32 bit integer) //______________________________________________________________________________ ClassImp(TH3I); @@ -4031,6 +4066,7 @@ TH3I::TH3I(const TH3I &h3i) : TH3(), TArrayI() //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior void TH3I::AddBinContent(Int_t bin) { @@ -4040,6 +4076,7 @@ void TH3I::AddBinContent(Int_t bin) //////////////////////////////////////////////////////////////////////////////// /// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior void TH3I::AddBinContent(Int_t bin, Double_t w) { @@ -4153,6 +4190,212 @@ TH3I operator/(TH3I &h1, TH3I &h2) } +//______________________________________________________________________________ +// TH3L methods +// TH3L a 3-D histogram with eight bytes per cell (64 bit integer) +//______________________________________________________________________________ + +ClassImp(TH3L); + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor. + +TH3L::TH3L(): TH3(), TArrayL64() +{ + SetBinsLength(27); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Destructor. + +TH3L::~TH3L() +{ +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor for fix bin size 3-D histograms +/// (see TH3::TH3 for explanation of parameters) + +TH3L::TH3L(const char *name,const char *title,Int_t nbinsx,Double_t xlow,Double_t xup + ,Int_t nbinsy,Double_t ylow,Double_t yup + ,Int_t nbinsz,Double_t zlow,Double_t zup) + :TH3(name,title,nbinsx,xlow,xup,nbinsy,ylow,yup,nbinsz,zlow,zup) +{ + TH3L::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); + + if (xlow >= xup || ylow >= yup || zlow >= zup) SetBuffer(fgBufferSize); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor for variable bin size 3-D histograms +/// (see TH3::TH3 for explanation of parameters) + +TH3L::TH3L(const char *name,const char *title,Int_t nbinsx,const Float_t *xbins + ,Int_t nbinsy,const Float_t *ybins + ,Int_t nbinsz,const Float_t *zbins) + :TH3(name,title,nbinsx,xbins,nbinsy,ybins,nbinsz,zbins) +{ + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor for variable bin size 3-D histograms +/// (see TH3::TH3 for explanation of parameters) + +TH3L::TH3L(const char *name,const char *title,Int_t nbinsx,const Double_t *xbins + ,Int_t nbinsy,const Double_t *ybins + ,Int_t nbinsz,const Double_t *zbins) + :TH3(name,title,nbinsx,xbins,nbinsy,ybins,nbinsz,zbins) +{ + TArrayL64::Set(fNcells); + if (fgDefaultSumw2) Sumw2(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Copy constructor. +/// The list of functions is not copied. (Use Clone() if needed) + +TH3L::TH3L(const TH3L &h3l) : TH3(), TArrayL64() +{ + h3l.TH3L::Copy(*this); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Increment bin content by 1. +/// Passing an out-of-range bin leads to undefined behavior + +void TH3L::AddBinContent(Int_t bin) +{ + if (fArray[bin] < LLONG_MAX) fArray[bin]++; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Increment bin content by w. +/// Passing an out-of-range bin leads to undefined behavior + +void TH3L::AddBinContent(Int_t bin, Double_t w) +{ + Long64_t newval = fArray[bin] + Long64_t(w); + if (newval > -LLONG_MAX && newval < LLONG_MAX) {fArray[bin] = Int_t(newval); return;} + if (newval < -LLONG_MAX) fArray[bin] = -LLONG_MAX; + if (newval > LLONG_MAX) fArray[bin] = LLONG_MAX; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Copy this 3-D histogram structure to newth3. + +void TH3L::Copy(TObject &newth3) const +{ + TH3::Copy(newth3); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Reset this histogram: contents, errors, etc. + +void TH3L::Reset(Option_t *option) +{ + TH3::Reset(option); + TArrayL64::Reset(); + // should also reset statistics once statistics are implemented for TH3 +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Set total number of bins including under/overflow +/// Reallocate bin contents array + +void TH3L::SetBinsLength(Int_t n) +{ + if (n < 0) n = (fXaxis.GetNbins()+2)*(fYaxis.GetNbins()+2)*(fZaxis.GetNbins()+2); + fNcells = n; + TArrayL64::Set(n); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator = + +TH3L& TH3L::operator=(const TH3L &h3l) +{ + if (this != &h3l) + h3l.TH3L::Copy(*this); + return *this; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator * + +TH3L operator*(Float_t c1, TH3L &h3l) +{ + TH3L hnew = h3l; + hnew.Scale(c1); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator + + +TH3L operator+(TH3L &h1, TH3L &h2) +{ + TH3L hnew = h1; + hnew.Add(&h2,1); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator _ + +TH3L operator-(TH3L &h1, TH3L &h2) +{ + TH3L hnew = h1; + hnew.Add(&h2,-1); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator * + +TH3L operator*(TH3L &h1, TH3L &h2) +{ + TH3L hnew = h1; + hnew.Multiply(&h2); + hnew.SetDirectory(nullptr); + return hnew; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Operator / + +TH3L operator/(TH3L &h1, TH3L &h2) +{ + TH3L hnew = h1; + hnew.Divide(&h2); + hnew.SetDirectory(nullptr); + return hnew; +} + + //______________________________________________________________________________ // TH3F methods // TH3F a 3-D histogram with four bytes per cell (float) diff --git a/hist/hist/src/THStack.cxx b/hist/hist/src/THStack.cxx index 54ebc15834c19..ba21253a673e0 100644 --- a/hist/hist/src/THStack.cxx +++ b/hist/hist/src/THStack.cxx @@ -59,7 +59,7 @@ The specific stack's drawing options are: - **NOSTACK** If option "nostack" is specified, histograms are all painted in the same pad as if the option "same" had been specified. - - **NOSTACKB** If the option "nostackb" is specified histograms are all painted in the same pad + - **NOSTACKB** If the option "nostackb" is specified histograms are all painted on the same pad next to each other as bar plots. - **PADS** if option "pads" is specified, the current pad/canvas is subdivided into @@ -67,10 +67,9 @@ The specific stack's drawing options are: is painted into a separate pad. - **NOCLEAR** By default the background of the histograms is erased before drawing the - histograms. The option "noclear" avoid this behaviour. This is useful - when drawing a THStack on top of an other plot. If the patterns used to - draw the histograms in the stack are transparents, then the plot behind - will be visible. + histograms. The option "noclear" avoids this behavior. This is useful when drawing a + THStack on top of another plot. If the patterns used to draw the histograms in the + stack are transparent, then the plot behind will be visible. See the THistPainter class for the list of valid histograms' painting options. @@ -488,16 +487,16 @@ TH1 *THStack::GetHistogram() const } //////////////////////////////////////////////////////////////////////////////// -/// returns the maximum of all added histograms -/// returns the maximum of all histograms if option "nostack". +/// returns the maximum of all added histograms smaller than maxval. +/// returns the maximum of all histograms, smaller than maxval, if option "nostack". -Double_t THStack::GetMaximum(Option_t *option) +Double_t THStack::GetMaximum(Option_t *option, Double_t maxval) { TString opt = option; opt.ToLower(); Bool_t lerr = kFALSE; if (opt.Contains("e")) lerr = kTRUE; - Double_t them=0, themax = -1e300, c1, e1; + Double_t them = 0, themax = -std::numeric_limits::max(), c1, e1; if (!fHists) return 0; Int_t nhists = fHists->GetSize(); TH1 *h; @@ -508,13 +507,13 @@ Double_t THStack::GetMaximum(Option_t *option) h = (TH1*)fStack->At(nhists-1); if (fHistogram) h->GetXaxis()->SetRange(fHistogram->GetXaxis()->GetFirst(), fHistogram->GetXaxis()->GetLast()); - themax = h->GetMaximum(); + themax = h->GetMaximum(maxval); } else { for (Int_t i=0;iAt(i); if (fHistogram) h->GetXaxis()->SetRange(fHistogram->GetXaxis()->GetFirst(), fHistogram->GetXaxis()->GetLast()); - them = h->GetMaximum(); + them = h->GetMaximum(maxval); if (fHistogram) h->GetXaxis()->SetRange(0,0); if (them > themax) themax = them; } @@ -537,16 +536,16 @@ Double_t THStack::GetMaximum(Option_t *option) } //////////////////////////////////////////////////////////////////////////////// -/// returns the minimum of all added histograms -/// returns the minimum of all histograms if option "nostack". +/// Returns the minimum of all added histograms larger than minval. +/// Returns the minimum of all histograms, larger than minval, if option "nostack". -Double_t THStack::GetMinimum(Option_t *option) +Double_t THStack::GetMinimum(Option_t *option, Double_t minval) { TString opt = option; opt.ToLower(); Bool_t lerr = kFALSE; if (opt.Contains("e")) lerr = kTRUE; - Double_t them=0, themin = 1e300, c1, e1; + Double_t them = 0, themin = std::numeric_limits::max(), c1, e1; if (!fHists) return 0; Int_t nhists = fHists->GetSize(); Int_t first,last; @@ -555,11 +554,11 @@ Double_t THStack::GetMinimum(Option_t *option) if (!opt.Contains("nostack")) { BuildStack(); h = (TH1*)fStack->At(nhists-1); - themin = h->GetMinimum(); + themin = h->GetMinimum(minval); } else { for (Int_t i=0;iAt(i); - them = h->GetMinimum(); + them = h->GetMinimum(minval); if (them <= 0 && gPad && gPad->GetLogy()) them = h->GetMinimum(0); if (them < themin) themin = them; } diff --git a/hist/hist/src/THn.cxx b/hist/hist/src/THn.cxx index 651e9b0bbf0a4..8e8a1fea0708d 100644 --- a/hist/hist/src/THn.cxx +++ b/hist/hist/src/THn.cxx @@ -138,7 +138,7 @@ To construct a THn object you must use one of its templated, derived THnD (typedef for THnT): bin content held by a Double_t, THnF (typedef for THnT): bin content held by a Float_t, - THnL (typedef for THnT): bin content held by a Long_t, + THnL (typedef for THnT): bin content held by a Long64_t, THnI (typedef for THnT): bin content held by an Int_t, THnS (typedef for THnT): bin content held by a Short_t, THnC (typedef for THnT): bin content held by a Char_t, diff --git a/hist/hist/src/THnBase.cxx b/hist/hist/src/THnBase.cxx index c3a8826d18032..e0e9d1345fb6a 100644 --- a/hist/hist/src/THnBase.cxx +++ b/hist/hist/src/THnBase.cxx @@ -318,7 +318,7 @@ THnBase* THnBase::CreateHnAny(const char* name, const char* title, // Create the corresponding THnSparse, depending on the storage // type of the TH1. The class name will be "TH??\0" where the first // ? is 1,2 or 3 and the second ? indicates the storage as C, S, - // I, F or D. + // I, L, F or D. THnBase* s = nullptr; const char* cname( h->ClassName() ); if (cname[0] == 'T' && cname[1] == 'H' @@ -338,6 +338,7 @@ break; case 'F': R__THNBCASE(F); case 'D': R__THNBCASE(D); case 'I': R__THNBCASE(I); + case 'L': R__THNBCASE(L); case 'S': R__THNBCASE(S); case 'C': R__THNBCASE(C); } @@ -399,11 +400,7 @@ THnBase* THnBase::CreateHnAny(const char* name, const char* title, else if (hn->InheritsFrom(THnC::Class())) bintype = 'C'; else if (hn->InheritsFrom(THnS::Class())) bintype = 'S'; else if (hn->InheritsFrom(THnI::Class())) bintype = 'I'; - else if (hn->InheritsFrom(THnL::Class())) bintype = 'L'; - else if (hn->InheritsFrom(THnL64::Class())) { - hn->Error("CreateHnAny", "Type THnSparse with Long64_t bins is not available!"); - return nullptr; - } + else if (hn->InheritsFrom(THnL::Class()) || hn->InheritsFrom(THnL64::Class())) bintype = 'L'; if (bintype) { type = TClass::GetClass(TString::Format("THnSparse%c", bintype)); } diff --git a/hist/hist/src/THnSparse.cxx b/hist/hist/src/THnSparse.cxx index cec819eea6b8a..4f61f8f656f5b 100644 --- a/hist/hist/src/THnSparse.cxx +++ b/hist/hist/src/THnSparse.cxx @@ -505,7 +505,7 @@ To construct a THnSparse object you must use one of its templated, derived classes: - THnSparseD (typedef for THnSparseT): bin content held by a Double_t, - THnSparseF (typedef for THnSparseT): bin content held by a Float_t, -- THnSparseL (typedef for THnSparseT): bin content held by a Long_t, +- THnSparseL (typedef for THnSparseT): bin content held by a Long64_t, - THnSparseI (typedef for THnSparseT): bin content held by an Int_t, - THnSparseS (typedef for THnSparseT): bin content held by a Short_t, - THnSparseC (typedef for THnSparseT): bin content held by a Char_t, diff --git a/hist/hist/src/TLimit.cxx b/hist/hist/src/TLimit.cxx index bad29f7888d31..ef4fde761e718 100644 --- a/hist/hist/src/TLimit.cxx +++ b/hist/hist/src/TLimit.cxx @@ -23,15 +23,12 @@ // Alex Read, "Modified Frequentist Analysis of Search Results (The CLs Method)" // CERN 2000-005 (30 May 2000) // -// see note about: "Should I use TRolke, TFeldmanCousins, TLimit?" -// in the TRolke class description. -// /////////////////////////////////////////////////////////////////////////// /** \class TLimit + \legacy{TLimit, Consider switching to RooStats.} \ingroup Hist - Algorithm to compute 95% C.L. limits using the Likelihood ratio - semi-bayesian method. + Algorithm to compute 95% CL limits using the Likelihood ratio semi-bayesian method. Implemented by C. Delaere from the mclimit code written by Tom Junk [HEP-EX/9902006]. See [http://cern.ch/thomasj/searchlimits/ecl.html](http://cern.ch/thomasj/searchlimits/ecl.html) for more details. @@ -78,6 +75,8 @@ infile->Close(); ~~~ More information can still be found on [this page](http://cern.ch/aleph-proj-alphapp/doc/tlimit.html) + \see https://doi.org/10.1088/0954-3899/28/10/313 and https://cds.cern.ch/record/451614/files/open-2000-205.pdf + \note see note about: "Should I use TRolke, TFeldmanCousins, TLimit?" in the TRolke class description. */ #include "TLimit.h" diff --git a/hist/hist/src/TProfile.cxx b/hist/hist/src/TProfile.cxx index 1c27f121412cc..b147526ab2268 100644 --- a/hist/hist/src/TProfile.cxx +++ b/hist/hist/src/TProfile.cxx @@ -44,7 +44,7 @@ ClassImp(TProfile); \begin{align} H(j) &= \sum w \cdot Y \\ E(j) &= \sum w \cdot Y^2 \\ - W(j) &= \sum w \\ + W(j) &= \sum w & &\text{if weights different from 1, the number of bin effective entries is used} \\ h(j) &= H(j) / W(j) & &\text{mean of Y,} \\ s(j) &= \sqrt{E(j)/W(j)- h(j)^2} & &\text{standard deviation of Y} \\ e(j) &= s(j)/\sqrt{W(j)} & &\text{standard error on the mean} \\ @@ -302,7 +302,10 @@ Bool_t TProfile::Add(const TH1 *h1, const TH1 *h2, Double_t c1, Double_t c2) Error("Add","Attempt to add a non-profile object"); return kFALSE; } - return TProfileHelper::Add(this, h1, h2, c1, c2); + Bool_t ret = TProfileHelper::Add(this, h1, h2, c1, c2); + if (c1 < 0 || c2 < 0) + ResetStats(); + return ret; } diff --git a/hist/hist/test/CMakeLists.txt b/hist/hist/test/CMakeLists.txt index 479e81c68be45..75be6b8defa1d 100644 --- a/hist/hist/test/CMakeLists.txt +++ b/hist/hist/test/CMakeLists.txt @@ -27,6 +27,8 @@ endif() ROOT_ADD_GTEST(testTF2 test_tf2.cxx LIBRARIES Hist) ROOT_ADD_GTEST(testTF3 test_tf3.cxx LIBRARIES Hist) +ROOT_ADD_GTEST(testTH1sa test_TH1_SaveAs.cxx LIBRARIES Hist) + if(clad) ROOT_ADD_GTEST(TFormulaGradientTests TFormulaGradientTests.cxx LIBRARIES Core MathCore Hist) ROOT_ADD_GTEST(TFormulaHessianTests TFormulaHessianTests.cxx LIBRARIES Core MathCore Hist) @@ -40,3 +42,6 @@ endif() # for details. ROOT_EXECUTABLE(tdirectoryfile_destructor_segfault tdirectoryfile_destructor_segfault.cxx LIBRARIES RIO Hist) ROOT_ADD_TEST(test-tdirectoryfile_destructor_segfault COMMAND tdirectoryfile_destructor_segfault) + +# Test for https://github.com/root-project/root/issues/6658 +ROOT_ADD_GTEST(test_projections test_projections.cxx LIBRARIES Hist) diff --git a/hist/hist/test/test_TH1_SaveAs.cxx b/hist/hist/test/test_TH1_SaveAs.cxx new file mode 100644 index 0000000000000..328e0ecca6d50 --- /dev/null +++ b/hist/hist/test/test_TH1_SaveAs.cxx @@ -0,0 +1,207 @@ +#include "gtest/gtest.h" + +#include "TString.h" +#include "TH1.h" +#include "TSystem.h" +#include +#include +#include +#include + +struct TestSaveAs { + static constexpr Int_t Nbins = 5; + static constexpr Int_t N = Nbins + 2; // 0 and N are the under/overflow bins, resp. + TString fnam = "H1dump."; + + void SaveHist(const TString &myext, const TString &myoption = "") + { + // Bin contents and bin errors + Double_t binc[N] = {5.2, 0, 10.8, 12.3, 9.5, 7.3, 15.2}; + Double_t bine[N] = {2.1, 0, 2.5, 2.1, 3.5, 2.7, 4.7}; + + TH1D h("h", "h_title", Nbins, 0, Nbins); + + for (int i = 0; i < N; ++i) { + h.SetBinContent(i, binc[i]); + h.SetBinError(i, bine[i]); + } + TString filename{fnam + myext}; + h.SaveAs(filename.Data(), myoption.Data()); + } + + bool IsGood_csv() + { + TString filename{fnam + "csv"}; + std::ifstream infile(filename.Data(), std::ios::in); + if (!infile) { + return false; + } + Int_t idx = 0; + TString ref[N + 1] = {"# BinLowEdge BinUpEdge BinContent ey", + "-1,0,5.2,2.1", + "0,1,0,0", + "1,2,10.8,2.5", + "2,3,12.3,2.1", + "3,4,9.5,3.5", + "4,5,7.3,2.7", + "5,6,15.2,4.7"}; + std::string line; + while (std::getline(infile, line)) { + idx++; + if (idx > N + 1) { + infile.close(); + return false; + } + if (line != ref[idx - 1]) { + infile.close(); + return false; + } + } + infile.close(); + return true; + } + + bool IsGood_tsv() + { + TString filename{fnam + "tsv"}; + std::ifstream infile(filename.Data(), std::ios::in); + if (!infile) { + return false; + } + Int_t idx = 0; + TString ref[N] = {"-1 0 5.2 2.1", "0 1 0 0", "1 2 10.8 2.5", "2 3 12.3 2.1", + "3 4 9.5 3.5", "4 5 7.3 2.7", "5 6 15.2 4.7"}; + std::string line; + while (std::getline(infile, line)) { + idx++; + if (idx > N) { + infile.close(); + return false; + } + if (line != ref[idx - 1]) { + infile.close(); + return false; + } + } + infile.close(); + return true; + } + + bool IsGood_txt() + { + TString filename{fnam + "txt"}; + std::ifstream infile(filename.Data(), std::ios::in); + if (!infile) { + return false; + } + Int_t idx = 0; + TString ref[N] = {"-1 0 5.2 2.1", "0 1 0 0", "1 2 10.8 2.5", "2 3 12.3 2.1", + "3 4 9.5 3.5", "4 5 7.3 2.7", "5 6 15.2 4.7"}; + std::string line; + while (std::getline(infile, line)) { + idx++; + if (idx > N) { + infile.close(); + return false; + } + if (line != ref[idx - 1]) { + infile.close(); + return false; + } + } + infile.close(); + return true; + } + + bool IsGood_C() + { + TString filename{fnam + "C"}; + std::ifstream infile(filename.Data(), std::ios::in); + if (!infile) { + return false; + } + constexpr Int_t NC = 29; // lines in C file (excl. empty and commented out lines) + Int_t idx = 0; + TString ref[NC] = {"{", + " TH1D *h__1 = new TH1D(\"h__1\",\"h_title\",5,0,5);", + " h__1->SetBinContent(0,5.2);", + " h__1->SetBinContent(2,10.8);", + " h__1->SetBinContent(3,12.3);", + " h__1->SetBinContent(4,9.5);", + " h__1->SetBinContent(5,7.3);", + " h__1->SetBinContent(6,15.2);", + " h__1->SetBinError(0,2.1);", + " h__1->SetBinError(2,2.5);", + " h__1->SetBinError(3,2.1);", + " h__1->SetBinError(4,3.5);", + " h__1->SetBinError(5,2.7);", + " h__1->SetBinError(6,4.7);", + " h__1->SetEntries(7);", + " Int_t ci; // for color index setting", + " TColor *color; // for color definition with alpha", + " ci = TColor::GetColor(\"#000099\");", + " h__1->SetLineColor(ci);", + " h__1->GetXaxis()->SetLabelFont(42);", + " h__1->GetXaxis()->SetTitleOffset(1);", + " h__1->GetXaxis()->SetTitleFont(42);", + " h__1->GetYaxis()->SetLabelFont(42);", + " h__1->GetYaxis()->SetTitleFont(42);", + " h__1->GetZaxis()->SetLabelFont(42);", + " h__1->GetZaxis()->SetTitleOffset(1);", + " h__1->GetZaxis()->SetTitleFont(42);", + " h__1->Draw(\"\");", + "}"}; + std::string line; + while (std::getline(infile, line)) { + // skip lines starting with '//' and short lines (empty, indentation spaces only) + if (line != "{" && line != "}" && (line.rfind("//", 0) == 0 || line.length() < 6)) { + continue; + } + idx++; + if (idx > NC) { + infile.close(); + return false; + } + if (line != ref[idx - 1]) { + infile.close(); + return false; + } + } + infile.close(); + return true; + } +}; + +/// Tests for TH1::SaveAs +/// In this test we export a TH1 to 4 files of types csv, tsv, txt and C, +/// and then read those files checking whether the contents are as expected +/// In the csv file, we include the header line + +TEST(TH1sa, SaveAsCSV) +{ + TestSaveAs t; + t.SaveHist("csv", "title"); + EXPECT_TRUE(t.IsGood_csv()) << "TH1::SaveAs test: Exported .csv file failed test"; + gSystem->Unlink("H1dump.csv"); +} +TEST(TH1sa, SaveAsTSV) +{ + TestSaveAs t; + t.SaveHist("tsv"); + EXPECT_TRUE(t.IsGood_tsv()) << "TH1::SaveAs test: Exported .tsv file failed test"; + gSystem->Unlink("H1dump.tsv"); +} +TEST(TH1sa, SaveAsTXT) +{ + TestSaveAs t; + t.SaveHist("txt"); + EXPECT_TRUE(t.IsGood_txt()) << "TH1::SaveAs test: Exported .txt file failed test"; + gSystem->Unlink("H1dump.txt"); +} +TEST(TH1sa, SaveAsC) +{ + TestSaveAs t; + t.SaveHist("C"); + EXPECT_TRUE(t.IsGood_C()) << "TH1::SaveAs test: Exported .C file failed test"; + gSystem->Unlink("H1dump.C"); +} diff --git a/hist/hist/test/test_TH2Poly_BinError.cxx b/hist/hist/test/test_TH2Poly_BinError.cxx index 145bfb4295e8c..eec04c7460c41 100644 --- a/hist/hist/test/test_TH2Poly_BinError.cxx +++ b/hist/hist/test/test_TH2Poly_BinError.cxx @@ -101,7 +101,7 @@ TEST(TH2Poly,BinErrorWeighted) } -TEST(TH2, SetBinError) +TEST(TH2Poly, SetBinError) { auto h2p = CreateHist(); @@ -129,8 +129,46 @@ TEST(TH2, SetBinError) // setting a new content does not set bin error h2p->SetBinContent(-1,3); EXPECT_EQ( 0, h2p->GetBinError(-1) ); - + } +TEST(TH2Poly, Copy) +{ + auto h2p = CreateHist(); + + // set content and error + h2p->SetBinContent(1, 10.0); + h2p->SetBinContent(2, 20.0); + h2p->SetBinContent(3, 30.0); + // + h2p->SetBinError(1, 2.5); + h2p->SetBinError(2, 3.5); + h2p->SetBinError(3, 4.5); + // set overflow bins + + h2p->SetBinContent(-9, 15.0); + h2p->SetBinError(-9, 5.); + h2p->SetBinContent(-5, 100.0); + h2p->SetBinError(-5, 8.); + + // test copying + auto h2p_copy = new TH2Poly(*h2p); + EXPECT_EQ( h2p_copy->GetNumberOfBins(), h2p->GetNumberOfBins() ); + for (int i = 1; i <= h2p->GetNumberOfBins(); i++) { + EXPECT_EQ( h2p_copy->GetBinContent(i), h2p->GetBinContent(i) ); + EXPECT_EQ( h2p_copy->GetBinError(i), h2p->GetBinError(i) ); + } + for (int i = -1; i > -10; i--) { + EXPECT_EQ( h2p_copy->GetBinContent(i), h2p->GetBinContent(i) ); + EXPECT_EQ( h2p_copy->GetBinError(i), h2p->GetBinError(i) ); + } + + // test statistics + for (int i = 0; i < 2; i++) { + EXPECT_EQ( h2p_copy->GetStdDev(i), h2p->GetMean(i) ); + EXPECT_EQ( h2p_copy->GetStdDev(i), h2p->GetStdDev(i) ); + } + +} diff --git a/hist/hist/test/test_projections.cxx b/hist/hist/test/test_projections.cxx new file mode 100644 index 0000000000000..6a85d36b13978 --- /dev/null +++ b/hist/hist/test/test_projections.cxx @@ -0,0 +1,69 @@ +#include "TH2F.h" +#include "TH3F.h" +#include "TProfile.h" // ProjectionX +#include "TProfile2D.h" // ProjectionX +#include "THashList.h" // GetLabels + +#include "gtest/gtest.h" + +template +void expect_list_eq_names(const V1 &v1, const V2 &v2) +{ + ASSERT_EQ(v1.GetEntries(), v2.GetEntries()); + for (decltype(v1.GetEntries()) i = 0; i < v1.GetEntries(); ++i) { + EXPECT_STREQ(v1.At(i)->GetName(), v2.At(i)->GetName()); + } +} + +// Test projection from 2D hist for labels/nbins +TEST(Projections, Issue_6658_2D) +{ + TH2F hist2d("hist", "", 2, 0, 2, 2, 0, 2); + auto xaxis_2d = hist2d.GetXaxis(); + xaxis_2d->SetBinLabel(1, "A"); + xaxis_2d->SetBinLabel(2, "B"); + auto xaxis_2d_nbins = xaxis_2d->GetNbins(); + auto *labels_2d = xaxis_2d->GetLabels(); + + auto *hist_px = hist2d.ProjectionX(); + auto xaxis_px = hist_px->GetXaxis(); + auto xaxis_px_nbins = xaxis_px->GetNbins(); + auto *labels_px = xaxis_px->GetLabels(); + + EXPECT_EQ(xaxis_2d_nbins, xaxis_px_nbins); + hist_px->LabelsDeflate(); + EXPECT_EQ(xaxis_2d_nbins, xaxis_px_nbins); + expect_list_eq_names(*labels_2d, *labels_px); + + auto prof_px = hist2d.ProfileX(); + auto prof_px_xaxis = prof_px->GetXaxis(); + auto prof_px_nbins = prof_px_xaxis->GetNbins(); + auto *prof_px_labels = prof_px_xaxis->GetLabels(); + EXPECT_EQ(xaxis_2d_nbins, prof_px_nbins); + expect_list_eq_names(*labels_2d, *prof_px_labels); +} + +// Test projection from 3D hist for labels/nbins +TEST(Projections, Issue_6658_3D) +{ + TH3F hist3d("hist3", "", 2, 0, 2, 2, 0, 3, 2, 0, 4); + auto *xaxis_3d = hist3d.GetXaxis(); + xaxis_3d->SetBinLabel(1, "A"); + xaxis_3d->SetBinLabel(2, "B"); + auto xaxis_3d_nbins = xaxis_3d->GetNbins(); + auto *labels_3d = xaxis_3d->GetLabels(); + + auto *hist_px = hist3d.ProjectionX("x"); + auto *xaxis_px = hist_px->GetXaxis(); + auto xaxis_px_nbins = xaxis_px->GetNbins(); + auto *labels_px = xaxis_px->GetLabels(); + EXPECT_EQ(xaxis_3d_nbins, xaxis_px_nbins); + expect_list_eq_names(*labels_3d, *labels_px); + + auto *prof2_px = hist3d.Project3DProfile("yx"); + auto *xaxis_prof2_px = prof2_px->GetXaxis(); + auto prof2_px_nbins = xaxis_prof2_px->GetNbins(); + auto *labels_prof2_px = xaxis_prof2_px->GetLabels(); + EXPECT_EQ(xaxis_3d_nbins, prof2_px_nbins); + expect_list_eq_names(*labels_3d, *labels_prof2_px); +} diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index c2cd9e11ac25c..b02f431942562 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -9533,6 +9533,10 @@ void THistPainter::PaintTriangles(Option_t *option) fYbuf[1] = rmax[1]; fXbuf[2] = rmin[2]; fYbuf[2] = rmax[2]; + fH->SetMaximum(rmax[2]); + fH->SetMinimum(rmin[2]); + fH->GetXaxis()->SetRangeUser(rmin[0],rmax[0]); + fH->GetYaxis()->SetRangeUser(rmin[1],rmax[1]); } else { fXbuf[0] = Hparam.xmin; fYbuf[0] = Hparam.xmax; diff --git a/hist/histv7/inc/ROOT/RHist.hxx b/hist/histv7/inc/ROOT/RHist.hxx index 6294091a017b7..f7e264e4d1e71 100644 --- a/hist/histv7/inc/ROOT/RHist.hxx +++ b/hist/histv7/inc/ROOT/RHist.hxx @@ -305,18 +305,21 @@ using RH1D = RHist<1, double, RHistStatContent, RHistStatUncertainty>; using RH1F = RHist<1, float, RHistStatContent, RHistStatUncertainty>; using RH1C = RHist<1, char, RHistStatContent>; using RH1I = RHist<1, int, RHistStatContent>; +using RH1L = RHist<1, int64_t, RHistStatContent>; using RH1LL = RHist<1, int64_t, RHistStatContent>; using RH2D = RHist<2, double, RHistStatContent, RHistStatUncertainty>; using RH2F = RHist<2, float, RHistStatContent, RHistStatUncertainty>; using RH2C = RHist<2, char, RHistStatContent>; using RH2I = RHist<2, int, RHistStatContent>; +using RH2L = RHist<2, int64_t, RHistStatContent>; using RH2LL = RHist<2, int64_t, RHistStatContent>; using RH3D = RHist<3, double, RHistStatContent, RHistStatUncertainty>; using RH3F = RHist<3, float, RHistStatContent, RHistStatUncertainty>; using RH3C = RHist<3, char, RHistStatContent>; using RH3I = RHist<3, int, RHistStatContent>; +using RH3L = RHist<3, int64_t, RHistStatContent>; using RH3LL = RHist<3, int64_t, RHistStatContent>; ///\} diff --git a/hist/spectrum/src/TSpectrum.cxx b/hist/spectrum/src/TSpectrum.cxx index b979e0cf8633b..ee33b153fda90 100644 --- a/hist/spectrum/src/TSpectrum.cxx +++ b/hist/spectrum/src/TSpectrum.cxx @@ -14,7 +14,7 @@ \brief Advanced Spectra Processing \author Miroslav Morhac - \legacy{TSpectrum, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} + \legacy{TSpectrum, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} This class contains advanced spectra processing functions for: diff --git a/hist/spectrum/src/TSpectrum2.cxx b/hist/spectrum/src/TSpectrum2.cxx index a04d984cdbf19..d0a92f3050038 100644 --- a/hist/spectrum/src/TSpectrum2.cxx +++ b/hist/spectrum/src/TSpectrum2.cxx @@ -6,7 +6,7 @@ \brief Advanced 2-dimensional spectra processing \author Miroslav Morhac - \legacy{TSpectrum2} + \legacy{TSpectrum2, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} This class contains advanced spectra processing functions. diff --git a/hist/spectrum/src/TSpectrum2Fit.cxx b/hist/spectrum/src/TSpectrum2Fit.cxx index 3478ac34ee2a8..62559f07690cc 100644 --- a/hist/spectrum/src/TSpectrum2Fit.cxx +++ b/hist/spectrum/src/TSpectrum2Fit.cxx @@ -6,7 +6,7 @@ \brief Advanced 2-dimensional spectra fitting functions \author Miroslav Morhac - \legacy{TSpectrum2Fit} + \legacy{TSpectrum2Fit, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} Class for fitting 2D spectra using AWMI (algorithm without matrix inversion) and conjugate gradient algorithms for symmetrical diff --git a/hist/spectrum/src/TSpectrum2Transform.cxx b/hist/spectrum/src/TSpectrum2Transform.cxx index 09911752d4467..1cb616b1f7efa 100644 --- a/hist/spectrum/src/TSpectrum2Transform.cxx +++ b/hist/spectrum/src/TSpectrum2Transform.cxx @@ -6,7 +6,7 @@ \brief Advanced 2-dimensional orthogonal transform functions \author Miroslav Morhac - \legacy{TSpectrum2Transform} + \legacy{TSpectrum2Transform, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} Class to carry out transforms of 2D spectra, its filtering and enhancement. It allows to calculate classic Fourier, Cosine, Sin, diff --git a/hist/spectrum/src/TSpectrum3.cxx b/hist/spectrum/src/TSpectrum3.cxx index 6b0971ba0f33a..9f2f25200df2e 100644 --- a/hist/spectrum/src/TSpectrum3.cxx +++ b/hist/spectrum/src/TSpectrum3.cxx @@ -6,7 +6,7 @@ \brief Advanced 3-dimensional spectra processing functions \author Miroslav Morhac - \legacy{TSpectrum3} + \legacy{TSpectrum3, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} This class contains advanced spectra processing functions. diff --git a/hist/spectrum/src/TSpectrumFit.cxx b/hist/spectrum/src/TSpectrumFit.cxx index e6f1e4220ca5c..8b9491922931c 100644 --- a/hist/spectrum/src/TSpectrumFit.cxx +++ b/hist/spectrum/src/TSpectrumFit.cxx @@ -6,7 +6,7 @@ \brief Advanced 1-dimensional spectra fitting functions \author Miroslav Morhac - \legacy{TSpectrumFit} + \legacy{TSpectrumFit, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} Class for fitting 1D spectra using AWMI (algorithm without matrix inversion) and conjugate gradient algorithms for symmetrical diff --git a/hist/spectrum/src/TSpectrumTransform.cxx b/hist/spectrum/src/TSpectrumTransform.cxx index 8444f817a506b..74a1fc3f4d5b3 100644 --- a/hist/spectrum/src/TSpectrumTransform.cxx +++ b/hist/spectrum/src/TSpectrumTransform.cxx @@ -6,7 +6,7 @@ \brief Advanced 1-dimensional orthogonal transform functions \author Miroslav Morhac - \legacy{TSpectrumTransform} + \legacy{TSpectrumTransform, For modeling a spectrum fitting and estimating the background one can use RooFit while for deconvolution and unfolding one can use TUnfold.} Class to carry out transforms of 1D spectra, its filtering and diff --git a/interpreter/CMakeLists.txt b/interpreter/CMakeLists.txt index 13030086ca40d..6ea9fbe53907c 100644 --- a/interpreter/CMakeLists.txt +++ b/interpreter/CMakeLists.txt @@ -241,6 +241,12 @@ if(builtin_llvm) get_directory_property(LLVM_TARGET_TRIPLE DIRECTORY llvm-project/llvm DEFINITION LLVM_TARGET_TRIPLE) #---Get back the potentially updated LLVM_TARGETS_TO_BUILD (expanding all/host/Native)--------------- get_directory_property(LLVM_TARGETS_TO_BUILD DIRECTORY llvm-project/llvm DEFINITION LLVM_TARGETS_TO_BUILD) + + if(MSVC) + set(LLVM_TABLEGEN_EXE "${LLVM_BINARY_DIR}/$/bin/llvm-tblgen.exe") + else() + set(LLVM_TABLEGEN_EXE "${LLVM_BINARY_DIR}/bin/llvm-tblgen") + endif() else() # Rely on llvm-config. set(CONFIG_OUTPUT) diff --git a/interpreter/cling/CMakeLists.txt b/interpreter/cling/CMakeLists.txt index 34e0c65cb000b..9775b07f1086f 100644 --- a/interpreter/cling/CMakeLists.txt +++ b/interpreter/cling/CMakeLists.txt @@ -546,6 +546,7 @@ if (TARGET clang-headers) list(APPEND LLVM_COMMON_DEPENDS clang-headers) endif() +add_subdirectory(include/cling/Interpreter) add_subdirectory(lib) if( CLING_INCLUDE_TESTS ) diff --git a/interpreter/cling/include/cling/Interpreter/CMakeLists.txt b/interpreter/cling/include/cling/Interpreter/CMakeLists.txt new file mode 100644 index 0000000000000..59821499e5e1e --- /dev/null +++ b/interpreter/cling/include/cling/Interpreter/CMakeLists.txt @@ -0,0 +1,5 @@ +include_directories(${LLVM_INCLUDE_DIRS}) + +set(LLVM_TARGET_DEFINITIONS ClingOptions.td) +tablegen(LLVM ClingOptions.inc -gen-opt-parser-defs) +add_public_tablegen_target(ClingDriverOptions) diff --git a/interpreter/cling/include/cling/Interpreter/ClingOptions.inc b/interpreter/cling/include/cling/Interpreter/ClingOptions.inc deleted file mode 100644 index c630759ec404f..0000000000000 --- a/interpreter/cling/include/cling/Interpreter/ClingOptions.inc +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef PREFIX -#error "Define PREFIX prior to including this file!" -#endif - -///////// -// Prefixes - -#define COMMA , -PREFIX(prefix_0, {llvm::StringLiteral("")}) -PREFIX(prefix_1, {llvm::StringLiteral("-") COMMA llvm::StringLiteral("")}) -PREFIX(prefix_3, {llvm::StringLiteral("-") COMMA llvm::StringLiteral("--") COMMA llvm::StringLiteral("")}) -PREFIX(prefix_2, {llvm::StringLiteral("--") COMMA llvm::StringLiteral("")}) -#undef COMMA - -#ifndef OPTION -#error "Define OPTION prior to including this file!" -#endif - -OPTION(prefix_0, "", INPUT, Input, INVALID, INVALID, 0, 0, 0, 0, 0, 0) -OPTION(prefix_0, "", UNKNOWN, Unknown, INVALID, INVALID, 0, 0, 0, 0, 0, 0) -#ifndef NDEBUG -OPTION(prefix_2, "debug-only=", _debugFlags_EQ, Joined, INVALID, INVALID, 0, 0, 0, - "Debug flags to enable", 0, 0) -OPTION(prefix_2, "debug-only", _debugFlags, Separate, INVALID, INVALID, 0, 0, 0, - "Debug flags to enable", 0, 0) -#endif -OPTION(prefix_2, "errorout", _errorout, Flag, INVALID, INVALID, 0, 0, 0, - "Do not recover from input errors", 0, 0) -// Re-implement to forward to our help -OPTION(prefix_3, "help", help, Flag, INVALID, INVALID, 0, 0, 0, - "Print this help text", 0, 0) -OPTION(prefix_1, "L", L, JoinedOrSeparate, INVALID, INVALID, 0, 0, 0, - "Add directory to library search path", "", 0) -OPTION(prefix_1, "l", l, JoinedOrSeparate, INVALID, INVALID, 0, 0, 0, - "Load a library before prompt", "", 0) -OPTION(prefix_2, "metastr=", _metastr_EQ, Joined, INVALID, INVALID, 0, 0, 0, - "Set the meta command tag, default '.'", 0, 0) -OPTION(prefix_2, "metastr", _metastr, Separate, INVALID, INVALID, 0, 0, 0, - "Set the meta command tag, default '.'", 0, 0) -OPTION(prefix_2, "nologo", _nologo, Flag, INVALID, INVALID, 0, 0, 0, - "Do not show startup-banner", 0, 0) -OPTION(prefix_3, "noruntime", noruntime, Flag, INVALID, INVALID, 0, 0, 0, - "Disable runtime support (no null checking, no value printing)", 0, 0) -OPTION(prefix_2, "ptrcheck", _ptrcheck, Flag, INVALID, INVALID, 0, 0, 0, - "Enable injection of pointer validity checks", 0, 0) -OPTION(prefix_3, "version", version, Flag, INVALID, INVALID, 0, 0, 0, - "Print the compiler version", 0, 0) -OPTION(prefix_1, "v", v, Flag, INVALID, INVALID, 0, 0, 0, - "Enable verbose output", 0, 0) diff --git a/interpreter/cling/include/cling/Interpreter/ClingOptions.td b/interpreter/cling/include/cling/Interpreter/ClingOptions.td new file mode 100644 index 0000000000000..69b34aba2dde8 --- /dev/null +++ b/interpreter/cling/include/cling/Interpreter/ClingOptions.td @@ -0,0 +1,29 @@ +//===--- ClingOptions.td - Options for cling -----------------------------------===// +// +// CLING - the C++ LLVM-based InterpreterG :) +// +//===----------------------------------------------------------------------===// +// +// This file defines the options accepted by cling. +// +//===----------------------------------------------------------------------===// + +// Include the common option parsing interfaces. +include "llvm/Option/OptParser.td" + +#ifndef NDEBUG +def _debugFlags_EQ : Joined<["--"], "debug-only=">; +def _debugFlags : Flag<["--"], "debug-only">; +#endif +def _errorout : Flag<["--"], "errorout">, HelpText<"Do not recover from input errors">; +// Re-implement to forward to our help +def help : Flag<["-", "--"], "help">, HelpText<"Print this help text">; +def L : JoinedOrSeparate<["-"], "L">, HelpText<"Add directory to library search path">, MetaVarName<"">; +def l : JoinedOrSeparate<["-"], "l">, HelpText<"Load a library before prompt">, MetaVarName<"">; +def _metastr_EQ : Joined<["--"], "metastr=">, HelpText<"Set the meta command tag, default '.'">; +def _metastr : Separate<["--"], "metastr">, HelpText<"Set the meta command tag, default '.'">; +def _nologo : Flag<["--"], "nologo">, HelpText<"Do not show startup-banner">; +def noruntime : Flag<["-", "--"], "noruntime">, HelpText<"Disable runtime support (no null checking, no value printing)">; +def _ptrcheck : Flag<["--"], "ptrcheck">, HelpText<"Enable injection of pointer validity checks">; +def version : Flag<["-", "--"], "version">, HelpText<"Print the compiler version">; +def v : Flag<["-"], "v">, HelpText<"Enable verbose output">; diff --git a/interpreter/cling/include/cling/Interpreter/RuntimePrintValue.h b/interpreter/cling/include/cling/Interpreter/RuntimePrintValue.h index b6f3f1b1057d0..164347f92b9fa 100644 --- a/interpreter/cling/include/cling/Interpreter/RuntimePrintValue.h +++ b/interpreter/cling/include/cling/Interpreter/RuntimePrintValue.h @@ -16,6 +16,9 @@ #include +#if __cplusplus >= 201703L +#include +#endif #include #if __cplusplus >= 202002L #include @@ -241,6 +244,13 @@ namespace cling { return collectionPrinterInternal::printValue_impl(obj); } +#if __cplusplus >= 201703L + // For std::filesystem::path + inline std::string printValue(const std::filesystem::path* obj) { + return obj->string(); + } +#endif + // Arrays template inline std::string printValue(const T (*obj)[N]) { diff --git a/interpreter/cling/lib/Interpreter/AutoloadCallback.cpp b/interpreter/cling/lib/Interpreter/AutoloadCallback.cpp index fcd9ee04efd69..1e0287e0a6773 100644 --- a/interpreter/cling/lib/Interpreter/AutoloadCallback.cpp +++ b/interpreter/cling/lib/Interpreter/AutoloadCallback.cpp @@ -51,7 +51,7 @@ namespace cling { = sema.getDiagnostics().getCustomDiagID(DiagnosticsEngine::Level::Note, "Type : %0 , Full Path: %1")*/; - if (header.startswith(llvm::StringRef(annoTag, lenAnnoTag))) + if (header.starts_with(llvm::StringRef(annoTag, lenAnnoTag))) sema.Diags.Report(l, id) << name << header.drop_front(lenAnnoTag); } @@ -89,7 +89,7 @@ namespace cling { if (!attr->isInherited()) { llvm::StringRef annotation = attr->getAnnotation(); assert(!annotation.empty() && "Empty annotation!"); - if (annotation.startswith(llvm::StringRef(annoTag, lenAnnoTag))) { + if (annotation.starts_with(llvm::StringRef(annoTag, lenAnnoTag))) { // autoload annotation. return true; } @@ -223,7 +223,7 @@ namespace cling { { if (!attr->isInherited()) { auto annot = attr->getAnnotation(); - if (annot.startswith(llvm::StringRef(annoTag, lenAnnoTag))) { + if (annot.starts_with(llvm::StringRef(annoTag, lenAnnoTag))) { if (annotations.first.empty()) { annotations.first = annot.drop_front(lenAnnoTag); } else { diff --git a/interpreter/cling/lib/Interpreter/BackendPasses.cpp b/interpreter/cling/lib/Interpreter/BackendPasses.cpp index 2f8249e132b9a..14a8eda06e466 100644 --- a/interpreter/cling/lib/Interpreter/BackendPasses.cpp +++ b/interpreter/cling/lib/Interpreter/BackendPasses.cpp @@ -53,7 +53,7 @@ namespace { if (!GV.hasName()) return false; - if (GV.getName().startswith(".str")) + if (GV.getName().starts_with(".str")) return false; llvm::GlobalValue::LinkageTypes LT = GV.getLinkage(); @@ -137,7 +137,7 @@ namespace { if (GV.getLinkage() != llvm::GlobalValue::ExternalLinkage) return false; - if (GV.getName().startswith("_ZT")) { + if (GV.getName().starts_with("_ZT")) { // Currently, if Cling sees the "key function" of a virtual class, it // emits typeinfo and vtable variables in every transaction llvm::Module // that reference them. Turn them into weak linkage to avoid duplicate @@ -378,6 +378,7 @@ void BackendPasses::CreatePasses(int OptLevel, llvm::ModulePassManager& MPM, P.equals("ModuleInlinerPass") || P.equals("InlinerPass") || P.equals("InlineAdvisorAnalysis") || P.equals("PartiallyInlineLibCallsPass") || + P.equals("RelLookupTableConverterPass") || P.equals("InlineCostAnnotationPrinterPass") || P.equals("InlineSizeEstimatorAnalysisPrinterPass") || P.equals("InlineSizeEstimatorAnalysis")) @@ -385,6 +386,11 @@ void BackendPasses::CreatePasses(int OptLevel, llvm::ModulePassManager& MPM, return true; }); + } else { + // Register a callback for disabling RelLookupTableConverterPass. + PIC.registerShouldRunOptionalPassCallback([](StringRef P, Any) { + return !P.equals("RelLookupTableConverterPass"); + }); } SI.registerCallbacks(PIC, &FAM); diff --git a/interpreter/cling/lib/Interpreter/CIFactory.cpp b/interpreter/cling/lib/Interpreter/CIFactory.cpp index 9b04287fb0440..7d376412ae0fe 100644 --- a/interpreter/cling/lib/Interpreter/CIFactory.cpp +++ b/interpreter/cling/lib/Interpreter/CIFactory.cpp @@ -1342,18 +1342,21 @@ namespace { if(COpts.CUDAHost) argvCompile.push_back("--cuda-host-only"); + // argv[0] already inserted, get the rest + argvCompile.insert(argvCompile.end(), argv+1, argv + argc); + #ifdef __linux__ // Keep frame pointer to make JIT stack unwinding reliable for profiling if (profilingEnabled) argvCompile.push_back("-fno-omit-frame-pointer"); #endif - // Disable optimizations and keep frame pointer when debugging - if (debuggingEnabled) - argvCompile.push_back("-O0 -fno-omit-frame-pointer"); - - // argv[0] already inserted, get the rest - argvCompile.insert(argvCompile.end(), argv+1, argv + argc); + // Disable optimizations and keep frame pointer when debugging, overriding + // other optimization options that might be in argv + if (debuggingEnabled) { + argvCompile.push_back("-O0"); + argvCompile.push_back("-fno-omit-frame-pointer"); + } // Add host specific includes, -resource-dir if necessary, and -isysroot std::string ClingBin = GetExecutablePath(argv[0]); diff --git a/interpreter/cling/lib/Interpreter/CMakeLists.txt b/interpreter/cling/lib/Interpreter/CMakeLists.txt index 87e83535309c5..e397da97f6bda 100644 --- a/interpreter/cling/lib/Interpreter/CMakeLists.txt +++ b/interpreter/cling/lib/Interpreter/CMakeLists.txt @@ -47,9 +47,9 @@ set(LLVM_LINK_COMPONENTS # clingInterpreter depends on Options.inc to be tablegen-ed # (target ClangDriverOptions) from in-tree builds. -set(CLING_DEPENDS) +set(CLING_DEPENDS ClingDriverOptions) if(TARGET ClangDriverOptions) - set(CLING_DEPENDS ClangDriverOptions) + set(CLING_DEPENDS "${CLING_DEPENDS};ClangDriverOptions") endif() # clangSema will make sure all of the dependencies of clingInterpreter are met. if(TARGET clangSema) diff --git a/interpreter/cling/lib/Interpreter/ClangInternalState.cpp b/interpreter/cling/lib/Interpreter/ClangInternalState.cpp index 255ef4e3db79f..c33e9057f734e 100644 --- a/interpreter/cling/lib/Interpreter/ClangInternalState.cpp +++ b/interpreter/cling/lib/Interpreter/ClangInternalState.cpp @@ -144,13 +144,13 @@ namespace cling { for (auto i = clang::Builtin::NotBuiltin+1; i != clang::Builtin::FirstTSBuiltin; ++i) { llvm::StringRef Name(BuiltinCtx.getName(i)); - if (Name.startswith("__builtin")) + if (Name.starts_with("__builtin")) builtinNames.emplace_back(Name); } for (auto&& BuiltinInfo: m_ASTContext.getTargetInfo().getTargetBuiltins()) { llvm::StringRef Name(BuiltinInfo.Name); - if (!Name.startswith("__builtin")) + if (!Name.starts_with("__builtin")) builtinNames.emplace_back(Name); #ifndef NDEBUG else // Make sure it's already in the list diff --git a/interpreter/cling/lib/Interpreter/ClingCodeCompleteConsumer.cpp b/interpreter/cling/lib/Interpreter/ClingCodeCompleteConsumer.cpp index 54d5265851d4f..3aeb36ca47bc4 100644 --- a/interpreter/cling/lib/Interpreter/ClingCodeCompleteConsumer.cpp +++ b/interpreter/cling/lib/Interpreter/ClingCodeCompleteConsumer.cpp @@ -61,17 +61,19 @@ namespace cling { CodeCompletionResult Result) { switch (Result.Kind) { case CodeCompletionResult::RK_Declaration: { - return !(Result.Declaration->getIdentifier() && - Result.Declaration->getIdentifier()->getName().startswith(Filter)); + return !( + Result.Declaration->getIdentifier() && + Result.Declaration->getIdentifier()->getName().starts_with(Filter)); } case CodeCompletionResult::RK_Keyword: { - return !((StringRef(Result.Keyword)).startswith(Filter)); + return !((StringRef(Result.Keyword)).starts_with(Filter)); } case CodeCompletionResult::RK_Macro: { - return !(Result.Macro->getName().startswith(Filter)); + return !(Result.Macro->getName().starts_with(Filter)); } case CodeCompletionResult::RK_Pattern: { - return !(StringRef((Result.Pattern->getAsString())).startswith(Filter)); + return !( + StringRef((Result.Pattern->getAsString())).starts_with(Filter)); } default: llvm_unreachable("Unknown code completion result Kind."); } diff --git a/interpreter/cling/lib/Interpreter/DeclUnloader.cpp b/interpreter/cling/lib/Interpreter/DeclUnloader.cpp index 8f3523c160af4..011327384551a 100644 --- a/interpreter/cling/lib/Interpreter/DeclUnloader.cpp +++ b/interpreter/cling/lib/Interpreter/DeclUnloader.cpp @@ -226,7 +226,7 @@ namespace { /// Find values that are marked as llvm.used. void FindUsedValues(const llvm::Module& m) { for (const llvm::GlobalVariable& GV : m.globals()) { - if (!GV.getName().startswith("llvm.used")) + if (!GV.getName().starts_with("llvm.used")) continue; const llvm::ConstantArray* Inits @@ -934,8 +934,9 @@ namespace cling { // clang cannot mangle everything in the ms-abi. #ifndef NDEBUG utils::DiagnosticsStore Errors(m_Sema->getDiagnostics(), false, false); - assert(Errors.empty() || (Errors.size() == 1 && - Errors[0].getMessage().startswith("cannot mangle this"))); + assert(Errors.empty() || + (Errors.size() == 1 && + Errors[0].getMessage().starts_with("cannot mangle this"))); #else utils::DiagnosticsOverride IgnoreMangleErrors(m_Sema->getDiagnostics()); #endif diff --git a/interpreter/cling/lib/Interpreter/DefinitionShadower.cpp b/interpreter/cling/lib/Interpreter/DefinitionShadower.cpp index e87b65af5618c..50eec9945c3cb 100644 --- a/interpreter/cling/lib/Interpreter/DefinitionShadower.cpp +++ b/interpreter/cling/lib/Interpreter/DefinitionShadower.cpp @@ -73,7 +73,7 @@ namespace cling { bool DefinitionShadower::isClingShadowNamespace(const DeclContext *DC) { auto NS = dyn_cast(DC); - return NS && NS->getName().startswith("__cling_N5"); + return NS && NS->getName().starts_with("__cling_N5"); } void DefinitionShadower::hideDecl(clang::NamedDecl *D) const { diff --git a/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp b/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp index 09babd0b33bce..50d7f3b672f94 100644 --- a/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp +++ b/interpreter/cling/lib/Interpreter/DynamicLibraryManager.cpp @@ -69,7 +69,7 @@ namespace cling { /// Example: substFront("@rpath/abc", "@rpath/", "/tmp") -> "/tmp/abc" static std::string substFront(llvm::StringRef original, llvm::StringRef pattern, llvm::StringRef replacement) { - if (!original.startswith_insensitive(pattern)) + if (!original.starts_with_insensitive(pattern)) return original.str(); llvm::SmallString<512> result(replacement); result.append(original.drop_front(pattern.size())); @@ -307,7 +307,7 @@ namespace cling { // Subst all known linker variables ($origin, @rpath, etc.) #ifdef __APPLE__ // On MacOS @rpath is preplaced by all paths in RPATH one by one. - if (libStem.startswith_insensitive("@rpath")) { + if (libStem.starts_with_insensitive("@rpath")) { for (auto& P : RPath) { std::string result = substFront(libStem, "@rpath", P); if (isSharedLibrary(result)) @@ -328,7 +328,7 @@ namespace cling { foundName = lookupLibMaybeAddExt(libStem, RPath, RunPath, libLoader); if (foundName.empty()) { llvm::StringRef libStemName = llvm::sys::path::filename(libStem); - if (!libStemName.startswith("lib")) { + if (!libStemName.starts_with("lib")) { // try with "lib" prefix: foundName = lookupLibMaybeAddExt( libStem.str().insert(libStem.size()-libStemName.size(), "lib"), diff --git a/interpreter/cling/lib/Interpreter/ExternalInterpreterSource.cpp b/interpreter/cling/lib/Interpreter/ExternalInterpreterSource.cpp index 1c83a4e0ecb3f..19ff292efe4d2 100644 --- a/interpreter/cling/lib/Interpreter/ExternalInterpreterSource.cpp +++ b/interpreter/cling/lib/Interpreter/ExternalInterpreterSource.cpp @@ -268,7 +268,7 @@ namespace cling { DeclarationName childDeclName = parentDecl->getDeclName(); if (auto II = childDeclName.getAsIdentifierInfo()) { StringRef name = II->getName(); - if (!name.empty() && name.startswith(filter)) + if (!name.empty() && name.starts_with(filter)) ImportDecl(parentDecl, childDeclName, childDeclName, childDeclContext); } diff --git a/interpreter/cling/lib/Interpreter/ForwardDeclPrinter.cpp b/interpreter/cling/lib/Interpreter/ForwardDeclPrinter.cpp index cc691fef9d7c7..80aba72672e90 100644 --- a/interpreter/cling/lib/Interpreter/ForwardDeclPrinter.cpp +++ b/interpreter/cling/lib/Interpreter/ForwardDeclPrinter.cpp @@ -191,7 +191,7 @@ namespace cling { // start in this case as the '
' still has the correct value. // FIXME: Once the C++ modules replaced the forward decls, remove this. if (D->getASTContext().getLangOpts().Modules && - llvm::StringRef(includeText).startswith("include ")) { + llvm::StringRef(includeText).starts_with("include ")) { includeText += strlen("include "); } diff --git a/interpreter/cling/lib/Interpreter/Interpreter.cpp b/interpreter/cling/lib/Interpreter/Interpreter.cpp index 26614871dd5a7..89d61a7d76683 100644 --- a/interpreter/cling/lib/Interpreter/Interpreter.cpp +++ b/interpreter/cling/lib/Interpreter/Interpreter.cpp @@ -193,7 +193,7 @@ namespace cling { // Disable suggestions for ROOT bool showSuggestions = - !llvm::StringRef(ClingStringify(CLING_VERSION)).startswith("ROOT"); + !llvm::StringRef(ClingStringify(CLING_VERSION)).starts_with("ROOT"); std::unique_ptr AutoLoadCB( new AutoloadCallback(&Interp, showSuggestions)); @@ -1125,7 +1125,7 @@ namespace cling { } bool Interpreter::isUniqueName(llvm::StringRef name) { - return name.startswith(utils::Synthesize::UniquePrefix); + return name.starts_with(utils::Synthesize::UniquePrefix); } clang::SourceLocation Interpreter::getSourceLocation(bool skipWrapper) const { diff --git a/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp b/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp index a72d0462e8f8c..9d730563b0305 100644 --- a/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp +++ b/interpreter/cling/lib/Interpreter/InterpreterCallbacks.cpp @@ -148,6 +148,16 @@ namespace cling { return m_Source->FindExternalVisibleDeclsByName(DC, Name); } + void LoadExternalSpecializations(const Decl *D, bool OnlyPartial) override { + m_Source->LoadExternalSpecializations(D, OnlyPartial); + } + + bool + LoadExternalSpecializations(const Decl *D, + ArrayRef TemplateArgs) override { + return m_Source->LoadExternalSpecializations(D, TemplateArgs); + } + virtual void completeVisibleDeclsMap(const DeclContext* DC) override { m_Source->completeVisibleDeclsMap(DC); } diff --git a/interpreter/cling/lib/Interpreter/LookupHelper.cpp b/interpreter/cling/lib/Interpreter/LookupHelper.cpp index 414f29a7bcc9d..d8d322c8d6f40 100644 --- a/interpreter/cling/lib/Interpreter/LookupHelper.cpp +++ b/interpreter/cling/lib/Interpreter/LookupHelper.cpp @@ -320,11 +320,11 @@ namespace cling { { bool issigned = false; bool isunsigned = false; - if (typeName.startswith("signed ")) { + if (typeName.starts_with("signed ")) { issigned = true; typeName = StringRef(typeName.data()+7, typeName.size()-7); } - if (!issigned && typeName.startswith("unsigned ")) { + if (!issigned && typeName.starts_with("unsigned ")) { isunsigned = true; typeName = StringRef(typeName.data()+9, typeName.size()-9); } @@ -386,7 +386,7 @@ namespace cling { llvm::StringRef quickTypeName = typeName.trim(); bool innerConst = false; bool outerConst = false; - if (quickTypeName.startswith("const ")) { + if (quickTypeName.starts_with("const ")) { // Use this syntax to avoid the redudant tests in substr. quickTypeName = StringRef(quickTypeName.data()+6, quickTypeName.size()-6); @@ -395,7 +395,7 @@ namespace cling { enum PointerType { kPointerType, kLRefType, kRRefType, }; - if (quickTypeName.endswith("const")) { + if (quickTypeName.ends_with("const")) { if (quickTypeName.size() < 6) return true; auto c = quickTypeName[quickTypeName.size()-6]; if (c==' ' || c=='&' || c=='*') { diff --git a/interpreter/cling/lib/Interpreter/NullDerefProtectionTransformer.cpp b/interpreter/cling/lib/Interpreter/NullDerefProtectionTransformer.cpp index a126b3c964fb9..f212614bdc5d3 100644 --- a/interpreter/cling/lib/Interpreter/NullDerefProtectionTransformer.cpp +++ b/interpreter/cling/lib/Interpreter/NullDerefProtectionTransformer.cpp @@ -234,7 +234,7 @@ class PointerCheckInjector : public RecursiveASTVisitor { return true; else if (Ann->getAnnotation() == "__cling__ptrcheck(on)") return false; - else if (Ann->getAnnotation().startswith("__cling__ptrcheck(")) { + else if (Ann->getAnnotation().starts_with("__cling__ptrcheck(")) { DiagnosticsEngine& Diags = S->getDiagnostics(); Diags.Report(Ann->getLocation(), Diags.getCustomDiagID( diff --git a/interpreter/cling/lib/MetaProcessor/InputValidator.cpp b/interpreter/cling/lib/MetaProcessor/InputValidator.cpp index ba833f375c53f..6464f135af7c6 100644 --- a/interpreter/cling/lib/MetaProcessor/InputValidator.cpp +++ b/interpreter/cling/lib/MetaProcessor/InputValidator.cpp @@ -84,9 +84,9 @@ namespace cling { Lex.LexAnyString(Tok); if (Tok.isNot(tok::eof)) { const llvm::StringRef PPtk = Tok.getIdent(); - if (PPtk.startswith("if")) { + if (PPtk.starts_with("if")) { m_ParenStack.push_back(tok::hash); - } else if (PPtk.startswith("endif") && + } else if (PPtk.starts_with("endif") && (PPtk.size() == 5 || PPtk[5] == '/' || isspace(PPtk[5]))) { if (m_ParenStack.empty() || m_ParenStack.back() != tok::hash) Res = kMismatch; diff --git a/interpreter/cling/lib/MetaProcessor/MetaParser.cpp b/interpreter/cling/lib/MetaProcessor/MetaParser.cpp index 245a8f666fbc4..86d598282e0e8 100644 --- a/interpreter/cling/lib/MetaProcessor/MetaParser.cpp +++ b/interpreter/cling/lib/MetaProcessor/MetaParser.cpp @@ -373,7 +373,7 @@ namespace cling { const Token& currTok = getCurTok(); if (currTok.is(tok::ident)) { llvm::StringRef ident = currTok.getIdent(); - if (ident.startswith("O")) { + if (ident.starts_with("O")) { if (ident.size() > 1) { int level = 0; if (!ident.substr(1).getAsInteger(10, level) && level >= 0) { diff --git a/interpreter/cling/lib/Utils/AST.cpp b/interpreter/cling/lib/Utils/AST.cpp index 4580d3d21bd77..ef34d9595c710 100644 --- a/interpreter/cling/lib/Utils/AST.cpp +++ b/interpreter/cling/lib/Utils/AST.cpp @@ -70,7 +70,7 @@ namespace utils { if (!ND->getDeclName().isIdentifier()) return false; - return ND->getName().startswith(Synthesize::UniquePrefix); + return ND->getName().starts_with(Synthesize::UniquePrefix); } void Analyze::maybeMangleDeclName(const GlobalDecl& GD, diff --git a/interpreter/cling/lib/Utils/Paths.cpp b/interpreter/cling/lib/Utils/Paths.cpp index 5327ffc43c662..b92665f5e31af 100644 --- a/interpreter/cling/lib/Utils/Paths.cpp +++ b/interpreter/cling/lib/Utils/Paths.cpp @@ -277,7 +277,7 @@ bool SplitPaths(llvm::StringRef PathStr, } // Trim trailing sep in case of A:B:C:D: - if (!PathStr.empty() && PathStr.endswith(Delim)) + if (!PathStr.empty() && PathStr.ends_with(Delim)) PathStr = PathStr.substr(0, PathStr.size()-Delim.size()); if (!PathStr.empty()) { diff --git a/interpreter/cling/lib/Utils/PlatformWin.cpp b/interpreter/cling/lib/Utils/PlatformWin.cpp index 545cc5dbb400a..0511d9e7e5307 100644 --- a/interpreter/cling/lib/Utils/PlatformWin.cpp +++ b/interpreter/cling/lib/Utils/PlatformWin.cpp @@ -227,7 +227,7 @@ static bool getWindows10SDKVersion(std::string& SDKPath, // There could be subfolders like "wdf" in the "Include" directory, so only // test names that start with "10." or match input. const bool Match = Candidate == UcrtCompiledVers; - if (Match || (Candidate.startswith("10.") && Candidate > SDKVersion)) { + if (Match || (Candidate.starts_with("10.") && Candidate > SDKVersion)) { SDKPath = DirIt->path(); Candidate.str().swap(SDKVersion); if (Match) diff --git a/interpreter/cling/lib/Utils/SourceNormalization.cpp b/interpreter/cling/lib/Utils/SourceNormalization.cpp index 57755195f4acc..0ff7f53b8f3b1 100644 --- a/interpreter/cling/lib/Utils/SourceNormalization.cpp +++ b/interpreter/cling/lib/Utils/SourceNormalization.cpp @@ -393,7 +393,7 @@ cling::utils::isUnnamedMacro(llvm::StringRef source, if (AfterHash) { if (Tok.is(tok::raw_identifier)) { StringRef keyword(Tok.getRawIdentifier()); - if (keyword.startswith("if")) { + if (keyword.starts_with("if")) { // This could well be // #if FOO // { diff --git a/interpreter/cling/test/Prompt/ValuePrinter/FileSystemPath.C b/interpreter/cling/test/Prompt/ValuePrinter/FileSystemPath.C new file mode 100644 index 0000000000000..feaa5f8119650 --- /dev/null +++ b/interpreter/cling/test/Prompt/ValuePrinter/FileSystemPath.C @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// CLING - the C++ LLVM-based InterpreterG :) +// +// This file is dual-licensed: you can choose to license it under the University +// of Illinois Open Source License or the GNU Lesser General Public License. See +// LICENSE.TXT for details. + +//------------------------------------------------------------------------------ +// RUN: cat %s | %cling | FileCheck %s + +#include +#if __cplusplus >= 201703L +#include +#endif + +#if __cplusplus >= 201703L +auto p = std::filesystem::path("/some/path/foo.cpp"); +p +#else +// Hack to prevent failure if feature does not exist! +std::cout << "(std::filesystem::path &)"; +std::cout << "/some/path/foo.cpp\n"; +#endif + // CHECK: (std::filesystem::path &) /some/path/foo.cpp diff --git a/interpreter/cling/tools/plugins/clad/CMakeLists.txt b/interpreter/cling/tools/plugins/clad/CMakeLists.txt index 36944ff501f53..3a98d632d06db 100644 --- a/interpreter/cling/tools/plugins/clad/CMakeLists.txt +++ b/interpreter/cling/tools/plugins/clad/CMakeLists.txt @@ -74,7 +74,7 @@ list(APPEND _clad_cmake_logging_settings LOG_OUTPUT_ON_FAILURE ON) ExternalProject_Add( clad GIT_REPOSITORY https://github.com/vgvassilev/clad.git - GIT_TAG v1.2 + GIT_TAG v1.3 UPDATE_COMMAND "" PATCH_COMMAND ${_clad_patch_command} CMAKE_ARGS -G ${CMAKE_GENERATOR} diff --git a/interpreter/llvm-project/clang/include/clang/AST/DeclTemplate.h b/interpreter/llvm-project/clang/include/clang/AST/DeclTemplate.h index 90858d507d658..c4aad0729ce1e 100644 --- a/interpreter/llvm-project/clang/include/clang/AST/DeclTemplate.h +++ b/interpreter/llvm-project/clang/include/clang/AST/DeclTemplate.h @@ -257,14 +257,14 @@ class TemplateArgumentList final /// stack object. It does not own its template arguments. enum OnStackType { OnStack }; + /// Create stable hash for the given arguments across compiler invocations. + static unsigned ComputeStableHash(ArrayRef Args); + /// Create a new template argument list that copies the given set of /// template arguments. static TemplateArgumentList *CreateCopy(ASTContext &Context, ArrayRef Args); - /// \brief Create hash for the given arguments. - static unsigned ComputeODRHash(ArrayRef Args); - /// Construct a new, temporary template argument list on the stack. /// /// The template argument list does not own the template arguments @@ -780,25 +780,6 @@ class RedeclarableTemplateDecl : public TemplateDecl, } void anchor() override; - struct LazySpecializationInfo { - uint32_t DeclID = ~0U; - unsigned ODRHash = ~0U; - bool IsPartial = false; - LazySpecializationInfo(uint32_t ID, unsigned Hash = ~0U, - bool Partial = false) - : DeclID(ID), ODRHash(Hash), IsPartial(Partial) { } - LazySpecializationInfo() { } - bool operator<(const LazySpecializationInfo &Other) const { - return DeclID < Other.DeclID; - } - bool operator==(const LazySpecializationInfo &Other) const { - assert((DeclID != Other.DeclID || ODRHash == Other.ODRHash) && - "Hashes differ!"); - assert((DeclID != Other.DeclID || IsPartial == Other.IsPartial) && - "Both must be the same kinds!"); - return DeclID == Other.DeclID; - } - }; protected: template struct SpecEntryTraits { @@ -842,16 +823,19 @@ class RedeclarableTemplateDecl : public TemplateDecl, void loadLazySpecializationsImpl(bool OnlyPartial = false) const; - void loadLazySpecializationsImpl(llvm::ArrayRef Args, + bool loadLazySpecializationsImpl(llvm::ArrayRef Args, TemplateParameterList *TPL = nullptr) const; - Decl *loadLazySpecializationImpl(LazySpecializationInfo &LazySpecInfo) const; - template typename SpecEntryTraits::DeclType* findSpecializationImpl(llvm::FoldingSetVector &Specs, void *&InsertPos, ProfileArguments &&...ProfileArgs); + template + typename SpecEntryTraits::DeclType* + findSpecializationLocally(llvm::FoldingSetVector &Specs, + void *&InsertPos, ProfileArguments &&...ProfileArgs); + template void addSpecializationImpl(llvm::FoldingSetVector &Specs, EntryType *Entry, void *InsertPos); @@ -867,13 +851,6 @@ class RedeclarableTemplateDecl : public TemplateDecl, llvm::PointerIntPair InstantiatedFromMember; - /// If non-null, points to an array of specializations (including - /// partial specializations) known only by their external declaration IDs. - /// - /// The first value in the array is the number of specializations/partial - /// specializations that follow. - LazySpecializationInfo *LazySpecializations = nullptr; - /// The set of "injected" template arguments used within this /// template. /// diff --git a/interpreter/llvm-project/clang/include/clang/AST/ExternalASTSource.h b/interpreter/llvm-project/clang/include/clang/AST/ExternalASTSource.h index 5a0e65e36c0e2..09c69b73f04d3 100644 --- a/interpreter/llvm-project/clang/include/clang/AST/ExternalASTSource.h +++ b/interpreter/llvm-project/clang/include/clang/AST/ExternalASTSource.h @@ -150,6 +150,19 @@ class ExternalASTSource : public RefCountedBase { virtual bool FindExternalVisibleDeclsByName(const DeclContext *DC, DeclarationName Name); + /// Load all the external specializations for the Decl \param D if \param + /// OnlyPartial is false. Otherwise, load all the external **partial** + /// specializations for the \param D. + virtual void LoadExternalSpecializations(const Decl *D, bool OnlyPartial); + + /// Load all the specializations for the Decl \param D with the same template + /// args specified by \param TemplateArgs. + /// + /// Return true if any external specialization loaded. Return false otherwise. + virtual bool + LoadExternalSpecializations(const Decl *D, + ArrayRef TemplateArgs); + /// Ensures that the table of all visible declarations inside this /// context is up to date. /// diff --git a/interpreter/llvm-project/clang/include/clang/Sema/MultiplexExternalSemaSource.h b/interpreter/llvm-project/clang/include/clang/Sema/MultiplexExternalSemaSource.h index b0bb15eccee1d..78fc4f2737c8f 100644 --- a/interpreter/llvm-project/clang/include/clang/Sema/MultiplexExternalSemaSource.h +++ b/interpreter/llvm-project/clang/include/clang/Sema/MultiplexExternalSemaSource.h @@ -97,6 +97,12 @@ class MultiplexExternalSemaSource : public ExternalSemaSource { bool FindExternalVisibleDeclsByName(const DeclContext *DC, DeclarationName Name) override; + void LoadExternalSpecializations(const Decl *D, bool OnlyPartial) override; + + bool + LoadExternalSpecializations(const Decl *D, + ArrayRef TemplateArgs) override; + /// Ensures that the table of all visible declarations inside this /// context is up to date. void completeVisibleDeclsMap(const DeclContext *DC) override; diff --git a/interpreter/llvm-project/clang/include/clang/Serialization/ASTBitCodes.h b/interpreter/llvm-project/clang/include/clang/Serialization/ASTBitCodes.h index 9ba94da03720e..111e4c56531a0 100644 --- a/interpreter/llvm-project/clang/include/clang/Serialization/ASTBitCodes.h +++ b/interpreter/llvm-project/clang/include/clang/Serialization/ASTBitCodes.h @@ -702,6 +702,8 @@ enum ASTRecordTypes { /// Record code for an unterminated \#pragma clang assume_nonnull begin /// recorded in a preamble. PP_ASSUME_NONNULL_LOC = 67, + + CXX_ADDED_TEMPLATE_SPECIALIZATION = 68, }; /// Record types used within a source manager block. @@ -1520,6 +1522,9 @@ enum DeclCode { /// A HLSLBufferDecl record. DECL_HLSL_BUFFER, + // A decls specilization record. + DECL_SPECIALIZATIONS, + /// An ImplicitConceptSpecializationDecl record. DECL_IMPLICIT_CONCEPT_SPECIALIZATION, diff --git a/interpreter/llvm-project/clang/include/clang/Serialization/ASTReader.h b/interpreter/llvm-project/clang/include/clang/Serialization/ASTReader.h index 468ba06f88cbe..c574ab845df2e 100644 --- a/interpreter/llvm-project/clang/include/clang/Serialization/ASTReader.h +++ b/interpreter/llvm-project/clang/include/clang/Serialization/ASTReader.h @@ -341,6 +341,9 @@ class ASTIdentifierLookupTrait; /// The on-disk hash table(s) used for DeclContext name lookup. struct DeclContextLookupTable; +/// The on-disk hash table(s) used for specialization decls. +struct LazySpecializationInfoLookupTable; + } // namespace reader } // namespace serialization @@ -596,21 +599,30 @@ class ASTReader llvm::DenseMap Lookups; + /// Map from decls to specialized decls. + llvm::DenseMap + SpecializationsLookups; + // Updates for visible decls can occur for other contexts than just the // TU, and when we read those update records, the actual context may not // be available yet, so have this pending map using the ID as a key. It - // will be realized when the context is actually loaded. - struct PendingVisibleUpdate { + // will be realized when the data is actually loaded. + struct UpdateData { ModuleFile *Mod; const unsigned char *Data; }; - using DeclContextVisibleUpdates = SmallVector; + using DeclContextVisibleUpdates = SmallVector; /// Updates to the visible declarations of declaration contexts that /// haven't been loaded yet. llvm::DenseMap PendingVisibleUpdates; + using SpecializationsUpdate = SmallVector; + llvm::DenseMap + PendingSpecializationsUpdates; + /// The set of C++ or Objective-C classes that have forward /// declarations that have not yet been linked to their definitions. llvm::SmallPtrSet PendingDefinitions; @@ -637,6 +649,11 @@ class ASTReader llvm::BitstreamCursor &Cursor, uint64_t Offset, serialization::DeclID ID); + bool ReadSpecializations(ModuleFile &M, llvm::BitstreamCursor &Cursor, + uint64_t Offset, Decl *D); + void AddSpecializations(const Decl *D, const unsigned char *Data, + ModuleFile &M); + /// A vector containing identifiers that have already been /// loaded. /// @@ -1327,6 +1344,11 @@ class ASTReader const serialization::reader::DeclContextLookupTable * getLoadedLookupTables(DeclContext *Primary) const; + /// Get the loaded specializations lookup tables for \p D, + /// if any. + serialization::reader::LazySpecializationInfoLookupTable * + getLoadedSpecializationsLookupTables(const Decl *D); + private: struct ImportedModule { ModuleFile *Mod; @@ -1963,6 +1985,12 @@ class ASTReader unsigned BlockID, uint64_t *StartOfBlockOffset = nullptr); + void LoadExternalSpecializations(const Decl *D, bool OnlyPartial) override; + + bool + LoadExternalSpecializations(const Decl *D, + ArrayRef TemplateArgs) override; + /// Finds all the visible declarations with a given name. /// The current implementation of this method just loads the entire /// lookup table as unmaterialized references. diff --git a/interpreter/llvm-project/clang/include/clang/Serialization/ASTWriter.h b/interpreter/llvm-project/clang/include/clang/Serialization/ASTWriter.h index 09ee1744e8945..80fc6b7532e7c 100644 --- a/interpreter/llvm-project/clang/include/clang/Serialization/ASTWriter.h +++ b/interpreter/llvm-project/clang/include/clang/Serialization/ASTWriter.h @@ -373,6 +373,10 @@ class ASTWriter : public ASTDeserializationListener, /// record containing modifications to them. DeclUpdateMap DeclUpdates; + using SpecializationUpdateMap = + llvm::MapVector>; + SpecializationUpdateMap SpecializationsUpdates; + using FirstLatestDeclMap = llvm::DenseMap; /// Map of first declarations from a chained PCH that point to the @@ -517,6 +521,13 @@ class ASTWriter : public ASTDeserializationListener, bool isLookupResultExternal(StoredDeclsList &Result, DeclContext *DC); bool isLookupResultEntirelyExternal(StoredDeclsList &Result, DeclContext *DC); + void GenerateSpecializationInfoLookupTable( + const NamedDecl *D, llvm::SmallVectorImpl &Specializations, + llvm::SmallVectorImpl &LookupTable); + uint64_t WriteSpecializationInfoLookupTable( + const NamedDecl *D, + llvm::SmallVectorImpl &Specializations); + void GenerateNameLookupTable(const DeclContext *DC, llvm::SmallVectorImpl &LookupTable); uint64_t WriteDeclContextLexicalBlock(ASTContext &Context, DeclContext *DC); @@ -528,6 +539,7 @@ class ASTWriter : public ASTDeserializationListener, void WriteReferencedSelectorsPool(Sema &SemaRef); void WriteIdentifierTable(Preprocessor &PP, IdentifierResolver &IdResolver, bool IsModule); + void WriteSpecializationsUpdates(); void WriteDeclUpdatesBlocks(RecordDataImpl &OffsetsRecord); void WriteDeclContextVisibleUpdate(const DeclContext *DC); void WriteFPPragmaOptions(const FPOptionsOverride &Opts); @@ -554,6 +566,7 @@ class ASTWriter : public ASTDeserializationListener, unsigned DeclEnumAbbrev = 0; unsigned DeclObjCIvarAbbrev = 0; unsigned DeclCXXMethodAbbrev = 0; + unsigned DeclSpecializationsAbbrev = 0; unsigned DeclRefExprAbbrev = 0; unsigned CharacterLiteralAbbrev = 0; diff --git a/interpreter/llvm-project/clang/lib/AST/DeclTemplate.cpp b/interpreter/llvm-project/clang/lib/AST/DeclTemplate.cpp index 2ded6d7e08d43..1b4a82e0ffa5f 100644 --- a/interpreter/llvm-project/clang/lib/AST/DeclTemplate.cpp +++ b/interpreter/llvm-project/clang/lib/AST/DeclTemplate.cpp @@ -16,12 +16,12 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/ExternalASTSource.h" +#include "clang/AST/ODRHash.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TemplateName.h" #include "clang/AST/Type.h" -#include "clang/AST/ODRHash.h" -#include "clang/AST/ExprCXX.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/LLVM.h" @@ -301,53 +301,30 @@ RedeclarableTemplateDecl::CommonBase *RedeclarableTemplateDecl::getCommonPtr() c } void RedeclarableTemplateDecl::loadLazySpecializationsImpl( - bool OnlyPartial/*=false*/) const { - // Grab the most recent declaration to ensure we've loaded any lazy - // redeclarations of this template. - CommonBase *CommonBasePtr = getMostRecentDecl()->getCommonPtr(); - if (auto *Specs = CommonBasePtr->LazySpecializations) { - if (!OnlyPartial) - CommonBasePtr->LazySpecializations = nullptr; - for (uint32_t I = 0, N = Specs[0].DeclID; I != N; ++I) { - // Skip over already loaded specializations. - if (!Specs[I+1].ODRHash) - continue; - if (!OnlyPartial || Specs[I+1].IsPartial) - (void)loadLazySpecializationImpl(Specs[I+1]); - } - } -} + bool OnlyPartial /*=false*/) const { + auto *ExternalSource = getASTContext().getExternalSource(); + if (!ExternalSource) + return; -Decl *RedeclarableTemplateDecl::loadLazySpecializationImpl( - LazySpecializationInfo &LazySpecInfo) const { - uint32_t ID = LazySpecInfo.DeclID; - assert(ID && "Loading already loaded specialization!"); - // Note that we loaded the specialization. - LazySpecInfo.DeclID = LazySpecInfo.ODRHash = LazySpecInfo.IsPartial = 0; - return getASTContext().getExternalSource()->GetExternalDecl(ID); + ExternalSource->LoadExternalSpecializations(this->getCanonicalDecl(), + OnlyPartial); + return; } -void -RedeclarableTemplateDecl::loadLazySpecializationsImpl(ArrayRef - Args, - TemplateParameterList *TPL) const { - CommonBase *CommonBasePtr = getMostRecentDecl()->getCommonPtr(); - if (auto *Specs = CommonBasePtr->LazySpecializations) { - unsigned Hash = TemplateArgumentList::ComputeODRHash(Args); - for (uint32_t I = 0, N = Specs[0].DeclID; I != N; ++I) - if (Specs[I+1].ODRHash && Specs[I+1].ODRHash == Hash) - (void)loadLazySpecializationImpl(Specs[I+1]); - } +bool RedeclarableTemplateDecl::loadLazySpecializationsImpl( + ArrayRef Args, TemplateParameterList *TPL) const { + auto *ExternalSource = getASTContext().getExternalSource(); + if (!ExternalSource) + return false; + + return ExternalSource->LoadExternalSpecializations(this->getCanonicalDecl(), Args); } template typename RedeclarableTemplateDecl::SpecEntryTraits::DeclType * -RedeclarableTemplateDecl::findSpecializationImpl( - llvm::FoldingSetVector &Specs, void *&InsertPos, +RedeclarableTemplateDecl::findSpecializationLocally(llvm::FoldingSetVector &Specs, void *&InsertPos, ProfileArguments&&... ProfileArgs) { - using SETraits = SpecEntryTraits; - - loadLazySpecializationsImpl(std::forward(ProfileArgs)...); + using SETraits = RedeclarableTemplateDecl::SpecEntryTraits; llvm::FoldingSetNodeID ID; EntryType::Profile(ID, std::forward(ProfileArgs)..., @@ -356,6 +333,24 @@ RedeclarableTemplateDecl::findSpecializationImpl( return Entry ? SETraits::getDecl(Entry)->getMostRecentDecl() : nullptr; } +template +typename RedeclarableTemplateDecl::SpecEntryTraits::DeclType * +RedeclarableTemplateDecl::findSpecializationImpl( + llvm::FoldingSetVector &Specs, void *&InsertPos, + ProfileArguments&&... ProfileArgs) { + if (auto *Found = findSpecializationLocally(Specs, InsertPos, + std::forward(ProfileArgs)...)) + return Found; + + // Try to load external specializations if we can't find the specialization + // locally. + if (!loadLazySpecializationsImpl(std::forward(ProfileArgs)...)) + return nullptr; + + return findSpecializationLocally(Specs, InsertPos, + std::forward(ProfileArgs)...); +} + template void RedeclarableTemplateDecl::addSpecializationImpl( llvm::FoldingSetVector &Specializations, EntryType *Entry, @@ -513,7 +508,7 @@ ClassTemplateDecl *ClassTemplateDecl::CreateDeserialized(ASTContext &C, } void ClassTemplateDecl::LoadLazySpecializations( - bool OnlyPartial/*=false*/) const { + bool OnlyPartial /*=false*/) const { loadLazySpecializationsImpl(OnlyPartial); } @@ -928,7 +923,20 @@ TemplateArgumentList::CreateCopy(ASTContext &Context, return new (Mem) TemplateArgumentList(Args); } -unsigned TemplateArgumentList::ComputeODRHash(ArrayRef Args) { +unsigned +TemplateArgumentList::ComputeStableHash(ArrayRef Args) { + // FIXME: ODR hashing may not be the best mechanism to hash the template + // arguments. ODR hashing is (or perhaps, should be) about determining whether + // two things are spelled the same way and have the same meaning (as required + // by the C++ ODR), whereas what we want here is whether they have the same + // meaning regardless of spelling. Maybe we can get away with reusing ODR + // hashing anyway, on the basis that any canonical, non-dependent template + // argument should have the same (invented) spelling in every translation + // unit, but it is not sure that's true in all cases. There may still be cases + // where the canonical type includes some aspect of "whatever we saw first", + // in which case the ODR hash can differ across translation units for + // non-dependent, canonical template arguments that are spelled differently + // but have the same meaning. But it is not easy to raise examples. ODRHash Hasher; for (TemplateArgument TA : Args) Hasher.AddTemplateArgument(TA); @@ -1292,7 +1300,7 @@ VarTemplateDecl *VarTemplateDecl::CreateDeserialized(ASTContext &C, } void VarTemplateDecl::LoadLazySpecializations( - bool OnlyPartial/*=false*/) const { + bool OnlyPartial /*=false*/) const { loadLazySpecializationsImpl(OnlyPartial); } diff --git a/interpreter/llvm-project/clang/lib/AST/ExternalASTSource.cpp b/interpreter/llvm-project/clang/lib/AST/ExternalASTSource.cpp index fbb1114a6ea3a..764f41b454640 100644 --- a/interpreter/llvm-project/clang/lib/AST/ExternalASTSource.cpp +++ b/interpreter/llvm-project/clang/lib/AST/ExternalASTSource.cpp @@ -104,6 +104,11 @@ ExternalASTSource::FindExternalVisibleDeclsByName(const DeclContext *DC, return false; } +void ExternalASTSource::LoadExternalSpecializations(const Decl *D, bool) {} + +bool ExternalASTSource::LoadExternalSpecializations( + const Decl *D, ArrayRef) { return false; } + void ExternalASTSource::completeVisibleDeclsMap(const DeclContext *DC) {} void ExternalASTSource::FindExternalLexicalDecls( diff --git a/interpreter/llvm-project/clang/lib/AST/ODRHash.cpp b/interpreter/llvm-project/clang/lib/AST/ODRHash.cpp index 92911389d0d9e..06d8348545ce5 100644 --- a/interpreter/llvm-project/clang/lib/AST/ODRHash.cpp +++ b/interpreter/llvm-project/clang/lib/AST/ODRHash.cpp @@ -139,8 +139,23 @@ void ODRHash::AddNestedNameSpecifier(const NestedNameSpecifier *NNS) { } void ODRHash::AddTemplateName(TemplateName Name) { - if (auto *TD = Name.getAsTemplateDecl()) - AddDecl(TD); + auto Kind = Name.getKind(); + ID.AddInteger(Kind); + + switch (Kind) { + case TemplateName::Template: + AddDecl(Name.getAsTemplateDecl()); + break; + // TODO: Support these cases. + case TemplateName::OverloadedTemplate: + case TemplateName::AssumedTemplate: + case TemplateName::QualifiedTemplate: + case TemplateName::DependentTemplate: + case TemplateName::SubstTemplateTemplateParm: + case TemplateName::SubstTemplateTemplateParmPack: + case TemplateName::UsingTemplate: + break; + } } void ODRHash::AddTemplateArgument(TemplateArgument TA) { diff --git a/interpreter/llvm-project/clang/lib/Sema/MultiplexExternalSemaSource.cpp b/interpreter/llvm-project/clang/lib/Sema/MultiplexExternalSemaSource.cpp index 55e015487f3bf..073323ad238c2 100644 --- a/interpreter/llvm-project/clang/lib/Sema/MultiplexExternalSemaSource.cpp +++ b/interpreter/llvm-project/clang/lib/Sema/MultiplexExternalSemaSource.cpp @@ -115,6 +115,20 @@ FindExternalVisibleDeclsByName(const DeclContext *DC, DeclarationName Name) { return AnyDeclsFound; } +void MultiplexExternalSemaSource::LoadExternalSpecializations( + const Decl *D, bool OnlyPartial) { + for (size_t i = 0; i < Sources.size(); ++i) + Sources[i]->LoadExternalSpecializations(D, OnlyPartial); +} + +bool MultiplexExternalSemaSource::LoadExternalSpecializations( + const Decl *D, ArrayRef TemplateArgs) { + bool AnySpecsLoaded = false; + for (size_t i = 0; i < Sources.size(); ++i) + AnySpecsLoaded |= Sources[i]->LoadExternalSpecializations(D, TemplateArgs); + return AnySpecsLoaded; +} + void MultiplexExternalSemaSource::completeVisibleDeclsMap(const DeclContext *DC){ for(size_t i = 0; i < Sources.size(); ++i) Sources[i]->completeVisibleDeclsMap(DC); diff --git a/interpreter/llvm-project/clang/lib/Serialization/ASTCommon.h b/interpreter/llvm-project/clang/lib/Serialization/ASTCommon.h index 296642e3674a4..485809f234113 100644 --- a/interpreter/llvm-project/clang/lib/Serialization/ASTCommon.h +++ b/interpreter/llvm-project/clang/lib/Serialization/ASTCommon.h @@ -23,7 +23,6 @@ namespace serialization { enum DeclUpdateKind { UPD_CXX_ADDED_IMPLICIT_MEMBER, - UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION, UPD_CXX_ADDED_ANONYMOUS_NAMESPACE, UPD_CXX_ADDED_FUNCTION_DEFINITION, UPD_CXX_ADDED_VAR_DEFINITION, diff --git a/interpreter/llvm-project/clang/lib/Serialization/ASTReader.cpp b/interpreter/llvm-project/clang/lib/Serialization/ASTReader.cpp index a797bdb4a534c..729aeb01981a9 100644 --- a/interpreter/llvm-project/clang/lib/Serialization/ASTReader.cpp +++ b/interpreter/llvm-project/clang/lib/Serialization/ASTReader.cpp @@ -1183,6 +1183,41 @@ void ASTDeclContextNameLookupTrait::ReadDataInto(internal_key_type, } } +ModuleFile *LazySpecializationInfoLookupTrait::ReadFileRef(const unsigned char *&d) { + using namespace llvm::support; + + uint32_t ModuleFileID = + endian::readNext(d); + return Reader.getLocalModuleFile(F, ModuleFileID); +} + +LazySpecializationInfoLookupTrait::internal_key_type +LazySpecializationInfoLookupTrait::ReadKey(const unsigned char *d, unsigned) { + using namespace llvm::support; + return endian::readNext(d); +} + +std::pair +LazySpecializationInfoLookupTrait::ReadKeyDataLength(const unsigned char *&d) { + return readULEBKeyDataLength(d); +} + +void LazySpecializationInfoLookupTrait::ReadDataInto(internal_key_type, + const unsigned char *d, + unsigned DataLen, + data_type_builder &Val) { + using namespace llvm::support; + + for (unsigned NumDecls = DataLen / serialization::reader::LazySpecializationInfo::Length; + NumDecls; --NumDecls) { + uint32_t LocalID = + endian::readNext(d); + const bool IsPartial = + endian::readNext(d); + Val.insert({Reader.getGlobalDeclID(F, LocalID), IsPartial}); + } +} + bool ASTReader::ReadLexicalDeclContextStorage(ModuleFile &M, BitstreamCursor &Cursor, uint64_t Offset, @@ -1268,7 +1303,49 @@ bool ASTReader::ReadVisibleDeclContextStorage(ModuleFile &M, // We can't safely determine the primary context yet, so delay attaching the // lookup table until we're done with recursive deserialization. auto *Data = (const unsigned char*)Blob.data(); - PendingVisibleUpdates[ID].push_back(PendingVisibleUpdate{&M, Data}); + PendingVisibleUpdates[ID].push_back(UpdateData{&M, Data}); + return false; +} + +void ASTReader::AddSpecializations(const Decl *D, const unsigned char *Data, + ModuleFile &M) { + D = D->getCanonicalDecl(); + SpecializationsLookups[D].Table.add( + &M, Data, reader::LazySpecializationInfoLookupTrait(*this, M)); +} + +bool ASTReader::ReadSpecializations(ModuleFile &M, BitstreamCursor &Cursor, + uint64_t Offset, Decl *D) { + assert(Offset != 0); + + SavedStreamPosition SavedPosition(Cursor); + if (llvm::Error Err = Cursor.JumpToBit(Offset)) { + Error(std::move(Err)); + return true; + } + + RecordData Record; + StringRef Blob; + Expected MaybeCode = Cursor.ReadCode(); + if (!MaybeCode) { + Error(MaybeCode.takeError()); + return true; + } + unsigned Code = MaybeCode.get(); + + Expected MaybeRecCode = Cursor.readRecord(Code, Record, &Blob); + if (!MaybeRecCode) { + Error(MaybeRecCode.takeError()); + return true; + } + unsigned RecCode = MaybeRecCode.get(); + if (RecCode != DECL_SPECIALIZATIONS) { + Error("Expected decl specs block"); + return true; + } + + auto *Data = (const unsigned char *)Blob.data(); + AddSpecializations(D, Data, M); return false; } @@ -3254,7 +3331,20 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F, unsigned Idx = 0; serialization::DeclID ID = ReadDeclID(F, Record, Idx); auto *Data = (const unsigned char*)Blob.data(); - PendingVisibleUpdates[ID].push_back(PendingVisibleUpdate{&F, Data}); + PendingVisibleUpdates[ID].push_back(UpdateData{&F, Data}); + // If we've already loaded the decl, perform the updates when we finish + // loading this block. + if (Decl *D = GetExistingDecl(ID)) + PendingUpdateRecords.push_back( + PendingUpdateRecord(ID, D, /*JustLoaded=*/false)); + break; + } + + case CXX_ADDED_TEMPLATE_SPECIALIZATION: { + unsigned Idx = 0; + serialization::DeclID ID = ReadDeclID(F, Record, Idx); + auto *Data = (const unsigned char *)Blob.data(); + PendingSpecializationsUpdates[ID].push_back(UpdateData{&F, Data}); // If we've already loaded the decl, perform the updates when we finish // loading this block. if (Decl *D = GetExistingDecl(ID)) @@ -7682,6 +7772,55 @@ Stmt *ASTReader::GetExternalDeclStmt(uint64_t Offset) { return ReadStmtFromStream(*Loc.F); } +void ASTReader::LoadExternalSpecializations(const Decl *D, bool OnlyPartial) { + assert(D); + + auto It = SpecializationsLookups.find(D); + if (It == SpecializationsLookups.end()) + return; + + // Get Decl may violate the iterator from SpecializationsLookups + llvm::SmallVector Infos + = It->second.Table.findAll(); + + // Since we've loaded all the specializations, we can erase it from + // the lookup table. + if (!OnlyPartial) + SpecializationsLookups.erase(It); + + Deserializing LookupResults(this); + for (auto &Info : Infos) + if (!OnlyPartial || Info.IsPartial) + GetDecl(Info.ID); +} + +bool ASTReader::LoadExternalSpecializations( + const Decl *D, ArrayRef TemplateArgs) { + assert(D); + + auto It = SpecializationsLookups.find(D); + if (It == SpecializationsLookups.end()) + return false; + + Deserializing LookupResults(this); + auto HashValue = TemplateArgumentList::ComputeStableHash(TemplateArgs); + + // Get Decl may violate the iterator from SpecializationsLookups + llvm::SmallVector Infos + = It->second.Table.find(HashValue); + + bool AnyNewDeclsFound = false; + for (auto &Info : Infos) { + if (GetExistingDecl(Info.ID)) + continue; + + AnyNewDeclsFound = true; + GetDecl(Info.ID); + } + + return AnyNewDeclsFound; +} + void ASTReader::FindExternalLexicalDecls( const DeclContext *DC, llvm::function_ref IsKindWeWant, SmallVectorImpl &Decls) { @@ -7856,6 +7995,13 @@ ASTReader::getLoadedLookupTables(DeclContext *Primary) const { return I == Lookups.end() ? nullptr : &I->second; } +serialization::reader::LazySpecializationInfoLookupTable * +ASTReader::getLoadedSpecializationsLookupTables(const Decl *D) { + assert(D->isCanonicalDecl()); + auto I = SpecializationsLookups.find(D); + return I == SpecializationsLookups.end() ? nullptr : &I->second; +} + /// Under non-PCH compilation the consumer receives the objc methods /// before receiving the implementation, and codegen depends on this. /// We simulate this by deserializing and passing to consumer the methods of the diff --git a/interpreter/llvm-project/clang/lib/Serialization/ASTReaderDecl.cpp b/interpreter/llvm-project/clang/lib/Serialization/ASTReaderDecl.cpp index 58c989543bf03..2ce507a2daa33 100644 --- a/interpreter/llvm-project/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/interpreter/llvm-project/clang/lib/Serialization/ASTReaderDecl.cpp @@ -86,8 +86,6 @@ namespace clang { const SourceLocation ThisDeclLoc; using RecordData = ASTReader::RecordData; - using LazySpecializationInfo - = RedeclarableTemplateDecl::LazySpecializationInfo; TypeID DeferredTypeID = 0; unsigned AnonymousDeclNumber; @@ -134,18 +132,6 @@ namespace clang { return Record.readString(); } - LazySpecializationInfo ReadLazySpecializationInfo() { - DeclID ID = readDeclID(); - unsigned Hash = Record.readInt(); - bool IsPartial = Record.readInt(); - return LazySpecializationInfo(ID, Hash, IsPartial); - } - - void readDeclIDList(SmallVectorImpl &IDs) { - for (unsigned I = 0, Size = Record.readInt(); I != Size; ++I) - IDs.push_back(ReadLazySpecializationInfo()); - } - Decl *readDecl() { return Record.readDecl(); } @@ -269,29 +255,6 @@ namespace clang { : Reader(Reader), Record(Record), Loc(Loc), ThisDeclID(thisDeclID), ThisDeclLoc(ThisDeclLoc) {} - template static - void AddLazySpecializations(T *D, - SmallVectorImpl& IDs) { - if (IDs.empty()) - return; - - // FIXME: We should avoid this pattern of getting the ASTContext. - ASTContext &C = D->getASTContext(); - - auto *&LazySpecializations = D->getCommonPtr()->LazySpecializations; - - if (auto &Old = LazySpecializations) { - IDs.insert(IDs.end(), Old + 1, Old + 1 + Old[0].DeclID); - llvm::sort(IDs); - IDs.erase(std::unique(IDs.begin(), IDs.end()), IDs.end()); - } - auto *Result = new (C) LazySpecializationInfo[1 + IDs.size()]; - *Result = IDs.size(); - std::copy(IDs.begin(), IDs.end(), Result + 1); - - LazySpecializations = Result; - } - template static Decl *getMostRecentDeclImpl(Redeclarable *D); static Decl *getMostRecentDeclImpl(...); @@ -323,7 +286,7 @@ namespace clang { void ReadFunctionDefinition(FunctionDecl *FD); void Visit(Decl *D); - void UpdateDecl(Decl *D, llvm::SmallVectorImpl&); + void UpdateDecl(Decl *D); static void setNextObjCCategory(ObjCCategoryDecl *Cat, ObjCCategoryDecl *Next) { @@ -430,6 +393,9 @@ namespace clang { std::pair VisitDeclContext(DeclContext *DC); + void ReadSpecializations(ModuleFile &M, Decl *D, + llvm::BitstreamCursor &Cursor); + template RedeclarableResult VisitRedeclarable(Redeclarable *D); @@ -2308,6 +2274,14 @@ void ASTDeclReader::VisitImplicitConceptSpecializationDecl( void ASTDeclReader::VisitRequiresExprBodyDecl(RequiresExprBodyDecl *D) { } +void ASTDeclReader::ReadSpecializations(ModuleFile &M, Decl *D, + llvm::BitstreamCursor &DeclsCursor) { + uint64_t Offset = ReadLocalOffset(); + bool Failed = Reader.ReadSpecializations(M, DeclsCursor, Offset, D); + (void)Failed; + assert(!Failed); +} + ASTDeclReader::RedeclarableResult ASTDeclReader::VisitRedeclarableTemplateDecl(RedeclarableTemplateDecl *D) { RedeclarableResult Redecl = VisitRedeclarable(D); @@ -2346,9 +2320,7 @@ void ASTDeclReader::VisitClassTemplateDecl(ClassTemplateDecl *D) { if (ThisDeclID == Redecl.getFirstID()) { // This ClassTemplateDecl owns a CommonPtr; read it to keep track of all of // the specializations. - SmallVector SpecIDs; - readDeclIDList(SpecIDs); - ASTDeclReader::AddLazySpecializations(D, SpecIDs); + ReadSpecializations(*Loc.F, D, Loc.F->DeclsCursor); } if (D->getTemplatedDecl()->TemplateOrInstantiation) { @@ -2374,9 +2346,7 @@ void ASTDeclReader::VisitVarTemplateDecl(VarTemplateDecl *D) { if (ThisDeclID == Redecl.getFirstID()) { // This VarTemplateDecl owns a CommonPtr; read it to keep track of all of // the specializations. - SmallVector SpecIDs; - readDeclIDList(SpecIDs); - ASTDeclReader::AddLazySpecializations(D, SpecIDs); + ReadSpecializations(*Loc.F, D, Loc.F->DeclsCursor); } } @@ -2484,9 +2454,7 @@ void ASTDeclReader::VisitFunctionTemplateDecl(FunctionTemplateDecl *D) { if (ThisDeclID == Redecl.getFirstID()) { // This FunctionTemplateDecl owns a CommonPtr; read it. - SmallVector SpecIDs; - readDeclIDList(SpecIDs); - ASTDeclReader::AddLazySpecializations(D, SpecIDs); + ReadSpecializations(*Loc.F, D, Loc.F->DeclsCursor); } } @@ -3684,6 +3652,7 @@ Decl *ASTReader::ReadDeclRecord(DeclID ID) { switch ((DeclCode)MaybeDeclCode.get()) { case DECL_CONTEXT_LEXICAL: case DECL_CONTEXT_VISIBLE: + case DECL_SPECIALIZATIONS: llvm_unreachable("Record cannot be de-serialized with readDeclRecord"); case DECL_TYPEDEF: D = TypedefDecl::CreateDeserialized(Context, ID); @@ -4056,10 +4025,6 @@ void ASTReader::loadDeclUpdateRecords(PendingUpdateRecord &Record) { ProcessingUpdatesRAIIObj ProcessingUpdates(*this); DeclUpdateOffsetsMap::iterator UpdI = DeclUpdateOffsets.find(ID); - using LazySpecializationInfo - = RedeclarableTemplateDecl::LazySpecializationInfo; - llvm::SmallVector PendingLazySpecializationIDs; - if (UpdI != DeclUpdateOffsets.end()) { auto UpdateOffsets = std::move(UpdI->second); DeclUpdateOffsets.erase(UpdI); @@ -4097,7 +4062,7 @@ void ASTReader::loadDeclUpdateRecords(PendingUpdateRecord &Record) { ASTDeclReader Reader(*this, Record, RecordLocation(F, Offset), ID, SourceLocation()); - Reader.UpdateDecl(D, PendingLazySpecializationIDs); + Reader.UpdateDecl(D); // We might have made this declaration interesting. If so, remember that // we need to hand it off to the consumer. @@ -4109,17 +4074,6 @@ void ASTReader::loadDeclUpdateRecords(PendingUpdateRecord &Record) { } } } - // Add the lazy specializations to the template. - assert((PendingLazySpecializationIDs.empty() || isa(D) || - isa(D)) && - "Must not have pending specializations"); - if (auto *CTD = dyn_cast(D)) - ASTDeclReader::AddLazySpecializations(CTD, PendingLazySpecializationIDs); - else if (auto *FTD = dyn_cast(D)) - ASTDeclReader::AddLazySpecializations(FTD, PendingLazySpecializationIDs); - else if (auto *VTD = dyn_cast(D)) - ASTDeclReader::AddLazySpecializations(VTD, PendingLazySpecializationIDs); - PendingLazySpecializationIDs.clear(); // Load the pending visible updates for this decl context, if it has any. auto I = PendingVisibleUpdates.find(ID); @@ -4134,6 +4088,16 @@ void ASTReader::loadDeclUpdateRecords(PendingUpdateRecord &Record) { reader::ASTDeclContextNameLookupTrait(*this, *Update.Mod)); DC->setHasExternalVisibleStorage(true); } + + // Load the pending specializations update for this decl, if it has any. + if (auto I = PendingSpecializationsUpdates.find(ID); + I != PendingSpecializationsUpdates.end()) { + auto SpecializationUpdates = std::move(I->second); + PendingSpecializationsUpdates.erase(I); + + for (const auto &Update : SpecializationUpdates) + AddSpecializations(D, Update.Data, *Update.Mod); + } } void ASTReader::loadPendingDeclChain(Decl *FirstLocal, uint64_t LocalOffset) { @@ -4328,8 +4292,7 @@ static void forAllLaterRedecls(DeclT *D, Fn F) { } } -void ASTDeclReader::UpdateDecl(Decl *D, - SmallVectorImpl &PendingLazySpecializationIDs) { +void ASTDeclReader::UpdateDecl(Decl *D) { while (Record.getIdx() < Record.size()) { switch ((DeclUpdateKind)Record.readInt()) { case UPD_CXX_ADDED_IMPLICIT_MEMBER: { @@ -4344,11 +4307,6 @@ void ASTDeclReader::UpdateDecl(Decl *D, break; } - case UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION: - // It will be added to the template's lazy specialization set. - PendingLazySpecializationIDs.push_back(ReadLazySpecializationInfo()); - break; - case UPD_CXX_ADDED_ANONYMOUS_NAMESPACE: { auto *Anon = readDeclAs(); diff --git a/interpreter/llvm-project/clang/lib/Serialization/ASTReaderInternals.h b/interpreter/llvm-project/clang/lib/Serialization/ASTReaderInternals.h index 5f55185071ed9..f4e255978f3f8 100644 --- a/interpreter/llvm-project/clang/lib/Serialization/ASTReaderInternals.h +++ b/interpreter/llvm-project/clang/lib/Serialization/ASTReaderInternals.h @@ -119,6 +119,102 @@ struct DeclContextLookupTable { MultiOnDiskHashTable Table; }; +struct LazySpecializationInfo { + // The Decl ID for the specialization. + DeclID ID; + // Whether or not this specialization is partial. + bool IsPartial; + + bool operator==(const LazySpecializationInfo &Other) { + assert(ID != Other.ID || IsPartial == Other.IsPartial); + return ID == Other.ID; + } + + // Records the size record in OnDiskHashTable. + // sizeof() may return 8 due to align requirements. + static constexpr unsigned Length = 5; +}; + +/// Class that performs lookup to specialized decls. +class LazySpecializationInfoLookupTrait { + ASTReader &Reader; + ModuleFile &F; + +public: + // Maximum number of lookup tables we allow before condensing the tables. + static const int MaxTables = 4; + + /// The lookup result is a list of global declaration IDs. + using data_type = SmallVector; + + struct data_type_builder { + data_type &Data; + llvm::DenseSet Found; + + data_type_builder(data_type &D) : Data(D) {} + + void insert(LazySpecializationInfo Info) { + // Just use a linear scan unless we have more than a few IDs. + if (Found.empty() && !Data.empty()) { + if (Data.size() <= 4) { + for (auto I : Found) + if (I == Info) + return; + Data.push_back(Info); + return; + } + + // Switch to tracking found IDs in the set. + Found.insert(Data.begin(), Data.end()); + } + + if (Found.insert(Info).second) + Data.push_back(Info); + } + }; + using hash_value_type = unsigned; + using offset_type = unsigned; + using file_type = ModuleFile *; + + using external_key_type = unsigned; + using internal_key_type = unsigned; + + explicit LazySpecializationInfoLookupTrait(ASTReader &Reader, ModuleFile &F) + : Reader(Reader), F(F) {} + + static bool EqualKey(const internal_key_type &a, const internal_key_type &b) { + return a == b; + } + + static hash_value_type ComputeHash(const internal_key_type &Key) { + return Key; + } + + static internal_key_type GetInternalKey(const external_key_type &Name) { + return Name; + } + + static std::pair + ReadKeyDataLength(const unsigned char *&d); + + internal_key_type ReadKey(const unsigned char *d, unsigned); + + void ReadDataInto(internal_key_type, const unsigned char *d, unsigned DataLen, + data_type_builder &Val); + + static void MergeDataInto(const data_type &From, data_type_builder &To) { + To.Data.reserve(To.Data.size() + From.size()); + for (LazySpecializationInfo Info : From) + To.insert(Info); + } + + file_type ReadFileRef(const unsigned char *&d); +}; + +struct LazySpecializationInfoLookupTable { + MultiOnDiskHashTable Table; +}; + /// Base class for the trait describing the on-disk hash table for the /// identifiers in an AST file. /// @@ -288,4 +384,29 @@ using HeaderFileInfoLookupTable = } // namespace clang +namespace llvm { +// ID is unique in LazySpecializationInfo, it is redundant to calculate IsPartial. +template <> struct DenseMapInfo { + using LazySpecializationInfo = clang::serialization::reader::LazySpecializationInfo; + using Wrapped = DenseMapInfo; + + static inline LazySpecializationInfo getEmptyKey() { + return {Wrapped::getEmptyKey(), false}; + } + + static inline LazySpecializationInfo getTombstoneKey() { + return {Wrapped::getTombstoneKey(), false}; + } + + static unsigned getHashValue(const LazySpecializationInfo &Key) { + return Wrapped::getHashValue(Key.ID); + } + + static bool isEqual(const LazySpecializationInfo &LHS, + const LazySpecializationInfo &RHS) { + return LHS.ID == RHS.ID; + } +}; +} // end namespace llvm + #endif // LLVM_CLANG_LIB_SERIALIZATION_ASTREADERINTERNALS_H diff --git a/interpreter/llvm-project/clang/lib/Serialization/ASTWriter.cpp b/interpreter/llvm-project/clang/lib/Serialization/ASTWriter.cpp index 5c71e51c7daf6..80a0fc12985a2 100644 --- a/interpreter/llvm-project/clang/lib/Serialization/ASTWriter.cpp +++ b/interpreter/llvm-project/clang/lib/Serialization/ASTWriter.cpp @@ -3849,6 +3849,152 @@ class ASTDeclContextNameLookupTrait { } // namespace +namespace { +class LazySpecializationInfoLookupTrait { + ASTWriter &Writer; + llvm::SmallVector Specs; + +public: + using key_type = unsigned; + using key_type_ref = key_type; + + /// A start and end index into Specs, representing a sequence of decls. + using data_type = std::pair; + using data_type_ref = const data_type &; + + using hash_value_type = unsigned; + using offset_type = unsigned; + + explicit LazySpecializationInfoLookupTrait(ASTWriter &Writer) : Writer(Writer) {} + + template data_type getData(Col &&C) { + unsigned Start = Specs.size(); + for (auto *D : C) { + bool IsPartial = isa(D); + Specs.push_back({Writer.GetDeclRef(getDeclForLocalLookup( + Writer.getLangOpts(), const_cast(D))), IsPartial}); + } + return std::make_pair(Start, Specs.size()); + } + + data_type + ImportData(const reader::LazySpecializationInfoLookupTrait::data_type &FromReader) { + unsigned Start = Specs.size(); + for (auto ID : FromReader) + Specs.push_back(ID); + return std::make_pair(Start, Specs.size()); + } + + static bool EqualKey(key_type_ref a, key_type_ref b) { return a == b; } + + hash_value_type ComputeHash(key_type Name) { return Name; } + + void EmitFileRef(raw_ostream &Out, ModuleFile *F) const { + assert(Writer.hasChain() && + "have reference to loaded module file but no chain?"); + + using namespace llvm::support; + endian::write(Out, Writer.getChain()->getModuleFileID(F), + endianness::little); + } + + std::pair EmitKeyDataLength(raw_ostream &Out, + key_type HashValue, + data_type_ref Lookup) { + // 4 bytes for each slot. + unsigned KeyLen = 4; + unsigned DataLen = serialization::reader::LazySpecializationInfo::Length * (Lookup.second - Lookup.first); + + return emitULEBKeyDataLength(KeyLen, DataLen, Out); + } + + void EmitKey(raw_ostream &Out, key_type HashValue, unsigned) { + using namespace llvm::support; + + endian::Writer LE(Out, endianness::little); + LE.write(HashValue); + } + + void EmitData(raw_ostream &Out, key_type_ref, data_type Lookup, + unsigned DataLen) { + using namespace llvm::support; + + endian::Writer LE(Out, endianness::little); + uint64_t Start = Out.tell(); + (void)Start; + for (unsigned I = Lookup.first, N = Lookup.second; I != N; ++I) { + LE.write(Specs[I].ID); + LE.write(Specs[I].IsPartial); + } + assert(Out.tell() - Start == DataLen && "Data length is wrong"); + } +}; + +unsigned CalculateODRHashForSpecs(const Decl *Spec) { + ArrayRef Args; + if (auto *CTSD = dyn_cast(Spec)) + Args = CTSD->getTemplateArgs().asArray(); + else if (auto *VTSD = dyn_cast(Spec)) + Args = VTSD->getTemplateArgs().asArray(); + else if (auto *FD = dyn_cast(Spec)) + Args = FD->getTemplateSpecializationArgs()->asArray(); + else + llvm_unreachable("New Specialization Kind?"); + + return TemplateArgumentList::ComputeStableHash(Args); +} +} // namespace + +void ASTWriter::GenerateSpecializationInfoLookupTable( + const NamedDecl *D, + llvm::SmallVectorImpl &Specializations, + llvm::SmallVectorImpl &LookupTable) { + assert(D->isFirstDecl()); + + // Create the on-disk hash table representation. + MultiOnDiskHashTableGenerator + Generator; + LazySpecializationInfoLookupTrait Trait(*this); + + llvm::DenseMap> + SpecializationMaps; + + for (auto *Specialization : Specializations) { + unsigned HashedValue = CalculateODRHashForSpecs(Specialization); + + auto Iter = SpecializationMaps.find(HashedValue); + if (Iter == SpecializationMaps.end()) + Iter = SpecializationMaps + .try_emplace(HashedValue, + llvm::SmallVector()) + .first; + + Iter->second.push_back(cast(Specialization)); + } + + for (auto Iter : SpecializationMaps) + Generator.insert(Iter.first, Trait.getData(Iter.second), Trait); + + auto *Lookups = + Chain ? Chain->getLoadedSpecializationsLookupTables(D) : nullptr; + Generator.emit(LookupTable, Trait, Lookups ? &Lookups->Table : nullptr); +} + +uint64_t ASTWriter::WriteSpecializationInfoLookupTable( + const NamedDecl *D, + llvm::SmallVectorImpl &Specializations) { + + llvm::SmallString<4096> LookupTable; + GenerateSpecializationInfoLookupTable(D, Specializations, LookupTable); + + uint64_t Offset = Stream.GetCurrentBitNo(); + RecordData::value_type Record[] = {DECL_SPECIALIZATIONS}; + Stream.EmitRecordWithBlob(DeclSpecializationsAbbrev, Record, LookupTable); + + return Offset; +} + bool ASTWriter::isLookupResultExternal(StoredDeclsList &Result, DeclContext *DC) { return Result.hasExternalDecls() && @@ -4984,7 +5130,7 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot, // Keep writing types, declarations, and declaration update records // until we've emitted all of them. - Stream.EnterSubblock(DECLTYPES_BLOCK_ID, /*bits for abbreviations*/5); + Stream.EnterSubblock(DECLTYPES_BLOCK_ID, /*bits for abbreviations*/6); DeclTypesBlockStartOffset = Stream.GetCurrentBitNo(); WriteTypeAbbrevs(); WriteDeclAbbrevs(); @@ -5007,6 +5153,10 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot, WriteTypeDeclOffsets(); if (!DeclUpdatesOffsetsRecord.empty()) Stream.EmitRecord(DECL_UPDATE_OFFSETS, DeclUpdatesOffsetsRecord); + + if (!SpecializationsUpdates.empty()) + WriteSpecializationsUpdates(); + WriteFileDeclIDsMap(); WriteSourceManagerBlock(Context.getSourceManager(), PP); WriteComments(); @@ -5159,6 +5309,27 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot, return writeUnhashedControlBlock(PP, Context); } +void ASTWriter::WriteSpecializationsUpdates() { + auto Abv = std::make_shared(); + Abv->Add(llvm::BitCodeAbbrevOp(CXX_ADDED_TEMPLATE_SPECIALIZATION)); + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::VBR, 6)); + Abv->Add(llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)); + auto UpdateSpecializationAbbrev = Stream.EmitAbbrev(std::move(Abv)); + + for (auto &SpecializationUpdate : SpecializationsUpdates) { + const NamedDecl *D = SpecializationUpdate.first; + + llvm::SmallString<4096> LookupTable; + GenerateSpecializationInfoLookupTable(D, SpecializationUpdate.second, + LookupTable); + + // Write the lookup table + RecordData::value_type Record[] = {CXX_ADDED_TEMPLATE_SPECIALIZATION, + getDeclID(D)}; + Stream.EmitRecordWithBlob(UpdateSpecializationAbbrev, Record, LookupTable); + } +} + void ASTWriter::WriteDeclUpdatesBlocks(RecordDataImpl &OffsetsRecord) { if (DeclUpdates.empty()) return; @@ -5188,25 +5359,6 @@ void ASTWriter::WriteDeclUpdatesBlocks(RecordDataImpl &OffsetsRecord) { assert(Update.getDecl() && "no decl to add?"); Record.push_back(GetDeclRef(Update.getDecl())); break; - case UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION: { - const Decl *Spec = Update.getDecl(); - assert(Spec && "no decl to add?"); - Record.push_back(GetDeclRef(Spec)); - ArrayRef Args; - if (auto *CTSD = dyn_cast(Spec)) - Args = CTSD->getTemplateArgs().asArray(); - else if (auto *VTSD = dyn_cast(Spec)) - Args = VTSD->getTemplateArgs().asArray(); - else if (auto *FD = dyn_cast(Spec)) - Args = FD->getTemplateSpecializationArgs()->asArray(); - assert(Args.size()); - Record.push_back(TemplateArgumentList::ComputeODRHash(Args)); - bool IsPartialSpecialization - = isa(Spec) || - isa(Spec); - Record.push_back(IsPartialSpecialization); - break; - } case UPD_CXX_ADDED_FUNCTION_DEFINITION: break; diff --git a/interpreter/llvm-project/clang/lib/Serialization/ASTWriterDecl.cpp b/interpreter/llvm-project/clang/lib/Serialization/ASTWriterDecl.cpp index 5d48ecffa2661..aa303a9e69592 100644 --- a/interpreter/llvm-project/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/interpreter/llvm-project/clang/lib/Serialization/ASTWriterDecl.cpp @@ -179,8 +179,9 @@ namespace clang { /// Collect the first declaration from each module file that provides a /// declaration of D. - void CollectFirstDeclFromEachModule(const Decl *D, bool IncludeLocal, - llvm::MapVector &Firsts) { + void CollectFirstDeclFromEachModule( + const Decl *D, bool IncludeLocal, + llvm::MapVector &Firsts) { // FIXME: We can skip entries that we know are implied by others. for (const Decl *R = D->getMostRecentDecl(); R; R = R->getPreviousDecl()) { @@ -195,7 +196,7 @@ namespace clang { /// provides a declaration of D. The intent is to provide a sufficient /// set such that reloading this set will load all current redeclarations. void AddFirstDeclFromEachModule(const Decl *D, bool IncludeLocal) { - llvm::MapVector Firsts; + llvm::MapVector Firsts; CollectFirstDeclFromEachModule(D, IncludeLocal, Firsts); for (const auto &F : Firsts) @@ -207,29 +208,15 @@ namespace clang { /// ODRHash of the template arguments of D which should provide enough /// information to load D only if the template instantiator needs it. void AddFirstSpecializationDeclFromEachModule(const Decl *D, - bool IncludeLocal) { - assert(isa(D) || - isa(D) || isa(D) && - "Must not be called with other decls"); - llvm::MapVector Firsts; - CollectFirstDeclFromEachModule(D, IncludeLocal, Firsts); + llvm::SmallVectorImpl &SpecsInMap) { + assert((isa(D) || + isa(D) || + isa(D)) && "Must not be called with other decls"); + llvm::MapVector Firsts; + CollectFirstDeclFromEachModule(D, /*IncludeLocal*/ true, Firsts); - for (const auto &F : Firsts) { - Record.AddDeclRef(F.second); - ArrayRef Args; - if (auto *CTSD = dyn_cast(D)) - Args = CTSD->getTemplateArgs().asArray(); - else if (auto *VTSD = dyn_cast(D)) - Args = VTSD->getTemplateArgs().asArray(); - else if (auto *FD = dyn_cast(D)) - Args = FD->getTemplateSpecializationArgs()->asArray(); - assert(Args.size()); - Record.push_back(TemplateArgumentList::ComputeODRHash(Args)); - bool IsPartialSpecialization - = isa(D) || - isa(D); - Record.push_back(IsPartialSpecialization); - } + for (const auto &F : Firsts) + SpecsInMap.push_back(F.second); } /// Get the specialization decl from an entry in the specialization list. @@ -256,22 +243,12 @@ namespace clang { // If we have any lazy specializations, and the external AST source is // our chained AST reader, we can just write out the DeclIDs. Otherwise, // we need to resolve them to actual declarations. - if (Writer.Chain != Writer.Context->getExternalSource() && - Common->LazySpecializations) { + if (Writer.Chain != Writer.Context->getExternalSource() && Writer.Chain && + Writer.Chain->getLoadedSpecializationsLookupTables(D)) { D->LoadLazySpecializations(); - assert(!Common->LazySpecializations); + assert(!Writer.Chain->getLoadedSpecializationsLookupTables(D)); } - using LazySpecializationInfo - = RedeclarableTemplateDecl::LazySpecializationInfo; - ArrayRef LazySpecializations; - if (auto *LS = Common->LazySpecializations) - LazySpecializations = llvm::ArrayRef(LS + 1, LS[0].DeclID); - - // Add a slot to the record for the number of specializations. - unsigned I = Record.size(); - Record.push_back(0); - // AddFirstDeclFromEachModule might trigger deserialization, invalidating // *Specializations iterators. llvm::SmallVector Specs; @@ -280,22 +257,15 @@ namespace clang { for (auto &Entry : getPartialSpecializations(Common)) Specs.push_back(getSpecializationDecl(Entry)); + llvm::SmallVector SpecsInOnDiskMap = Specs; + for (auto *D : Specs) { assert(D->isCanonicalDecl() && "non-canonical decl in set"); - AddFirstSpecializationDeclFromEachModule(D, /*IncludeLocal*/true); - } - for (auto &SpecInfo : LazySpecializations) { - Record.push_back(SpecInfo.DeclID); - Record.push_back(SpecInfo.ODRHash); - Record.push_back(SpecInfo.IsPartial); + AddFirstSpecializationDeclFromEachModule(D, SpecsInOnDiskMap); } - // Update the size entry we added earlier. We linerized the - // LazySpecializationInfo members and we need to adjust the size as we - // will read them always together. - assert ((Record.size() - I - 1) % 3 == 0 - && "Must be divisible by LazySpecializationInfo count!"); - Record[I] = (Record.size() - I - 1) / 3; + Record.AddOffset( + Writer.WriteSpecializationInfoLookupTable(D, SpecsInOnDiskMap)); } /// Ensure that this template specialization is associated with the specified @@ -316,8 +286,8 @@ namespace clang { if (Writer.getFirstLocalDecl(Specialization) != Specialization) return; - Writer.DeclUpdates[Template].push_back(ASTWriter::DeclUpdate( - UPD_CXX_ADDED_TEMPLATE_SPECIALIZATION, Specialization)); + Writer.SpecializationsUpdates[cast(Template)].push_back( + cast(Specialization)); } }; } @@ -2463,6 +2433,11 @@ void ASTWriter::WriteDeclAbbrevs() { Abv->Add(BitCodeAbbrevOp(serialization::DECL_CONTEXT_VISIBLE)); Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); DeclContextVisibleLookupAbbrev = Stream.EmitAbbrev(std::move(Abv)); + + Abv = std::make_shared(); + Abv->Add(BitCodeAbbrevOp(serialization::DECL_SPECIALIZATIONS)); + Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); + DeclSpecializationsAbbrev = Stream.EmitAbbrev(std::move(Abv)); } /// isRequiredDecl - Check if this is a "required" Decl, which must be seen by diff --git a/interpreter/llvm-project/llvm-project.tag b/interpreter/llvm-project/llvm-project.tag index 4e2fa77c45814..924d14f48f654 100644 --- a/interpreter/llvm-project/llvm-project.tag +++ b/interpreter/llvm-project/llvm-project.tag @@ -1 +1 @@ -ROOT-llvm16-20240129-01 +ROOT-llvm16-20240223-01 diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 34814ab66e619..c7ac7c28bef9c 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -11,9 +11,6 @@ add_subdirectory(xml) if(xml) add_subdirectory(xmlparser) endif() -if(gfal) - add_subdirectory(gfal) -endif() if(dcache) add_subdirectory(dcache) endif() diff --git a/io/gfal/CMakeLists.txt b/io/gfal/CMakeLists.txt deleted file mode 100644 index b60e7f8ae29a7..0000000000000 --- a/io/gfal/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. -# All rights reserved. -# -# For the licensing terms see $ROOTSYS/LICENSE. -# For the list of contributors see $ROOTSYS/README/CREDITS. - -############################################################################ -# CMakeLists.txt file for building ROOT io/gfal package -############################################################################ - -add_definitions(-D_FILE_OFFSET_BITS=64) - -ROOT_STANDARD_LIBRARY_PACKAGE(GFAL - HEADERS TGFALFile.h - SOURCES src/TGFALFile.cxx - LIBRARIES ${GFAL_LIBRARIES} - DEPENDENCIES Core Net RIO) - -target_include_directories(GFAL PRIVATE ${GFAL_INCLUDE_DIRS}) diff --git a/io/gfal/inc/LinkDef.h b/io/gfal/inc/LinkDef.h deleted file mode 100644 index 8feee18ece41d..0000000000000 --- a/io/gfal/inc/LinkDef.h +++ /dev/null @@ -1,20 +0,0 @@ -/* @(#)root/gfal:$Id$ */ - -/************************************************************************* - * Copyright (C) 1995-2005, Rene Brun and Fons Rademakers. * - * All rights reserved. * - * * - * For the licensing terms see $ROOTSYS/LICENSE. * - * For the list of contributors see $ROOTSYS/README/CREDITS. * - *************************************************************************/ - -#ifdef __CINT__ - -#pragma link off all globals; -#pragma link off all classes; -#pragma link off all functions; - -#pragma link C++ class TGFALFile; -#pragma link C++ class TGFALSystem; - -#endif diff --git a/io/gfal/inc/TGFALFile.h b/io/gfal/inc/TGFALFile.h deleted file mode 100644 index 818763f358c78..0000000000000 --- a/io/gfal/inc/TGFALFile.h +++ /dev/null @@ -1,70 +0,0 @@ -// @(#)root/gfal:$Id$ -// Author: Fons Rademakers 8/12/2005 - -/************************************************************************* - * Copyright (C) 1995-2005, Rene Brun and Fons Rademakers. * - * All rights reserved. * - * * - * For the licensing terms see $ROOTSYS/LICENSE. * - * For the list of contributors see $ROOTSYS/README/CREDITS. * - *************************************************************************/ - -#ifndef ROOT_TGFALFile -#define ROOT_TGFALFile - -#include "TFile.h" -#include "TSystem.h" - - -class TGFALFile : public TFile { - -private: - Bool_t fStatCached; /// -#include "TROOT.h" -#include "TUrl.h" - -#include - -// GFAL2 doesn't use special names for 64 bit versions -#if defined(_GFAL2_API_) || defined(GFAL2_API_) || defined(_GFAL2_API) || defined(_GFAL2_API_H_) || defined(GFAL2_API_H_) || defined(_GFAL2_API_H) -#define gfal_lseek64 gfal_lseek -#define gfal_open64 gfal_open -#define gfal_readdir64 gfal_readdir -#define gfal_stat64 gfal_stat -#define dirent64 dirent -#define stat64 stat -#endif - -#include "TGFALFile.h" - -ClassImp(TGFALFile); -ClassImp(TGFALSystem); - -//////////////////////////////////////////////////////////////////////////////// -/// Create a GFAL file object. -/// -/// A GFAL file is the same as a TFile -/// except that it is being accessed via the underlaying Grid access -/// mechanism. The url argument must be of the form: gfal:/lfn/file.root -/// If the file specified in the URL does not exist, is not accessable -/// or can not be created the kZombie bit will be set in the TGFALFile -/// object. Use IsZombie() to see if the file is accessable. -/// For a description of the option and other arguments see the TFile ctor. -/// The preferred interface to this constructor is via TFile::Open(). - -TGFALFile::TGFALFile(const char *url, Option_t *option, const char *ftitle, - Int_t compress) - : TFile(url, "NET", ftitle, compress) -{ - fStatCached = kFALSE; - - fOption = option; - fOption.ToUpper(); - - if (fOption == "NEW") - fOption = "CREATE"; - - Bool_t create = (fOption == "CREATE") ? kTRUE : kFALSE; - Bool_t recreate = (fOption == "RECREATE") ? kTRUE : kFALSE; - Bool_t update = (fOption == "UPDATE") ? kTRUE : kFALSE; - Bool_t read = (fOption == "READ") ? kTRUE : kFALSE; - if (!create && !recreate && !update && !read) { - read = kTRUE; - fOption = "READ"; - } - - TString stmp; - char *fname; - if ((fname = gSystem->ExpandPathName(fUrl.GetFileAndOptions()))) { - stmp = fname; - delete [] fname; - fname = (char *)stmp.Data(); - } else { - Error("TGFALFile", "error expanding path %s", fUrl.GetFileAndOptions()); - goto zombie; - } - - if (recreate) { - if (::gfal_access(fname, kFileExists) == 0) - ::gfal_unlink(fname); - recreate = kFALSE; - create = kTRUE; - fOption = "CREATE"; - } - if (create && ::gfal_access(fname, kFileExists) == 0) { - Error("TGFALFile", "file %s already exists", fname); - goto zombie; - } - if (update) { - if (::gfal_access(fname, kFileExists) != 0) { - update = kFALSE; - create = kTRUE; - } - if (update && ::gfal_access(fname, kWritePermission) != 0) { - Error("TGFALFile", "no write permission, could not open file %s", fname); - goto zombie; - } - } - if (read) { -#ifdef GFAL_ACCESS_FIXED - if (::gfal_access(fname, kFileExists) != 0) { - Error("TGFALFile", "file %s does not exist", fname); - goto zombie; - } - if (::gfal_access(fname, kReadPermission) != 0) { - Error("TGFALFile", "no read permission, could not open file %s", fname); - goto zombie; - } -#endif - } - - // Connect to file system stream - fRealName = fname; - - if (create || update) { -#ifndef WIN32 - fD = SysOpen(fname, O_RDWR | O_CREAT, 0644); -#else - fD = SysOpen(fname, O_RDWR | O_CREAT | O_BINARY, S_IREAD | S_IWRITE); -#endif - if (fD == -1) { - SysError("TGFALFile", "file %s can not be opened", fname); - goto zombie; - } - fWritable = kTRUE; - } else { -#ifndef WIN32 - fD = SysOpen(fname, O_RDONLY, 0644); -#else - fD = SysOpen(fname, O_RDONLY | O_BINARY, S_IREAD | S_IWRITE); -#endif - if (fD == -1) { - SysError("TGFALFile", "file %s can not be opened for reading", fname); - goto zombie; - } - fWritable = kFALSE; - } - - Init(create); - - return; - -zombie: - // error in file opening occured, make this object a zombie - MakeZombie(); - gDirectory = gROOT; -} - -//////////////////////////////////////////////////////////////////////////////// -/// GFAL file dtor. Close and flush directory structure. - -TGFALFile::~TGFALFile() -{ - Close(); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Interface to system open. All arguments like in POSIX open. - -Int_t TGFALFile::SysOpen(const char *pathname, Int_t flags, UInt_t mode) -{ - Int_t ret = ::gfal_open64(pathname, flags, (Int_t) mode); - - return ret; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Interface to system close. All arguments like in POSIX close. - -Int_t TGFALFile::SysClose(Int_t fd) -{ - Int_t ret = ::gfal_close(fd); - - return ret; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Interface to system read. All arguments like in POSIX read. - -Int_t TGFALFile::SysRead(Int_t fd, void *buf, Int_t len) -{ - Int_t ret = ::gfal_read(fd, buf, len); - - return ret; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Interface to system write. All arguments like in POSIX write. - -Int_t TGFALFile::SysWrite(Int_t fd, const void *buf, Int_t len) -{ - Int_t ret = ::gfal_write(fd, buf, len); - - return ret; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Interface to system lseek. All arguments like in POSIX lseek -/// except that the offset and return value are Long_t to be able to -/// handle 64 bit file systems. - -Long64_t TGFALFile::SysSeek(Int_t fd, Long64_t offset, Int_t whence) -{ - Long64_t ret = ::gfal_lseek64(fd, offset, whence); - - return ret; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Interface to TSystem:GetPathInfo(). Generally implemented via -/// stat() or fstat(). - -Int_t TGFALFile::SysStat(Int_t /*fd*/, Long_t *id, Long64_t *size, Long_t *flags, - Long_t *modtime) -{ - struct stat64 &statbuf = fStatBuffer; - - if (fOption != "READ" || !fStatCached) { - // We are not in read mode, or the file status information is not yet - // in the cache. Update or read the status information with gfal_stat(). - - if (::gfal_stat64(fRealName, &statbuf) >= 0) - fStatCached = kTRUE; - } - - if (fStatCached) { - if (id) - *id = (statbuf.st_dev << 24) + statbuf.st_ino; - if (size) - *size = statbuf.st_size; - if (modtime) - *modtime = statbuf.st_mtime; - if (flags) { - *flags = 0; - if (statbuf.st_mode & ((S_IEXEC)|(S_IEXEC>>3)|(S_IEXEC>>6))) - *flags |= 1; - if ((statbuf.st_mode & S_IFMT) == S_IFDIR) - *flags |= 2; - if ((statbuf.st_mode & S_IFMT) != S_IFREG && - (statbuf.st_mode & S_IFMT) != S_IFDIR) - *flags |= 4; - } - return 0; - } - - return 1; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Read specified byte range from remote file via GFAL. -/// Returns kTRUE in case of error. - -Bool_t TGFALFile::ReadBuffer(char *buf, Int_t len) -{ - Int_t st; - if ((st = ReadBufferViaCache(buf, len))) { - if (st == 2) - return kTRUE; - return kFALSE; - } - - return TFile::ReadBuffer(buf, len); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Read specified byte range from remote file via GFAL. -/// Returns kTRUE in case of error. - -Bool_t TGFALFile::ReadBuffer(char *buf, Long64_t pos, Int_t len) -{ - SetOffset(pos); - Int_t st; - if ((st = ReadBufferViaCache(buf, len))) { - if (st == 2) - return kTRUE; - return kFALSE; - } - - return TFile::ReadBuffer(buf, pos, len); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Write specified byte range to remote file via GFAL. -/// Returns kTRUE in case of error. - -Bool_t TGFALFile::WriteBuffer(const char *buf, Int_t len) -{ - if (!IsOpen() || !fWritable) return kTRUE; - - Int_t st; - if ((st = WriteBufferViaCache(buf, len))) { - if (st == 2) - return kTRUE; - return kFALSE; - } - - return TFile::WriteBuffer(buf, len); -} - -/** -\class TGFALSystem -\ingroup IO - -Directory handler for GFAL. -*/ - -//////////////////////////////////////////////////////////////////////////////// -/// Create helper class that allows directory access via GFAL. - -TGFALSystem::TGFALSystem() : TSystem("-gfal", "GFAL Helper System") -{ - // name must start with '-' to bypass the TSystem singleton check - SetName("gfal"); - - fDirp = 0; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Make a directory via GFAL. - -Int_t TGFALSystem::MakeDirectory(const char *dir) -{ - TUrl url(dir); - - Int_t ret = ::gfal_mkdir(url.GetFileAndOptions(), 0755); - - return ret; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Open a directory via GFAL. Returns an opaque pointer to a dir -/// structure. Returns 0 in case of error. - -void *TGFALSystem::OpenDirectory(const char *dir) -{ - if (fDirp) { - Error("OpenDirectory", "invalid directory pointer (should never happen)"); - fDirp = 0; - } - - TUrl url(dir); - - struct stat64 finfo; - - if (::gfal_stat64(url.GetFileAndOptions(), &finfo) < 0) - return 0; - - if ((finfo.st_mode & S_IFMT) != S_IFDIR) - return 0; - - fDirp = (void*) ::gfal_opendir(url.GetFileAndOptions()); - - return fDirp; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Free directory via GFAL. - -void TGFALSystem::FreeDirectory(void *dirp) -{ - if (dirp != fDirp) { - Error("FreeDirectory", "invalid directory pointer (should never happen)"); - return; - } - - if (dirp) - ::gfal_closedir((DIR*)dirp); - - fDirp = 0; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Get directory entry via GFAL. Returns 0 in case no more entries. - -const char *TGFALSystem::GetDirEntry(void *dirp) -{ - if (dirp != fDirp) { - Error("GetDirEntry", "invalid directory pointer (should never happen)"); - return 0; - } - - struct dirent64 *dp; - - if (dirp) { - dp = ::gfal_readdir64((DIR*)dirp); - if (!dp) - return 0; - return dp->d_name; - } - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Get info about a file. Info is returned in the form of a FileStat_t -/// structure (see TSystem.h). -/// The function returns 0 in case of success and 1 if the file could -/// not be stat'ed. - -Int_t TGFALSystem::GetPathInfo(const char *path, FileStat_t &buf) -{ - TUrl url(path); - - struct stat64 sbuf; - - if (path && ::gfal_stat64(url.GetFileAndOptions(), &sbuf) >= 0) { - - buf.fDev = sbuf.st_dev; - buf.fIno = sbuf.st_ino; - buf.fMode = sbuf.st_mode; - buf.fUid = sbuf.st_uid; - buf.fGid = sbuf.st_gid; - buf.fSize = sbuf.st_size; - buf.fMtime = sbuf.st_mtime; - buf.fIsLink = kFALSE; - - return 0; - } - return 1; -} - -//////////////////////////////////////////////////////////////////////////////// -/// Returns FALSE if one can access a file using the specified access mode. -/// Mode is the same as for the Unix access(2) function. -/// Attention, bizarre convention of return value!! - -Bool_t TGFALSystem::AccessPathName(const char *path, EAccessMode mode) -{ - TUrl url(path); - - if (::gfal_access(url.GetFileAndOptions(), mode) == 0) - return kFALSE; - - return kTRUE; -} diff --git a/io/io/inc/TBufferFile.h b/io/io/inc/TBufferFile.h index 393318ce0e87c..57ca611463a8d 100644 --- a/io/io/inc/TBufferFile.h +++ b/io/io/inc/TBufferFile.h @@ -189,24 +189,24 @@ class TBufferFile : public TBufferIO { void WriteArrayFloat16(const Float_t *f, Int_t n, TStreamerElement *ele = nullptr) override; void WriteArrayDouble32(const Double_t *d, Int_t n, TStreamerElement *ele = nullptr) override; - void WriteFastArray(const Bool_t *b, Int_t n) override; - void WriteFastArray(const Char_t *c, Int_t n) override; - void WriteFastArrayString(const Char_t *c, Int_t n) override; - void WriteFastArray(const UChar_t *c, Int_t n) override; - void WriteFastArray(const Short_t *h, Int_t n) override; - void WriteFastArray(const UShort_t *h, Int_t n) override; - void WriteFastArray(const Int_t *i, Int_t n) override; - void WriteFastArray(const UInt_t *i, Int_t n) override; - void WriteFastArray(const Long_t *l, Int_t n) override; - void WriteFastArray(const ULong_t *l, Int_t n) override; - void WriteFastArray(const Long64_t *l, Int_t n) override; - void WriteFastArray(const ULong64_t *l, Int_t n) override; - void WriteFastArray(const Float_t *f, Int_t n) override; - void WriteFastArray(const Double_t *d, Int_t n) override; - void WriteFastArrayFloat16(const Float_t *f, Int_t n, TStreamerElement *ele = nullptr) override; - void WriteFastArrayDouble32(const Double_t *d, Int_t n, TStreamerElement *ele = nullptr) override; - void WriteFastArray(void *start, const TClass *cl, Int_t n=1, TMemberStreamer *s = nullptr) override; - Int_t WriteFastArray(void **startp, const TClass *cl, Int_t n=1, Bool_t isPreAlloc=kFALSE, TMemberStreamer *s = nullptr) override; + void WriteFastArray(const Bool_t *b, Long64_t n) override; + void WriteFastArray(const Char_t *c, Long64_t n) override; + void WriteFastArrayString(const Char_t *c, Long64_t n) override; + void WriteFastArray(const UChar_t *c, Long64_t n) override; + void WriteFastArray(const Short_t *h, Long64_t n) override; + void WriteFastArray(const UShort_t *h, Long64_t n) override; + void WriteFastArray(const Int_t *i, Long64_t n) override; + void WriteFastArray(const UInt_t *i, Long64_t n) override; + void WriteFastArray(const Long_t *l, Long64_t n) override; + void WriteFastArray(const ULong_t *l, Long64_t n) override; + void WriteFastArray(const Long64_t *l, Long64_t n) override; + void WriteFastArray(const ULong64_t *l, Long64_t n) override; + void WriteFastArray(const Float_t *f, Long64_t n) override; + void WriteFastArray(const Double_t *d, Long64_t n) override; + void WriteFastArrayFloat16(const Float_t *f, Long64_t n, TStreamerElement *ele = nullptr) override; + void WriteFastArrayDouble32(const Double_t *d, Long64_t n, TStreamerElement *ele = nullptr) override; + void WriteFastArray(void *start, const TClass *cl, Long64_t n=1, TMemberStreamer *s = nullptr) override; + Int_t WriteFastArray(void **startp, const TClass *cl, Long64_t n=1, Bool_t isPreAlloc=kFALSE, TMemberStreamer *s = nullptr) override; void StreamObject(void *obj, const std::type_info &typeinfo, const TClass* onFileClass = nullptr) override; void StreamObject(void *obj, const char *className, const TClass* onFileClass = nullptr) override; @@ -514,16 +514,16 @@ inline void TBufferFile::WriteArray(const ULong64_t *ll, Int_t n) { TBufferFile::WriteArray((const Long64_t *)ll, n); } //______________________________________________________________________________ -inline void TBufferFile::WriteFastArray(const UChar_t *c, Int_t n) +inline void TBufferFile::WriteFastArray(const UChar_t *c, Long64_t n) { TBufferFile::WriteFastArray((const Char_t *)c, n); } //______________________________________________________________________________ -inline void TBufferFile::WriteFastArray(const UShort_t *h, Int_t n) +inline void TBufferFile::WriteFastArray(const UShort_t *h, Long64_t n) { TBufferFile::WriteFastArray((const Short_t *)h, n); } //______________________________________________________________________________ -inline void TBufferFile::WriteFastArray(const UInt_t *i, Int_t n) +inline void TBufferFile::WriteFastArray(const UInt_t *i, Long64_t n) { TBufferFile::WriteFastArray((const Int_t *)i, n); } //______________________________________________________________________________ -inline void TBufferFile::WriteFastArray(const ULong64_t *ll, Int_t n) +inline void TBufferFile::WriteFastArray(const ULong64_t *ll, Long64_t n) { TBufferFile::WriteFastArray((const Long64_t *)ll, n); } #endif diff --git a/io/io/inc/TBufferJSON.h b/io/io/inc/TBufferJSON.h index 6bb16778fb59d..368959d94820e 100644 --- a/io/io/inc/TBufferJSON.h +++ b/io/io/inc/TBufferJSON.h @@ -176,22 +176,22 @@ class TBufferJSON final : public TBufferText { void WriteArray(const Float_t *f, Int_t n) final; void WriteArray(const Double_t *d, Int_t n) final; - void WriteFastArray(const Bool_t *b, Int_t n) final; - void WriteFastArray(const Char_t *c, Int_t n) final; - void WriteFastArrayString(const Char_t *c, Int_t n) final; - void WriteFastArray(const UChar_t *c, Int_t n) final; - void WriteFastArray(const Short_t *h, Int_t n) final; - void WriteFastArray(const UShort_t *h, Int_t n) final; - void WriteFastArray(const Int_t *i, Int_t n) final; - void WriteFastArray(const UInt_t *i, Int_t n) final; - void WriteFastArray(const Long_t *l, Int_t n) final; - void WriteFastArray(const ULong_t *l, Int_t n) final; - void WriteFastArray(const Long64_t *l, Int_t n) final; - void WriteFastArray(const ULong64_t *l, Int_t n) final; - void WriteFastArray(const Float_t *f, Int_t n) final; - void WriteFastArray(const Double_t *d, Int_t n) final; - void WriteFastArray(void *start, const TClass *cl, Int_t n = 1, TMemberStreamer *s = nullptr) final; - Int_t WriteFastArray(void **startp, const TClass *cl, Int_t n = 1, Bool_t isPreAlloc = kFALSE, + void WriteFastArray(const Bool_t *b, Long64_t n) final; + void WriteFastArray(const Char_t *c, Long64_t n) final; + void WriteFastArrayString(const Char_t *c, Long64_t n) final; + void WriteFastArray(const UChar_t *c, Long64_t n) final; + void WriteFastArray(const Short_t *h, Long64_t n) final; + void WriteFastArray(const UShort_t *h, Long64_t n) final; + void WriteFastArray(const Int_t *i, Long64_t n) final; + void WriteFastArray(const UInt_t *i, Long64_t n) final; + void WriteFastArray(const Long_t *l, Long64_t n) final; + void WriteFastArray(const ULong_t *l, Long64_t n) final; + void WriteFastArray(const Long64_t *l, Long64_t n) final; + void WriteFastArray(const ULong64_t *l, Long64_t n) final; + void WriteFastArray(const Float_t *f, Long64_t n) final; + void WriteFastArray(const Double_t *d, Long64_t n) final; + void WriteFastArray(void *start, const TClass *cl, Long64_t n = 1, TMemberStreamer *s = nullptr) final; + Int_t WriteFastArray(void **startp, const TClass *cl, Long64_t n = 1, Bool_t isPreAlloc = kFALSE, TMemberStreamer *s = nullptr) final; void StreamObject(void *obj, const TClass *cl, const TClass *onFileClass = nullptr) final; @@ -311,7 +311,7 @@ class TBufferJSON final : public TBufferText { void JsonReadFastArray(T *arr, Int_t arrsize, bool asstring = false); template - void JsonWriteFastArray(const T *arr, Int_t arrsize, const char *typname, + void JsonWriteFastArray(const T *arr, Long64_t arrsize, const char *typname, void (TBufferJSON::*method)(const T *, Int_t, const char *)); TString fOutBuffer; /// #include #include +#include +#include #include "TFile.h" #include "TBufferFile.h" @@ -196,7 +198,7 @@ void TBufferFile::ReadTString(TString &s) else nbig = nwh; - s.Clobber(nbig); + nbig = s.Clobber(nbig); // update length since Clobber clamps to MaxSize (if Fatal does not abort) char *data = s.GetPointer(); data[nbig] = 0; s.SetSize(nbig); @@ -314,10 +316,15 @@ void TBufferFile::WriteCharStar(char *s) //////////////////////////////////////////////////////////////////////////////// /// Set byte count at position cntpos in the buffer. Generate warning if /// count larger than kMaxMapCount. The count is excluded its own size. +/// \note If underflow or overflow, an Error ir raised (stricter checks in Debug mode) void TBufferFile::SetByteCount(UInt_t cntpos, Bool_t packInVersion) { - UInt_t cnt = UInt_t(fBufCur - fBuffer) - cntpos - sizeof(UInt_t); + assert( (sizeof(UInt_t) + cntpos) < static_cast(fBufCur - fBuffer) + && (fBufCur >= fBuffer) + && static_cast(fBufCur - fBuffer) <= std::numeric_limits::max() + && "Byte count position is after the end of the buffer"); + const UInt_t cnt = UInt_t(fBufCur - fBuffer) - cntpos - sizeof(UInt_t); char *buf = (char *)(fBuffer + cntpos); // if true, pack byte count in two consecutive shorts, so it can @@ -1937,10 +1944,17 @@ void TBufferFile::WriteArrayDouble32(const Double_t *d, Int_t n, TStreamerElemen //////////////////////////////////////////////////////////////////////////////// /// Write array of n bools into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Bool_t *b, Int_t n) +void TBufferFile::WriteFastArray(const Bool_t *b, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(UChar_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(UChar_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -1956,10 +1970,17 @@ void TBufferFile::WriteFastArray(const Bool_t *b, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n characters into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Char_t *c, Int_t n) +void TBufferFile::WriteFastArray(const Char_t *c, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Char_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Char_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -1970,8 +1991,9 @@ void TBufferFile::WriteFastArray(const Char_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n characters into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArrayString(const Char_t *c, Int_t n) +void TBufferFile::WriteFastArrayString(const Char_t *c, Long64_t n) { if (n < 255) { *this << (UChar_t)n; @@ -1980,7 +2002,13 @@ void TBufferFile::WriteFastArrayString(const Char_t *c, Int_t n) *this << n; } - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Char_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Char_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -1991,10 +2019,17 @@ void TBufferFile::WriteFastArrayString(const Char_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n shorts into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Short_t *h, Int_t n) +void TBufferFile::WriteFastArray(const Short_t *h, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Short_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Short_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2015,10 +2050,18 @@ void TBufferFile::WriteFastArray(const Short_t *h, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n ints into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Int_t *ii, Int_t n) +void TBufferFile::WriteFastArray(const Int_t *ii, Long64_t n) { - if (n <= 0) return; + + constexpr Int_t dataWidth = 4; + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Int_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2038,11 +2081,18 @@ void TBufferFile::WriteFastArray(const Int_t *ii, Int_t n) } //////////////////////////////////////////////////////////////////////////////// -/// Write array of n longs into the I/O buffer. +/// Write array of n longs into the I/O buffer with 8-byte width. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Long_t *ll, Int_t n) +void TBufferFile::WriteFastArray(const Long_t *ll, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = 8; + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = 8*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2051,13 +2101,20 @@ void TBufferFile::WriteFastArray(const Long_t *ll, Int_t n) } //////////////////////////////////////////////////////////////////////////////// -/// Write array of n unsigned longs into the I/O buffer. +/// Write array of n unsigned longs into the I/O buffer with 8-byte width. /// This is an explicit case for unsigned longs since signed longs /// have a special tobuf(). +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const ULong_t *ll, Int_t n) +void TBufferFile::WriteFastArray(const ULong_t *ll, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = 8; + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = 8*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2067,10 +2124,17 @@ void TBufferFile::WriteFastArray(const ULong_t *ll, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n long longs into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Long64_t *ll, Int_t n) +void TBufferFile::WriteFastArray(const Long64_t *ll, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Long64_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Long64_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2086,10 +2150,17 @@ void TBufferFile::WriteFastArray(const Long64_t *ll, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n floats into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Float_t *f, Int_t n) +void TBufferFile::WriteFastArray(const Float_t *f, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Float_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Float_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2110,10 +2181,17 @@ void TBufferFile::WriteFastArray(const Float_t *f, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n doubles into the I/O buffer. +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(const Double_t *d, Int_t n) +void TBufferFile::WriteFastArray(const Double_t *d, Long64_t n) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Double_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Double_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2130,10 +2208,17 @@ void TBufferFile::WriteFastArray(const Double_t *d, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of n floats (as truncated float) into the I/O buffer. /// see comments about Float16_t encoding at TBufferFile::WriteFloat16 +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArrayFloat16(const Float_t *f, Int_t n, TStreamerElement *ele) +void TBufferFile::WriteFastArrayFloat16(const Float_t *f, Long64_t n, TStreamerElement *ele) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Float_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Float_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2181,10 +2266,17 @@ void TBufferFile::WriteFastArrayFloat16(const Float_t *f, Int_t n, TStreamerElem //////////////////////////////////////////////////////////////////////////////// /// Write array of n doubles (as float) into the I/O buffer. /// see comments about Double32_t encoding at TBufferFile::WriteDouble32 +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArrayDouble32(const Double_t *d, Int_t n, TStreamerElement *ele) +void TBufferFile::WriteFastArrayDouble32(const Double_t *d, Long64_t n, TStreamerElement *ele) { - if (n <= 0) return; + constexpr Int_t dataWidth = static_cast(sizeof(Float_t)); + const Int_t maxElements = (std::numeric_limits::max() - Length())/dataWidth; + if (n < 0 || n > maxElements) + { + Fatal("WriteFastArray", "Not enough space left in the buffer (1GB limit). %lld elements is greater than the max left of %d", n, maxElements); + return; // In case the user re-route the error handler to not die when Fatal is called + } Int_t l = sizeof(Float_t)*n; if (fBufCur + l > fBufMax) AutoExpand(fBufSize+l); @@ -2239,8 +2331,9 @@ void TBufferFile::WriteFastArrayDouble32(const Double_t *d, Int_t n, TStreamerEl //////////////////////////////////////////////////////////////////////////////// /// Write an array of object starting at the address 'start' and of length 'n' /// the objects in the array are assumed to be of class 'cl' +/// \note Due to the current limit of the buffer size, the function aborts execution of the program in case of underflow or overflow. See https://github.com/root-project/root/issues/6734 for more details. -void TBufferFile::WriteFastArray(void *start, const TClass *cl, Int_t n, +void TBufferFile::WriteFastArray(void *start, const TClass *cl, Long64_t n, TMemberStreamer *streamer) { if (streamer) { @@ -2250,6 +2343,11 @@ void TBufferFile::WriteFastArray(void *start, const TClass *cl, Int_t n, char *obj = (char*)start; if (!n) n=1; + else if (n < 0) + { + Fatal("WriteFastArray", "Negative number of elements %lld", n); + return; // In case the user re-route the error handler to not die when Fatal is called + } int size = cl->Size(); for(Int_t j=0; j) we can assume that the pointer @@ -2275,7 +2374,7 @@ Int_t TBufferFile::WriteFastArray(void **start, const TClass *cl, Int_t n, (*streamer)(*this,(void*)start,0); return 0; } - + if (n < 0) return -1; int strInfo = 0; Int_t res = 0; diff --git a/io/io/src/TBufferJSON.cxx b/io/io/src/TBufferJSON.cxx index 7ee3a6062b6e7..f38b4c34b8116 100644 --- a/io/io/src/TBufferJSON.cxx +++ b/io/io/src/TBufferJSON.cxx @@ -3254,7 +3254,7 @@ void TBufferJSON::WriteArray(const Double_t *d, Int_t n) /// either JsonWriteArrayCompress() or JsonWriteConstChar() template -void TBufferJSON::JsonWriteFastArray(const T *arr, Int_t arrsize, const char *typname, +void TBufferJSON::JsonWriteFastArray(const T *arr, Long64_t arrsize, const char *typname, void (TBufferJSON::*method)(const T *, Int_t, const char *)) { JsonPushValue(); @@ -3291,7 +3291,7 @@ void TBufferJSON::JsonWriteFastArray(const T *arr, Int_t arrsize, const char *ty //////////////////////////////////////////////////////////////////////////////// /// Write array of Bool_t to buffer -void TBufferJSON::WriteFastArray(const Bool_t *b, Int_t n) +void TBufferJSON::WriteFastArray(const Bool_t *b, Long64_t n) { JsonWriteFastArray(b, n, "Bool", &TBufferJSON::JsonWriteArrayCompress); } @@ -3303,7 +3303,7 @@ void TBufferJSON::WriteFastArray(const Bool_t *b, Int_t n) /// or some special characters, uses regular array. From array size 1000 it /// will be automatically converted into base64 coding -void TBufferJSON::WriteFastArray(const Char_t *c, Int_t n) +void TBufferJSON::WriteFastArray(const Char_t *c, Long64_t n) { Bool_t need_blob = false; Bool_t has_zero = false; @@ -3325,7 +3325,7 @@ void TBufferJSON::WriteFastArray(const Char_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Char_t to buffer -void TBufferJSON::WriteFastArrayString(const Char_t *c, Int_t n) +void TBufferJSON::WriteFastArrayString(const Char_t *c, Long64_t n) { JsonWriteFastArray(c, n, "Int8", &TBufferJSON::JsonWriteConstChar); } @@ -3333,7 +3333,7 @@ void TBufferJSON::WriteFastArrayString(const Char_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UChar_t to buffer -void TBufferJSON::WriteFastArray(const UChar_t *c, Int_t n) +void TBufferJSON::WriteFastArray(const UChar_t *c, Long64_t n) { JsonWriteFastArray(c, n, "Uint8", &TBufferJSON::JsonWriteArrayCompress); } @@ -3341,7 +3341,7 @@ void TBufferJSON::WriteFastArray(const UChar_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Short_t to buffer -void TBufferJSON::WriteFastArray(const Short_t *h, Int_t n) +void TBufferJSON::WriteFastArray(const Short_t *h, Long64_t n) { JsonWriteFastArray(h, n, "Int16", &TBufferJSON::JsonWriteArrayCompress); } @@ -3349,7 +3349,7 @@ void TBufferJSON::WriteFastArray(const Short_t *h, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UShort_t to buffer -void TBufferJSON::WriteFastArray(const UShort_t *h, Int_t n) +void TBufferJSON::WriteFastArray(const UShort_t *h, Long64_t n) { JsonWriteFastArray(h, n, "Uint16", &TBufferJSON::JsonWriteArrayCompress); } @@ -3357,7 +3357,7 @@ void TBufferJSON::WriteFastArray(const UShort_t *h, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Int_t to buffer -void TBufferJSON::WriteFastArray(const Int_t *i, Int_t n) +void TBufferJSON::WriteFastArray(const Int_t *i, Long64_t n) { JsonWriteFastArray(i, n, "Int32", &TBufferJSON::JsonWriteArrayCompress); } @@ -3365,7 +3365,7 @@ void TBufferJSON::WriteFastArray(const Int_t *i, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UInt_t to buffer -void TBufferJSON::WriteFastArray(const UInt_t *i, Int_t n) +void TBufferJSON::WriteFastArray(const UInt_t *i, Long64_t n) { JsonWriteFastArray(i, n, "Uint32", &TBufferJSON::JsonWriteArrayCompress); } @@ -3373,7 +3373,7 @@ void TBufferJSON::WriteFastArray(const UInt_t *i, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Long_t to buffer -void TBufferJSON::WriteFastArray(const Long_t *l, Int_t n) +void TBufferJSON::WriteFastArray(const Long_t *l, Long64_t n) { JsonWriteFastArray(l, n, "Int64", &TBufferJSON::JsonWriteArrayCompress); } @@ -3381,7 +3381,7 @@ void TBufferJSON::WriteFastArray(const Long_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of ULong_t to buffer -void TBufferJSON::WriteFastArray(const ULong_t *l, Int_t n) +void TBufferJSON::WriteFastArray(const ULong_t *l, Long64_t n) { JsonWriteFastArray(l, n, "Uint64", &TBufferJSON::JsonWriteArrayCompress); } @@ -3389,7 +3389,7 @@ void TBufferJSON::WriteFastArray(const ULong_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Long64_t to buffer -void TBufferJSON::WriteFastArray(const Long64_t *l, Int_t n) +void TBufferJSON::WriteFastArray(const Long64_t *l, Long64_t n) { JsonWriteFastArray(l, n, "Int64", &TBufferJSON::JsonWriteArrayCompress); } @@ -3397,7 +3397,7 @@ void TBufferJSON::WriteFastArray(const Long64_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of ULong64_t to buffer -void TBufferJSON::WriteFastArray(const ULong64_t *l, Int_t n) +void TBufferJSON::WriteFastArray(const ULong64_t *l, Long64_t n) { JsonWriteFastArray(l, n, "Uint64", &TBufferJSON::JsonWriteArrayCompress); } @@ -3405,7 +3405,7 @@ void TBufferJSON::WriteFastArray(const ULong64_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Float_t to buffer -void TBufferJSON::WriteFastArray(const Float_t *f, Int_t n) +void TBufferJSON::WriteFastArray(const Float_t *f, Long64_t n) { JsonWriteFastArray(f, n, "Float32", &TBufferJSON::JsonWriteArrayCompress); } @@ -3413,7 +3413,7 @@ void TBufferJSON::WriteFastArray(const Float_t *f, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Double_t to buffer -void TBufferJSON::WriteFastArray(const Double_t *d, Int_t n) +void TBufferJSON::WriteFastArray(const Double_t *d, Long64_t n) { JsonWriteFastArray(d, n, "Float64", &TBufferJSON::JsonWriteArrayCompress); } @@ -3421,10 +3421,10 @@ void TBufferJSON::WriteFastArray(const Double_t *d, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Recall TBuffer function to avoid gcc warning message -void TBufferJSON::WriteFastArray(void *start, const TClass *cl, Int_t n, TMemberStreamer * /* streamer */) +void TBufferJSON::WriteFastArray(void *start, const TClass *cl, Long64_t n, TMemberStreamer * /* streamer */) { if (gDebug > 2) - Info("WriteFastArray", "void *start cl:%s n:%d", cl ? cl->GetName() : "---", n); + Info("WriteFastArray", "void *start cl:%s n:%lld", cl ? cl->GetName() : "---", n); // if (streamer) { // JsonDisablePostprocessing(); @@ -3474,11 +3474,11 @@ void TBufferJSON::WriteFastArray(void *start, const TClass *cl, Int_t n, TMember //////////////////////////////////////////////////////////////////////////////// /// Recall TBuffer function to avoid gcc warning message -Int_t TBufferJSON::WriteFastArray(void **start, const TClass *cl, Int_t n, Bool_t isPreAlloc, +Int_t TBufferJSON::WriteFastArray(void **start, const TClass *cl, Long64_t n, Bool_t isPreAlloc, TMemberStreamer * /* streamer */) { if (gDebug > 2) - Info("WriteFastArray", "void **startp cl:%s n:%d", cl->GetName(), n); + Info("WriteFastArray", "void **startp cl:%s n:%lld", cl->GetName(), n); // if (streamer) { // JsonDisablePostprocessing(); diff --git a/io/io/src/TBufferText.cxx b/io/io/src/TBufferText.cxx index d2d82c1be8234..c2ca5b38584b4 100644 --- a/io/io/src/TBufferText.cxx +++ b/io/io/src/TBufferText.cxx @@ -659,7 +659,7 @@ void TBufferText::WriteArrayDouble32(const Double_t *d, Int_t n, TStreamerElemen //////////////////////////////////////////////////////////////////////////////// /// Write array of Float16_t to buffer -void TBufferText::WriteFastArrayFloat16(const Float_t *f, Int_t n, TStreamerElement * /*ele*/) +void TBufferText::WriteFastArrayFloat16(const Float_t *f, Long64_t n, TStreamerElement * /*ele*/) { WriteFastArray(f, n); } @@ -667,7 +667,7 @@ void TBufferText::WriteFastArrayFloat16(const Float_t *f, Int_t n, TStreamerElem //////////////////////////////////////////////////////////////////////////////// /// Write array of Double32_t to buffer -void TBufferText::WriteFastArrayDouble32(const Double_t *d, Int_t n, TStreamerElement * /*ele*/) +void TBufferText::WriteFastArrayDouble32(const Double_t *d, Long64_t n, TStreamerElement * /*ele*/) { WriteFastArray(d, n); } diff --git a/io/sql/inc/TBufferSQL2.h b/io/sql/inc/TBufferSQL2.h index ff82a0bb6a47a..f2e85e41c02d4 100644 --- a/io/sql/inc/TBufferSQL2.h +++ b/io/sql/inc/TBufferSQL2.h @@ -219,22 +219,22 @@ class TBufferSQL2 final : public TBufferText { void WriteArray(const Float_t *f, Int_t n) final; void WriteArray(const Double_t *d, Int_t n) final; - void WriteFastArray(const Bool_t *b, Int_t n) final; - void WriteFastArray(const Char_t *c, Int_t n) final; - void WriteFastArray(const UChar_t *c, Int_t n) final; - void WriteFastArray(const Short_t *h, Int_t n) final; - void WriteFastArray(const UShort_t *h, Int_t n) final; - void WriteFastArray(const Int_t *i, Int_t n) final; - void WriteFastArray(const UInt_t *i, Int_t n) final; - void WriteFastArray(const Long_t *l, Int_t n) final; - void WriteFastArray(const ULong_t *l, Int_t n) final; - void WriteFastArray(const Long64_t *l, Int_t n) final; - void WriteFastArray(const ULong64_t *l, Int_t n) final; - void WriteFastArray(const Float_t *f, Int_t n) final; - void WriteFastArray(const Double_t *d, Int_t n) final; - void WriteFastArrayString(const Char_t *c, Int_t n) final; - void WriteFastArray(void *start, const TClass *cl, Int_t n = 1, TMemberStreamer *s = nullptr) final; - Int_t WriteFastArray(void **startp, const TClass *cl, Int_t n = 1, Bool_t isPreAlloc = kFALSE, + void WriteFastArray(const Bool_t *b, Long64_t n) final; + void WriteFastArray(const Char_t *c, Long64_t n) final; + void WriteFastArray(const UChar_t *c, Long64_t n) final; + void WriteFastArray(const Short_t *h, Long64_t n) final; + void WriteFastArray(const UShort_t *h, Long64_t n) final; + void WriteFastArray(const Int_t *i, Long64_t n) final; + void WriteFastArray(const UInt_t *i, Long64_t n) final; + void WriteFastArray(const Long_t *l, Long64_t n) final; + void WriteFastArray(const ULong_t *l, Long64_t n) final; + void WriteFastArray(const Long64_t *l, Long64_t n) final; + void WriteFastArray(const ULong64_t *l, Long64_t n) final; + void WriteFastArray(const Float_t *f, Long64_t n) final; + void WriteFastArray(const Double_t *d, Long64_t n) final; + void WriteFastArrayString(const Char_t *c, Long64_t n) final; + void WriteFastArray(void *start, const TClass *cl, Long64_t n = 1, TMemberStreamer *s = nullptr) final; + Int_t WriteFastArray(void **startp, const TClass *cl, Long64_t n = 1, Bool_t isPreAlloc = kFALSE, TMemberStreamer *s = nullptr) final; void StreamObject(void *obj, const TClass *cl, const TClass *onFileClass = nullptr) final; diff --git a/io/sql/src/TBufferSQL2.cxx b/io/sql/src/TBufferSQL2.cxx index 6ab0d633ac0ce..c4e646d98f9cc 100644 --- a/io/sql/src/TBufferSQL2.cxx +++ b/io/sql/src/TBufferSQL2.cxx @@ -1504,7 +1504,7 @@ void TBufferSQL2::WriteArray(const Double_t *d, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Bool_t to buffer -void TBufferSQL2::WriteFastArray(const Bool_t *b, Int_t n) +void TBufferSQL2::WriteFastArray(const Bool_t *b, Long64_t n) { SqlWriteArray(b, n); } @@ -1513,7 +1513,7 @@ void TBufferSQL2::WriteFastArray(const Bool_t *b, Int_t n) /// Write array of Char_t to buffer /// it will be reproduced as CharStar node with string as attribute -void TBufferSQL2::WriteFastArray(const Char_t *c, Int_t n) +void TBufferSQL2::WriteFastArray(const Char_t *c, Long64_t n) { Bool_t usedefault = (n == 0); @@ -1540,7 +1540,7 @@ void TBufferSQL2::WriteFastArray(const Char_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UChar_t to buffer -void TBufferSQL2::WriteFastArray(const UChar_t *c, Int_t n) +void TBufferSQL2::WriteFastArray(const UChar_t *c, Long64_t n) { SqlWriteArray(c, n); } @@ -1548,7 +1548,7 @@ void TBufferSQL2::WriteFastArray(const UChar_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Short_t to buffer -void TBufferSQL2::WriteFastArray(const Short_t *h, Int_t n) +void TBufferSQL2::WriteFastArray(const Short_t *h, Long64_t n) { SqlWriteArray(h, n); } @@ -1556,7 +1556,7 @@ void TBufferSQL2::WriteFastArray(const Short_t *h, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UShort_t to buffer -void TBufferSQL2::WriteFastArray(const UShort_t *h, Int_t n) +void TBufferSQL2::WriteFastArray(const UShort_t *h, Long64_t n) { SqlWriteArray(h, n); } @@ -1564,7 +1564,7 @@ void TBufferSQL2::WriteFastArray(const UShort_t *h, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Int_t to buffer -void TBufferSQL2::WriteFastArray(const Int_t *i, Int_t n) +void TBufferSQL2::WriteFastArray(const Int_t *i, Long64_t n) { SqlWriteArray(i, n); } @@ -1572,7 +1572,7 @@ void TBufferSQL2::WriteFastArray(const Int_t *i, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UInt_t to buffer -void TBufferSQL2::WriteFastArray(const UInt_t *i, Int_t n) +void TBufferSQL2::WriteFastArray(const UInt_t *i, Long64_t n) { SqlWriteArray(i, n); } @@ -1580,7 +1580,7 @@ void TBufferSQL2::WriteFastArray(const UInt_t *i, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Long_t to buffer -void TBufferSQL2::WriteFastArray(const Long_t *l, Int_t n) +void TBufferSQL2::WriteFastArray(const Long_t *l, Long64_t n) { SqlWriteArray(l, n); } @@ -1588,7 +1588,7 @@ void TBufferSQL2::WriteFastArray(const Long_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of ULong_t to buffer -void TBufferSQL2::WriteFastArray(const ULong_t *l, Int_t n) +void TBufferSQL2::WriteFastArray(const ULong_t *l, Long64_t n) { SqlWriteArray(l, n); } @@ -1596,7 +1596,7 @@ void TBufferSQL2::WriteFastArray(const ULong_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Long64_t to buffer -void TBufferSQL2::WriteFastArray(const Long64_t *l, Int_t n) +void TBufferSQL2::WriteFastArray(const Long64_t *l, Long64_t n) { SqlWriteArray(l, n); } @@ -1604,7 +1604,7 @@ void TBufferSQL2::WriteFastArray(const Long64_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of ULong64_t to buffer -void TBufferSQL2::WriteFastArray(const ULong64_t *l, Int_t n) +void TBufferSQL2::WriteFastArray(const ULong64_t *l, Long64_t n) { SqlWriteArray(l, n); } @@ -1612,7 +1612,7 @@ void TBufferSQL2::WriteFastArray(const ULong64_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Float_t to buffer -void TBufferSQL2::WriteFastArray(const Float_t *f, Int_t n) +void TBufferSQL2::WriteFastArray(const Float_t *f, Long64_t n) { SqlWriteArray(f, n); } @@ -1620,7 +1620,7 @@ void TBufferSQL2::WriteFastArray(const Float_t *f, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Double_t to buffer -void TBufferSQL2::WriteFastArray(const Double_t *d, Int_t n) +void TBufferSQL2::WriteFastArray(const Double_t *d, Long64_t n) { SqlWriteArray(d, n); } @@ -1629,7 +1629,7 @@ void TBufferSQL2::WriteFastArray(const Double_t *d, Int_t n) /// Write array of n characters into the I/O buffer. /// Used only by TLeafC, just dummy implementation here -void TBufferSQL2::WriteFastArrayString(const Char_t *c, Int_t n) +void TBufferSQL2::WriteFastArrayString(const Char_t *c, Long64_t n) { SqlWriteArray(c, n); } @@ -1640,7 +1640,7 @@ void TBufferSQL2::WriteFastArrayString(const Char_t *c, Int_t n) /// buf.StreamObject(obj, cl). In that case it is easy to understand where /// object data is started and finished -void TBufferSQL2::WriteFastArray(void *start, const TClass *cl, Int_t n, TMemberStreamer *streamer) +void TBufferSQL2::WriteFastArray(void *start, const TClass *cl, Long64_t n, TMemberStreamer *streamer) { if (streamer) { StreamObjectExtra(start, streamer, cl, 0); @@ -1663,7 +1663,7 @@ void TBufferSQL2::WriteFastArray(void *start, const TClass *cl, Int_t n, TMember /// buf.StreamObject(obj, cl). In that case it is easy to understand where /// object data is started and finished -Int_t TBufferSQL2::WriteFastArray(void **start, const TClass *cl, Int_t n, Bool_t isPreAlloc, TMemberStreamer *streamer) +Int_t TBufferSQL2::WriteFastArray(void **start, const TClass *cl, Long64_t n, Bool_t isPreAlloc, TMemberStreamer *streamer) { Bool_t oldStyle = kFALSE; // flag used to reproduce old-style I/O actions for kSTLp diff --git a/io/xml/inc/TBufferXML.h b/io/xml/inc/TBufferXML.h index be61c1332a336..4210e1eb159dc 100644 --- a/io/xml/inc/TBufferXML.h +++ b/io/xml/inc/TBufferXML.h @@ -147,22 +147,22 @@ class TBufferXML final : public TBufferText, public TXMLSetup { void WriteArray(const Float_t *f, Int_t n) final; void WriteArray(const Double_t *d, Int_t n) final; - void WriteFastArray(const Bool_t *b, Int_t n) final; - void WriteFastArray(const Char_t *c, Int_t n) final; - void WriteFastArray(const UChar_t *c, Int_t n) final; - void WriteFastArray(const Short_t *h, Int_t n) final; - void WriteFastArray(const UShort_t *h, Int_t n) final; - void WriteFastArray(const Int_t *i, Int_t n) final; - void WriteFastArray(const UInt_t *i, Int_t n) final; - void WriteFastArray(const Long_t *l, Int_t n) final; - void WriteFastArray(const ULong_t *l, Int_t n) final; - void WriteFastArray(const Long64_t *l, Int_t n) final; - void WriteFastArray(const ULong64_t *l, Int_t n) final; - void WriteFastArray(const Float_t *f, Int_t n) final; - void WriteFastArray(const Double_t *d, Int_t n) final; - void WriteFastArrayString(const Char_t *c, Int_t n) final; - void WriteFastArray(void *start, const TClass *cl, Int_t n = 1, TMemberStreamer *s = nullptr) final; - Int_t WriteFastArray(void **startp, const TClass *cl, Int_t n = 1, Bool_t isPreAlloc = kFALSE, + void WriteFastArray(const Bool_t *b, Long64_t n) final; + void WriteFastArray(const Char_t *c, Long64_t n) final; + void WriteFastArray(const UChar_t *c, Long64_t n) final; + void WriteFastArray(const Short_t *h, Long64_t n) final; + void WriteFastArray(const UShort_t *h, Long64_t n) final; + void WriteFastArray(const Int_t *i, Long64_t n) final; + void WriteFastArray(const UInt_t *i, Long64_t n) final; + void WriteFastArray(const Long_t *l, Long64_t n) final; + void WriteFastArray(const ULong_t *l, Long64_t n) final; + void WriteFastArray(const Long64_t *l, Long64_t n) final; + void WriteFastArray(const ULong64_t *l, Long64_t n) final; + void WriteFastArray(const Float_t *f, Long64_t n) final; + void WriteFastArray(const Double_t *d, Long64_t n) final; + void WriteFastArrayString(const Char_t *c, Long64_t n) final; + void WriteFastArray(void *start, const TClass *cl, Long64_t n = 1, TMemberStreamer *s = nullptr) final; + Int_t WriteFastArray(void **startp, const TClass *cl, Long64_t n = 1, Bool_t isPreAlloc = kFALSE, TMemberStreamer *s = nullptr) final; void StreamObject(void *obj, const TClass *cl, const TClass *onFileClass = nullptr) final; @@ -311,7 +311,7 @@ class TBufferXML final : public TBufferText, public TXMLSetup { R__ALWAYS_INLINE void XmlWriteArray(const T *arr, Int_t arrsize); template - R__ALWAYS_INLINE void XmlWriteFastArray(const T *arr, Int_t n); + R__ALWAYS_INLINE void XmlWriteFastArray(const T *arr, Long64_t n); XMLNodePointer_t XmlWriteObject(const void *obj, const TClass *objClass, Bool_t cacheReuse); void *XmlReadObject(void *obj, TClass **cl = nullptr); diff --git a/io/xml/src/TBufferXML.cxx b/io/xml/src/TBufferXML.cxx index a88b3322ba4f0..f6c615b283b3b 100644 --- a/io/xml/src/TBufferXML.cxx +++ b/io/xml/src/TBufferXML.cxx @@ -2157,7 +2157,7 @@ void TBufferXML::WriteArray(const Double_t *d, Int_t n) /// chain of several elements should be produced template -R__ALWAYS_INLINE void TBufferXML::XmlWriteFastArray(const T *arr, Int_t n) +R__ALWAYS_INLINE void TBufferXML::XmlWriteFastArray(const T *arr, Long64_t n) { BeforeIOoperation(); if (n <= 0) @@ -2171,7 +2171,7 @@ R__ALWAYS_INLINE void TBufferXML::XmlWriteFastArray(const T *arr, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Bool_t to buffer -void TBufferXML::WriteFastArray(const Bool_t *b, Int_t n) +void TBufferXML::WriteFastArray(const Bool_t *b, Long64_t n) { XmlWriteFastArray(b, n); } @@ -2181,7 +2181,7 @@ void TBufferXML::WriteFastArray(const Bool_t *b, Int_t n) /// If array does not include any special characters, /// it will be reproduced as CharStar node with string as attribute -void TBufferXML::WriteFastArray(const Char_t *c, Int_t n) +void TBufferXML::WriteFastArray(const Char_t *c, Long64_t n) { Bool_t usedefault = (n == 0); const Char_t *buf = c; @@ -2207,7 +2207,7 @@ void TBufferXML::WriteFastArray(const Char_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UChar_t to buffer -void TBufferXML::WriteFastArray(const UChar_t *c, Int_t n) +void TBufferXML::WriteFastArray(const UChar_t *c, Long64_t n) { XmlWriteFastArray(c, n); } @@ -2215,7 +2215,7 @@ void TBufferXML::WriteFastArray(const UChar_t *c, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Short_t to buffer -void TBufferXML::WriteFastArray(const Short_t *h, Int_t n) +void TBufferXML::WriteFastArray(const Short_t *h, Long64_t n) { XmlWriteFastArray(h, n); } @@ -2223,7 +2223,7 @@ void TBufferXML::WriteFastArray(const Short_t *h, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UShort_t to buffer -void TBufferXML::WriteFastArray(const UShort_t *h, Int_t n) +void TBufferXML::WriteFastArray(const UShort_t *h, Long64_t n) { XmlWriteFastArray(h, n); } @@ -2231,7 +2231,7 @@ void TBufferXML::WriteFastArray(const UShort_t *h, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Int_t to buffer -void TBufferXML::WriteFastArray(const Int_t *i, Int_t n) +void TBufferXML::WriteFastArray(const Int_t *i, Long64_t n) { XmlWriteFastArray(i, n); } @@ -2239,7 +2239,7 @@ void TBufferXML::WriteFastArray(const Int_t *i, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of UInt_t to buffer -void TBufferXML::WriteFastArray(const UInt_t *i, Int_t n) +void TBufferXML::WriteFastArray(const UInt_t *i, Long64_t n) { XmlWriteFastArray(i, n); } @@ -2247,7 +2247,7 @@ void TBufferXML::WriteFastArray(const UInt_t *i, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Long_t to buffer -void TBufferXML::WriteFastArray(const Long_t *l, Int_t n) +void TBufferXML::WriteFastArray(const Long_t *l, Long64_t n) { XmlWriteFastArray(l, n); } @@ -2255,7 +2255,7 @@ void TBufferXML::WriteFastArray(const Long_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of ULong_t to buffer -void TBufferXML::WriteFastArray(const ULong_t *l, Int_t n) +void TBufferXML::WriteFastArray(const ULong_t *l, Long64_t n) { XmlWriteFastArray(l, n); } @@ -2263,7 +2263,7 @@ void TBufferXML::WriteFastArray(const ULong_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Long64_t to buffer -void TBufferXML::WriteFastArray(const Long64_t *l, Int_t n) +void TBufferXML::WriteFastArray(const Long64_t *l, Long64_t n) { XmlWriteFastArray(l, n); } @@ -2271,7 +2271,7 @@ void TBufferXML::WriteFastArray(const Long64_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of ULong64_t to buffer -void TBufferXML::WriteFastArray(const ULong64_t *l, Int_t n) +void TBufferXML::WriteFastArray(const ULong64_t *l, Long64_t n) { XmlWriteFastArray(l, n); } @@ -2279,7 +2279,7 @@ void TBufferXML::WriteFastArray(const ULong64_t *l, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Float_t to buffer -void TBufferXML::WriteFastArray(const Float_t *f, Int_t n) +void TBufferXML::WriteFastArray(const Float_t *f, Long64_t n) { XmlWriteFastArray(f, n); } @@ -2287,7 +2287,7 @@ void TBufferXML::WriteFastArray(const Float_t *f, Int_t n) //////////////////////////////////////////////////////////////////////////////// /// Write array of Double_t to buffer -void TBufferXML::WriteFastArray(const Double_t *d, Int_t n) +void TBufferXML::WriteFastArray(const Double_t *d, Long64_t n) { XmlWriteFastArray(d, n); } @@ -2296,7 +2296,7 @@ void TBufferXML::WriteFastArray(const Double_t *d, Int_t n) /// Write array of n characters into the I/O buffer. /// Used only by TLeafC, just dummy implementation here -void TBufferXML::WriteFastArrayString(const Char_t *c, Int_t n) +void TBufferXML::WriteFastArrayString(const Char_t *c, Long64_t n) { WriteFastArray(c, n); } @@ -2305,7 +2305,7 @@ void TBufferXML::WriteFastArrayString(const Char_t *c, Int_t n) /// Write an array of object starting at the address 'start' and of length 'n' /// the objects in the array are assumed to be of class 'cl' -void TBufferXML::WriteFastArray(void *start, const TClass *cl, Int_t n, TMemberStreamer *streamer) +void TBufferXML::WriteFastArray(void *start, const TClass *cl, Long64_t n, TMemberStreamer *streamer) { if (streamer) { (*streamer)(*this, start, 0); @@ -2330,7 +2330,7 @@ void TBufferXML::WriteFastArray(void *start, const TClass *cl, Int_t n, TMemberS /// - 0: success /// - 2: truncated success (i.e actual class is missing. Only ptrClass saved.) -Int_t TBufferXML::WriteFastArray(void **start, const TClass *cl, Int_t n, Bool_t isPreAlloc, TMemberStreamer *streamer) +Int_t TBufferXML::WriteFastArray(void **start, const TClass *cl, Long64_t n, Bool_t isPreAlloc, TMemberStreamer *streamer) { // if isPreAlloc is true (data member has a ->) we can assume that the pointer // is never 0. diff --git a/js/build/jsroot.js b/js/build/jsroot.js index 9ef391844bbef..9182516fe5c06 100644 --- a/js/build/jsroot.js +++ b/js/build/jsroot.js @@ -1,4 +1,4 @@ -// https://root.cern/js/ v7.5.99 +// https://root.cern/js/ v7.6.99 (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : @@ -11,7 +11,7 @@ const version_id = 'dev', /** @summary version date * @desc Release date in format day/month/year like '14/04/2022' */ -version_date = '21/11/2023', +version_date = '28/02/2024', /** @summary version id and date * @desc Produced by concatenation of {@link version_id} and {@link version_date} @@ -221,7 +221,7 @@ settings = { DragAndDrop: !nodejs, /** @summary Interactive dragging of TGraph points */ DragGraphs: true, - /** @summary Show progress box */ + /** @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', @@ -296,7 +296,9 @@ settings = { /** @summary Strip axis labels trailing 0 or replace 10^0 by 1 */ StripAxisLabels: true, /** @summary Draw TF1 by default as curve or line */ - FuncAsCurve: false + FuncAsCurve: false, + /** @summary Time zone used for date/time display of file time */ + TimeZone: '' }, /** @namespace @@ -1105,7 +1107,7 @@ function create$1(typename, target) { create$1(clTBox, obj); extend$1(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 1, fY2NDC: 1, fBorderSize: 0, fInit: 1, fShadowColor: 1, - fCornerRadius: 0, fOption: 'brNDC', fName: 'title' }); + fCornerRadius: 0, fOption: 'brNDC', fName: '' }); break; case clTAttText: extend$1(obj, { fTextAngle: 0, fTextSize: 0, fTextAlign: 22, fTextColor: 1, fTextFont: 42 }); @@ -1125,7 +1127,7 @@ function create$1(typename, target) { 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), + 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 }); break; case clTPaletteAxis: @@ -2394,7 +2396,7 @@ const radians = Math.PI / 180; const degrees$1 = 180 / Math.PI; // https://observablehq.com/@mbostock/lab-and-rgb -const K = 18, +const K$1 = 18, Xn = 0.96422, Yn = 1, Zn = 0.82521, @@ -2431,10 +2433,10 @@ function Lab(l, a, b, 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); + return new Lab(this.l + K$1 * (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); + return new Lab(this.l - K$1 * (k == null ? 1 : k), this.a, this.b, this.opacity); }, rgb() { var y = (this.l + 16) / 116, @@ -2495,10 +2497,10 @@ function hcl2lab(o) { define(Hcl, hcl, extend(Color$1, { brighter(k) { - return new Hcl(this.h, this.c, this.l + K * (k == null ? 1 : k), this.opacity); + return new Hcl(this.h, this.c, this.l + K$1 * (k == null ? 1 : k), this.opacity); }, darker(k) { - return new Hcl(this.h, this.c, this.l - K * (k == null ? 1 : k), this.opacity); + return new Hcl(this.h, this.c, this.l - K$1 * (k == null ? 1 : k), this.opacity); }, rgb() { return hcl2lab(this).rgb(); @@ -8002,23 +8004,27 @@ class FontHandler { } /** @summary Assigns font-related attributes */ - setFont(selection) { - if (this.base64 && this.painter) { - const svg = this.painter.getCanvSvg(), - clname = 'custom_font_' + this.name, - 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('type', 'text/css') - .attr('class', clname) - .property('$fonthandler', this) - .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}') }`); - } + addCustomFontToSvg(svg) { + if (!this.base64 || !this.name) + return; + const clname = 'custom_font_' + this.name, 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()) { + console.log('Adding style entry for class', clname); + defs.append('style') + .attr('class', clname) + .property('$fonthandler', this) + .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) @@ -8091,6 +8097,12 @@ function addCustomFont(index, name, format, base64) { 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 detectFont(node) { @@ -8133,12 +8145,11 @@ function detectFont(node) { } const symbols_map = { - // greek letters + // greek letters from symbols.ttf '#alpha': '\u03B1', '#beta': '\u03B2', '#chi': '\u03C7', '#delta': '\u03B4', - '#digamma': '\u03DD', '#varepsilon': '\u03B5', '#phi': '\u03C6', '#gamma': '\u03B3', @@ -8146,8 +8157,6 @@ const symbols_map = { '#iota': '\u03B9', '#varphi': '\u03C6', '#kappa': '\u03BA', - '#koppa': '\u03DF', - '#sampi': '\u03E1', '#lambda': '\u03BB', '#mu': '\u03BC', '#nu': '\u03BD', @@ -8156,13 +8165,9 @@ const symbols_map = { '#theta': '\u03B8', '#rho': '\u03C1', '#sigma': '\u03C3', - '#stigma': '\u03DB', - '#san': '\u03FB', - '#sho': '\u03F8', '#tau': '\u03C4', '#upsilon': '\u03C5', '#varomega': '\u03D6', - '#varcoppa': '\u03D9', '#omega': '\u03C9', '#xi': '\u03BE', '#psi': '\u03C8', @@ -8171,7 +8176,6 @@ const symbols_map = { '#Beta': '\u0392', '#Chi': '\u03A7', '#Delta': '\u0394', - '#Digamma': '\u03DC', '#Epsilon': '\u0395', '#Phi': '\u03A6', '#Gamma': '\u0393', @@ -8179,9 +8183,6 @@ const symbols_map = { '#Iota': '\u0399', '#vartheta': '\u03D1', '#Kappa': '\u039A', - '#Koppa': '\u03DE', - '#varKoppa': '\u03D8', - '#Sampi': '\u03E0', '#Lambda': '\u039B', '#Mu': '\u039C', '#Nu': '\u039D', @@ -8190,9 +8191,6 @@ const symbols_map = { '#Theta': '\u0398', '#Rho': '\u03A1', '#Sigma': '\u03A3', - '#Stigma': '\u03DA', - '#San': '\u03FA', - '#Sho': '\u03F7', '#Tau': '\u03A4', '#Upsilon': '\u03A5', '#varsigma': '\u03C2', @@ -8202,16 +8200,8 @@ const symbols_map = { '#Zeta': '\u0396', '#varUpsilon': '\u03D2', '#epsilon': '\u03B5', - '#P': '\u00B6', - - // only required for MathJax to provide correct replacement - '#sqrt': '\u221A', - '#bar': '', - '#overline': '', - '#underline': '', - '#strike': '', - // from TLatex tables #2 & #3 + // second set from symbols.ttf '#leq': '\u2264', '#/': '\u2044', '#infty': '\u221E', @@ -8225,9 +8215,8 @@ const symbols_map = { '#uparrow': '\u2191', '#rightarrow': '\u2192', '#downarrow': '\u2193', - '#circ': '\u02C6', // ^ + '#circ': '\u2E30', '#pm': '\xB1', - '#mp': '\u2213', '#doublequote': '\u2033', '#geq': '\u2265', '#times': '\xD7', @@ -8251,12 +8240,11 @@ const symbols_map = { '#oslash': '\u2205', '#cap': '\u2229', '#cup': '\u222A', - '#supseteq': '\u2287', '#supset': '\u2283', + '#supseteq': '\u2287', '#notsubset': '\u2284', - '#subseteq': '\u2286', '#subset': '\u2282', - '#int': '\u222B', + '#subseteq': '\u2286', '#in': '\u2208', '#notin': '\u2209', '#angle': '\u2220', @@ -8266,7 +8254,7 @@ const symbols_map = { '#trademark': '\u2122', '#prod': '\u220F', '#surd': '\u221A', - '#upoint': '\u02D9', + '#upoint': '\u2027', '#corner': '\xAC', '#wedge': '\u2227', '#vee': '\u2228', @@ -8275,24 +8263,45 @@ const symbols_map = { '#Uparrow': '\u21D1', '#Rightarrow': '\u21D2', '#Downarrow': '\u21D3', + '#void2': '', // dummy, placeholder '#LT': '\x3C', '#void1': '\xAE', '#copyright': '\xA9', - '#void3': '\u2122', + '#void3': '\u2122', // it is dummy placeholder, TM '#sum': '\u2211', '#arctop': '\u239B', - '#lbar': '\u23B8', + '#lbar': '\u23A2', '#arcbottom': '\u239D', - '#void8': '', + '#void4': '', // dummy, placeholder + '#void8': '\u23A2', // same as lbar '#bottombar': '\u230A', '#arcbar': '\u23A7', '#ltbar': '\u23A8', '#AA': '\u212B', - '#aa': '\u00E5', + '#aa': '\xE5', '#void06': '', '#GT': '\x3E', + '#int': '\u222B', '#forall': '\u2200', '#exists': '\u2203', + // 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', @@ -8310,12 +8319,25 @@ const symbols_map = { '#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 + + +/** @summary Create a single regex to detect any symbol to replace, apply longer symbols first * @private */ -symbolsRegexCache = new RegExp('(' + Object.keys(symbols_map).join('|').replace(/\\\{/g, '{').replace(/\\\}/g, '}') + ')', 'g'), +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 */ @@ -8392,15 +8414,86 @@ const latex_features = [ { 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]; +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]; +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], + +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 = 0; + 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 + } + } + const code = symbol.charCodeAt(0); + if (code > 0x80) + symbolsPdfMap[code] = letter; + } + if (++cnt > 54 + 82) break; + } + } + 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.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.innerHTML = new_html; + return true; +} function replaceSymbols(s, kind) { const m = (kind === 'Wingdings') ? wingdingsMap : symbolsMap; @@ -10118,7 +10211,7 @@ class BasePainter { enlarge = select(doc.body) .append('div') .attr('id', 'jsroot_enlarge_div') - .attr('style', 'position: fixed; margin: 0px; border: 0px; padding: 0px; inset: 1px; background: white; opacity: 0.95; z-index: 100; overflow: hidden;'); + .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); @@ -10223,12 +10316,13 @@ function addHighlightStyle(elem, drag) { * @private */ async function svgToPDF(args, as_buffer) { const nodejs = isNodeJs(); - let _jspdf, _svg2pdf; + let _jspdf, _svg2pdf, need_symbols = false; const pr = nodejs ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(h => { _jspdf = h; return Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }); }).then(h => { _svg2pdf = h.default; }) : loadScript(exports.source_dir + 'scripts/jspdf.umd.min.js').then(() => loadScript(exports.source_dir + 'scripts/svg2pdf.umd.min.js')).then(() => { _jspdf = globalThis.jspdf; _svg2pdf = globalThis.svg2pdf; }), - restore_fonts = [], restore_dominant = [], node_transform = args.node.getAttribute('transform'), custom_fonts = {}; + restore_fonts = [], restore_dominant = [], restore_text = [], + node_transform = args.node.getAttribute('transform'), custom_fonts = {}; if (args.reset_tranform) args.node.removeAttribute('transform'); @@ -10251,17 +10345,25 @@ async function svgToPDF(args, as_buffer) { if (!args.can_modify) restore_dominant.push(this); // keep to restore it } else if (args.can_modify && nodejs && this.getAttribute('dy') === '.4em') this.setAttribute('dy', '.2em'); // better allignment in PDF + + if (replaceSymbolsInTextNode(this)) { + need_symbols = true; + if (!args.can_modify) restore_text.push(this); // keep to restore it + } }); if (nodejs) { const doc = internals.nodejs_document; doc.oldFunc = 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.oldFunc(ns, kind); res.getBBox = function() { let width = 50, height = 10; if (this.tagName === 'text') { + // TODO: use jsDOC fonts for label width estimation const font = detectFont(this); width = approximateLabelWidth(this.textContent, font); height = font.size; @@ -10286,11 +10388,38 @@ async function svgToPDF(args, as_buffer) { if (!fh || custom_fonts[fh.name] || (fh.format !== 'ttf')) return; const filename = fh.name.toLowerCase().replace(/\s/g, '') + '.ttf'; doc.addFileToVFS(filename, fh.base64); - doc.addFont(filename, fh.name, 'normal'); + doc.addFont(filename, fh.name, 'normal', 'normal', (fh.name === 'symbol') ? 'StandardEncoding' : 'Identity-H'); custom_fonts[fh.name] = true; }); - return _svg2pdf.svg2pdf(args.node, doc, { x: 5, y: 5, width: args.width, height: args.height }) + let pr2 = Promise.resolve(true); + + if (need_symbols && !custom_fonts.symbol) { + if (!getCustomFont('symbol')) { + pr2 = nodejs + ? Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; }).then(fs => { + const base64 = fs.readFileSync('../../fonts/symbol.ttf').toString('base64'); + console.log('reading symbol.ttf', base64.length); + addCustomFont(25, 'symbol', 'ttf', base64); + }) + : httpRequest(exports.source_dir+'fonts/symbol.ttf', 'bin').then(buf => { + const base64 = btoa_func(buf); + addCustomFont(25, 'symbol', 'ttf', base64); + }); + } + + pr2 = pr2.then(() => { + const fh = getCustomFont('symbol'), + handler = new FontHandler(1242, 10); + handler.name = 'symbol'; + handler.base64 = fh.base64; + handler.addCustomFontToSvg(select(args.node)); + doc.addFileToVFS('symbol.ttf', fh.base64); + doc.addFont('symbol.ttf', 'symbol', 'normal', 'normal', 'StandardEncoding' /* 'WinAnsiEncoding' */); + }); + } + + return pr2.then(() => _svg2pdf.svg2pdf(args.node, doc, { x: 5, y: 5, width: args.width, height: args.height })) .then(() => { if (args.reset_tranform && !args.can_modify && node_transform) args.node.setAttribute('transform', node_transform); @@ -10300,13 +10429,18 @@ async function svgToPDF(args, as_buffer) { node.setAttribute('dominant-baseline', 'middle'); node.removeAttribute('dy'); }); + + restore_text.forEach(node => { node.innerHTML = node.$originalHTML; }); + const res = as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring'); if (nodejs) { globalThis.document = undefined; + globalThis.CSSStyleSheet = undefined; + globalThis.CSSStyleRule = undefined; internals.nodejs_document.createElementNS = internals.nodejs_document.oldFunc; if (as_buffer) return Buffer.from(res); } - return res; + return res; }); }); } @@ -10325,16 +10459,14 @@ async function svgToImage(svg, image_format, as_buffer) { if (image_format === 'pdf') return svgToPDF(svg, as_buffer); - if (!isNodeJs()) { - // required with df104.py/df105.py example with RCanvas - const doctype = ''; - svg = encodeURIComponent(doctype + svg); - svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { - const c = String.fromCharCode('0x'+p1); - return c === '%' ? '%25' : c; - }); - svg = decodeURIComponent(svg); - } + // required with df104.py/df105.py example with RCanvas or any special symbols in TLatex + const doctype = ''; + svg = encodeURIComponent(doctype + svg); + svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { + const c = String.fromCharCode('0x'+p1); + return c === '%' ? '%25' : c; + }); + svg = decodeURIComponent(svg); const img_src = 'data:image/svg+xml;base64,' + btoa_func(svg); @@ -10376,6 +10508,20 @@ async function svgToImage(svg, image_format, as_buffer) { }); } +/** @summary Convert Date object into string used preconfigured 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 (err) { + res = ''; + } + } + return res || dt.toLocaleString('en-GB'); +} + const clTLinearGradient = 'TLinearGradient', clTRadialGradient = 'TRadialGradient', kWhite = 0, kBlack = 1; @@ -12052,8 +12198,10 @@ class ObjectPainter extends BasePainter { * Such string typically used as object tooltip. * If result string larger than 20 symbols, it will be cutted. */ getObjectHint() { - const hint = this.getItemName() || this.getObjectName() || this.getClassName() || ''; - return (hint.length <= 20) ? hint : hint.slice(0, 17) + '...'; + const iname = this.getItemName(); + if (iname) + return (iname.length > 20) ? '...' + iname.slice(iname.length - 17) : iname; + return this.getObjectName() || this.getClassName() || ''; } /** @summary returns color from current list of colors @@ -12285,18 +12433,22 @@ class ObjectPainter extends BasePainter { * 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} [noround] - 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) { + getAxisToSvgFunc(isndc, nornd, use_frame_coordinates) { const func = { isndc, nornd }, use_frame = this.draw_g?.property('in_frame'); - if (use_frame) func.main = this.getFramePainter(); + if (use_frame || (use_frame_coordinates && !isndc)) + func.main = this.getFramePainter(); if (func.main?.grx && func.main?.gry) { + func.x0 = (use_frame_coordinates && !isndc) ? func.main.getFrameX() : 0; + func.y0 = (use_frame_coordinates && !isndc) ? func.main.getFrameY() : 0; if (nornd) { - func.x = function(x) { return this.main.grx(x); }; - func.y = function(y) { return this.main.gry(y); }; + func.x = function(x) { return this.x0 + this.main.grx(x); }; + func.y = function(y) { return this.y0 + this.main.gry(y); }; } else { - func.x = function(x) { return Math.round(this.main.grx(x)); }; - func.y = function(y) { return Math.round(this.main.gry(y)); }; + func.x = function(x) { return this.x0 + Math.round(this.main.grx(x)); }; + func.y = function(y) { return this.y0 + Math.round(this.main.gry(y)); }; } } else if (!use_frame) { const pp = this.getPadPainter(); @@ -58444,42 +58596,54 @@ tgamma: gamma * @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 {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) { +function showProgress(msg, tmout, click_handle) { if (isBatchMode() || (typeof document === 'undefined')) return; - const id = 'jsroot_progressbox'; + + const id = 'jsroot_progressbox', modal = (settings.ProgressBox === 'modal') && isFunc(internals._modalProgress) ? internals._modalProgress : null; let box = select('#' + id); - if (!settings.ProgressBox) + if (!settings.ProgressBox) { + if (modal) modal(); return box.remove(); + } if ((arguments.length === 0) || !msg) { if ((tmout !== -1) || (!box.empty() && box.property('with_timeout'))) box.remove(); + if (modal) modal(); return; } - 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'); - } + 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); + box.property('with_timeout', false); - if (isStr(msg)) - box.select('p').html(msg); - else { - box.html(''); - box.node().appendChild(msg); - } + const p = box.select('p'); - box.select('p').attr('style', 'font-size: 10px; margin-left: 10px; margin-right: 10px; margin-top: 3px; margin-bottom: 3px'); + 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)) { - box.property('with_timeout', true); + if (!box.empty()) + box.property('with_timeout', true); setTimeout(() => showProgress('', -1), tmout); } } @@ -59707,7 +59871,9 @@ class JSRootMenu { 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.addchk(settings.ProgressBox, 'Progress box', flag => { settings.ProgressBox = 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.add('endsub:'); this.add('sub:Drawing'); @@ -59746,8 +59912,9 @@ class JSRootMenu { const setStyleField = arg => { gStyle[arg.slice(1)] = parseInt(arg[0]); }, addStyleIntField = (name, field, arr) => { this.add('sub:' + name); + const curr = gStyle[field] >= arr.length ? 1 : gStyle[field]; for (let v = 0; v < arr.length; ++v) - this.addchk(gStyle[field] === v, arr[v], `${v}${field}`, setStyleField); + this.addchk(curr === v, arr[v], `${v}${field}`, setStyleField); this.add('endsub:'); }; @@ -59755,8 +59922,9 @@ class JSRootMenu { this.add('sub:Canvas'); this.addColorMenu('Color', gStyle.fCanvasColor, col => { gStyle.fCanvasColor = col; }); - this.addchk(gStyle.fOptDate, 'Draw date', flag => { gStyle.fOptDate = flag ? 1 : 0; }); - this.addchk(gStyle.fOptFile, 'Draw item', flag => { gStyle.fOptFile = flag ? 1 : 0; }); + 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.add('endsub:'); @@ -60319,18 +60487,21 @@ class StandaloneMenu extends JSRootMenu { } /** @summary Run modal elements with standalone code */ - async runModal(title, main_content, args) { + createModal(title, main_content, args) { if (!args) args = {}; - const dlg_id = this.menuname + '_dialog'; + + if (!args.Ok) args.Ok = 'Ok'; + + const modal = { args }, dlg_id = (this?.menuname ?? 'root_modal') + '_dialog'; select(`#${dlg_id}`).remove(); select(`#${dlg_id}_block`).remove(); - const w = Math.min(args.width || 450, Math.round(0.9*browser.screenWidth)), - block = select('body').append('div') + const w = Math.min(args.width || 450, Math.round(0.9*browser.screenWidth)); + modal.block = select('body').append('div') .attr('id', `${dlg_id}_block`) .attr('class', 'jsroot_dialog_block') - .attr('style', 'z-index: 100000; position: absolute; inset: 0px; opacity: 0.2; background-color: white'), - element = select('body') + .attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white'); + modal.element = select('body') .append('div') .attr('id', dlg_id) .attr('class', 'jsroot_dialog') @@ -60345,38 +60516,61 @@ class StandaloneMenu extends JSRootMenu { `
${title}
`+ `
${main_content}
`+ '
'+ - ''+ + ``+ (args.btns ? '' : '') + '
'); - return new Promise(resolveFunc => { - element.on('keyup', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); - resolveFunc(evnt.code === 'Enter' ? element.node() : null); - element.remove(); - block.remove(); - } - }); - element.on('keydown', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); + 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); } - }); - element.selectAll('.jsroot_dialog_button').on('click', evnt => { - resolveFunc(args.btns && (select(evnt.target).text() === 'Ok') ? element.node() : null); - element.remove(); - block.remove(); - }); + } + }; - let f = element.select('.jsroot_dialog_content').select('input'); - if (f.empty()) f = element.select('.jsroot_dialog_footer').select('button'); - if (!f.empty()) f.node().focus(); + 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(); + 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 @@ -60422,6 +60616,23 @@ function showPainterMenu(evnt, painter, 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) { @@ -60450,7 +60661,7 @@ function getTimeOffset(axis) { sof = sof.slice(pos + 1); if (!Number.isInteger(val) || (val < min) || (val > max)) return min; return val; - }, year = next('-', 1970, 2300), + }, year = next('-', 1900, 2900), month = next('-', 1, 12) - 1, day = next(' ', 1, 31), hour = next(':', 0, 23), @@ -61259,29 +61470,29 @@ class TAxisPainter extends ObjectPainter { title_g.property('shift_x', new_x) .property('shift_y', new_y); - const axis = this.getObject(), abits = EAxisBits, - axis2 = this.source_axis, - set_bit = (bit, on) => { - if (axis.TestBit(bit) !== on) axis.InvertBit(bit); + const axis = this.getObject(), axis2 = this.source_axis, + setBit = (bit, on) => { + if (axis && axis.TestBit(bit) !== on) axis.InvertBit(bit); if (axis2 && axis2.TestBit(bit) !== on) axis2.InvertBit(bit); }; this.titleOffset = (vertical ? new_x : new_y) / offset_k; - axis.fTitleOffset = this.titleOffset / this.offsetScaling / this.titleSize; - if (axis2) axis2.fTitleOffset = axis.fTitleOffset; + const offset = this.titleOffset / this.offsetScaling / this.titleSize; + if (axis) axis.fTitleOffset = offset; + if (axis2) axis2.fTitleOffset = offset; if (curr_indx === 1) { - set_bit(abits.kCenterTitle, true); this.titleCenter = true; - set_bit(abits.kOppositeTitle, false); this.titleOpposite = false; + setBit(EAxisBits.kCenterTitle, true); this.titleCenter = true; + setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; } else if (curr_indx === 0) { - set_bit(abits.kCenterTitle, false); this.titleCenter = false; - set_bit(abits.kOppositeTitle, true); this.titleOpposite = true; + setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, true); this.titleOpposite = true; } else { - set_bit(abits.kCenterTitle, false); this.titleCenter = false; - set_bit(abits.kOppositeTitle, false); this.titleOpposite = false; + setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; } - this.submitAxisExec(`SetTitleOffset(${axis.fTitleOffset});;SetBit(${abits.kCenterTitle},${this.titleCenter?1:0})`); + this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter?1:0})`); drag_rect.remove(); drag_rect = null; @@ -61476,9 +61687,10 @@ class TAxisPainter extends ObjectPainter { this.drawText(arg); - if (lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { + // 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, 0.9*axis_step/labelsFont.size); + textscale = Math.min(textscale, 1.1*axis_step/labelsFont.size); } lastpos = pos; @@ -61530,9 +61742,11 @@ class TAxisPainter extends ObjectPainter { /** @summary Extract major draw attributes, which are also used in interactive operations * @private */ extractDrawAttributes(scalingSize, w, h) { - const axis = this.getObject(), - pp = this.getPadPainter(), - pad_w = pp?.getPadWidth() || scalingSize || w/0.8, // use factor 0.8 as ratio between frame and pad size + 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; let tickSize = 0, tickScalingSize = 0, titleColor, titleFontId, offset; @@ -61732,12 +61946,12 @@ class TAxisPainter extends ObjectPainter { title_shift_x = Math.round(title_offest_k * this.titleOffset); - if ((this.name === 'zaxis') && this.is_gaxis && ('getBoundingClientRect' in axis_g.node())) { - // special handling for color palette labels - draw them always on right side - const rect = axis_g.node().getBoundingClientRect(); - if (title_shift_x < rect.width - this.ticksSize) - title_shift_x = Math.round(rect.width - this.ticksSize); - } + // if ((this.name === 'zaxis') && this.is_gaxis && ('getBoundingClientRect' in axis_g.node())) { + // // special handling for color palette labels - draw them always on right side + // const rect = axis_g.node().getBoundingClientRect(); + // if (title_shift_x < rect.width - this.ticksSize) + // title_shift_x = Math.round(rect.width - this.ticksSize); + // } title_shift_y = Math.round(this.titleCenter ? h/2 : (xor_reverse ? h : 0)); @@ -62211,6 +62425,19 @@ const TooltipHandler = { } } + 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) : ''; @@ -62305,24 +62532,14 @@ const TooltipHandler = { gapminx = -1111, gapmaxx = -1111; const minhinty = -frame_shift.y, cp = this.getCanvPainter(), - maxhinty = cp.getPadHeight() - frame_rect.y - frame_shift.y, - FindPosInGap = y => { - for (let n = 0; (n < hints.length) && (y < maxhinty); ++n) { - const hint = hints[n]; - if (!hint) continue; - if ((hint.y >= y - 5) && (hint.y <= y + hint.height + 5)) { - y = hint.y + 10; - n = -1; - } - } - return y; - }; + 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}`); + group = hintsg.selectChild(`.painter_hint_${n}`); - if (show_only_best && (hint !== best_hint)) hint = null; + if (show_only_best && (hint !== best_hint)) + hint = null; if (hint === null) { group.remove(); @@ -62342,16 +62559,23 @@ const TooltipHandler = { 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 { - gapy = FindPosInGap(gapy); + for (let n = 0; (n < hints.length) && (gapy < maxhinty); ++n) { + const hint = hints[n]; + if (!hint) continue; + if ((hint.y >= gapy - 5) && (hint.y <= gapy + hint.height + 5)) { + gapy = hint.y + 10; + n = -1; + } + } if ((gapminx === -1111) && (gapmaxx === -1111)) gapminx = gapmaxx = hint.x; gapminx = Math.min(gapminx, hint.x); gapmaxx = Math.min(gapmaxx, hint.x); } group.attr('x', posx) - .attr('y', curry) - .property('curry', curry) - .property('gapy', gapy); + .attr('y', curry) + .property('curry', curry) + .property('gapy', gapy); curry += hint.height + 5; gapy += hint.height + 5; @@ -62360,7 +62584,7 @@ const TooltipHandler = { group.selectAll('*').remove(); group.attr('width', 60) - .attr('height', hint.height); + .attr('height', hint.height); const r = group.append('rect') .attr('x', 0) @@ -62377,8 +62601,11 @@ const TooltipHandler = { } r.attr('stroke-width', hint.exact ? 3 : 1); - for (let l = 0; l < (hint.lines ? hint.lines.length : 0); l++) { - if (hint.lines[l] !== null) { + 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) @@ -62387,9 +62614,8 @@ const TooltipHandler = { .style('fill', 'black') .style('pointer-events', 'none') .call(font.func) - .text(hint.lines[l]), - - box = getElementRect(txt, 'bbox'); + .text(line), + box = getElementRect(txt, 'bbox'); actualw = Math.max(actualw, box.width); } @@ -63558,7 +63784,7 @@ class TFramePainter extends ObjectPainter { umax = pad[`fU${name}max`], eps = 1e-7; - if (name === 'x') { + 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; @@ -63581,12 +63807,12 @@ class TFramePainter extends ObjectPainter { let aname = name; if (this.swap_xy) aname = (name === 'x') ? 'y' : 'x'; - const smin = `scale_${aname}min`, - smax = `scale_${aname}max`; + const smin = this[`scale_${aname}min`], + smax = this[`scale_${aname}max`]; - eps = (this[smax] - this[smin]) * 1e-7; + eps = (smax - smin) * 1e-7; - if ((Math.abs(umin - this[smin]) > eps) || (Math.abs(umax - this[smax]) > eps)) { + if ((Math.abs(umin - smin) > eps) || (Math.abs(umax - smax) > eps)) { this[`zoom_${aname}min`] = umin; this[`zoom_${aname}max`] = umax; } @@ -63655,6 +63881,9 @@ class TFramePainter extends ObjectPainter { if (opts.ndim > 1) this.applyAxisZoom('y'); if (opts.ndim > 2) this.applyAxisZoom('z'); + // TODO: extraction of PAD ranges must be done much earlier in hist painter + // normally histogram MUST set this ranges + // to be fixed after 7.6.0 release if (opts.check_pad_range === 'pad_range') { const canp = this.getCanvPainter(); // ignore range set in the online canvas @@ -63711,7 +63940,7 @@ class TFramePainter extends ObjectPainter { noexp_changed: this.y_noexp_changed, symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < 0.01*this.ymax) ? 0.3 * opts.ymin_nz : 0, + log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5*opts.ymin_nz : 0, logminfactor: logminfactorY }); this.y_handle.assignFrameMembers(this, 'y'); @@ -63786,7 +64015,7 @@ class TFramePainter extends ObjectPainter { log: this.swap_xy ? pad.fLogx : pad.fLogy, noexp_changed: this.y2_noexp_changed, logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < 0.01*this.y2max) ? 0.3 * opts.ymin_nz : 0, + 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'); @@ -65396,7 +65625,7 @@ class TabsDisplay extends MDIDisplay { if (top.empty()) { top = dom.append('div').attr('class', 'jsroot_tabs') - .attr('style', 'display: flex; flex-direction: column; position: absolute; overflow: hidden; inset: 0px 0px 0px 0px'); + .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') @@ -65443,7 +65672,7 @@ class TabsDisplay extends MDIDisplay { const draw_frame = main.append('div') .attr('frame_title', title) .attr('class', 'jsroot_tabs_draw') - .attr('style', 'overflow: hidden; position: absolute; inset: 0px') + .attr('style', 'overflow: hidden; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px;') .property('frame_id', frame_id); this.modifyTabsFrame(frame_id, 'activate'); @@ -66010,7 +66239,7 @@ class BrowserLayout { input_style = settings.DarkMode ? `background-color: #222; color: ${text_color}` : ''; injectStyle( - '.jsroot_browser { pointer-events: none; position: absolute; inset: 0px; margin: 0px; border: 0px; overflow: hidden; }'+ + '.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} }`+ @@ -66032,7 +66261,7 @@ class BrowserLayout { main.append('div').attr('id', this.drawing_divid()) .classed('jsroot_draw_area', true) .style('position', 'absolute') - .style('inset', '0px'); + .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); if (with_browser) main.append('div').classed('jsroot_browser', true); @@ -66779,6 +67008,7 @@ class TPadPainter extends ObjectPainter { delete this._snap_primitives; delete this._last_grayscale; delete this._custom_colors; + delete this._custom_palette_indexes; delete this._custom_palette_colors; delete this.root_colors; @@ -66893,6 +67123,33 @@ class TPadPainter extends ObjectPainter { * @private */ getNumPainters() { return this.painters.length; } + /** @summary Provides automatic color + * @desc Uses ROOT colors palette if possible + * @private */ + getAutoColor(numprimitives) { + if (!numprimitives) + numprimitives = this._num_primitives || 5; + if (numprimitives < 2) numprimitives = 2; + + let indx = this._auto_color ?? 0; + this._auto_color = (indx + 1) % numprimitives; + if (indx >= numprimitives) indx = numprimitives - 1; + + const indexes = this._custom_palette_indexes || this.getCanvPainter()?._custom_palette_indexes; + + 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 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 subpads, 'objects' only for object in current pad @@ -66956,7 +67213,7 @@ class TPadPainter extends ObjectPainter { const cp = this.getCanvPainter(); - let lineatt = this.is_active_pad && (cp?.highlight_gpad !== false) ? new TAttLineHandler({ style: 1, width: 1, color: 'red' }) : this.lineatt; + 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' }); @@ -67107,7 +67364,7 @@ class TPadPainter extends ObjectPainter { if (this._fixed_size) svg.attr('width', rect.width).attr('height', rect.height); else - svg.style('width', '100%').style('height', '100%').style('inset', '0px'); + 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); @@ -67139,21 +67396,26 @@ class TPadPainter extends ObjectPainter { if (!gStyle.fOptDate) dt.remove(); else { - if (dt.empty()) dt = info.append('text').attr('class', 'canvas_date'); - const date = new Date(), - posy = Math.round(rect.height * (1 - gStyle.fDateY)); + if (dt.empty()) + dt = info.append('text').attr('class', 'canvas_date'); + const posy = Math.round(rect.height * (1 - gStyle.fDateY)), + date = new Date(); let posx = Math.round(rect.width * gStyle.fDateX); - if (!is_batch && (posx < 25)) posx = 25; - if (gStyle.fOptDate > 1) date.setTime(gStyle.fOptDate*1000); + 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(date.toLocaleString('en-GB')); + .text(convertDate(date)); } - if (!gStyle.fOptFile || !this.getItemName()) + const iname = this.getItemName(); + if (iname) + this.drawItemNameOnCanvas(iname); + else if (!gStyle.fOptFile) info.selectChild('.canvas_item').remove(); - else - this.drawItemNameOnCanvas(this.getItemName()); return true; } @@ -67163,14 +67425,22 @@ class TPadPainter extends ObjectPainter { drawItemNameOnCanvas(item_name) { const info = this.getLayerSvg('info_layer', this.this_pad_name); let df = info.selectChild('.canvas_item'); - if (!gStyle.fOptFile || !item_name) + 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'); + 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(item_name); + .text(fname); + } + if (((gStyle.fOptDate === 2) || (gStyle.fOptDate === 3)) && fitem?._file) { + info.selectChild('.canvas_date') + .text(convertDate(gStyle.fOptDate === 2 ? fitem._file.fDatimeC.getDate() : fitem._file.fDatimeM.getDate())); } } @@ -67397,7 +67667,7 @@ class TPadPainter extends ObjectPainter { if ((obj._typename === clTObjArray) && (obj.name === 'ListOfColors')) { if (this.options?.CreatePalette) { let arr = []; - for (let n = obj.arr.length - this.options.CreatePalette; n= numprimitives) indx = numprimitives - 1; - const palindx = Math.round(indx * (pal.getLength()-3) / (numprimitives-1)), - colvalue = pal.getColor(palindx); - - return this.addColor(colvalue); - } - - this._auto_color = this._auto_color % 8; - return indx+2; - } - /** @summary Create necessary histogram draw attributes */ - createHistDrawAttributes() { - const histo = this.getHisto(); + createHistDrawAttributes(only_check_auto) { + const histo = this.getHisto(), o = this.options; - if (this.options._pfc || this.options._plc || this.options._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(); - let exec = ''; - if (this.options._pfc) { histo.fFillColor = icolor; exec += `SetFillColor(${icolor});;`; delete this.fillatt; } - if (this.options._plc) { histo.fLineColor = icolor; exec += `SetLineColor(${icolor});;`; delete this.lineatt; } - if (this.options._pmc) { histo.fMarkerColor = icolor; exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } - this.options._pfc = this.options._plc = this.options._pmc = false; - this._auto_exec = exec; // can be reused when sending option back to server + if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { + const pp = this.getPadPainter(); + if (isFunc(pp?.getAutoColor)) { + const icolor = pp.getAutoColor(histo.$num_histos); + this._auto_exec = ''; // can be reused when sending option back to server + if (o._pfc > 1) { o._pfc = 1; histo.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } + if (o._plc > 1) { o._plc = 1; histo.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } + if (o._pmc > 1) { o._pmc = 1; histo.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } } } - this.createAttFill({ attr: histo, color: this.options.histoFillColor, pattern: this.options.histoFillPattern, kind: 1 }); - - this.createAttLine({ attr: histo, color0: this.options.histoLineColor }); + if (only_check_auto) + this.deleteAttr(); + else { + this.createAttFill({ attr: histo, color: this.options.histoFillColor, pattern: this.options.histoFillPattern, kind: 1 }); + this.createAttLine({ attr: histo, color0: this.options.histoLineColor }); + } } /** @summary Update axes attributes in target histogram @@ -72204,7 +72464,8 @@ class THistPainter extends ObjectPainter { updateObject(obj, opt) { const histo = this.getHisto(), fp = this.getFramePainter(), - pp = this.getPadPainter(); + pp = this.getPadPainter(), + o = this.options; if (obj !== histo) { if (!this.matchObjectType(obj)) return false; @@ -72222,14 +72483,22 @@ class THistPainter extends ObjectPainter { } // special treatment for webcanvas - also name can be changed - if (this.snapid !== undefined) + if (this.snapid !== undefined) { histo.fName = obj.fName; + o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas + } - histo.fFillColor = obj.fFillColor; + if (!o._pfc) + histo.fFillColor = obj.fFillColor; histo.fFillStyle = obj.fFillStyle; - histo.fLineColor = obj.fLineColor; + if (!o._plc) + histo.fLineColor = obj.fLineColor; histo.fLineStyle = obj.fLineStyle; histo.fLineWidth = obj.fLineWidth; + if (!o._pmc) + histo.fMarkerColor = obj.fMarkerColor; + histo.fMarkerSize = obj.fMarkerSize; + histo.fMarkerStyle = obj.fMarkerStyle; histo.fEntries = obj.fEntries; histo.fTsumw = obj.fTsumw; @@ -72260,7 +72529,7 @@ class THistPainter extends ObjectPainter { histo.fSumw2 = obj.fSumw2; if (this.getDimension() === 1) - this.options.decodeSumw2(histo); + o.decodeSumw2(histo); if (this.isTProfile()) histo.fBinEntries = obj.fBinEntries; @@ -72276,14 +72545,14 @@ class THistPainter extends ObjectPainter { const changed_opt = (histo.fOption !== obj.fOption); histo.fOption = obj.fOption; - if (((opt !== undefined) && (this.options.original !== opt)) || changed_opt) + if (((opt !== undefined) && (o.original !== opt)) || changed_opt) this.decodeOptions(opt || histo.fOption); } - if (!this.options.ominimum) - this.options.minimum = histo.fMinimum; - if (!this.options.omaximum) - this.options.maximum = histo.fMaximum; + if (!o.ominimum) + o.minimum = histo.fMinimum; + if (!o.omaximum) + o.maximum = histo.fMaximum; if (this.snapid || !fp || !fp.zoomChangedInteractive()) this.checkPadRange(); @@ -72450,7 +72719,7 @@ class THistPainter extends ObjectPainter { /** @summary Fill option object used in TWebCanvas */ fillWebObjectOptions(res) { - if (this._auto_exec) { + if (this._auto_exec && res) { res.fcust = 'auto_exec:' + this._auto_exec; delete this._auto_exec; } @@ -78987,13 +79256,12 @@ let TH1Painter$2 = class TH1Painter extends THistPainter { else hsum += histo.getBinContent(0) + histo.getBinContent(this.nbinsx + 1); - this.stat_entries = hsum; - if (histo.fEntries > 1) this.stat_entries = histo.fEntries; + this.stat_entries = (histo.fEntries > 1) ? histo.fEntries : hsum; this.hmin = hmin; this.hmax = hmax; - this.ymin_nz = hmin_nz; // value can be used to show optimal log scale + // this.ymin_nz = hmin_nz; // value can be used to show optimal log scale if ((this.nbinsx === 0) || ((Math.abs(hmin) < 1e-300) && (Math.abs(hmax) < 1e-300))) this.draw_content = false; @@ -79010,10 +79278,16 @@ let TH1Painter$2 = class TH1Painter extends THistPainter { this.ymin = 0; this.ymax = hmin * 2; } } else { - const dy = (hmax - hmin) * gStyle.fHistTopMargin; - this.ymin = hmin - dy; - if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0; - this.ymax = hmax + dy; + const pad = this.getPadPainter()?.getRootPad(), + pad_logy = (this.options.BarStyle >= 20) ? pad.fLogx : (pad?.fLogv ?? pad?.fLogy); + if (pad_logy) { + this.ymin = (hmin_nz || hmin) * 0.5; + this.ymax = hmax*2*(0.9/0.95); + } else { + this.ymin = hmin - (hmax - hmin) * gStyle.fHistTopMargin; + if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0; + this.ymax = hmax + (hmax - this.ymin) * gStyle.fHistTopMargin; + } } } @@ -79364,13 +79638,13 @@ let TH1Painter$2 = class TH1Painter extends THistPainter { xaxis = histo.fXaxis, exclude_zero = !this.options.Zero, show_errors = this.options.Error, - show_line = this.options.Line, show_curve = this.options.Curve, show_text = this.options.Text, text_profile = show_text && (this.options.TextKind === 'E') && this.isTProfile() && histo.fBinEntries, grpnts = []; let res = '', lastbin = false, show_markers = this.options.Mark, + show_line = this.options.Line, startx, startmidx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax, path_fill = null, path_err = null, path_marker = null, path_line = '', hints_err = null, hints_marker = null, hsz = 5, @@ -79384,7 +79658,8 @@ let TH1Painter$2 = class TH1Painter extends THistPainter { if (this.options.ErrorKind === 2) { if (this.fillatt.empty()) show_markers = true; else path_fill = ''; - } else if (this.options.Error) { + } else if (show_errors) { + show_line = false; path_err = ''; hints_err = want_tooltip ? '' : null; do_err = true; @@ -79474,8 +79749,10 @@ let TH1Painter$2 = class TH1Painter extends THistPainter { path_err += `M${midx-dlw},${my-yerr1+dend}h${2*dlw}m${-dlw},0v${yerr1+yerr2-2*dend}m${-dlw},0h${2*dlw}`; else path_err += `M${midx},${my-yerr1+dend}v${yerr1+yerr2-2*dend}`; - if (hints_err !== null) - hints_err += `M${midx-edx},${my-yerr1}h${2*edx}v${yerr1+yerr2}h${-2*edx}z`; + if (hints_err !== null) { + const he1 = Math.max(yerr1, 5), he2 = Math.max(yerr2, 5); + hints_err += `M${midx-edx},${my-he1}h${2*edx}v${he1+he2}h${-2*edx}z`; + } }, draw_bin = bin => { if (extract_bin(bin)) { if (show_text) { @@ -80161,7 +80438,7 @@ class TH1Painter extends TH1Painter$2 { if (reason === 'resize') { if (is_main && main.resize3D()) main.render3D(); } else { - this.deleteAttr(); + this.createHistDrawAttributes(true); this.scanContent(true); // may be required for axis drawings @@ -80431,7 +80708,7 @@ class TH2Painter extends TH2Painter$2 { if (logz && (this.zmin <= 0)) this.zmin = this.zmax * 1e-5; - this.deleteAttr(); + this.createHistDrawAttributes(true); if (is_main) { assignFrame3DMethods(main); @@ -80542,6 +80819,7 @@ function proivdeEvalPar(obj, check_save) { .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, ') @@ -80625,14 +80903,20 @@ function _getTF1Save(func, x) { * @desc First try evaluate, if not possible - check saved buffer * @private */ function getTF1Value(func, x, skip_eval = undefined) { - let y = 0; + let y = 0, iserr = false; if (!func) return 0; - if (!skip_eval && !func.evalPar) - proivdeEvalPar(func); + if (!skip_eval && !func.evalPar) { + try { + if (!proivdeEvalPar(func)) + iserr = true; + } catch { + iserr = true; + } + } - if (func.evalPar) { + if (func.evalPar && !iserr) { try { y = func.evalPar(x); return y; @@ -80749,8 +81033,14 @@ class TF1Painter extends TH1Painter$2 { const np = Math.max(tf1.fNpx, 100); let iserror = false; - if (!tf1.evalPar && !proivdeEvalPar(tf1)) - iserror = true; + if (!tf1.evalPar) { + try { + if (!proivdeEvalPar(tf1)) + iserror = true; + } catch { + iserror = true; + } + } ensureBins(np); @@ -95473,6 +95763,18 @@ function addUserStreamer(type, user_streamer) { CustomStreamers[type] = user_streamer; } +function getTDatimeDate() { + const res = new Date(); + res.setFullYear((this.fDatime >>> 26) + 1995); + res.setMonth(((this.fDatime << 6) >>> 28) - 1); + res.setDate((this.fDatime << 10) >>> 27); + res.setHours((this.fDatime << 15) >>> 27); + res.setMinutes((this.fDatime << 20) >>> 26); + res.setSeconds((this.fDatime << 26) >>> 26); + res.setMilliseconds(0); + return res; +} + /** @summary these are streamers which do not handle version regularly * @desc used for special classes like TRef or TBasket @@ -95486,17 +95788,7 @@ const DirectStreamers = { TDatime(buf, obj) { obj.fDatime = buf.ntou4(); - // obj.GetDate = function() { - // let res = new Date(); - // res.setFullYear((this.fDatime >>> 26) + 1995); - // res.setMonth((this.fDatime << 6) >>> 28); - // res.setDate((this.fDatime << 10) >>> 27); - // res.setHours((this.fDatime << 15) >>> 27); - // res.setMinutes((this.fDatime << 20) >>> 26); - // res.setSeconds((this.fDatime << 26) >>> 26); - // res.setMilliseconds(0); - // return res; - // } + obj.getDate = getTDatimeDate; }, TKey(buf, key) { @@ -97731,8 +98023,10 @@ class TFile { const progress_offest = sum1 / sum_total, progress_this = (sum2 - sum1) / sum_total; xhr.addEventListener('progress', oEvent => { - if (oEvent.lengthComputable) - progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total); + if (oEvent.lengthComputable) { + if (progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total) === 'break') + xhr.abort(); + } }); } else if (first_block_retry && isFunc(xhr.addEventListener)) { xhr.addEventListener('progress', oEvent => { @@ -100172,7 +100466,7 @@ class TDrawSelector extends TSelector { const now = new Date().getTime(); if (now - this.lasttm > this.monitoring) { this.lasttm = now; - if (this.progress_callback) + if (isFunc(this.progress_callback)) this.progress_callback(this.hist); } } @@ -100993,7 +101287,7 @@ async function treeProcess(tree, selector, args) { const portion = (handle.staged_prev + value * (handle.staged_now - handle.staged_prev)) / (handle.process_max - handle.process_min); - handle.selector.ShowProgress(portion); + return handle.selector.ShowProgress(portion); } function ProcessBlobs(blobs, places) { @@ -101152,7 +101446,10 @@ async function treeProcess(tree, selector, args) { if (handle.process_max > handle.process_min) portion = (handle.staged_prev - handle.process_min) / (handle.process_max - handle.process_min); - handle.selector.ShowProgress(portion); + if (handle.selector.ShowProgress(portion) === 'break') { + handle.selector.Terminate(true); + return resolveFunc(handle.selector); + } handle.progress_showtm = new Date().getTime(); @@ -101906,7 +102203,10 @@ async function draw(dom, obj, opt) { promise = getPromise(handle.func(dom, obj, opt)); return promise.then(p => { - if (!painter) painter = 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) @@ -102994,19 +103294,19 @@ class HierarchyPainter extends BasePainter { /** @summary Create file hierarchy * @private */ - fileHierarchy(file) { - const painter = this, - - folder = { - _name: file.fFileName, - _title: (file.fTitle ? file.fTitle + ', path: ' : '') + file.fFullURL + `, size: ${getSizeStr(file.fEND)}`, - _kind: kindTFile, - _file: file, - _fullurl: file.fFullURL, - _localfile: file.fLocalFile, - _had_direct_read: false, - // this is central get method, item or itemname can be used, returns promise - _get(item, itemname) { + fileHierarchy(file, folder) { + const painter = this; + if (!folder) folder = {}; + + folder._name = file.fFileName; + folder._title = (file.fTitle ? file.fTitle + ', path: ' : '') + file.fFullURL + `, size: ${getSizeStr(file.fEND)}, modified: ${convertDate(file.fDatimeM.getDate())}`; + folder._kind = kindTFile; + folder._file = file; + folder._fullurl = file.fFullURL; + folder._localfile = file.fLocalFile; + folder._had_direct_read = false; + // this is central get method, item or itemname can be used, returns promise + folder._get = function(item, itemname) { if (item?._readobj) return Promise.resolve(item._readobj); @@ -103051,8 +103351,7 @@ class HierarchyPainter extends BasePainter { if (this._localfile) return openFile(this._localfile).then(f => readFileObject(f)); if (this._fullurl) return openFile(this._fullurl).then(f => readFileObject(f)); return Promise.resolve(null); - } - }; + }; keysHierarchy(folder, file.fKeys, file, ''); @@ -103538,7 +103837,7 @@ class HierarchyPainter extends BasePainter { } const pr = this.expandItem(this.itemFullName(hitem)); - if (isPromise(pr)) + if (isPromise(pr) && isObject(promises)) promises.push(pr); if (hitem._childs !== undefined) hitem._isopen = true; return hitem._isopen; @@ -104834,6 +105133,7 @@ class HierarchyPainter extends BasePainter { if (isfileopened) return; return httpRequest(filepath, 'object').then(res => { + if (!res) return; const h1 = { _jsonfile: filepath, _kind: prROOT + res._typename, _jsontmp: res, _name: filepath.split('/').pop() }; if (res.fTitle) h1._title = res.fTitle; h1._get = function(item /* ,itemname */) { @@ -104874,6 +105174,18 @@ class HierarchyPainter extends BasePainter { } } + /** @summary Find ROOT file which corresponds to provided item name + * @private */ + findRootFileForItem(itemname) { + let item = this.findItem(itemname); + while (item) { + if ((item._kind === kindTFile) && item._fullurl && item._file) + return item; + item = item?._parent; + } + return null; + } + /** @summary Open ROOT file * @param {string} filepath - URL to ROOT file, argument for openFile * @return {Promise} when file is opened */ @@ -104910,6 +105222,61 @@ class HierarchyPainter extends BasePainter { }).finally(() => showProgress()); } + /** @summary Create list of files for specified directory */ + async listServerDir(dirname) { + return httpRequest(dirname, 'text').then(res => { + if (!res) return false; + const h = { _name: 'Files', _kind: kTopFolder, _childs: [], _isopen: true }; + let p = 0; + while (p < res.length) { + p = res.indexOf('a href="', p+1); + if (p < 0) break; + p += 8; + const p2 = res.indexOf('"', p+1); + if (p2 < 0) break; + + const fname = res.slice(p, p2); + p = p2 + 1; + if ((fname.lastIndexOf('.root') === fname.length - 5) && (fname.length > 5)) { + h._childs.push({ + _name: fname, _title: dirname + fname, _url: dirname + fname, _kind: kindTFile, + _click_action: 'expand', _more: true, _obj: {}, + _expand: item => { + return openFile(item._url).then(file => { + if (!file) return false; + delete item._exapnd; + delete item._more; + delete item._click_action; + delete item._obj; + item._isopen = true; + this.fileHierarchy(file, item); + this.updateTreeNode(item); + }); + } + }); + } else if (((fname.lastIndexOf('.json.gz') === fname.length - 8) && (fname.length > 8)) || + ((fname.lastIndexOf('.json') === fname.length - 5) && (fname.length > 5))) { + h._childs.push({ + _name: fname, _title: dirname + fname, _jsonfile: dirname + fname, _can_draw: true, + _get: item => { + return httpRequest(item._jsonfile, 'object').then(res => { + if (res) { + item._kind = prROOT + res._typename; + item._jsontmp = res; + this.updateTreeNode(item); + } + return res; + }); + } + }); + } + } + if (h._childs.length > 0) + this.h = h; + return true; + }); + } + /** @summary Apply loaded TStyle object * @desc One also can specify item name of JSON file name where style is loaded * @param {object|string} style - either TStyle object of item name where object can be load */ @@ -105534,6 +105901,7 @@ class HierarchyPainter extends BasePainter { let prereq = getOption('prereq') || '', load = getOption('load'), + dir = getOption('dir'), inject = getOption('inject'), filesarr = getOptionAsArray('#file;files'), itemsarr = getOptionAsArray('#item;items'), @@ -105648,7 +106016,9 @@ class HierarchyPainter extends BasePainter { promise = this.openJsonFile(jsonarr.shift()); else if (filesarr.length > 0) promise = this.openRootFile(filesarr.shift()); - else if (expanditems.length > 0) + else if (dir) { + promise = this.listServerDir(dir); dir = ''; + } else if (expanditems.length > 0) promise = this.expandItem(expanditems.shift()); else if (style.length > 0) promise = this.applyStyle(style.shift()); @@ -106099,10 +106469,13 @@ function readStyleFromURL(url) { const d = decodeUrl(url); - function get_bool(name, field) { + function get_bool(name, field, special) { if (d.has(name)) { const val = d.get(name); - settings[field] = (val !== '0') && (val !== 'false') && (val !== 'off'); + if (special && (val === special)) + settings[field] = special; + else + settings[field] = (val !== '0') && (val !== 'false') && (val !== 'off'); } } @@ -106176,7 +106549,11 @@ function readStyleFromURL(url) { settings.Latex = constants$1.Latex.fromString(latex); if (d.has('nomenu')) settings.ContextMenu = false; - if (d.has('noprogress')) settings.ProgressBox = false; + if (d.has('noprogress')) + settings.ProgressBox = false; + else + get_bool('progress', 'ProgressBox', 'modal'); + if (d.has('notouch')) browser.touches = false; if (d.has('adjframe')) settings.CanAdjustFrame = true; @@ -106228,6 +106605,14 @@ function readStyleFromURL(url) { get_int_style('optdate', 'fOptDate', 1); get_int_style('optfile', 'fOptFile', 1); get_int_style('opttitle', 'fOptTitle', 1); + if (d.has('utc')) + settings.TimeZone = 'UTC'; + else if (d.has('timezone')) { + settings.TimeZone = d.get('timezone'); + if ((settings.TimeZone === 'default') || (settings.TimeZone === 'dflt')) + settings.TimeZone = ''; + } + gStyle.fStatFormat = d.get('statfmt', gStyle.fStatFormat); gStyle.fFitFormat = d.get('fitfmt', gStyle.fFitFormat); } @@ -106273,7 +106658,7 @@ async function buildGUI(gui_element, gui_kind = '') { else { select('html').style('height', '100%'); select('body').style('min-height', '100%').style('margin', 0).style('overflow', 'hidden'); - myDiv.style('position', 'absolute').style('inset', '0px').style('padding', '1px'); + myDiv.style('position', 'absolute').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0).style('padding', '1px'); } } @@ -106967,9 +107352,12 @@ let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { if (d.check('POS3D_', true)) res.pos3d = d.partAsInt() - 0.5; - res._pfc = d.check('PFC'); - res._plc = d.check('PLC'); - res._pmc = d.check('PMC'); + if (d.check('PFC') && !res._pfc) + res._pfc = 2; + if (d.check('PLC') && !res._plc) + res._plc = 2; + if (d.check('PMC') && !res._pmc) + res._pmc = 2; if (d.check('A')) res.Axis = d.check('I') ? 'A;' : _a; // I means invisible axis if (d.check('X+')) { res.Axis += 'X+'; res.second_x = has_main; } @@ -107686,6 +108074,28 @@ let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { console.log('Load ./hist/TGraphPainter.mjs to draw graph in 3D'); } + /** @summary Create necessary histogram draw attributes */ + createGraphDrawAttributes(only_check_auto) { + const graph = this.getGraph(), o = this.options; + if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { + const pp = this.getPadPainter(); + if (isFunc(pp?.getAutoColor)) { + const icolor = pp.getAutoColor(graph.$num_graphs); + this._auto_exec = ''; // can be reused when sending option back to server + if (o._pfc > 1) { o._pfc = 1; graph.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } + if (o._plc > 1) { o._plc = 1; graph.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } + if (o._pmc > 1) { o._pmc = 1; graph.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } + } + } + + if (only_check_auto) + this.deleteAttr(); + else { + this.createAttLine({ attr: graph, can_excl: true }); + this.createAttFill({ attr: graph }); + } + } + /** @summary draw TGraph */ drawGraph() { const pmain = this.get_main(), @@ -107703,19 +108113,7 @@ let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { this.createG(!pmain.pad_layer); - if (this.options._pfc || this.options._plc || this.options._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(); - if (this.options._pfc) { graph.fFillColor = icolor; delete this.fillatt; } - if (this.options._plc) { graph.fLineColor = icolor; delete this.lineatt; } - if (this.options._pmc) { graph.fMarkerColor = icolor; delete this.markeratt; } - this.options._pfc = this.options._plc = this.options._pmc = false; - } - } - - this.createAttLine({ attr: graph, can_excl: true }); - this.createAttFill({ attr: graph }); + this.createGraphDrawAttributes(); this.fillatt.used = false; // mark used only when really used @@ -108151,6 +108549,14 @@ let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { this.submitCanvExec(exec); } + /** @summary Fill option object used in TWebCanvas */ + fillWebObjectOptions(res) { + if (this._auto_exec && res) { + res.fcust = 'auto_exec:' + this._auto_exec; + delete this._auto_exec; + } + } + /** @summary Fill context menu */ fillContextMenuItems(menu) { if (!this.snapid) @@ -108173,8 +108579,8 @@ let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { if (method.fName === 'InsertPoint') { if (pnt) { const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - userx = funcs.revertAxis('x', pnt.x) ?? 0, - usery = funcs.revertAxis('y', pnt.y) ?? 0; + userx = funcs.revertAxis('x', pnt.x) ?? 0, + usery = funcs.revertAxis('y', pnt.y) ?? 0; this.submitCanvExec(`AddPoint(${userx.toFixed(3)}, ${usery.toFixed(3)})`, method.$execid); } } else if (method.$execid && (hint?.binindx !== undefined)) @@ -108197,6 +108603,23 @@ let TGraphPainter$1 = class TGraphPainter extends ObjectPainter { graph.fNpoints = obj.fNpoints; graph.fMinimum = obj.fMinimum; graph.fMaximum = obj.fMaximum; + + const o = this.options; + + if (this.snapid !== undefined) + o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas + + if (!o._pfc) + graph.fFillColor = obj.fFillColor; + graph.fFillStyle = obj.fFillStyle; + if (!o._plc) + graph.fLineColor = obj.fLineColor; + graph.fLineStyle = obj.fLineStyle; + graph.fLineWidth = obj.fLineWidth; + if (!o._pmc) + graph.fMarkerColor = obj.fMarkerColor; + graph.fMarkerSize = obj.fMarkerSize; + graph.fMarkerStyle = obj.fMarkerStyle; } /** @summary Update TGraph object */ @@ -108363,6 +108786,8 @@ class TGraphPainter extends TGraphPainter$1 { if (fp.zoom_xmin !== fp.zoom_xmax) if ((this.options.pos3d < fp.zoom_xmin) || (this.options.pos3d > fp.zoom_xmax)) return; + this.createGraphDrawAttributes(true); + const drawbins = this.optimizeBins(1000); let first = 0, last = drawbins.length-1; @@ -108491,60 +108916,38 @@ async function drawPolyMarker3D$1() { }); } -function treeShowProgress(handle, str) { - if (isBatchMode() || (typeof document === 'undefined')) return; - - if (!str) - return showProgress(); - - const main_box = document.createElement('p'), - text_node = document.createTextNode(str); - - main_box.appendChild(text_node); - main_box.title = 'Click on element to break'; - - main_box.onclick = () => { - if (!handle._break) handle._break = 0; - - if (++handle._break < 3) { - main_box.title = 'Will break after next I/O operation'; - text_node.nodeValue = 'Breaking ... '; - return; - } - if (isFunc(handle.Abort)) - handle.Abort(); - showProgress(); - }; - - showProgress(main_box); -} - - /** @summary Show TTree::Draw progress during processing * @private */ TDrawSelector.prototype.ShowProgress = function(value) { + let msg, ret; if ((value === undefined) || !Number.isFinite(value)) - return showProgress(); - - 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; - } + 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; + 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; + 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)} % `; + } - treeShowProgress(this, `TTree draw ${(value * 100).toFixed(ndig)} % `); + showProgress(msg, -1, () => { this._break = 1; }); + return ret; }; /** @summary Draw result of tree drawing @@ -108605,18 +109008,21 @@ async function treeDrawProgress(obj, final) { if (!this.last_pr) this.last_pr = Promise.resolve(true); - return this.last_pr.then(() => { - if (this.obj_painter) - this.last_pr = this.obj_painter.redrawObject(obj).then(() => this.obj_painter); - else { - this.last_pr = drawTreeDrawResult(this.drawid, obj).then(p => { - this.obj_painter = p; - if (!final) this.last_pr = null; - return p; // return painter for histogram - }); - } + return this.last_pr.then(() => { + if (this.obj_painter) + this.last_pr = 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).then(p => { + this.obj_painter = p; + if (!final) this.last_pr = null; + return p; // return painter for histogram + }); + } - return final ? this.last_pr : null; + return final ? this.last_pr : null; }); } @@ -108949,7 +109355,7 @@ async function drawTree(dom, obj, opt) { let pr; if (args.expr === 'testio') { args.testio = true; - args.showProgress = msg => treeShowProgress(args, msg); + args.showProgress = msg => showProgress(msg, -1, () => { args._break = 1; }); pr = treeIOTest(tree, args); } else if (args.expr || args.branch) pr = treeDraw(tree, args); @@ -109003,12 +109409,26 @@ class THStackPainter extends ObjectPainter { lst.Add(clone(stack.fHists.arr[0]), stack.fHists.opt[0]); for (let i = 1; i < nhists; ++i) { const hnext = clone(stack.fHists.arr[i]), - hnextopt = stack.fHists.opt[i], - hprev = lst.arr[i-1]; - - if ((hnext.fNbins !== hprev.fNbins) || - (hnext.fXaxis.fXmin !== hprev.fXaxis.fXmin) || - (hnext.fXaxis.fXmax !== hprev.fXaxis.fXmax)) { + hnextopt = stack.fHists.opt[i], + hprev = lst.arr[i-1], + xnext = hnext.fXaxis, xprev = hprev.fXaxis; + + let match = (xnext.fNbins === xprev.fNbins) && + (xnext.fXmin === xprev.fXmin) && + (xnext.fXmax === xprev.fXmax); + + if (!match && (xnext.fNbins > 0) && (xnext.fNbins < xprev.fNbins) && (xnext.fXmin === xprev.fXmin) && + (Math.abs((xnext.fXmax - xnext.fXmin)/xnext.fNbins - (xprev.fXmax - xprev.fXmin)/xprev.fNbins) < 0.0001)) { + // simple extension of histogram to make sum + const arr = new Array(hprev.fNcells).fill(0); + for (let n = 1; n <= xnext.fNbins; ++n) + arr[n] = hnext.fArray[n]; + hnext.fNcells = hprev.fNcells; + Object.assign(xnext, xprev); + hnext.fArray = arr; + match = true; + } + if (!match) { console.warn(`When drawing THStack, cannot sum-up histograms ${hnext.fName} and ${hprev.fName}`); lst.Clear(); return false; @@ -109131,6 +109551,8 @@ class THStackPainter extends ObjectPainter { hopt += ' ' + this.options.hopt; if (this.options.draw_errors && !hopt) hopt = 'E'; + if (!this.options.pads) + hopt += ' same nostat' + this.options.auto; return hopt; } @@ -109147,17 +109569,6 @@ class THStackPainter extends ObjectPainter { subid = this.options.nostack ? `hists_${rindx}` : `stack_${rindx}`, hist = hlst.arr[rindx], hopt = this.getHistDrawOption(hist, hlst.opt[rindx]); - let exec = ''; - - if (this.options._pfc || this.options._plc || this.options._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(nhists); - if (this.options._pfc) { hist.fFillColor = icolor; exec += `SetFillColor(${icolor});;`; } - if (this.options._plc) { hist.fLineColor = icolor; exec += `SetLineColor(${icolor});;`; } - if (this.options._pmc) { hist.fMarkerColor = icolor; exec += `SetMarkerColor(${icolor});;`; } - } - } // handling of 'pads' draw option if (pad_painter) { @@ -109170,7 +109581,6 @@ class THStackPainter extends ObjectPainter { return this.hdraw_func(subpad_painter.getDom(), hist, hopt).then(subp => { if (subp) { subp.setSecondaryId(this, subid); - subp._auto_exec = exec; this.painters.push(subp); } subpad_painter.selectCurrentPad(prev_name); @@ -109182,8 +109592,11 @@ class THStackPainter extends ObjectPainter { // also used to provide tooltips if ((rindx > 0) && !this.options.nostack) hist.$baseh = hlst.arr[rindx - 1]; + // this number used for auto colors creation + if (this.options.auto) + hist.$num_histos = nhists; - return this.hdraw_func(this.getDom(), hist, hopt + ' same nostat').then(subp => { + return this.hdraw_func(this.getDom(), hist, hopt).then(subp => { subp.setSecondaryId(this, subid); this.painters.push(subp); return this.drawNextHisto(indx+1, pad_painter); @@ -109193,7 +109606,7 @@ class THStackPainter extends ObjectPainter { /** @summary Decode draw options of THStack painter */ decodeOptions(opt) { if (!this.options) this.options = {}; - Object.assign(this.options, { ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '' }); + Object.assign(this.options, { ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '', auto: '' }); const stack = this.getObject(), hist = stack.fHistogram || (stack.fHists ? stack.fHists.arr[0] : null) || (stack.fStack ? stack.fStack.arr[0] : null), @@ -109227,9 +109640,7 @@ class THStackPainter extends ObjectPainter { d.check('NOCLEAR'); // ignore noclear option - this.options._pfc = d.check('PFC'); - this.options._plc = d.check('PLC'); - this.options._pmc = d.check('PMC'); + ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) this.options.auto += ' ' + f; }); this.options.pads = d.check('PADS'); if (this.options.pads) this.options.nostack = true; @@ -109329,10 +109740,11 @@ class THStackPainter extends ObjectPainter { nhists = hlst?.arr?.length ?? 0; if (nhists !== this.painters.length) { + this.did_update = 1; this.getPadPainter()?.cleanPrimitives(objp => this.painters.indexOf(objp) >= 0); this.painters = []; - this.did_update = true; } else { + this.did_update = 2; for (let indx = 0; indx < nhists; ++indx) { const rindx = this.options.horder ? indx : nhists - indx - 1, hist = hlst.arr[rindx]; @@ -109345,10 +109757,18 @@ class THStackPainter extends ObjectPainter { /** @summary Redraw THStack * @desc Do something if previous update had changed number of histograms */ - redraw() { - if (this.did_update) { + redraw(reason) { + if (this.did_update === 1) { delete this.did_update; return this.drawNextHisto(0, this.options.pads ? this.getPadPainter() : null); + } else if (this.did_update === 2) { + delete this.did_update; + const redrawSub = indx => { + if (indx >= this.painters.length) + return Promise.resolve(this); + return this.painters[indx].redraw(reason).then(() => redrawSub(indx+1)); + }; + return redrawSub(0); } } @@ -111906,6 +112326,8 @@ __proto__: null, TScatterPainter: TScatterPainter }); +const kLineNDC = BIT(14); + class TLinePainter extends ObjectPainter { /** @summary Start interactive moving */ @@ -111944,14 +112366,16 @@ class TLinePainter extends ObjectPainter { /** @summary Calculate line coordinates */ prepareDraw() { - const line = this.getObject(), kLineNDC = BIT(14); + const line = this.getObject(); this.isndc = line.TestBit(kLineNDC); - this.x1 = this.axisToSvg('x', line.fX1, this.isndc, true); - this.y1 = this.axisToSvg('y', line.fY1, this.isndc, true); - this.x2 = this.axisToSvg('x', line.fX2, this.isndc, true); - this.y2 = this.axisToSvg('y', line.fY2, this.isndc, true); + const func = this.getAxisToSvgFunc(this.isndc, true, 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); this.createAttLine({ attr: line }); } @@ -111967,10 +112391,10 @@ class TLinePainter extends ObjectPainter { /** @summary Redraw line */ redraw() { - this.prepareDraw(); - this.createG(); + this.prepareDraw(); + const elem = this.draw_g.append('svg:path') .attr('d', this.createPath()) .call(this.lineatt.func); @@ -112010,10 +112434,16 @@ class TRatioPlotPainter extends ObjectPainter { if (xmin === xmax) { const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad)?.getFramePainter()?.x_handle; if (!x_handle) return; - xmin = x_handle.full_min; - xmax = x_handle.full_max; + 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; @@ -112033,20 +112463,18 @@ class TRatioPlotPainter extends ObjectPainter { low_p = pp.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad), low_main = low_p?.getMainPainter(), low_fp = low_p?.getFramePainter(); - let lbl_size = 20, promise_up = Promise.resolve(true); + 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 - lbl_size = up_main.getHisto().fYaxis.fLabelSize; - if (lbl_size < 1) lbl_size = Math.round(lbl_size*Math.min(up_p.getPadWidth(), up_p.getPadHeight())); + 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 - h.fYaxis.fLabelSize = lbl_size; - h.fYaxis.fTitleSize = lbl_size; up_p.getRootPad().fTicky = 1; @@ -112070,7 +112498,7 @@ class TRatioPlotPainter extends ObjectPainter { this._ratio_low_fp.fX2NDC = this.fX2NDC; this._ratio_low_fp.o_sizeChanged(); }; - return true; + return this; }); } @@ -112082,10 +112510,9 @@ class TRatioPlotPainter extends ObjectPainter { low_main.options.Axis = 0; // draw both axes const h = low_main.getHisto(); h.fXaxis.fTitle = 'x'; - h.fXaxis.fLabelSize = lbl_size; - h.fXaxis.fTitleSize = lbl_size; - h.fYaxis.fLabelSize = lbl_size; - h.fYaxis.fTitleSize = lbl_size; + + h.fXaxis.$use_top_pad = true; + h.fYaxis.$use_top_pad = true; low_p.getRootPad().fTicky = 1; low_p.forEachPainterInPad(objp => { @@ -112200,7 +112627,7 @@ let TMultiGraphPainter$2 = class TMultiGraphPainter extends ObjectPainter { const ngr = Math.min(graphs.arr.length, this.painters.length); for (let i = 0; i < ngr; ++i) { - if (this.painters[i].updateObject(graphs.arr[i], graphs.opt[i])) + if (this.painters[i].updateObject(graphs.arr[i], (graphs.opt[i] || this._restopt) + this._auto)) isany = true; } @@ -112376,37 +112803,27 @@ let TMultiGraphPainter$2 = class TMultiGraphPainter extends ObjectPainter { } /** @summary method draws next graph */ - async drawNextGraph(indx, opt) { + async drawNextGraph(indx) { const graphs = this.getObject().fGraphs; - let exec = ''; // at the end of graphs drawing draw functions (if any) - if (indx >= graphs.arr.length) { - this._pfc = this._plc = this._pmc = false; // disable auto coloring at the end + if (indx >= graphs.arr.length) return this; - } - const gr = graphs.arr[indx], o = graphs.opt[indx] || opt || ''; + const gr = graphs.arr[indx], + draw_opt = (graphs.opt[indx] || this._restopt) + this._auto; - // if there is auto colors assignment, try to provide it - if (this._pfc || this._plc || this._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(graphs.arr.length); - if (this._pfc) { gr.fFillColor = icolor; exec += `SetFillColor(${icolor});;`; } - if (this._plc) { gr.fLineColor = icolor; exec += `SetLineColor(${icolor});;`; } - if (this._pmc) { gr.fMarkerColor = icolor; exec += `SetMarkerColor(${icolor});;`; } - } - } + // used in automatic colors numbering + if (this._auto) + gr.$num_graphs = graphs.arr.length; - return this.drawGraph(gr, o, graphs.arr.length - indx).then(subp => { + return this.drawGraph(gr, draw_opt, graphs.arr.length - indx).then(subp => { if (subp) { subp.setSecondaryId(this, `graphs_${indx}`); this.painters.push(subp); - subp._auto_exec = exec; } - return this.drawNextGraph(indx+1, opt); + return this.drawNextGraph(indx+1); }); } @@ -112416,14 +112833,15 @@ let TMultiGraphPainter$2 = class TMultiGraphPainter extends ObjectPainter { const d = new DrawOptions(opt); painter._3d = d.check('3D'); - painter._pfc = d.check('PFC'); - painter._plc = d.check('PLC'); - painter._pmc = d.check('PMC'); + painter._auto = ''; // extra options for auto colors + ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) painter._auto += ' ' + f; }); let hopt = ''; if (d.check('FB') && painter._3d) hopt += 'FB'; // will be directly combined with LEGO PadDrawOptions.forEach(name => { if (d.check(name)) hopt += ';' + name; }); + painter._restopt = d.remain(); + let promise = Promise.resolve(true); if (d.check('A') || !painter.getMainPainter()) { const mgraph = painter.getObject(), @@ -112437,7 +112855,7 @@ let TMultiGraphPainter$2 = class TMultiGraphPainter extends ObjectPainter { return promise.then(() => { painter.addToPadPrimitives(); - return painter.drawNextGraph(0, d.remain()); + return painter.drawNextGraph(0); }).then(() => { const handler = new FunctionsHandler(painter, painter.getPadPainter(), painter.getObject().fFunctions, true); return handler.drawNext(0); // returns painter @@ -113826,15 +114244,30 @@ class TGaxisPainter extends TAxisPainter { } /** @summary Check if there is function for TGaxis can be found */ - checkFuncion() { + async checkFuncion() { const gaxis = this.getObject(); - if (!gaxis.fFunctionName) + if (!gaxis.fFunctionName) { this.axis_func = null; - else - this.axis_func = this.getPadPainter()?.findInPrimitives(gaxis.fFunctionName, clTF1); + return; + } + const func = this.getPadPainter()?.findInPrimitives(gaxis.fFunctionName, clTF1); - if (this.axis_func) - proivdeEvalPar(this.axis_func); + 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) + proivdeEvalPar(f); + }); } /** @summary Create handle for custom function in the axis */ @@ -113891,9 +114324,8 @@ class TGaxisPainter extends TAxisPainter { return ensureTCanvas(painter, false).then(() => { if (opt) painter.convertTo(opt); - painter.checkFuncion(); - return painter.redraw(); - }); + return painter.checkFuncion(); + }).then(() => painter.redraw()); } } // class TGaxisPainter @@ -116064,7 +116496,7 @@ class RFramePainter extends RObjectPainter { log: this.swap_xy ? this.logx : this.logy, symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < 0.01*this.ymax) ? 0.3 * opts.ymin_nz : 0, + 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'); @@ -117317,7 +117749,7 @@ class RPadPainter extends RObjectPainter { .style('width', '100%') .style('height', '100%') .style('position', 'absolute') - .style('inset', '0px'); + .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); } svg.style('filter', settings.DarkMode ? 'invert(100%)' : null); @@ -118523,6 +118955,295 @@ class RPadPainter extends RObjectPainter { } // class RPadPainter +/** + * [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 = 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) { + // rightrotate + 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(); +} + +// secret session key used for hashing connections keys +// only if set, all messages from and to server signed with HMAC hash +let sessionKey = ''; + +/** @summary HMAC implementation + * @desc see https://en.wikipedia.org/wiki/HMAC for more details + * @private */ +function HMAC(key, m, o) { + const kbis = sha256(sessionKey + key), + block_size = 64, + opad = 0x5c, ipad = 0x36, + ko = [], ki = []; + while (kbis.length < block_size) + kbis.push(0); + for (let i = 0; i < kbis.length; ++i) { + const code = kbis[i]; + ko.push(code ^ opad); + ki.push(code ^ ipad); + } + + const hash = sha256_2(ki, (o === undefined) ? m : new Uint8Array(m, o)); + + return sha256_2(ko, hash, true); +} + /** * @summary Class emulating web socket with long-poll http requests * @@ -118531,12 +119252,13 @@ class RPadPainter extends RObjectPainter { class LongPollSocket { - constructor(addr, _raw, _args) { + constructor(addr, _raw, _handle, _counter) { this.path = addr; this.connid = null; this.req = null; this.raw = _raw; - this.args = _args; + this.handle = _handle; + this.counter = _counter; this.nextRequest('', 'connect'); } @@ -118546,18 +119268,20 @@ class LongPollSocket { let url = this.path, reqmode = 'buf', post = null; if (kind === 'connect') { url += this.raw ? '?raw_connect' : '?txt_connect'; - if (this.args) url += '&' + this.args; + if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); console.log(`longpoll connect ${url} raw = ${this.raw}`); this.connid = 'connect'; } else if (kind === 'close') { if ((this.connid === null) || (this.connid === 'close')) return; url += `?connection=${this.connid}&close`; + if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); this.connid = 'close'; reqmode = 'text;sync'; // use sync mode to close connection before browser window closed } else if ((this.connid === null) || (typeof this.connid !== 'number')) { if (!browser.qt5) console.error('No connection'); } else { url += '?connection=' + this.connid; + if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); if (kind === 'dummy') url += '&dummy'; } @@ -118766,6 +119490,8 @@ class WebWindowHandle { this.credits = credits || 10; this.cansend = this.credits; this.ackn = this.credits; + this.send_seq = 1; // sequence counter of send messages + this.recv_seq = 0; // sequence counter of received messages } /** @summary Returns arguments specified in the RWebWindow::SetUserArgs() method @@ -118911,11 +119637,15 @@ class WebWindowHandle { if (this.cansend <= 0) console.error(`should be queued before sending cansend: ${this.cansend}`); - const prefix = `${this.ackn}:${this.cansend}:${chid}:`; + const prefix = `${this.send_seq++}:${this.ackn}:${this.cansend}:${chid}:`; this.ackn = 0; this.cansend--; // decrease number of allowed send packets - this._websocket.send(prefix + msg); + let hash = 'none'; + if (this.key && sessionKey) + hash = HMAC(this.key, `${prefix}${msg}`); + + this._websocket.send(`${hash}:${prefix}${msg}`); if ((this.kind === 'websocket') || (this.kind === 'longpoll')) { if (this.timerid) clearTimeout(this.timerid); @@ -119007,9 +119737,14 @@ class WebWindowHandle { setHRef(path) { if (isStr(path) && (path.indexOf('?') > 0)) { this.href = path.slice(0, path.indexOf('?')); - this.key = decodeUrl(path).get('key'); - } else + const d = decodeUrl(path); + this.key = d.get('key'); + this.token = d.get('token'); + } else { this.href = path; + delete this.key; + delete this.token; + } } /** @summary Return href part @@ -119028,17 +119763,28 @@ class WebWindowHandle { return addr; } + /** @summary provide connection args for the web socket + * @private */ + getConnArgs(ntry) { + let args = ''; + if (this.key) { + const k = HMAC(this.key, `attempt_${ntry}`); + args += `key=${k}&ntry=${ntry}`; + } + if (this.token) { + if (args) args += '&'; + args += `token=${this.token}`; + } + return args; + } + /** @summary Create configured socket for current object. * @private */ connect(href) { this.close(); if (!href && this.href) href = this.href; - let ntry = 0, args = (this.key ? ('key=' + this.key) : ''); - if (this.token) { - if (args) args += '&'; - args += 'token=' + this.token; - } + let ntry = 0; const retry_open = first_time => { if (this.state !== 0) return; @@ -119070,13 +119816,13 @@ class WebWindowHandle { console.log(`configure protocol log ${path}`); } else if ((this.kind === 'websocket') && first_time) { path = path.replace('http://', 'ws://').replace('https://', 'wss://') + 'root.websocket'; - if (args) path += '?' + args; + path += '?' + this.getConnArgs(ntry); console.log(`configure websocket ${path}`); this._websocket = new WebSocket(path); } else { path += 'root.longpoll'; console.log(`configure longpoll ${path}`); - this._websocket = new LongPollSocket(path, (this.kind === 'rawlongpoll'), args); + this._websocket = new LongPollSocket(path, (this.kind === 'rawlongpoll'), this, ntry); } if (!this._websocket) return; @@ -119094,18 +119840,39 @@ class WebWindowHandle { let msg = e.data; if (this.next_binary) { - const binchid = this.next_binary; + const binchid = this.next_binary, + server_hash = this.next_binary_hash; delete this.next_binary; + delete this.next_binary_hash; if (msg instanceof Blob) { // convert Blob object to BufferArray const reader = new FileReader(), qitem = this.reserveQueueItem(); // The file's text will be printed here - reader.onload = event => this.markQueueItemDone(qitem, event.target.result, 0); + reader.onload = event => { + let result = event.target.result; + if (this.key && sessionKey) { + const hash = HMAC(this.key, result, 0); + if (hash !== server_hash) { + console.log('Discard binary buffer because of HMAC mismatch'); + result = new ArrayBuffer(0); + } + } + + this.markQueueItemDone(qitem, result, 0); + }; reader.readAsArrayBuffer(msg, e.offset || 0); } else { // this is from CEF or LongPoll handler - this.provideData(binchid, msg, e.offset || 0); + let result = msg; + if (this.key && sessionKey) { + const hash = HMAC(this.key, result, e.offset || 0); + if (hash !== server_hash) { + console.log('Discard binary buffer because of HMAC mismatch'); + result = new ArrayBuffer(0); + } + } + this.provideData(binchid, result, e.offset || 0); } return; @@ -119114,17 +119881,34 @@ class WebWindowHandle { if (!isStr(msg)) return console.log(`unsupported message kind: ${typeof msg}`); - const i1 = msg.indexOf(':'), - credit = parseInt(msg.slice(0, i1)), + const i0 = msg.indexOf(':'), + server_hash = msg.slice(0, i0), + i1 = msg.indexOf(':', i0 + 1), + seq_id = Number.parseInt(msg.slice(i0 + 1, i1)), i2 = msg.indexOf(':', i1 + 1), - // cansend = parseInt(msg.slice(i1 + 1, i2)), // TODO: take into account when sending messages + credit = Number.parseInt(msg.slice(i1 + 1, i2)), i3 = msg.indexOf(':', i2 + 1), - chid = parseInt(msg.slice(i2 + 1, i3)); + // cansend = parseInt(msg.slice(i2 + 1, i3)), // TODO: take into account when sending messages + i4 = msg.indexOf(':', i3 + 1), + chid = Number.parseInt(msg.slice(i3 + 1, i4)); + + // for authentication HMAC checksum and sequence id is important + // HMAC used to authenticate server + // sequence id is necessary to exclude submission of same packet again + if (this.key && sessionKey) { + const client_hash = HMAC(this.key, msg.slice(i0+1)); + if (server_hash !== client_hash) + return console.log(`Failure checking server md5 sum ${server_hash}`); + } + + if (seq_id <= this.recv_seq) + return console.log(`Failure with packet sequence ${seq_id} <= ${this.recv_seq}`); + this.recv_seq = seq_id; // sequence id of received packet this.ackn++; // count number of received packets, this.cansend += credit; // how many packets client can send - msg = msg.slice(i3 + 1); + msg = msg.slice(i4 + 1); if (chid === 0) { console.log(`GET chid=0 message ${msg}`); @@ -119134,13 +119918,19 @@ class WebWindowHandle { } else if (msg.indexOf('NEW_KEY=') === 0) { const newkey = msg.slice(8); this.close(true); - if (typeof sessionStorage !== 'undefined') + let href = (typeof document !== 'undefined') ? document.URL : null; + if (isStr(href) && (typeof window !== 'undefined') && window?.history) { + const p = href.indexOf('?key='); + if (p > 0) href = href.slice(0, p); + window.history.replaceState(window.history.state, undefined, `${href}?key=${newkey}`); + } else if (typeof sessionStorage !== 'undefined') sessionStorage.setItem('RWebWindow_Key', newkey); location.reload(true); } - } else if (msg === '$$binary$$') + } else if (msg.slice(0, 10) === '$$binary$$') { this.next_binary = chid; - else if (msg === '$$nullbinary$$') + this.next_binary_hash = msg.slice(10); + } else if (msg === '$$nullbinary$$') this.provideData(chid, new ArrayBuffer(0), 0); else this.provideData(chid, msg); @@ -125024,6 +125814,7 @@ exports.cleanup = cleanup; exports.clone = clone; exports.compressSVG = compressSVG; exports.constants = constants$1; +exports.convertDate = convertDate; exports.create = create$1; exports.createGeoPainter = createGeoPainter; exports.createHistogram = createHistogram; diff --git a/js/changes.md b/js/changes.md index d337031e6e402..41c3a65263d60 100644 --- a/js/changes.md +++ b/js/changes.md @@ -1,6 +1,13 @@ # JSROOT changelog ## Changes in dev +1. Use own "jsroot-gl" package from npm because of gcc13 problems +2. Let plot current time, file creation or modification time with `&optdate=[1,2,3]` URL parameters +3. Let plot file name, full file name or item name with `&optfile=[1,2,3]` URL parameters +4. Improve TRatioPlot axis and lines drawing + + +## Changes in 7.6.0 1. Implement "tickz" draw option, used for color palette ticks 2. Implement skewness and kurtosis calculations for histogram stats box 3. Introduce "logv" draw option for `TH3`, configures logarithmic scale for box volume @@ -17,11 +24,34 @@ 14. Handle TCanvas IsEdiatable flag to disable some interactive features 15. Support PDF creation using jsPDF and svg2pdf.js - in browser and node.js 16. Implement custom fonts support in TWebCanvas -17. Fix - do not add `THStack` and `TMultiGraph` to legend -18. Fix - correctly use margin in `TPaveText` class -19. Fix - correctly draw endcaps in legend errors -20. Fix - correctly read leaf with fixed-size array -21. Fix - vertical position of up elements like {M}^{2} in TLatex +17. List of ROOT/JSON files on server with `&dir=` URL parameter #283 +18. Load TGaxis function from the file #282 +19. Let display progress messages in modal element #285 +20. Fix - do not add `THStack` and `TMultiGraph` to legend +21. Fix - correctly use margin in `TPaveText` class +22. Fix - correctly draw endcaps in legend errors +23. Fix - vertical position of up elements like {M}^{2} in TLatex +24. Fix - let draw THStack with diff binning hists +25. Fix - better tooltip name for the items +26. Fix - better logy scale selection + + +## Changes in 7.5.4 +1. Fix - catch exception when parsing TF1 formula +2. Fix - properly check THStack histograms axes when doing sum +3. Fix - correctly handle negative offset on time axis +4. Fix - do not use `inset` because of old Chrome browsers +5. Fix - properly provide object hints + + +## Changes in 7.5.3 +1. Fix - draw histograms with negative bins #276 +2. Fix - correctly read TLeaf with fixed-size array +3. Fix - bug in options handling in startGUI +4. Fix - greyscale support in TLegend drawing +5. Fix - correctly use text font for TGaxis title +6. Fix - preserve auto colors in THStack #277 +7. Fix - correctly set pave name #278 ## Changes in 7.5.2 diff --git a/js/modules/base/BasePainter.mjs b/js/modules/base/BasePainter.mjs index 4d154c11334aa..b9526ab964d9d 100644 --- a/js/modules/base/BasePainter.mjs +++ b/js/modules/base/BasePainter.mjs @@ -1,7 +1,7 @@ import { select as d3_select } from '../d3.mjs'; -import { settings, internals, isNodeJs, isFunc, isStr, isObject, btoa_func, getDocument, source_dir, loadScript } from '../core.mjs'; -import { detectFont } from './FontHandler.mjs'; -import { approximateLabelWidth } from './latex.mjs'; +import { settings, internals, isNodeJs, isFunc, isStr, isObject, btoa_func, getDocument, source_dir, loadScript, httpRequest } from '../core.mjs'; +import { detectFont, addCustomFont, getCustomFont, FontHandler } from './FontHandler.mjs'; +import { approximateLabelWidth, replaceSymbolsInTextNode } from './latex.mjs'; /** @summary Returns visible rect of element @@ -614,7 +614,7 @@ class BasePainter { enlarge = d3_select(doc.body) .append('div') .attr('id', 'jsroot_enlarge_div') - .attr('style', 'position: fixed; margin: 0px; border: 0px; padding: 0px; inset: 1px; background: white; opacity: 0.95; z-index: 100; overflow: hidden;'); + .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); @@ -719,12 +719,13 @@ function addHighlightStyle(elem, drag) { * @private */ async function svgToPDF(args, as_buffer) { const nodejs = isNodeJs(); - let _jspdf, _svg2pdf; + let _jspdf, _svg2pdf, need_symbols = false; const pr = nodejs - ? import('jspdf').then(h => { _jspdf = h; return import('svg2pdf.js'); }).then(h => { _svg2pdf = h.default; }) + ? import('../../scripts/jspdf.es.min.js').then(h => { _jspdf = h; return import('svg2pdf.js'); }).then(h => { _svg2pdf = h.default; }) : loadScript(source_dir + 'scripts/jspdf.umd.min.js').then(() => loadScript(source_dir + 'scripts/svg2pdf.umd.min.js')).then(() => { _jspdf = globalThis.jspdf; _svg2pdf = globalThis.svg2pdf; }), - restore_fonts = [], restore_dominant = [], node_transform = args.node.getAttribute('transform'), custom_fonts = {}; + restore_fonts = [], restore_dominant = [], restore_text = [], + node_transform = args.node.getAttribute('transform'), custom_fonts = {}; if (args.reset_tranform) args.node.removeAttribute('transform'); @@ -747,17 +748,25 @@ async function svgToPDF(args, as_buffer) { if (!args.can_modify) restore_dominant.push(this); // keep to restore it } else if (args.can_modify && nodejs && this.getAttribute('dy') === '.4em') this.setAttribute('dy', '.2em'); // better allignment in PDF + + if (replaceSymbolsInTextNode(this)) { + need_symbols = true; + if (!args.can_modify) restore_text.push(this); // keep to restore it + } }); if (nodejs) { const doc = internals.nodejs_document; doc.oldFunc = 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.oldFunc(ns, kind); res.getBBox = function() { let width = 50, height = 10; if (this.tagName === 'text') { + // TODO: use jsDOC fonts for label width estimation const font = detectFont(this); width = approximateLabelWidth(this.textContent, font); height = font.size; @@ -782,11 +791,38 @@ async function svgToPDF(args, as_buffer) { if (!fh || custom_fonts[fh.name] || (fh.format !== 'ttf')) return; const filename = fh.name.toLowerCase().replace(/\s/g, '') + '.ttf'; doc.addFileToVFS(filename, fh.base64); - doc.addFont(filename, fh.name, 'normal'); + doc.addFont(filename, fh.name, 'normal', 'normal', (fh.name === 'symbol') ? 'StandardEncoding' : 'Identity-H'); custom_fonts[fh.name] = true; }); - return _svg2pdf.svg2pdf(args.node, doc, { x: 5, y: 5, width: args.width, height: args.height }) + let pr2 = Promise.resolve(true); + + if (need_symbols && !custom_fonts.symbol) { + if (!getCustomFont('symbol')) { + pr2 = nodejs + ? import('fs').then(fs => { + const base64 = fs.readFileSync('../../fonts/symbol.ttf').toString('base64'); + console.log('reading symbol.ttf', base64.length); + addCustomFont(25, 'symbol', 'ttf', base64); + }) + : httpRequest(source_dir+'fonts/symbol.ttf', 'bin').then(buf => { + const base64 = btoa_func(buf); + addCustomFont(25, 'symbol', 'ttf', base64); + }); + } + + pr2 = pr2.then(() => { + const fh = getCustomFont('symbol'), + handler = new FontHandler(1242, 10); + handler.name = 'symbol'; + handler.base64 = fh.base64; + handler.addCustomFontToSvg(d3_select(args.node)); + doc.addFileToVFS('symbol.ttf', fh.base64); + doc.addFont('symbol.ttf', 'symbol', 'normal', 'normal', 'StandardEncoding' /* 'WinAnsiEncoding' */); + }); + } + + return pr2.then(() => _svg2pdf.svg2pdf(args.node, doc, { x: 5, y: 5, width: args.width, height: args.height })) .then(() => { if (args.reset_tranform && !args.can_modify && node_transform) args.node.setAttribute('transform', node_transform); @@ -796,13 +832,18 @@ async function svgToPDF(args, as_buffer) { node.setAttribute('dominant-baseline', 'middle'); node.removeAttribute('dy'); }); + + restore_text.forEach(node => { node.innerHTML = node.$originalHTML; }); + const res = as_buffer ? doc.output('arraybuffer') : doc.output('dataurlstring'); if (nodejs) { globalThis.document = undefined; + globalThis.CSSStyleSheet = undefined; + globalThis.CSSStyleRule = undefined; internals.nodejs_document.createElementNS = internals.nodejs_document.oldFunc; if (as_buffer) return Buffer.from(res); } - return res; + return res; }); }); } @@ -821,16 +862,14 @@ async function svgToImage(svg, image_format, as_buffer) { if (image_format === 'pdf') return svgToPDF(svg, as_buffer); - if (!isNodeJs()) { - // required with df104.py/df105.py example with RCanvas - const doctype = ''; - svg = encodeURIComponent(doctype + svg); - svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { - const c = String.fromCharCode('0x'+p1); - return c === '%' ? '%25' : c; - }); - svg = decodeURIComponent(svg); - } + // required with df104.py/df105.py example with RCanvas or any special symbols in TLatex + const doctype = ''; + svg = encodeURIComponent(doctype + svg); + svg = svg.replace(/%([0-9A-F]{2})/g, (match, p1) => { + const c = String.fromCharCode('0x'+p1); + return c === '%' ? '%25' : c; + }); + svg = decodeURIComponent(svg); const img_src = 'data:image/svg+xml;base64,' + btoa_func(svg); @@ -872,6 +911,20 @@ async function svgToImage(svg, image_format, as_buffer) { }); } -export { getElementRect, getAbsPosInCanvas, +/** @summary Convert Date object into string used preconfigured 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 (err) { + res = ''; + } + } + return res || dt.toLocaleString('en-GB'); +} + +export { getElementRect, getAbsPosInCanvas, convertDate, DrawOptions, TRandom, floatToString, buildSvgCurve, compressSVG, BasePainter, _loadJSDOM, makeTranslate, addHighlightStyle, svgToImage }; diff --git a/js/modules/base/FontHandler.mjs b/js/modules/base/FontHandler.mjs index 9e81fd43dce44..558e695bda8e1 100644 --- a/js/modules/base/FontHandler.mjs +++ b/js/modules/base/FontHandler.mjs @@ -71,23 +71,27 @@ class FontHandler { } /** @summary Assigns font-related attributes */ - setFont(selection) { - if (this.base64 && this.painter) { - const svg = this.painter.getCanvSvg(), - clname = 'custom_font_' + this.name, - 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('type', 'text/css') - .attr('class', clname) - .property('$fonthandler', this) - .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}') }`); - } + addCustomFontToSvg(svg) { + if (!this.base64 || !this.name) + return; + const clname = 'custom_font_' + this.name, 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()) { + console.log('Adding style entry for class', clname); + defs.append('style') + .attr('class', clname) + .property('$fonthandler', this) + .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) @@ -160,6 +164,12 @@ function addCustomFont(index, name, format, base64) { 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 detectFont(node) { @@ -201,4 +211,4 @@ function detectFont(node) { return handler; } -export { FontHandler, addCustomFont, detectFont }; +export { FontHandler, addCustomFont, getCustomFont, detectFont }; diff --git a/js/modules/base/ObjectPainter.mjs b/js/modules/base/ObjectPainter.mjs index e647e506c6b09..cccb3f48e76b8 100644 --- a/js/modules/base/ObjectPainter.mjs +++ b/js/modules/base/ObjectPainter.mjs @@ -213,8 +213,10 @@ class ObjectPainter extends BasePainter { * Such string typically used as object tooltip. * If result string larger than 20 symbols, it will be cutted. */ getObjectHint() { - const hint = this.getItemName() || this.getObjectName() || this.getClassName() || ''; - return (hint.length <= 20) ? hint : hint.slice(0, 17) + '...'; + const iname = this.getItemName(); + if (iname) + return (iname.length > 20) ? '...' + iname.slice(iname.length - 17) : iname; + return this.getObjectName() || this.getClassName() || ''; } /** @summary returns color from current list of colors @@ -446,18 +448,22 @@ class ObjectPainter extends BasePainter { * 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} [noround] - 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) { + getAxisToSvgFunc(isndc, nornd, use_frame_coordinates) { const func = { isndc, nornd }, use_frame = this.draw_g?.property('in_frame'); - if (use_frame) func.main = this.getFramePainter(); + if (use_frame || (use_frame_coordinates && !isndc)) + func.main = this.getFramePainter(); if (func.main?.grx && func.main?.gry) { + func.x0 = (use_frame_coordinates && !isndc) ? func.main.getFrameX() : 0; + func.y0 = (use_frame_coordinates && !isndc) ? func.main.getFrameY() : 0; if (nornd) { - func.x = function(x) { return this.main.grx(x); }; - func.y = function(y) { return this.main.gry(y); }; + func.x = function(x) { return this.x0 + this.main.grx(x); }; + func.y = function(y) { return this.y0 + this.main.gry(y); }; } else { - func.x = function(x) { return Math.round(this.main.grx(x)); }; - func.y = function(y) { return Math.round(this.main.gry(y)); }; + func.x = function(x) { return this.x0 + Math.round(this.main.grx(x)); }; + func.y = function(y) { return this.y0 + Math.round(this.main.gry(y)); }; } } else if (!use_frame) { const pp = this.getPadPainter(); diff --git a/js/modules/base/base3d.mjs b/js/modules/base/base3d.mjs index 049185613297c..41b86ba8974c7 100644 --- a/js/modules/base/base3d.mjs +++ b/js/modules/base/base3d.mjs @@ -449,7 +449,7 @@ async function createRender3D(width, height, render3d, args) { args.canvas.addEventListener = () => {}; // dummy args.canvas.removeEventListener = () => {}; // dummy args.canvas.style = {}; - return import('gl'); + return import('jsroot-gl'); }).then(node_gl => { const gl = node_gl.default(width, height, { preserveDrawingBuffer: true }); if (!gl) throw Error('Fail to create headless-gl'); diff --git a/js/modules/base/latex.mjs b/js/modules/base/latex.mjs index bcf40a6ca4d79..9a63f9b6567d5 100644 --- a/js/modules/base/latex.mjs +++ b/js/modules/base/latex.mjs @@ -4,12 +4,11 @@ import { FontHandler } from './FontHandler.mjs'; const symbols_map = { - // greek letters + // greek letters from symbols.ttf '#alpha': '\u03B1', '#beta': '\u03B2', '#chi': '\u03C7', '#delta': '\u03B4', - '#digamma': '\u03DD', '#varepsilon': '\u03B5', '#phi': '\u03C6', '#gamma': '\u03B3', @@ -17,8 +16,6 @@ const symbols_map = { '#iota': '\u03B9', '#varphi': '\u03C6', '#kappa': '\u03BA', - '#koppa': '\u03DF', - '#sampi': '\u03E1', '#lambda': '\u03BB', '#mu': '\u03BC', '#nu': '\u03BD', @@ -27,13 +24,9 @@ const symbols_map = { '#theta': '\u03B8', '#rho': '\u03C1', '#sigma': '\u03C3', - '#stigma': '\u03DB', - '#san': '\u03FB', - '#sho': '\u03F8', '#tau': '\u03C4', '#upsilon': '\u03C5', '#varomega': '\u03D6', - '#varcoppa': '\u03D9', '#omega': '\u03C9', '#xi': '\u03BE', '#psi': '\u03C8', @@ -42,7 +35,6 @@ const symbols_map = { '#Beta': '\u0392', '#Chi': '\u03A7', '#Delta': '\u0394', - '#Digamma': '\u03DC', '#Epsilon': '\u0395', '#Phi': '\u03A6', '#Gamma': '\u0393', @@ -50,9 +42,6 @@ const symbols_map = { '#Iota': '\u0399', '#vartheta': '\u03D1', '#Kappa': '\u039A', - '#Koppa': '\u03DE', - '#varKoppa': '\u03D8', - '#Sampi': '\u03E0', '#Lambda': '\u039B', '#Mu': '\u039C', '#Nu': '\u039D', @@ -61,9 +50,6 @@ const symbols_map = { '#Theta': '\u0398', '#Rho': '\u03A1', '#Sigma': '\u03A3', - '#Stigma': '\u03DA', - '#San': '\u03FA', - '#Sho': '\u03F7', '#Tau': '\u03A4', '#Upsilon': '\u03A5', '#varsigma': '\u03C2', @@ -73,16 +59,8 @@ const symbols_map = { '#Zeta': '\u0396', '#varUpsilon': '\u03D2', '#epsilon': '\u03B5', - '#P': '\u00B6', - - // only required for MathJax to provide correct replacement - '#sqrt': '\u221A', - '#bar': '', - '#overline': '', - '#underline': '', - '#strike': '', - // from TLatex tables #2 & #3 + // second set from symbols.ttf '#leq': '\u2264', '#/': '\u2044', '#infty': '\u221E', @@ -96,9 +74,8 @@ const symbols_map = { '#uparrow': '\u2191', '#rightarrow': '\u2192', '#downarrow': '\u2193', - '#circ': '\u02C6', // ^ + '#circ': '\u2E30', '#pm': '\xB1', - '#mp': '\u2213', '#doublequote': '\u2033', '#geq': '\u2265', '#times': '\xD7', @@ -122,12 +99,11 @@ const symbols_map = { '#oslash': '\u2205', '#cap': '\u2229', '#cup': '\u222A', - '#supseteq': '\u2287', '#supset': '\u2283', + '#supseteq': '\u2287', '#notsubset': '\u2284', - '#subseteq': '\u2286', '#subset': '\u2282', - '#int': '\u222B', + '#subseteq': '\u2286', '#in': '\u2208', '#notin': '\u2209', '#angle': '\u2220', @@ -137,7 +113,7 @@ const symbols_map = { '#trademark': '\u2122', '#prod': '\u220F', '#surd': '\u221A', - '#upoint': '\u02D9', + '#upoint': '\u2027', '#corner': '\xAC', '#wedge': '\u2227', '#vee': '\u2228', @@ -146,24 +122,45 @@ const symbols_map = { '#Uparrow': '\u21D1', '#Rightarrow': '\u21D2', '#Downarrow': '\u21D3', + '#void2': '', // dummy, placeholder '#LT': '\x3C', '#void1': '\xAE', '#copyright': '\xA9', - '#void3': '\u2122', + '#void3': '\u2122', // it is dummy placeholder, TM '#sum': '\u2211', '#arctop': '\u239B', - '#lbar': '\u23B8', + '#lbar': '\u23A2', '#arcbottom': '\u239D', - '#void8': '', + '#void4': '', // dummy, placeholder + '#void8': '\u23A2', // same as lbar '#bottombar': '\u230A', '#arcbar': '\u23A7', '#ltbar': '\u23A8', '#AA': '\u212B', - '#aa': '\u00E5', + '#aa': '\xE5', '#void06': '', '#GT': '\x3E', + '#int': '\u222B', '#forall': '\u2200', '#exists': '\u2203', + // 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', @@ -181,12 +178,25 @@ const symbols_map = { '#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 + + +/** @summary Create a single regex to detect any symbol to replace, apply longer symbols first * @private */ -symbolsRegexCache = new RegExp('(' + Object.keys(symbols_map).join('|').replace(/\\\{/g, '{').replace(/\\\}/g, '}') + ')', 'g'), +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 */ @@ -263,15 +273,86 @@ const latex_features = [ { 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]; +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]; +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], + +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 = 0; + 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 + } + } + const code = symbol.charCodeAt(0); + if (code > 0x80) + symbolsPdfMap[code] = letter; + } + if (++cnt > 54 + 82) break; + } + } + 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.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.innerHTML = new_html; + return true; +} function replaceSymbols(s, kind) { const m = (kind === 'Wingdings') ? wingdingsMap : symbolsMap; @@ -1379,4 +1460,5 @@ async function typesetMathjax(node) { return loadMathjax().then(mj => mj.typesetPromise(node ? [node] : undefined)); } -export { symbols_map, translateLaTeX, producePlainText, isPlainText, produceLatex, loadMathjax, produceMathjax, typesetMathjax, approximateLabelWidth }; +export { symbols_map, translateLaTeX, producePlainText, isPlainText, produceLatex, loadMathjax, + produceMathjax, typesetMathjax, approximateLabelWidth, replaceSymbolsInTextNode }; diff --git a/js/modules/base/md5.mjs b/js/modules/base/md5.mjs new file mode 100644 index 0000000000000..36b674c3fed43 --- /dev/null +++ b/js/modules/base/md5.mjs @@ -0,0 +1,275 @@ +/* + * JavaScript MD5 + * https://github.com/blueimp/JavaScript-MD5 + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * https://opensource.org/licenses/MIT + * + * Based on + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + + +/** @private */ +function safeAdd(x, y) { + const lsw = (x & 0xffff) + (y & 0xffff), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); +} + +/** @private */ +function bitRotateLeft(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); +} + +/** @private */ +function md5cmn(q, a, b, x, s, t) { + return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); +} + +/** @private */ +function md5ff(a, b, c, d, x, s, t) { + return md5cmn((b & c) | (~b & d), a, b, x, s, t); +} + +/** @private */ +function md5gg(a, b, c, d, x, s, t) { + return md5cmn((b & d) | (c & ~d), a, b, x, s, t); +} + +/** @private */ +function md5hh(a, b, c, d, x, s, t) { + return md5cmn(b ^ c ^ d, a, b, x, s, t); +} + +/** @private */ +function md5ii(a, b, c, d, x, s, t) { + return md5cmn(c ^ (b | ~d), a, b, x, s, t); +} + +/** + * Calculate the MD5 of an array of little-endian words, and a bit length. + * + * @param {Array} x Array of little-endian words + * @param {number} len Bit length + * @returns {Array} MD5 Array + * @private */ +function binlMD5(x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << len % 32; + x[(((len + 64) >>> 9) << 4) + 14] = len; + + let a = 1732584193, + b = -271733879, + c = -1732584194, + d = 271733878; + + for (let i = 0; i < x.length; i += 16) { + const olda = a, oldb = b, oldc = c, oldd = d; + + a = md5ff(a, b, c, d, x[i], 7, -680876936); + d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); + c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); + b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); + a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); + d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); + c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); + b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); + a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); + d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); + c = md5ff(c, d, a, b, x[i + 10], 17, -42063); + b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); + a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); + d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); + c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); + b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); + + a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); + d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); + c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); + b = md5gg(b, c, d, a, x[i], 20, -373897302); + a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); + d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); + c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); + b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); + a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); + d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); + c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); + b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); + a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); + d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); + c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); + b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); + + a = md5hh(a, b, c, d, x[i + 5], 4, -378558); + d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); + c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); + b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); + a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); + d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); + c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); + b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); + a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); + d = md5hh(d, a, b, c, x[i], 11, -358537222); + c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); + b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); + a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); + d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); + c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); + b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); + + a = md5ii(a, b, c, d, x[i], 6, -198630844); + d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); + c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); + b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); + a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); + d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); + c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); + b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); + a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); + d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); + c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); + b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); + a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); + d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); + c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); + b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); + + a = safeAdd(a, olda); + b = safeAdd(b, oldb); + c = safeAdd(c, oldc); + d = safeAdd(d, oldd); + } + return [a, b, c, d]; +} + +/** + * Convert an array of little-endian words to a string + * + * @param {Array} input MD5 Array + * @returns {string} MD5 string + */ +function binl2rstr(input) { + let output = ''; + const length32 = input.length * 32; + for (let i = 0; i < length32; i += 8) + output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff); + return output; +} + +/** + * Convert a raw string to an array of little-endian words + * Characters >255 have their high-byte silently ignored. + * + * @param {string} input Raw input string + * @returns {Array} Array of little-endian words + */ +function rstr2binl(input) { + const output = []; + output[(input.length >> 2) - 1] = undefined; + for (let i = 0; i < output.length; i += 1) + output[i] = 0; + const length8 = input.length * 8; + for (let i = 0; i < length8; i += 8) + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32; + return output; +} + +function rstr2binl_2(input, arr) { + const fulllen = input.length + arr.length, + output = []; + + output[(fulllen >> 2) - 1] = undefined; + for (let i = 0; i < output.length; i += 1) + output[i] = 0; + const length8 = input.length * 8; + for (let i = 0; i < length8; i += 8) + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32; + + const length8_2 = arr.length * 8; + for (let i2 = 0; i2 < length8_2; i2 += 8) { + const i = length8 + i2; + output[i >> 5] |= (arr[i2 / 8] & 0xff) << i % 32; + } + + return output; +} + +/** + * Calculate the MD5 of a raw string + * + * @param {string} s Input string + * @returns {string} Raw MD5 string + */ +function rstrMD5(s) { + return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)); +} + +function rstrMD5_2(s, arr) { + return binl2rstr(binlMD5(rstr2binl_2(s, arr), (s.length + arr.length) * 8)); +} + +/** + * Convert a raw string to a hex string + * + * @param {string} input Raw input string + * @returns {string} Hex encoded string + */ +function rstr2hex(input) { + const hexTab = '0123456789abcdef'; + let output = ''; + for (let i = 0; i < input.length; i += 1) { + const x = input.charCodeAt(i); + output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f); + } + return output; +} + +/** + * Encode a string as UTF-8 + * + * @param {string} input Input string + * @returns {string} UTF8 string + */ +function str2rstrUTF8(input) { + return decodeURIComponent(encodeURIComponent(input)); +} + +/** + * Encodes input string as raw MD5 string + * + * @param {string} s Input string + * @returns {string} Raw MD5 string + */ +function rawMD5(s) { + return rstrMD5(str2rstrUTF8(s)); +} + +function rawMD5_2(s, arr) { + return rstrMD5_2(str2rstrUTF8(s), arr); +} + +/** + * Encodes input string as Hex encoded string + * + * @param {string} s Input string + * @returns {string} Hex encoded string + */ +function hexMD5(s) { + return rstr2hex(rawMD5(s)); +} + +function hexMD5_2(s, arr) { + return rstr2hex(rawMD5_2(s, arr)); +} + + +export { hexMD5, hexMD5_2 }; diff --git a/js/modules/base/sha256.mjs b/js/modules/base/sha256.mjs new file mode 100644 index 0000000000000..39de41622ad46 --- /dev/null +++ b/js/modules/base/sha256.mjs @@ -0,0 +1,265 @@ +/** + * [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 = 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) { + // rightrotate + 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/core.mjs b/js/modules/core.mjs index 7a2c3ebcc2325..95d49592c6c2d 100644 --- a/js/modules/core.mjs +++ b/js/modules/core.mjs @@ -4,7 +4,7 @@ const version_id = 'dev', /** @summary version date * @desc Release date in format day/month/year like '14/04/2022' */ -version_date = '21/11/2023', +version_date = '28/02/2024', /** @summary version id and date * @desc Produced by concatenation of {@link version_id} and {@link version_date} @@ -215,7 +215,7 @@ settings = { DragAndDrop: !nodejs, /** @summary Interactive dragging of TGraph points */ DragGraphs: true, - /** @summary Show progress box */ + /** @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', @@ -290,7 +290,9 @@ settings = { /** @summary Strip axis labels trailing 0 or replace 10^0 by 1 */ StripAxisLabels: true, /** @summary Draw TF1 by default as curve or line */ - FuncAsCurve: false + FuncAsCurve: false, + /** @summary Time zone used for date/time display of file time */ + TimeZone: '' }, /** @namespace @@ -1099,7 +1101,7 @@ function create(typename, target) { create(clTBox, obj); extend(obj, { fX1NDC: 0, fY1NDC: 0, fX2NDC: 1, fY2NDC: 1, fBorderSize: 0, fInit: 1, fShadowColor: 1, - fCornerRadius: 0, fOption: 'brNDC', fName: 'title' }); + fCornerRadius: 0, fOption: 'brNDC', fName: '' }); break; case clTAttText: extend(obj, { fTextAngle: 0, fTextSize: 0, fTextAlign: 22, fTextColor: 1, fTextFont: 42 }); @@ -1119,7 +1121,7 @@ function create(typename, target) { case clTLegend: create(clTPave, obj); create(clTAttText, obj); - extend(obj, { fColumnSeparation: 0, fEntrySeparation: 0.1, fMargin: 0.25, fNColumns: 1, fPrimitives: create(clTList), + 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 }); break; case clTPaletteAxis: diff --git a/js/modules/draw.mjs b/js/modules/draw.mjs index b4f2b2050a8f7..e7ba85f65d367 100644 --- a/js/modules/draw.mjs +++ b/js/modules/draw.mjs @@ -384,7 +384,10 @@ async function draw(dom, obj, opt) { promise = getPromise(handle.func(dom, obj, opt)); return promise.then(p => { - if (!painter) painter = 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) diff --git a/js/modules/draw/TGaxisPainter.mjs b/js/modules/draw/TGaxisPainter.mjs index caca6549a780c..bd684d26b9ee4 100644 --- a/js/modules/draw/TGaxisPainter.mjs +++ b/js/modules/draw/TGaxisPainter.mjs @@ -5,6 +5,7 @@ import { EAxisBits, TAxisPainter } from '../gpad/TAxisPainter.mjs'; import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; import { addMoveHandler } from '../gui/utils.mjs'; import { assignContextMenu } from '../gui/menu.mjs'; +import { getHPainter } from '../gui/display.mjs'; import { proivdeEvalPar } from '../hist/TF1Painter.mjs'; @@ -144,15 +145,30 @@ class TGaxisPainter extends TAxisPainter { } /** @summary Check if there is function for TGaxis can be found */ - checkFuncion() { + async checkFuncion() { const gaxis = this.getObject(); - if (!gaxis.fFunctionName) + if (!gaxis.fFunctionName) { this.axis_func = null; - else - this.axis_func = this.getPadPainter()?.findInPrimitives(gaxis.fFunctionName, clTF1); + 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; + }); + } + } - if (this.axis_func) - proivdeEvalPar(this.axis_func); + return promise.then(f => { + this.axis_func = f; + if (f) + proivdeEvalPar(f); + }); } /** @summary Create handle for custom function in the axis */ @@ -209,9 +225,8 @@ class TGaxisPainter extends TAxisPainter { return ensureTCanvas(painter, false).then(() => { if (opt) painter.convertTo(opt); - painter.checkFuncion(); - return painter.redraw(); - }); + return painter.checkFuncion(); + }).then(() => painter.redraw()); } } // class TGaxisPainter diff --git a/js/modules/draw/TLinePainter.mjs b/js/modules/draw/TLinePainter.mjs index 6e21303557c6e..3c0fe9838a709 100644 --- a/js/modules/draw/TLinePainter.mjs +++ b/js/modules/draw/TLinePainter.mjs @@ -4,6 +4,9 @@ import { ensureTCanvas } from '../gpad/TCanvasPainter.mjs'; import { addMoveHandler } from '../gui/utils.mjs'; import { assignContextMenu, kToFront } from '../gui/menu.mjs'; + +const kLineNDC = BIT(14); + class TLinePainter extends ObjectPainter { /** @summary Start interactive moving */ @@ -42,14 +45,16 @@ class TLinePainter extends ObjectPainter { /** @summary Calculate line coordinates */ prepareDraw() { - const line = this.getObject(), kLineNDC = BIT(14); + const line = this.getObject(); this.isndc = line.TestBit(kLineNDC); - this.x1 = this.axisToSvg('x', line.fX1, this.isndc, true); - this.y1 = this.axisToSvg('y', line.fY1, this.isndc, true); - this.x2 = this.axisToSvg('x', line.fX2, this.isndc, true); - this.y2 = this.axisToSvg('y', line.fY2, this.isndc, true); + const func = this.getAxisToSvgFunc(this.isndc, true, 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); this.createAttLine({ attr: line }); } @@ -65,10 +70,10 @@ class TLinePainter extends ObjectPainter { /** @summary Redraw line */ redraw() { - this.prepareDraw(); - this.createG(); + this.prepareDraw(); + const elem = this.draw_g.append('svg:path') .attr('d', this.createPath()) .call(this.lineatt.func); diff --git a/js/modules/draw/TRatioPlotPainter.mjs b/js/modules/draw/TRatioPlotPainter.mjs index 451b99df2b829..7516f7530d40d 100644 --- a/js/modules/draw/TRatioPlotPainter.mjs +++ b/js/modules/draw/TRatioPlotPainter.mjs @@ -18,10 +18,16 @@ class TRatioPlotPainter extends ObjectPainter { if (xmin === xmax) { const x_handle = this.getPadPainter()?.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad)?.getFramePainter()?.x_handle; if (!x_handle) return; - xmin = x_handle.full_min; - xmax = x_handle.full_max; + 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; @@ -41,20 +47,18 @@ class TRatioPlotPainter extends ObjectPainter { low_p = pp.findPainterFor(ratio.fLowerPad, 'lower_pad', clTPad), low_main = low_p?.getMainPainter(), low_fp = low_p?.getFramePainter(); - let lbl_size = 20, promise_up = Promise.resolve(true); + 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 - lbl_size = up_main.getHisto().fYaxis.fLabelSize; - if (lbl_size < 1) lbl_size = Math.round(lbl_size*Math.min(up_p.getPadWidth(), up_p.getPadHeight())); + 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 - h.fYaxis.fLabelSize = lbl_size; - h.fYaxis.fTitleSize = lbl_size; up_p.getRootPad().fTicky = 1; @@ -78,7 +82,7 @@ class TRatioPlotPainter extends ObjectPainter { this._ratio_low_fp.fX2NDC = this.fX2NDC; this._ratio_low_fp.o_sizeChanged(); }; - return true; + return this; }); } @@ -90,10 +94,9 @@ class TRatioPlotPainter extends ObjectPainter { low_main.options.Axis = 0; // draw both axes const h = low_main.getHisto(); h.fXaxis.fTitle = 'x'; - h.fXaxis.fLabelSize = lbl_size; - h.fXaxis.fTitleSize = lbl_size; - h.fYaxis.fLabelSize = lbl_size; - h.fYaxis.fTitleSize = lbl_size; + + h.fXaxis.$use_top_pad = true; + h.fYaxis.$use_top_pad = true; low_p.getRootPad().fTicky = 1; low_p.forEachPainterInPad(objp => { diff --git a/js/modules/draw/TTree.mjs b/js/modules/draw/TTree.mjs index de2c1327c300a..408ca4e5b342e 100644 --- a/js/modules/draw/TTree.mjs +++ b/js/modules/draw/TTree.mjs @@ -13,60 +13,38 @@ import { drawPolyMarker3D } from '../draw/TPolyMarker3D.mjs'; import { showProgress, registerForResize } from '../gui/utils.mjs'; -function treeShowProgress(handle, str) { - if (isBatchMode() || (typeof document === 'undefined')) return; - - if (!str) - return showProgress(); - - const main_box = document.createElement('p'), - text_node = document.createTextNode(str); - - main_box.appendChild(text_node); - main_box.title = 'Click on element to break'; - - main_box.onclick = () => { - if (!handle._break) handle._break = 0; - - if (++handle._break < 3) { - main_box.title = 'Will break after next I/O operation'; - text_node.nodeValue = 'Breaking ... '; - return; - } - if (isFunc(handle.Abort)) - handle.Abort(); - showProgress(); - }; - - showProgress(main_box); -} - - /** @summary Show TTree::Draw progress during processing * @private */ TDrawSelector.prototype.ShowProgress = function(value) { + let msg, ret; if ((value === undefined) || !Number.isFinite(value)) - return showProgress(); + 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; + } - 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)} % `; } - 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; - - treeShowProgress(this, `TTree draw ${(value * 100).toFixed(ndig)} % `); + showProgress(msg, -1, () => { this._break = 1; }); + return ret; }; /** @summary Draw result of tree drawing @@ -127,18 +105,21 @@ async function treeDrawProgress(obj, final) { if (!this.last_pr) this.last_pr = Promise.resolve(true); - return this.last_pr.then(() => { - if (this.obj_painter) - this.last_pr = this.obj_painter.redrawObject(obj).then(() => this.obj_painter); - else { - this.last_pr = drawTreeDrawResult(this.drawid, obj).then(p => { - this.obj_painter = p; - if (!final) this.last_pr = null; - return p; // return painter for histogram - }); - } - - return final ? this.last_pr : null; + return this.last_pr.then(() => { + if (this.obj_painter) + this.last_pr = 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).then(p => { + this.obj_painter = p; + if (!final) this.last_pr = null; + return p; // return painter for histogram + }); + } + + return final ? this.last_pr : null; }); } @@ -471,7 +452,7 @@ async function drawTree(dom, obj, opt) { let pr; if (args.expr === 'testio') { args.testio = true; - args.showProgress = msg => treeShowProgress(args, msg); + args.showProgress = msg => showProgress(msg, -1, () => { args._break = 1; }); pr = treeIOTest(tree, args); } else if (args.expr || args.branch) pr = treeDraw(tree, args); diff --git a/js/modules/gpad/RFramePainter.mjs b/js/modules/gpad/RFramePainter.mjs index 0c08d1cbb5984..b17bfa5dfe400 100644 --- a/js/modules/gpad/RFramePainter.mjs +++ b/js/modules/gpad/RFramePainter.mjs @@ -316,7 +316,7 @@ class RFramePainter extends RObjectPainter { log: this.swap_xy ? this.logx : this.logy, symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < 0.01*this.ymax) ? 0.3 * opts.ymin_nz : 0, + 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'); diff --git a/js/modules/gpad/RPadPainter.mjs b/js/modules/gpad/RPadPainter.mjs index d659cbc090cea..30bb8bed4c23d 100644 --- a/js/modules/gpad/RPadPainter.mjs +++ b/js/modules/gpad/RPadPainter.mjs @@ -424,7 +424,7 @@ class RPadPainter extends RObjectPainter { .style('width', '100%') .style('height', '100%') .style('position', 'absolute') - .style('inset', '0px'); + .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); } svg.style('filter', settings.DarkMode ? 'invert(100%)' : null); diff --git a/js/modules/gpad/TAxisPainter.mjs b/js/modules/gpad/TAxisPainter.mjs index e692fcffa3f36..0789be3900e37 100644 --- a/js/modules/gpad/TAxisPainter.mjs +++ b/js/modules/gpad/TAxisPainter.mjs @@ -28,7 +28,7 @@ function getTimeOffset(axis) { sof = sof.slice(pos + 1); if (!Number.isInteger(val) || (val < min) || (val > max)) return min; return val; - }, year = next('-', 1970, 2300), + }, year = next('-', 1900, 2900), month = next('-', 1, 12) - 1, day = next(' ', 1, 31), hour = next(':', 0, 23), @@ -837,29 +837,29 @@ class TAxisPainter extends ObjectPainter { title_g.property('shift_x', new_x) .property('shift_y', new_y); - const axis = this.getObject(), abits = EAxisBits, - axis2 = this.source_axis, - set_bit = (bit, on) => { - if (axis.TestBit(bit) !== on) axis.InvertBit(bit); + const axis = this.getObject(), axis2 = this.source_axis, + setBit = (bit, on) => { + if (axis && axis.TestBit(bit) !== on) axis.InvertBit(bit); if (axis2 && axis2.TestBit(bit) !== on) axis2.InvertBit(bit); }; this.titleOffset = (vertical ? new_x : new_y) / offset_k; - axis.fTitleOffset = this.titleOffset / this.offsetScaling / this.titleSize; - if (axis2) axis2.fTitleOffset = axis.fTitleOffset; + const offset = this.titleOffset / this.offsetScaling / this.titleSize; + if (axis) axis.fTitleOffset = offset; + if (axis2) axis2.fTitleOffset = offset; if (curr_indx === 1) { - set_bit(abits.kCenterTitle, true); this.titleCenter = true; - set_bit(abits.kOppositeTitle, false); this.titleOpposite = false; + setBit(EAxisBits.kCenterTitle, true); this.titleCenter = true; + setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; } else if (curr_indx === 0) { - set_bit(abits.kCenterTitle, false); this.titleCenter = false; - set_bit(abits.kOppositeTitle, true); this.titleOpposite = true; + setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, true); this.titleOpposite = true; } else { - set_bit(abits.kCenterTitle, false); this.titleCenter = false; - set_bit(abits.kOppositeTitle, false); this.titleOpposite = false; + setBit(EAxisBits.kCenterTitle, false); this.titleCenter = false; + setBit(EAxisBits.kOppositeTitle, false); this.titleOpposite = false; } - this.submitAxisExec(`SetTitleOffset(${axis.fTitleOffset});;SetBit(${abits.kCenterTitle},${this.titleCenter?1:0})`); + this.submitAxisExec(`SetTitleOffset(${offset});;SetBit(${EAxisBits.kCenterTitle},${this.titleCenter?1:0})`); drag_rect.remove(); drag_rect = null; @@ -1054,9 +1054,10 @@ class TAxisPainter extends ObjectPainter { this.drawText(arg); - if (lastpos && (pos !== lastpos) && ((this.vertical && !rotate_lbls) || (!this.vertical && rotate_lbls))) { + // 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, 0.9*axis_step/labelsFont.size); + textscale = Math.min(textscale, 1.1*axis_step/labelsFont.size); } lastpos = pos; @@ -1108,9 +1109,11 @@ class TAxisPainter extends ObjectPainter { /** @summary Extract major draw attributes, which are also used in interactive operations * @private */ extractDrawAttributes(scalingSize, w, h) { - const axis = this.getObject(), - pp = this.getPadPainter(), - pad_w = pp?.getPadWidth() || scalingSize || w/0.8, // use factor 0.8 as ratio between frame and pad size + 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; let tickSize = 0, tickScalingSize = 0, titleColor, titleFontId, offset; @@ -1310,12 +1313,12 @@ class TAxisPainter extends ObjectPainter { title_shift_x = Math.round(title_offest_k * this.titleOffset); - if ((this.name === 'zaxis') && this.is_gaxis && ('getBoundingClientRect' in axis_g.node())) { - // special handling for color palette labels - draw them always on right side - const rect = axis_g.node().getBoundingClientRect(); - if (title_shift_x < rect.width - this.ticksSize) - title_shift_x = Math.round(rect.width - this.ticksSize); - } + // if ((this.name === 'zaxis') && this.is_gaxis && ('getBoundingClientRect' in axis_g.node())) { + // // special handling for color palette labels - draw them always on right side + // const rect = axis_g.node().getBoundingClientRect(); + // if (title_shift_x < rect.width - this.ticksSize) + // title_shift_x = Math.round(rect.width - this.ticksSize); + // } title_shift_y = Math.round(this.titleCenter ? h/2 : (xor_reverse ? h : 0)); diff --git a/js/modules/gpad/TFramePainter.mjs b/js/modules/gpad/TFramePainter.mjs index cf3a45fc953ed..0d92e85800485 100644 --- a/js/modules/gpad/TFramePainter.mjs +++ b/js/modules/gpad/TFramePainter.mjs @@ -448,6 +448,19 @@ const TooltipHandler = { } } + 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) : ''; @@ -542,24 +555,14 @@ const TooltipHandler = { gapminx = -1111, gapmaxx = -1111; const minhinty = -frame_shift.y, cp = this.getCanvPainter(), - maxhinty = cp.getPadHeight() - frame_rect.y - frame_shift.y, - FindPosInGap = y => { - for (let n = 0; (n < hints.length) && (y < maxhinty); ++n) { - const hint = hints[n]; - if (!hint) continue; - if ((hint.y >= y - 5) && (hint.y <= y + hint.height + 5)) { - y = hint.y + 10; - n = -1; - } - } - return y; - }; + 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}`); + group = hintsg.selectChild(`.painter_hint_${n}`); - if (show_only_best && (hint !== best_hint)) hint = null; + if (show_only_best && (hint !== best_hint)) + hint = null; if (hint === null) { group.remove(); @@ -579,16 +582,23 @@ const TooltipHandler = { 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 { - gapy = FindPosInGap(gapy); + for (let n = 0; (n < hints.length) && (gapy < maxhinty); ++n) { + const hint = hints[n]; + if (!hint) continue; + if ((hint.y >= gapy - 5) && (hint.y <= gapy + hint.height + 5)) { + gapy = hint.y + 10; + n = -1; + } + } if ((gapminx === -1111) && (gapmaxx === -1111)) gapminx = gapmaxx = hint.x; gapminx = Math.min(gapminx, hint.x); gapmaxx = Math.min(gapmaxx, hint.x); } group.attr('x', posx) - .attr('y', curry) - .property('curry', curry) - .property('gapy', gapy); + .attr('y', curry) + .property('curry', curry) + .property('gapy', gapy); curry += hint.height + 5; gapy += hint.height + 5; @@ -597,7 +607,7 @@ const TooltipHandler = { group.selectAll('*').remove(); group.attr('width', 60) - .attr('height', hint.height); + .attr('height', hint.height); const r = group.append('rect') .attr('x', 0) @@ -614,8 +624,11 @@ const TooltipHandler = { } r.attr('stroke-width', hint.exact ? 3 : 1); - for (let l = 0; l < (hint.lines ? hint.lines.length : 0); l++) { - if (hint.lines[l] !== null) { + 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) @@ -624,9 +637,8 @@ const TooltipHandler = { .style('fill', 'black') .style('pointer-events', 'none') .call(font.func) - .text(hint.lines[l]), - - box = getElementRect(txt, 'bbox'); + .text(line), + box = getElementRect(txt, 'bbox'); actualw = Math.max(actualw, box.width); } @@ -1795,7 +1807,7 @@ class TFramePainter extends ObjectPainter { umax = pad[`fU${name}max`], eps = 1e-7; - if (name === 'x') { + 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; @@ -1818,12 +1830,12 @@ class TFramePainter extends ObjectPainter { let aname = name; if (this.swap_xy) aname = (name === 'x') ? 'y' : 'x'; - const smin = `scale_${aname}min`, - smax = `scale_${aname}max`; + const smin = this[`scale_${aname}min`], + smax = this[`scale_${aname}max`]; - eps = (this[smax] - this[smin]) * 1e-7; + eps = (smax - smin) * 1e-7; - if ((Math.abs(umin - this[smin]) > eps) || (Math.abs(umax - this[smax]) > eps)) { + if ((Math.abs(umin - smin) > eps) || (Math.abs(umax - smax) > eps)) { this[`zoom_${aname}min`] = umin; this[`zoom_${aname}max`] = umax; } @@ -1892,6 +1904,9 @@ class TFramePainter extends ObjectPainter { if (opts.ndim > 1) this.applyAxisZoom('y'); if (opts.ndim > 2) this.applyAxisZoom('z'); + // TODO: extraction of PAD ranges must be done much earlier in hist painter + // normally histogram MUST set this ranges + // to be fixed after 7.6.0 release if (opts.check_pad_range === 'pad_range') { const canp = this.getCanvPainter(); // ignore range set in the online canvas @@ -1948,7 +1963,7 @@ class TFramePainter extends ObjectPainter { noexp_changed: this.y_noexp_changed, symlog: this.swap_xy ? opts.symlog_x : opts.symlog_y, logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < 0.01*this.ymax) ? 0.3 * opts.ymin_nz : 0, + log_min_nz: opts.ymin_nz && (opts.ymin_nz <= this.ymax) ? 0.5*opts.ymin_nz : 0, logminfactor: logminfactorY }); this.y_handle.assignFrameMembers(this, 'y'); @@ -2023,7 +2038,7 @@ class TFramePainter extends ObjectPainter { log: this.swap_xy ? pad.fLogx : pad.fLogy, noexp_changed: this.y2_noexp_changed, logcheckmin: (opts.ndim < 2) || this.swap_xy, - log_min_nz: opts.ymin_nz && (opts.ymin_nz < 0.01*this.y2max) ? 0.3 * opts.ymin_nz : 0, + 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'); diff --git a/js/modules/gpad/TPadPainter.mjs b/js/modules/gpad/TPadPainter.mjs index e1ea7a65c739d..863f22df537bd 100644 --- a/js/modules/gpad/TPadPainter.mjs +++ b/js/modules/gpad/TPadPainter.mjs @@ -3,15 +3,15 @@ import { gStyle, settings, constants, browser, internals, BIT, isObject, isFunc, isStr, clTObjArray, clTPaveText, clTColor, clTPad, clTStyle, clTLegend, clTHStack, clTMultiGraph, clTLegendEntry } from '../core.mjs'; import { select as d3_select, rgb as d3_rgb } from '../d3.mjs'; -import { ColorPalette, adoptRootColors, getGrayColors, extendRootColors, getRGBfromTColor, decodeWebCanvasColors } from '../base/colors.mjs'; -import { getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, svgToImage } from '../base/BasePainter.mjs'; +import { ColorPalette, adoptRootColors, getColorPalette, getGrayColors, extendRootColors, getRGBfromTColor, decodeWebCanvasColors } from '../base/colors.mjs'; +import { getElementRect, getAbsPosInCanvas, DrawOptions, compressSVG, makeTranslate, convertDate, svgToImage } from '../base/BasePainter.mjs'; import { ObjectPainter, selectActivePad, getActivePad } 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 } from '../gui/display.mjs'; +import { BrowserLayout, getHPainter } from '../gui/display.mjs'; const clTButton = 'TButton', kIsGrayscale = BIT(22); @@ -288,6 +288,7 @@ class TPadPainter extends ObjectPainter { delete this._snap_primitives; delete this._last_grayscale; delete this._custom_colors; + delete this._custom_palette_indexes; delete this._custom_palette_colors; delete this.root_colors; @@ -402,6 +403,33 @@ class TPadPainter extends ObjectPainter { * @private */ getNumPainters() { return this.painters.length; } + /** @summary Provides automatic color + * @desc Uses ROOT colors palette if possible + * @private */ + getAutoColor(numprimitives) { + if (!numprimitives) + numprimitives = this._num_primitives || 5; + if (numprimitives < 2) numprimitives = 2; + + let indx = this._auto_color ?? 0; + this._auto_color = (indx + 1) % numprimitives; + if (indx >= numprimitives) indx = numprimitives - 1; + + const indexes = this._custom_palette_indexes || this.getCanvPainter()?._custom_palette_indexes; + + 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 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 subpads, 'objects' only for object in current pad @@ -465,7 +493,7 @@ class TPadPainter extends ObjectPainter { const cp = this.getCanvPainter(); - let lineatt = this.is_active_pad && (cp?.highlight_gpad !== false) ? new TAttLineHandler({ style: 1, width: 1, color: 'red' }) : this.lineatt; + 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' }); @@ -616,7 +644,7 @@ class TPadPainter extends ObjectPainter { if (this._fixed_size) svg.attr('width', rect.width).attr('height', rect.height); else - svg.style('width', '100%').style('height', '100%').style('inset', '0px'); + 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); @@ -648,21 +676,26 @@ class TPadPainter extends ObjectPainter { if (!gStyle.fOptDate) dt.remove(); else { - if (dt.empty()) dt = info.append('text').attr('class', 'canvas_date'); - const date = new Date(), - posy = Math.round(rect.height * (1 - gStyle.fDateY)); + if (dt.empty()) + dt = info.append('text').attr('class', 'canvas_date'); + const posy = Math.round(rect.height * (1 - gStyle.fDateY)), + date = new Date(); let posx = Math.round(rect.width * gStyle.fDateX); - if (!is_batch && (posx < 25)) posx = 25; - if (gStyle.fOptDate > 1) date.setTime(gStyle.fOptDate*1000); + 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(date.toLocaleString('en-GB')); + .text(convertDate(date)); } - if (!gStyle.fOptFile || !this.getItemName()) + const iname = this.getItemName(); + if (iname) + this.drawItemNameOnCanvas(iname); + else if (!gStyle.fOptFile) info.selectChild('.canvas_item').remove(); - else - this.drawItemNameOnCanvas(this.getItemName()); return true; } @@ -672,14 +705,22 @@ class TPadPainter extends ObjectPainter { drawItemNameOnCanvas(item_name) { const info = this.getLayerSvg('info_layer', this.this_pad_name); let df = info.selectChild('.canvas_item'); - if (!gStyle.fOptFile || !item_name) + 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'); + 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(item_name); + .text(fname); + } + if (((gStyle.fOptDate === 2) || (gStyle.fOptDate === 3)) && fitem?._file) { + info.selectChild('.canvas_date') + .text(convertDate(gStyle.fOptDate === 2 ? fitem._file.fDatimeC.getDate() : fitem._file.fDatimeM.getDate())); } } @@ -906,7 +947,7 @@ class TPadPainter extends ObjectPainter { if ((obj._typename === clTObjArray) && (obj.name === 'ListOfColors')) { if (this.options?.CreatePalette) { let arr = []; - for (let n = obj.arr.length - this.options.CreatePalette; n readFileObject(f)); if (this._fullurl) return openFile(this._fullurl).then(f => readFileObject(f)); return Promise.resolve(null); - } - }; + }; keysHierarchy(folder, file.fKeys, file, ''); @@ -1325,7 +1324,7 @@ class HierarchyPainter extends BasePainter { } const pr = this.expandItem(this.itemFullName(hitem)); - if (isPromise(pr)) + if (isPromise(pr) && isObject(promises)) promises.push(pr); if (hitem._childs !== undefined) hitem._isopen = true; return hitem._isopen; @@ -2621,6 +2620,7 @@ class HierarchyPainter extends BasePainter { if (isfileopened) return; return httpRequest(filepath, 'object').then(res => { + if (!res) return; const h1 = { _jsonfile: filepath, _kind: prROOT + res._typename, _jsontmp: res, _name: filepath.split('/').pop() }; if (res.fTitle) h1._title = res.fTitle; h1._get = function(item /* ,itemname */) { @@ -2661,6 +2661,18 @@ class HierarchyPainter extends BasePainter { } } + /** @summary Find ROOT file which corresponds to provided item name + * @private */ + findRootFileForItem(itemname) { + let item = this.findItem(itemname); + while (item) { + if ((item._kind === kindTFile) && item._fullurl && item._file) + return item; + item = item?._parent; + } + return null; + } + /** @summary Open ROOT file * @param {string} filepath - URL to ROOT file, argument for openFile * @return {Promise} when file is opened */ @@ -2697,6 +2709,61 @@ class HierarchyPainter extends BasePainter { }).finally(() => showProgress()); } + /** @summary Create list of files for specified directory */ + async listServerDir(dirname) { + return httpRequest(dirname, 'text').then(res => { + if (!res) return false; + const h = { _name: 'Files', _kind: kTopFolder, _childs: [], _isopen: true }; + let p = 0; + while (p < res.length) { + p = res.indexOf('a href="', p+1); + if (p < 0) break; + p += 8; + const p2 = res.indexOf('"', p+1); + if (p2 < 0) break; + + const fname = res.slice(p, p2); + p = p2 + 1; + if ((fname.lastIndexOf('.root') === fname.length - 5) && (fname.length > 5)) { + h._childs.push({ + _name: fname, _title: dirname + fname, _url: dirname + fname, _kind: kindTFile, + _click_action: 'expand', _more: true, _obj: {}, + _expand: item => { + return openFile(item._url).then(file => { + if (!file) return false; + delete item._exapnd; + delete item._more; + delete item._click_action; + delete item._obj; + item._isopen = true; + this.fileHierarchy(file, item); + this.updateTreeNode(item); + }); + } + }); + } else if (((fname.lastIndexOf('.json.gz') === fname.length - 8) && (fname.length > 8)) || + ((fname.lastIndexOf('.json') === fname.length - 5) && (fname.length > 5))) { + h._childs.push({ + _name: fname, _title: dirname + fname, _jsonfile: dirname + fname, _can_draw: true, + _get: item => { + return httpRequest(item._jsonfile, 'object').then(res => { + if (res) { + item._kind = prROOT + res._typename; + item._jsontmp = res; + this.updateTreeNode(item); + } + return res; + }); + } + }); + } + } + if (h._childs.length > 0) + this.h = h; + return true; + }); + } + /** @summary Apply loaded TStyle object * @desc One also can specify item name of JSON file name where style is loaded * @param {object|string} style - either TStyle object of item name where object can be load */ @@ -3321,6 +3388,7 @@ class HierarchyPainter extends BasePainter { let prereq = getOption('prereq') || '', load = getOption('load'), + dir = getOption('dir'), inject = getOption('inject'), filesarr = getOptionAsArray('#file;files'), itemsarr = getOptionAsArray('#item;items'), @@ -3435,7 +3503,9 @@ class HierarchyPainter extends BasePainter { promise = this.openJsonFile(jsonarr.shift()); else if (filesarr.length > 0) promise = this.openRootFile(filesarr.shift()); - else if (expanditems.length > 0) + else if (dir) { + promise = this.listServerDir(dir); dir = ''; + } else if (expanditems.length > 0) promise = this.expandItem(expanditems.shift()); else if (style.length > 0) promise = this.applyStyle(style.shift()); diff --git a/js/modules/gui/display.mjs b/js/modules/gui/display.mjs index f1347570e61a1..da2adb64adada 100644 --- a/js/modules/gui/display.mjs +++ b/js/modules/gui/display.mjs @@ -660,7 +660,7 @@ class TabsDisplay extends MDIDisplay { if (top.empty()) { top = dom.append('div').attr('class', 'jsroot_tabs') - .attr('style', 'display: flex; flex-direction: column; position: absolute; overflow: hidden; inset: 0px 0px 0px 0px'); + .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') @@ -707,7 +707,7 @@ class TabsDisplay extends MDIDisplay { const draw_frame = main.append('div') .attr('frame_title', title) .attr('class', 'jsroot_tabs_draw') - .attr('style', 'overflow: hidden; position: absolute; inset: 0px') + .attr('style', 'overflow: hidden; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px;') .property('frame_id', frame_id); this.modifyTabsFrame(frame_id, 'activate'); @@ -1274,7 +1274,7 @@ class BrowserLayout { input_style = settings.DarkMode ? `background-color: #222; color: ${text_color}` : ''; injectStyle( - '.jsroot_browser { pointer-events: none; position: absolute; inset: 0px; margin: 0px; border: 0px; overflow: hidden; }'+ + '.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} }`+ @@ -1296,7 +1296,7 @@ class BrowserLayout { main.append('div').attr('id', this.drawing_divid()) .classed('jsroot_draw_area', true) .style('position', 'absolute') - .style('inset', '0px'); + .style('left', 0).style('top', 0).style('bottom', 0).style('right', 0); if (with_browser) main.append('div').classed('jsroot_browser', true); diff --git a/js/modules/gui/menu.mjs b/js/modules/gui/menu.mjs index a1d62e0eadae9..377dedb8adc4d 100644 --- a/js/modules/gui/menu.mjs +++ b/js/modules/gui/menu.mjs @@ -1,4 +1,4 @@ -import { settings, browser, gStyle, isObject, isFunc, isStr, clTGaxis, kInspect, getDocument } from '../core.mjs'; +import { settings, internals, browser, gStyle, isObject, isFunc, isStr, clTGaxis, kInspect, getDocument } from '../core.mjs'; import { rgb as d3_rgb, select as d3_select } from '../d3.mjs'; import { selectgStyle, saveSettings, readSettings, saveStyle, getColorExec } from './utils.mjs'; import { getColor } from '../base/colors.mjs'; @@ -707,7 +707,9 @@ class JSRootMenu { 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.addchk(settings.ProgressBox, 'Progress box', flag => { settings.ProgressBox = 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.add('endsub:'); this.add('sub:Drawing'); @@ -746,8 +748,9 @@ class JSRootMenu { const setStyleField = arg => { gStyle[arg.slice(1)] = parseInt(arg[0]); }, addStyleIntField = (name, field, arr) => { this.add('sub:' + name); + const curr = gStyle[field] >= arr.length ? 1 : gStyle[field]; for (let v = 0; v < arr.length; ++v) - this.addchk(gStyle[field] === v, arr[v], `${v}${field}`, setStyleField); + this.addchk(curr === v, arr[v], `${v}${field}`, setStyleField); this.add('endsub:'); }; @@ -755,8 +758,9 @@ class JSRootMenu { this.add('sub:Canvas'); this.addColorMenu('Color', gStyle.fCanvasColor, col => { gStyle.fCanvasColor = col; }); - this.addchk(gStyle.fOptDate, 'Draw date', flag => { gStyle.fOptDate = flag ? 1 : 0; }); - this.addchk(gStyle.fOptFile, 'Draw item', flag => { gStyle.fOptFile = flag ? 1 : 0; }); + 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.add('endsub:'); @@ -1319,18 +1323,21 @@ class StandaloneMenu extends JSRootMenu { } /** @summary Run modal elements with standalone code */ - async runModal(title, main_content, args) { + createModal(title, main_content, args) { if (!args) args = {}; - const dlg_id = this.menuname + '_dialog'; + + if (!args.Ok) args.Ok = 'Ok'; + + const modal = { args }, dlg_id = (this?.menuname ?? 'root_modal') + '_dialog'; d3_select(`#${dlg_id}`).remove(); d3_select(`#${dlg_id}_block`).remove(); - const w = Math.min(args.width || 450, Math.round(0.9*browser.screenWidth)), - block = d3_select('body').append('div') + const w = Math.min(args.width || 450, Math.round(0.9*browser.screenWidth)); + modal.block = d3_select('body').append('div') .attr('id', `${dlg_id}_block`) .attr('class', 'jsroot_dialog_block') - .attr('style', 'z-index: 100000; position: absolute; inset: 0px; opacity: 0.2; background-color: white'), - element = d3_select('body') + .attr('style', 'z-index: 100000; position: absolute; left: 0px; top: 0px; bottom: 0px; right: 0px; opacity: 0.2; background-color: white'); + modal.element = d3_select('body') .append('div') .attr('id', dlg_id) .attr('class', 'jsroot_dialog') @@ -1345,38 +1352,61 @@ class StandaloneMenu extends JSRootMenu { `
${title}
`+ `
${main_content}
`+ '
'+ - ''+ + ``+ (args.btns ? '' : '') + '
'); - return new Promise(resolveFunc => { - element.on('keyup', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); - resolveFunc(evnt.code === 'Enter' ? element.node() : null); - element.remove(); - block.remove(); - } - }); - element.on('keydown', evnt => { - if ((evnt.code === 'Enter') || (evnt.code === 'Escape')) { - evnt.preventDefault(); - evnt.stopPropagation(); + 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); } - }); - element.selectAll('.jsroot_dialog_button').on('click', evnt => { - resolveFunc(args.btns && (d3_select(evnt.target).text() === 'Ok') ? element.node() : null); - element.remove(); - block.remove(); - }); + } + }; + + 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 && (d3_select(evnt.target).text() === args.Ok) ? modal.element.node() : null); + }); - let f = element.select('.jsroot_dialog_content').select('input'); - if (f.empty()) f = element.select('.jsroot_dialog_footer').select('button'); - if (!f.empty()) f.node().focus(); + 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(); + 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 @@ -1422,6 +1452,23 @@ function showPainterMenu(evnt, painter, 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) { diff --git a/js/modules/gui/utils.mjs b/js/modules/gui/utils.mjs index 0874117663fb0..039e48dceefa5 100644 --- a/js/modules/gui/utils.mjs +++ b/js/modules/gui/utils.mjs @@ -1,4 +1,4 @@ -import { settings, browser, gStyle, isBatchMode, isNodeJs, isObject, isFunc, isStr, source_dir, atob_func, btoa_func } from '../core.mjs'; +import { settings, internals, browser, gStyle, isBatchMode, isNodeJs, isObject, isFunc, isStr, source_dir, atob_func, btoa_func } from '../core.mjs'; import { select as d3_select, pointer as d3_pointer, drag as d3_drag, color as d3_color } from '../d3.mjs'; import { BasePainter } from '../base/BasePainter.mjs'; import { resize } from '../base/ObjectPainter.mjs'; @@ -8,42 +8,54 @@ import { getRootColors } from '../base/colors.mjs'; * @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 {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) { +function showProgress(msg, tmout, click_handle) { if (isBatchMode() || (typeof document === 'undefined')) return; - const id = 'jsroot_progressbox'; + + const id = 'jsroot_progressbox', modal = (settings.ProgressBox === 'modal') && isFunc(internals._modalProgress) ? internals._modalProgress : null; let box = d3_select('#' + id); - if (!settings.ProgressBox) + if (!settings.ProgressBox) { + if (modal) modal(); return box.remove(); + } if ((arguments.length === 0) || !msg) { if ((tmout !== -1) || (!box.empty() && box.property('with_timeout'))) box.remove(); + if (modal) modal(); return; } - if (box.empty()) { - box = d3_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'); - } + if (modal) { + box.remove(); + modal(msg, click_handle); + } else { + if (box.empty()) { + box = d3_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); + box.property('with_timeout', false); - if (isStr(msg)) - box.select('p').html(msg); - else { - box.html(''); - box.node().appendChild(msg); - } + 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' : ''); + } - box.select('p').attr('style', 'font-size: 10px; margin-left: 10px; margin-right: 10px; margin-top: 3px; margin-bottom: 3px'); + p.attr('style', 'font-size: 10px; margin-left: 10px; margin-right: 10px; margin-top: 3px; margin-bottom: 3px'); + } if (Number.isFinite(tmout) && (tmout > 0)) { - box.property('with_timeout', true); + if (!box.empty()) + box.property('with_timeout', true); setTimeout(() => showProgress('', -1), tmout); } } diff --git a/js/modules/hist/TF1Painter.mjs b/js/modules/hist/TF1Painter.mjs index 8adc1b52fe547..ddf2668e0fa03 100644 --- a/js/modules/hist/TF1Painter.mjs +++ b/js/modules/hist/TF1Painter.mjs @@ -58,6 +58,7 @@ function proivdeEvalPar(obj, check_save) { .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, ') @@ -141,14 +142,20 @@ function _getTF1Save(func, x) { * @desc First try evaluate, if not possible - check saved buffer * @private */ function getTF1Value(func, x, skip_eval = undefined) { - let y = 0; + let y = 0, iserr = false; if (!func) return 0; - if (!skip_eval && !func.evalPar) - proivdeEvalPar(func); + if (!skip_eval && !func.evalPar) { + try { + if (!proivdeEvalPar(func)) + iserr = true; + } catch { + iserr = true; + } + } - if (func.evalPar) { + if (func.evalPar && !iserr) { try { y = func.evalPar(x); return y; @@ -265,8 +272,14 @@ class TF1Painter extends TH1Painter { const np = Math.max(tf1.fNpx, 100); let iserror = false; - if (!tf1.evalPar && !proivdeEvalPar(tf1)) - iserror = true; + if (!tf1.evalPar) { + try { + if (!proivdeEvalPar(tf1)) + iserror = true; + } catch { + iserror = true; + } + } ensureBins(np); diff --git a/js/modules/hist/TGraphPainter.mjs b/js/modules/hist/TGraphPainter.mjs index 8bdaaf8d7f319..a2027a07a4bab 100644 --- a/js/modules/hist/TGraphPainter.mjs +++ b/js/modules/hist/TGraphPainter.mjs @@ -14,6 +14,8 @@ class TGraphPainter extends TGraphPainter2D { if (fp.zoom_xmin !== fp.zoom_xmax) if ((this.options.pos3d < fp.zoom_xmin) || (this.options.pos3d > fp.zoom_xmax)) return; + this.createGraphDrawAttributes(true); + const drawbins = this.optimizeBins(1000); let first = 0, last = drawbins.length-1; diff --git a/js/modules/hist/TH1Painter.mjs b/js/modules/hist/TH1Painter.mjs index ca3471e757510..3f05762c28cb0 100644 --- a/js/modules/hist/TH1Painter.mjs +++ b/js/modules/hist/TH1Painter.mjs @@ -23,7 +23,7 @@ class TH1Painter extends TH1Painter2D { if (reason === 'resize') { if (is_main && main.resize3D()) main.render3D(); } else { - this.deleteAttr(); + this.createHistDrawAttributes(true); this.scanContent(true); // may be required for axis drawings diff --git a/js/modules/hist/TH2Painter.mjs b/js/modules/hist/TH2Painter.mjs index 47106f73c5ea4..f74fbe9ee39ff 100644 --- a/js/modules/hist/TH2Painter.mjs +++ b/js/modules/hist/TH2Painter.mjs @@ -233,7 +233,7 @@ class TH2Painter extends TH2Painter2D { if (logz && (this.zmin <= 0)) this.zmin = this.zmax * 1e-5; - this.deleteAttr(); + this.createHistDrawAttributes(true); if (is_main) { assignFrame3DMethods(main); diff --git a/js/modules/hist/THStackPainter.mjs b/js/modules/hist/THStackPainter.mjs index e9de4386797a0..c4e4bdf240103 100644 --- a/js/modules/hist/THStackPainter.mjs +++ b/js/modules/hist/THStackPainter.mjs @@ -1,4 +1,4 @@ -import { clone, create, createHistogram, setHistogramTitle, isFunc, gStyle, clTList, clTH1I, clTH2, clTH2I, kNoZoom, kNoStats } from '../core.mjs'; +import { clone, create, createHistogram, setHistogramTitle, gStyle, clTList, clTH1I, clTH2, clTH2I, kNoZoom, kNoStats } from '../core.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter, EAxisBits } from '../base/ObjectPainter.mjs'; import { TH1Painter } from './TH1Painter.mjs'; @@ -42,12 +42,26 @@ class THStackPainter extends ObjectPainter { lst.Add(clone(stack.fHists.arr[0]), stack.fHists.opt[0]); for (let i = 1; i < nhists; ++i) { const hnext = clone(stack.fHists.arr[i]), - hnextopt = stack.fHists.opt[i], - hprev = lst.arr[i-1]; - - if ((hnext.fNbins !== hprev.fNbins) || - (hnext.fXaxis.fXmin !== hprev.fXaxis.fXmin) || - (hnext.fXaxis.fXmax !== hprev.fXaxis.fXmax)) { + hnextopt = stack.fHists.opt[i], + hprev = lst.arr[i-1], + xnext = hnext.fXaxis, xprev = hprev.fXaxis; + + let match = (xnext.fNbins === xprev.fNbins) && + (xnext.fXmin === xprev.fXmin) && + (xnext.fXmax === xprev.fXmax); + + if (!match && (xnext.fNbins > 0) && (xnext.fNbins < xprev.fNbins) && (xnext.fXmin === xprev.fXmin) && + (Math.abs((xnext.fXmax - xnext.fXmin)/xnext.fNbins - (xprev.fXmax - xprev.fXmin)/xprev.fNbins) < 0.0001)) { + // simple extension of histogram to make sum + const arr = new Array(hprev.fNcells).fill(0); + for (let n = 1; n <= xnext.fNbins; ++n) + arr[n] = hnext.fArray[n]; + hnext.fNcells = hprev.fNcells; + Object.assign(xnext, xprev); + hnext.fArray = arr; + match = true; + } + if (!match) { console.warn(`When drawing THStack, cannot sum-up histograms ${hnext.fName} and ${hprev.fName}`); lst.Clear(); return false; @@ -170,6 +184,8 @@ class THStackPainter extends ObjectPainter { hopt += ' ' + this.options.hopt; if (this.options.draw_errors && !hopt) hopt = 'E'; + if (!this.options.pads) + hopt += ' same nostat' + this.options.auto; return hopt; } @@ -186,17 +202,6 @@ class THStackPainter extends ObjectPainter { subid = this.options.nostack ? `hists_${rindx}` : `stack_${rindx}`, hist = hlst.arr[rindx], hopt = this.getHistDrawOption(hist, hlst.opt[rindx]); - let exec = ''; - - if (this.options._pfc || this.options._plc || this.options._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(nhists); - if (this.options._pfc) { hist.fFillColor = icolor; exec += `SetFillColor(${icolor});;`; } - if (this.options._plc) { hist.fLineColor = icolor; exec += `SetLineColor(${icolor});;`; } - if (this.options._pmc) { hist.fMarkerColor = icolor; exec += `SetMarkerColor(${icolor});;`; } - } - } // handling of 'pads' draw option if (pad_painter) { @@ -209,7 +214,6 @@ class THStackPainter extends ObjectPainter { return this.hdraw_func(subpad_painter.getDom(), hist, hopt).then(subp => { if (subp) { subp.setSecondaryId(this, subid); - subp._auto_exec = exec; this.painters.push(subp); } subpad_painter.selectCurrentPad(prev_name); @@ -221,8 +225,11 @@ class THStackPainter extends ObjectPainter { // also used to provide tooltips if ((rindx > 0) && !this.options.nostack) hist.$baseh = hlst.arr[rindx - 1]; + // this number used for auto colors creation + if (this.options.auto) + hist.$num_histos = nhists; - return this.hdraw_func(this.getDom(), hist, hopt + ' same nostat').then(subp => { + return this.hdraw_func(this.getDom(), hist, hopt).then(subp => { subp.setSecondaryId(this, subid); this.painters.push(subp); return this.drawNextHisto(indx+1, pad_painter); @@ -232,7 +239,7 @@ class THStackPainter extends ObjectPainter { /** @summary Decode draw options of THStack painter */ decodeOptions(opt) { if (!this.options) this.options = {}; - Object.assign(this.options, { ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '' }); + Object.assign(this.options, { ndim: 1, nostack: false, same: false, horder: true, has_errors: false, draw_errors: false, hopt: '', auto: '' }); const stack = this.getObject(), hist = stack.fHistogram || (stack.fHists ? stack.fHists.arr[0] : null) || (stack.fStack ? stack.fStack.arr[0] : null), @@ -266,9 +273,7 @@ class THStackPainter extends ObjectPainter { d.check('NOCLEAR'); // ignore noclear option - this.options._pfc = d.check('PFC'); - this.options._plc = d.check('PLC'); - this.options._pmc = d.check('PMC'); + ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) this.options.auto += ' ' + f; }); this.options.pads = d.check('PADS'); if (this.options.pads) this.options.nostack = true; @@ -368,10 +373,11 @@ class THStackPainter extends ObjectPainter { nhists = hlst?.arr?.length ?? 0; if (nhists !== this.painters.length) { + this.did_update = 1; this.getPadPainter()?.cleanPrimitives(objp => this.painters.indexOf(objp) >= 0); this.painters = []; - this.did_update = true; } else { + this.did_update = 2; for (let indx = 0; indx < nhists; ++indx) { const rindx = this.options.horder ? indx : nhists - indx - 1, hist = hlst.arr[rindx]; @@ -384,10 +390,18 @@ class THStackPainter extends ObjectPainter { /** @summary Redraw THStack * @desc Do something if previous update had changed number of histograms */ - redraw() { - if (this.did_update) { + redraw(reason) { + if (this.did_update === 1) { delete this.did_update; return this.drawNextHisto(0, this.options.pads ? this.getPadPainter() : null); + } else if (this.did_update === 2) { + delete this.did_update; + const redrawSub = indx => { + if (indx >= this.painters.length) + return Promise.resolve(this); + return this.painters[indx].redraw(reason).then(() => redrawSub(indx+1)); + }; + return redrawSub(0); } } diff --git a/js/modules/hist/TPavePainter.mjs b/js/modules/hist/TPavePainter.mjs index df62172562493..a03e4e7f9ebef 100644 --- a/js/modules/hist/TPavePainter.mjs +++ b/js/modules/hist/TPavePainter.mjs @@ -505,8 +505,9 @@ class TPavePainter extends ObjectPainter { // individual positioning const align = entry.fTextAlign || this.textatt.align, halign = Math.floor(align/10), + valign = align % 10, x = entry.fX ? entry.fX*width : (halign === 1 ? margin_x : (halign === 2 ? width / 2 : width - margin_x)), - y = entry.fY ? (1 - entry.fY)*height : texty, + y = entry.fY ? (1 - entry.fY)*height : (texty + (valign === 2 ? stepy / 2 : (valign === 3 ? stepy : 0))), sub_g = text_g.append('svg:g'); this.startTextDrawing(this.textatt.font, this.textatt.getAltSize(entry.fTextSize, pad_height), sub_g); @@ -518,7 +519,7 @@ class TPavePainter extends ObjectPainter { } else { // default position if (num_default++ === 0) - this.startTextDrawing(this.textatt.font, height/(nlines * 1.2), text_g, max_font_size); + this.startTextDrawing(this.textatt.font, 0.85*height/nlines, text_g, max_font_size); this.drawText({ x: margin_x, y: texty, width: width - 2*margin_x, height: stepy, align: entry.fTextAlign || this.textatt.align, @@ -848,11 +849,12 @@ class TPavePainter extends ObjectPainter { axis.fTitle = zaxis.fTitle; axis.fTitleSize = zaxis.fTitleSize; axis.fTitleOffset = zaxis.fTitleOffset; - axis.fTitleColor = zaxis.fTitleColor; + axis.fTextColor = zaxis.fTitleColor; + axis.fTextFont = zaxis.fTitleFont; axis.fLineColor = zaxis.fAxisColor; - axis.fTextSize = zaxis.fLabelSize; - axis.fTextColor = zaxis.fLabelColor; - axis.fTextFont = zaxis.fLabelFont; + axis.fLabelSize = zaxis.fLabelSize; + axis.fLabelColor = zaxis.fLabelColor; + axis.fLabelFont = zaxis.fLabelFont; axis.fLabelOffset = zaxis.fLabelOffset; this.z_handle.setHistPainter(main, 'z'); this.z_handle.source_axis = zaxis; diff --git a/js/modules/hist2d/TGraphPainter.mjs b/js/modules/hist2d/TGraphPainter.mjs index 104ac63b1159f..104ce1ad5f10f 100644 --- a/js/modules/hist2d/TGraphPainter.mjs +++ b/js/modules/hist2d/TGraphPainter.mjs @@ -151,9 +151,12 @@ class TGraphPainter extends ObjectPainter { if (d.check('POS3D_', true)) res.pos3d = d.partAsInt() - 0.5; - res._pfc = d.check('PFC'); - res._plc = d.check('PLC'); - res._pmc = d.check('PMC'); + if (d.check('PFC') && !res._pfc) + res._pfc = 2; + if (d.check('PLC') && !res._plc) + res._plc = 2; + if (d.check('PMC') && !res._pmc) + res._pmc = 2; if (d.check('A')) res.Axis = d.check('I') ? 'A;' : _a; // I means invisible axis if (d.check('X+')) { res.Axis += 'X+'; res.second_x = has_main; } @@ -870,6 +873,28 @@ class TGraphPainter extends ObjectPainter { console.log('Load ./hist/TGraphPainter.mjs to draw graph in 3D'); } + /** @summary Create necessary histogram draw attributes */ + createGraphDrawAttributes(only_check_auto) { + const graph = this.getGraph(), o = this.options; + if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { + const pp = this.getPadPainter(); + if (isFunc(pp?.getAutoColor)) { + const icolor = pp.getAutoColor(graph.$num_graphs); + this._auto_exec = ''; // can be reused when sending option back to server + if (o._pfc > 1) { o._pfc = 1; graph.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } + if (o._plc > 1) { o._plc = 1; graph.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } + if (o._pmc > 1) { o._pmc = 1; graph.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } + } + } + + if (only_check_auto) + this.deleteAttr(); + else { + this.createAttLine({ attr: graph, can_excl: true }); + this.createAttFill({ attr: graph }); + } + } + /** @summary draw TGraph */ drawGraph() { const pmain = this.get_main(), @@ -887,19 +912,7 @@ class TGraphPainter extends ObjectPainter { this.createG(!pmain.pad_layer); - if (this.options._pfc || this.options._plc || this.options._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(); - if (this.options._pfc) { graph.fFillColor = icolor; delete this.fillatt; } - if (this.options._plc) { graph.fLineColor = icolor; delete this.lineatt; } - if (this.options._pmc) { graph.fMarkerColor = icolor; delete this.markeratt; } - this.options._pfc = this.options._plc = this.options._pmc = false; - } - } - - this.createAttLine({ attr: graph, can_excl: true }); - this.createAttFill({ attr: graph }); + this.createGraphDrawAttributes(); this.fillatt.used = false; // mark used only when really used @@ -1335,6 +1348,14 @@ class TGraphPainter extends ObjectPainter { this.submitCanvExec(exec); } + /** @summary Fill option object used in TWebCanvas */ + fillWebObjectOptions(res) { + if (this._auto_exec && res) { + res.fcust = 'auto_exec:' + this._auto_exec; + delete this._auto_exec; + } + } + /** @summary Fill context menu */ fillContextMenuItems(menu) { if (!this.snapid) @@ -1357,8 +1378,8 @@ class TGraphPainter extends ObjectPainter { if (method.fName === 'InsertPoint') { if (pnt) { const funcs = pmain.getGrFuncs(this.options.second_x, this.options.second_y), - userx = funcs.revertAxis('x', pnt.x) ?? 0, - usery = funcs.revertAxis('y', pnt.y) ?? 0; + userx = funcs.revertAxis('x', pnt.x) ?? 0, + usery = funcs.revertAxis('y', pnt.y) ?? 0; this.submitCanvExec(`AddPoint(${userx.toFixed(3)}, ${usery.toFixed(3)})`, method.$execid); } } else if (method.$execid && (hint?.binindx !== undefined)) @@ -1381,6 +1402,23 @@ class TGraphPainter extends ObjectPainter { graph.fNpoints = obj.fNpoints; graph.fMinimum = obj.fMinimum; graph.fMaximum = obj.fMaximum; + + const o = this.options; + + if (this.snapid !== undefined) + o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas + + if (!o._pfc) + graph.fFillColor = obj.fFillColor; + graph.fFillStyle = obj.fFillStyle; + if (!o._plc) + graph.fLineColor = obj.fLineColor; + graph.fLineStyle = obj.fLineStyle; + graph.fLineWidth = obj.fLineWidth; + if (!o._pmc) + graph.fMarkerColor = obj.fMarkerColor; + graph.fMarkerSize = obj.fMarkerSize; + graph.fMarkerStyle = obj.fMarkerStyle; } /** @summary Update TGraph object */ diff --git a/js/modules/hist2d/TH1Painter.mjs b/js/modules/hist2d/TH1Painter.mjs index 3fa014c3b2991..8da5e61d78e8a 100644 --- a/js/modules/hist2d/TH1Painter.mjs +++ b/js/modules/hist2d/TH1Painter.mjs @@ -85,13 +85,12 @@ class TH1Painter extends THistPainter { else hsum += histo.getBinContent(0) + histo.getBinContent(this.nbinsx + 1); - this.stat_entries = hsum; - if (histo.fEntries > 1) this.stat_entries = histo.fEntries; + this.stat_entries = (histo.fEntries > 1) ? histo.fEntries : hsum; this.hmin = hmin; this.hmax = hmax; - this.ymin_nz = hmin_nz; // value can be used to show optimal log scale + // this.ymin_nz = hmin_nz; // value can be used to show optimal log scale if ((this.nbinsx === 0) || ((Math.abs(hmin) < 1e-300) && (Math.abs(hmax) < 1e-300))) this.draw_content = false; @@ -108,10 +107,16 @@ class TH1Painter extends THistPainter { this.ymin = 0; this.ymax = hmin * 2; } } else { - const dy = (hmax - hmin) * gStyle.fHistTopMargin; - this.ymin = hmin - dy; - if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0; - this.ymax = hmax + dy; + const pad = this.getPadPainter()?.getRootPad(), + pad_logy = (this.options.BarStyle >= 20) ? pad.fLogx : (pad?.fLogv ?? pad?.fLogy); + if (pad_logy) { + this.ymin = (hmin_nz || hmin) * 0.5; + this.ymax = hmax*2*(0.9/0.95); + } else { + this.ymin = hmin - (hmax - hmin) * gStyle.fHistTopMargin; + if ((this.ymin < 0) && (hmin >= 0)) this.ymin = 0; + this.ymax = hmax + (hmax - this.ymin) * gStyle.fHistTopMargin; + } } } @@ -462,13 +467,13 @@ class TH1Painter extends THistPainter { xaxis = histo.fXaxis, exclude_zero = !this.options.Zero, show_errors = this.options.Error, - show_line = this.options.Line, show_curve = this.options.Curve, show_text = this.options.Text, text_profile = show_text && (this.options.TextKind === 'E') && this.isTProfile() && histo.fBinEntries, grpnts = []; let res = '', lastbin = false, show_markers = this.options.Mark, + show_line = this.options.Line, startx, startmidx, currx, curry, x, grx, y, gry, curry_min, curry_max, prevy, prevx, i, bestimin, bestimax, path_fill = null, path_err = null, path_marker = null, path_line = '', hints_err = null, hints_marker = null, hsz = 5, @@ -482,7 +487,8 @@ class TH1Painter extends THistPainter { if (this.options.ErrorKind === 2) { if (this.fillatt.empty()) show_markers = true; else path_fill = ''; - } else if (this.options.Error) { + } else if (show_errors) { + show_line = false; path_err = ''; hints_err = want_tooltip ? '' : null; do_err = true; @@ -572,8 +578,10 @@ class TH1Painter extends THistPainter { path_err += `M${midx-dlw},${my-yerr1+dend}h${2*dlw}m${-dlw},0v${yerr1+yerr2-2*dend}m${-dlw},0h${2*dlw}`; else path_err += `M${midx},${my-yerr1+dend}v${yerr1+yerr2-2*dend}`; - if (hints_err !== null) - hints_err += `M${midx-edx},${my-yerr1}h${2*edx}v${yerr1+yerr2}h${-2*edx}z`; + if (hints_err !== null) { + const he1 = Math.max(yerr1, 5), he2 = Math.max(yerr2, 5); + hints_err += `M${midx-edx},${my-he1}h${2*edx}v${he1+he2}h${-2*edx}z`; + } }, draw_bin = bin => { if (extract_bin(bin)) { if (show_text) { diff --git a/js/modules/hist2d/THistPainter.mjs b/js/modules/hist2d/THistPainter.mjs index ee27258ac41ba..c7cd2672aa8bd 100644 --- a/js/modules/hist2d/THistPainter.mjs +++ b/js/modules/hist2d/THistPainter.mjs @@ -40,7 +40,7 @@ class THistDrawOptions { Mode3D: false, x3dscale: 1, y3dscale: 1, Render3D: constants.Render3D.Default, FrontBox: true, BackBox: true, - _pmc: false, _plc: false, _pfc: false, need_fillcol: false, + need_fillcol: false, minimum: kNoZoom, maximum: kNoZoom, ymin: 0, ymax: 0, cutg: null, IgnoreMainScale: false }); } @@ -377,11 +377,14 @@ class THistDrawOptions { if ((hdim === 3) && d.check('FB')) this.FrontBox = false; if ((hdim === 3) && d.check('BB')) this.BackBox = false; - this._pfc = d.check('PFC'); - this._plc = d.check('PLC') || this.AutoColor; - this._pmc = d.check('PMC'); + if (d.check('PFC') && !this._pfc) + this._pfc = 2; + if ((d.check('PLC') || this.AutoColor) && !this._plc) + this._plc = 2; + if (d.check('PMC') && !this._pmc) + this._pmc = 2; - if (d.check('L')) { this.Line = true; this.Hist = false; this.Error = false; } + if (d.check('L')) { this.Line = true; this.Hist = false; } if (d.check('F')) { this.Fill = true; this.need_fillcol = true; } if (d.check('A')) this.Axis = -1; @@ -915,49 +918,27 @@ class THistPainter extends ObjectPainter { this.check_pad_range = use_pad ? 'pad_range' : true; } - /** @summary Generates automatic color for some objects painters */ - createAutoColor(numprimitives) { - if (!numprimitives) - numprimitives = this.getPadPainter()?.getRootPad(true)?.fPrimitves?.arr?.length || 5; - - let indx = this._auto_color || 0; - this._auto_color = indx + 1; - - const pal = this.getHistPalette(); - - if (pal) { - if (numprimitives < 2) numprimitives = 2; - if (indx >= numprimitives) indx = numprimitives - 1; - const palindx = Math.round(indx * (pal.getLength()-3) / (numprimitives-1)), - colvalue = pal.getColor(palindx); - - return this.addColor(colvalue); - } - - this._auto_color = this._auto_color % 8; - return indx+2; - } - /** @summary Create necessary histogram draw attributes */ - createHistDrawAttributes() { - const histo = this.getHisto(); - - if (this.options._pfc || this.options._plc || this.options._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(); - let exec = ''; - if (this.options._pfc) { histo.fFillColor = icolor; exec += `SetFillColor(${icolor});;`; delete this.fillatt; } - if (this.options._plc) { histo.fLineColor = icolor; exec += `SetLineColor(${icolor});;`; delete this.lineatt; } - if (this.options._pmc) { histo.fMarkerColor = icolor; exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } - this.options._pfc = this.options._plc = this.options._pmc = false; - this._auto_exec = exec; // can be reused when sending option back to server + createHistDrawAttributes(only_check_auto) { + const histo = this.getHisto(), o = this.options; + + if (o._pfc > 1 || o._plc > 1 || o._pmc > 1) { + const pp = this.getPadPainter(); + if (isFunc(pp?.getAutoColor)) { + const icolor = pp.getAutoColor(histo.$num_histos); + this._auto_exec = ''; // can be reused when sending option back to server + if (o._pfc > 1) { o._pfc = 1; histo.fFillColor = icolor; this._auto_exec += `SetFillColor(${icolor});;`; delete this.fillatt; } + if (o._plc > 1) { o._plc = 1; histo.fLineColor = icolor; this._auto_exec += `SetLineColor(${icolor});;`; delete this.lineatt; } + if (o._pmc > 1) { o._pmc = 1; histo.fMarkerColor = icolor; this._auto_exec += `SetMarkerColor(${icolor});;`; delete this.markeratt; } } } - this.createAttFill({ attr: histo, color: this.options.histoFillColor, pattern: this.options.histoFillPattern, kind: 1 }); - - this.createAttLine({ attr: histo, color0: this.options.histoLineColor }); + if (only_check_auto) + this.deleteAttr(); + else { + this.createAttFill({ attr: histo, color: this.options.histoFillColor, pattern: this.options.histoFillPattern, kind: 1 }); + this.createAttLine({ attr: histo, color0: this.options.histoLineColor }); + } } /** @summary Update axes attributes in target histogram @@ -1000,7 +981,8 @@ class THistPainter extends ObjectPainter { updateObject(obj, opt) { const histo = this.getHisto(), fp = this.getFramePainter(), - pp = this.getPadPainter(); + pp = this.getPadPainter(), + o = this.options; if (obj !== histo) { if (!this.matchObjectType(obj)) return false; @@ -1018,14 +1000,22 @@ class THistPainter extends ObjectPainter { } // special treatment for webcanvas - also name can be changed - if (this.snapid !== undefined) + if (this.snapid !== undefined) { histo.fName = obj.fName; + o._pfc = o._plc = o._pmc = 0; // auto colors should be processed in web canvas + } - histo.fFillColor = obj.fFillColor; + if (!o._pfc) + histo.fFillColor = obj.fFillColor; histo.fFillStyle = obj.fFillStyle; - histo.fLineColor = obj.fLineColor; + if (!o._plc) + histo.fLineColor = obj.fLineColor; histo.fLineStyle = obj.fLineStyle; histo.fLineWidth = obj.fLineWidth; + if (!o._pmc) + histo.fMarkerColor = obj.fMarkerColor; + histo.fMarkerSize = obj.fMarkerSize; + histo.fMarkerStyle = obj.fMarkerStyle; histo.fEntries = obj.fEntries; histo.fTsumw = obj.fTsumw; @@ -1056,7 +1046,7 @@ class THistPainter extends ObjectPainter { histo.fSumw2 = obj.fSumw2; if (this.getDimension() === 1) - this.options.decodeSumw2(histo); + o.decodeSumw2(histo); if (this.isTProfile()) histo.fBinEntries = obj.fBinEntries; @@ -1072,14 +1062,14 @@ class THistPainter extends ObjectPainter { const changed_opt = (histo.fOption !== obj.fOption); histo.fOption = obj.fOption; - if (((opt !== undefined) && (this.options.original !== opt)) || changed_opt) + if (((opt !== undefined) && (o.original !== opt)) || changed_opt) this.decodeOptions(opt || histo.fOption); } - if (!this.options.ominimum) - this.options.minimum = histo.fMinimum; - if (!this.options.omaximum) - this.options.maximum = histo.fMaximum; + if (!o.ominimum) + o.minimum = histo.fMinimum; + if (!o.omaximum) + o.maximum = histo.fMaximum; if (this.snapid || !fp || !fp.zoomChangedInteractive()) this.checkPadRange(); @@ -1246,7 +1236,7 @@ class THistPainter extends ObjectPainter { /** @summary Fill option object used in TWebCanvas */ fillWebObjectOptions(res) { - if (this._auto_exec) { + if (this._auto_exec && res) { res.fcust = 'auto_exec:' + this._auto_exec; delete this._auto_exec; } diff --git a/js/modules/hist2d/TMultiGraphPainter.mjs b/js/modules/hist2d/TMultiGraphPainter.mjs index bc31c8a226e89..18004007ce515 100644 --- a/js/modules/hist2d/TMultiGraphPainter.mjs +++ b/js/modules/hist2d/TMultiGraphPainter.mjs @@ -1,4 +1,4 @@ -import { create, createHistogram, isFunc, clTH1I, clTH2I, clTObjString, clTHashList, kNoZoom, kNoStats } from '../core.mjs'; +import { create, createHistogram, clTH1I, clTH2I, clTObjString, clTHashList, kNoZoom, kNoStats } from '../core.mjs'; import { DrawOptions } from '../base/BasePainter.mjs'; import { ObjectPainter } from '../base/ObjectPainter.mjs'; import { FunctionsHandler } from './THistPainter.mjs'; @@ -53,7 +53,7 @@ class TMultiGraphPainter extends ObjectPainter { const ngr = Math.min(graphs.arr.length, this.painters.length); for (let i = 0; i < ngr; ++i) { - if (this.painters[i].updateObject(graphs.arr[i], graphs.opt[i])) + if (this.painters[i].updateObject(graphs.arr[i], (graphs.opt[i] || this._restopt) + this._auto)) isany = true; } @@ -229,37 +229,27 @@ class TMultiGraphPainter extends ObjectPainter { } /** @summary method draws next graph */ - async drawNextGraph(indx, opt) { + async drawNextGraph(indx) { const graphs = this.getObject().fGraphs; - let exec = ''; // at the end of graphs drawing draw functions (if any) - if (indx >= graphs.arr.length) { - this._pfc = this._plc = this._pmc = false; // disable auto coloring at the end + if (indx >= graphs.arr.length) return this; - } - const gr = graphs.arr[indx], o = graphs.opt[indx] || opt || ''; + const gr = graphs.arr[indx], + draw_opt = (graphs.opt[indx] || this._restopt) + this._auto; - // if there is auto colors assignment, try to provide it - if (this._pfc || this._plc || this._pmc) { - const mp = this.getMainPainter(); - if (isFunc(mp?.createAutoColor)) { - const icolor = mp.createAutoColor(graphs.arr.length); - if (this._pfc) { gr.fFillColor = icolor; exec += `SetFillColor(${icolor});;`; } - if (this._plc) { gr.fLineColor = icolor; exec += `SetLineColor(${icolor});;`; } - if (this._pmc) { gr.fMarkerColor = icolor; exec += `SetMarkerColor(${icolor});;`; } - } - } + // used in automatic colors numbering + if (this._auto) + gr.$num_graphs = graphs.arr.length; - return this.drawGraph(gr, o, graphs.arr.length - indx).then(subp => { + return this.drawGraph(gr, draw_opt, graphs.arr.length - indx).then(subp => { if (subp) { subp.setSecondaryId(this, `graphs_${indx}`); this.painters.push(subp); - subp._auto_exec = exec; } - return this.drawNextGraph(indx+1, opt); + return this.drawNextGraph(indx+1); }); } @@ -269,14 +259,15 @@ class TMultiGraphPainter extends ObjectPainter { const d = new DrawOptions(opt); painter._3d = d.check('3D'); - painter._pfc = d.check('PFC'); - painter._plc = d.check('PLC'); - painter._pmc = d.check('PMC'); + painter._auto = ''; // extra options for auto colors + ['PFC', 'PLC', 'PMC'].forEach(f => { if (d.check(f)) painter._auto += ' ' + f; }); let hopt = ''; if (d.check('FB') && painter._3d) hopt += 'FB'; // will be directly combined with LEGO PadDrawOptions.forEach(name => { if (d.check(name)) hopt += ';' + name; }); + painter._restopt = d.remain(); + let promise = Promise.resolve(true); if (d.check('A') || !painter.getMainPainter()) { const mgraph = painter.getObject(), @@ -290,7 +281,7 @@ class TMultiGraphPainter extends ObjectPainter { return promise.then(() => { painter.addToPadPrimitives(); - return painter.drawNextGraph(0, d.remain()); + return painter.drawNextGraph(0); }).then(() => { const handler = new FunctionsHandler(painter, painter.getPadPainter(), painter.getObject().fFunctions, true); return handler.drawNext(0); // returns painter diff --git a/js/modules/io.mjs b/js/modules/io.mjs index d90462200c141..401add3809d09 100644 --- a/js/modules/io.mjs +++ b/js/modules/io.mjs @@ -486,6 +486,18 @@ function addUserStreamer(type, user_streamer) { CustomStreamers[type] = user_streamer; } +function getTDatimeDate() { + const res = new Date(); + res.setFullYear((this.fDatime >>> 26) + 1995); + res.setMonth(((this.fDatime << 6) >>> 28) - 1); + res.setDate((this.fDatime << 10) >>> 27); + res.setHours((this.fDatime << 15) >>> 27); + res.setMinutes((this.fDatime << 20) >>> 26); + res.setSeconds((this.fDatime << 26) >>> 26); + res.setMilliseconds(0); + return res; +} + /** @summary these are streamers which do not handle version regularly * @desc used for special classes like TRef or TBasket @@ -499,17 +511,7 @@ const DirectStreamers = { TDatime(buf, obj) { obj.fDatime = buf.ntou4(); - // obj.GetDate = function() { - // let res = new Date(); - // res.setFullYear((this.fDatime >>> 26) + 1995); - // res.setMonth((this.fDatime << 6) >>> 28); - // res.setDate((this.fDatime << 10) >>> 27); - // res.setHours((this.fDatime << 15) >>> 27); - // res.setMinutes((this.fDatime << 20) >>> 26); - // res.setSeconds((this.fDatime << 26) >>> 26); - // res.setMilliseconds(0); - // return res; - // } + obj.getDate = getTDatimeDate; }, TKey(buf, key) { @@ -2744,8 +2746,10 @@ class TFile { const progress_offest = sum1 / sum_total, progress_this = (sum2 - sum1) / sum_total; xhr.addEventListener('progress', oEvent => { - if (oEvent.lengthComputable) - progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total); + if (oEvent.lengthComputable) { + if (progress_callback(progress_offest + progress_this * oEvent.loaded / oEvent.total) === 'break') + xhr.abort(); + } }); } else if (first_block_retry && isFunc(xhr.addEventListener)) { xhr.addEventListener('progress', oEvent => { diff --git a/js/modules/tree.mjs b/js/modules/tree.mjs index 8f4918e901875..d2cb58964426e 100644 --- a/js/modules/tree.mjs +++ b/js/modules/tree.mjs @@ -1419,7 +1419,7 @@ class TDrawSelector extends TSelector { const now = new Date().getTime(); if (now - this.lasttm > this.monitoring) { this.lasttm = now; - if (this.progress_callback) + if (isFunc(this.progress_callback)) this.progress_callback(this.hist); } } @@ -2240,7 +2240,7 @@ async function treeProcess(tree, selector, args) { const portion = (handle.staged_prev + value * (handle.staged_now - handle.staged_prev)) / (handle.process_max - handle.process_min); - handle.selector.ShowProgress(portion); + return handle.selector.ShowProgress(portion); } function ProcessBlobs(blobs, places) { @@ -2399,7 +2399,10 @@ async function treeProcess(tree, selector, args) { if (handle.process_max > handle.process_min) portion = (handle.staged_prev - handle.process_min) / (handle.process_max - handle.process_min); - handle.selector.ShowProgress(portion); + if (handle.selector.ShowProgress(portion) === 'break') { + handle.selector.Terminate(true); + return resolveFunc(handle.selector); + } handle.progress_showtm = new Date().getTime(); diff --git a/js/modules/webwindow.mjs b/js/modules/webwindow.mjs index 27a8c9ed269ea..c6fcf6a6c94df 100644 --- a/js/modules/webwindow.mjs +++ b/js/modules/webwindow.mjs @@ -1,7 +1,33 @@ import { httpRequest, createHttpRequest, loadScript, decodeUrl, browser, setBatchMode, isBatchMode, isObject, isFunc, isStr, btoa_func } from './core.mjs'; import { closeCurrentWindow, showProgress, loadOpenui5 } from './gui/utils.mjs'; +import { sha256, sha256_2 } from './base/sha256.mjs'; + + +// secret session key used for hashing connections keys +// only if set, all messages from and to server signed with HMAC hash +let sessionKey = ''; + +/** @summary HMAC implementation + * @desc see https://en.wikipedia.org/wiki/HMAC for more details + * @private */ +function HMAC(key, m, o) { + const kbis = sha256(sessionKey + key), + block_size = 64, + opad = 0x5c, ipad = 0x36, + ko = [], ki = []; + while (kbis.length < block_size) + kbis.push(0); + for (let i = 0; i < kbis.length; ++i) { + const code = kbis[i]; + ko.push(code ^ opad); + ki.push(code ^ ipad); + } + + const hash = sha256_2(ki, (o === undefined) ? m : new Uint8Array(m, o)); + return sha256_2(ko, hash, true); +} /** * @summary Class emulating web socket with long-poll http requests @@ -11,12 +37,13 @@ import { closeCurrentWindow, showProgress, loadOpenui5 } from './gui/utils.mjs'; class LongPollSocket { - constructor(addr, _raw, _args) { + constructor(addr, _raw, _handle, _counter) { this.path = addr; this.connid = null; this.req = null; this.raw = _raw; - this.args = _args; + this.handle = _handle; + this.counter = _counter; this.nextRequest('', 'connect'); } @@ -26,18 +53,20 @@ class LongPollSocket { let url = this.path, reqmode = 'buf', post = null; if (kind === 'connect') { url += this.raw ? '?raw_connect' : '?txt_connect'; - if (this.args) url += '&' + this.args; + if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); console.log(`longpoll connect ${url} raw = ${this.raw}`); this.connid = 'connect'; } else if (kind === 'close') { if ((this.connid === null) || (this.connid === 'close')) return; url += `?connection=${this.connid}&close`; + if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); this.connid = 'close'; reqmode = 'text;sync'; // use sync mode to close connection before browser window closed } else if ((this.connid === null) || (typeof this.connid !== 'number')) { if (!browser.qt5) console.error('No connection'); } else { url += '?connection=' + this.connid; + if (this.handle) url += '&' + this.handle.getConnArgs(this.counter++); if (kind === 'dummy') url += '&dummy'; } @@ -246,6 +275,8 @@ class WebWindowHandle { this.credits = credits || 10; this.cansend = this.credits; this.ackn = this.credits; + this.send_seq = 1; // sequence counter of send messages + this.recv_seq = 0; // sequence counter of received messages } /** @summary Returns arguments specified in the RWebWindow::SetUserArgs() method @@ -391,11 +422,15 @@ class WebWindowHandle { if (this.cansend <= 0) console.error(`should be queued before sending cansend: ${this.cansend}`); - const prefix = `${this.ackn}:${this.cansend}:${chid}:`; + const prefix = `${this.send_seq++}:${this.ackn}:${this.cansend}:${chid}:`; this.ackn = 0; this.cansend--; // decrease number of allowed send packets - this._websocket.send(prefix + msg); + let hash = 'none'; + if (this.key && sessionKey) + hash = HMAC(this.key, `${prefix}${msg}`); + + this._websocket.send(`${hash}:${prefix}${msg}`); if ((this.kind === 'websocket') || (this.kind === 'longpoll')) { if (this.timerid) clearTimeout(this.timerid); @@ -487,9 +522,14 @@ class WebWindowHandle { setHRef(path) { if (isStr(path) && (path.indexOf('?') > 0)) { this.href = path.slice(0, path.indexOf('?')); - this.key = decodeUrl(path).get('key'); - } else + const d = decodeUrl(path); + this.key = d.get('key'); + this.token = d.get('token'); + } else { this.href = path; + delete this.key; + delete this.token; + } } /** @summary Return href part @@ -508,17 +548,28 @@ class WebWindowHandle { return addr; } + /** @summary provide connection args for the web socket + * @private */ + getConnArgs(ntry) { + let args = ''; + if (this.key) { + const k = HMAC(this.key, `attempt_${ntry}`); + args += `key=${k}&ntry=${ntry}`; + } + if (this.token) { + if (args) args += '&'; + args += `token=${this.token}`; + } + return args; + } + /** @summary Create configured socket for current object. * @private */ connect(href) { this.close(); if (!href && this.href) href = this.href; - let ntry = 0, args = (this.key ? ('key=' + this.key) : ''); - if (this.token) { - if (args) args += '&'; - args += 'token=' + this.token; - } + let ntry = 0; const retry_open = first_time => { if (this.state !== 0) return; @@ -550,13 +601,13 @@ class WebWindowHandle { console.log(`configure protocol log ${path}`); } else if ((this.kind === 'websocket') && first_time) { path = path.replace('http://', 'ws://').replace('https://', 'wss://') + 'root.websocket'; - if (args) path += '?' + args; + path += '?' + this.getConnArgs(ntry); console.log(`configure websocket ${path}`); this._websocket = new WebSocket(path); } else { path += 'root.longpoll'; console.log(`configure longpoll ${path}`); - this._websocket = new LongPollSocket(path, (this.kind === 'rawlongpoll'), args); + this._websocket = new LongPollSocket(path, (this.kind === 'rawlongpoll'), this, ntry); } if (!this._websocket) return; @@ -574,18 +625,39 @@ class WebWindowHandle { let msg = e.data; if (this.next_binary) { - const binchid = this.next_binary; + const binchid = this.next_binary, + server_hash = this.next_binary_hash; delete this.next_binary; + delete this.next_binary_hash; if (msg instanceof Blob) { // convert Blob object to BufferArray const reader = new FileReader(), qitem = this.reserveQueueItem(); // The file's text will be printed here - reader.onload = event => this.markQueueItemDone(qitem, event.target.result, 0); + reader.onload = event => { + let result = event.target.result; + if (this.key && sessionKey) { + const hash = HMAC(this.key, result, 0); + if (hash !== server_hash) { + console.log('Discard binary buffer because of HMAC mismatch'); + result = new ArrayBuffer(0); + } + } + + this.markQueueItemDone(qitem, result, 0); + }; reader.readAsArrayBuffer(msg, e.offset || 0); } else { // this is from CEF or LongPoll handler - this.provideData(binchid, msg, e.offset || 0); + let result = msg; + if (this.key && sessionKey) { + const hash = HMAC(this.key, result, e.offset || 0); + if (hash !== server_hash) { + console.log('Discard binary buffer because of HMAC mismatch'); + result = new ArrayBuffer(0); + } + } + this.provideData(binchid, result, e.offset || 0); } return; @@ -594,17 +666,34 @@ class WebWindowHandle { if (!isStr(msg)) return console.log(`unsupported message kind: ${typeof msg}`); - const i1 = msg.indexOf(':'), - credit = parseInt(msg.slice(0, i1)), + const i0 = msg.indexOf(':'), + server_hash = msg.slice(0, i0), + i1 = msg.indexOf(':', i0 + 1), + seq_id = Number.parseInt(msg.slice(i0 + 1, i1)), i2 = msg.indexOf(':', i1 + 1), - // cansend = parseInt(msg.slice(i1 + 1, i2)), // TODO: take into account when sending messages + credit = Number.parseInt(msg.slice(i1 + 1, i2)), i3 = msg.indexOf(':', i2 + 1), - chid = parseInt(msg.slice(i2 + 1, i3)); + // cansend = parseInt(msg.slice(i2 + 1, i3)), // TODO: take into account when sending messages + i4 = msg.indexOf(':', i3 + 1), + chid = Number.parseInt(msg.slice(i3 + 1, i4)); + + // for authentication HMAC checksum and sequence id is important + // HMAC used to authenticate server + // sequence id is necessary to exclude submission of same packet again + if (this.key && sessionKey) { + const client_hash = HMAC(this.key, msg.slice(i0+1)); + if (server_hash !== client_hash) + return console.log(`Failure checking server md5 sum ${server_hash}`); + } + + if (seq_id <= this.recv_seq) + return console.log(`Failure with packet sequence ${seq_id} <= ${this.recv_seq}`); + this.recv_seq = seq_id; // sequence id of received packet this.ackn++; // count number of received packets, this.cansend += credit; // how many packets client can send - msg = msg.slice(i3 + 1); + msg = msg.slice(i4 + 1); if (chid === 0) { console.log(`GET chid=0 message ${msg}`); @@ -614,13 +703,19 @@ class WebWindowHandle { } else if (msg.indexOf('NEW_KEY=') === 0) { const newkey = msg.slice(8); this.close(true); - if (typeof sessionStorage !== 'undefined') + let href = (typeof document !== 'undefined') ? document.URL : null; + if (isStr(href) && (typeof window !== 'undefined') && window?.history) { + const p = href.indexOf('?key='); + if (p > 0) href = href.slice(0, p); + window.history.replaceState(window.history.state, undefined, `${href}?key=${newkey}`); + } else if (typeof sessionStorage !== 'undefined') sessionStorage.setItem('RWebWindow_Key', newkey); location.reload(true); } - } else if (msg === '$$binary$$') + } else if (msg.slice(0, 10) === '$$binary$$') { this.next_binary = chid; - else if (msg === '$$nullbinary$$') + this.next_binary_hash = msg.slice(10); + } else if (msg === '$$nullbinary$$') this.provideData(chid, new ArrayBuffer(0), 0); else this.provideData(chid, msg); @@ -700,34 +795,63 @@ async function connectWebWindow(arg) { else if (!isObject(arg)) arg = {}; - const d = decodeUrl(); - let new_key; + let d_key, d_token, new_key; - if (typeof sessionStorage !== 'undefined') { - new_key = sessionStorage.getItem('RWebWindow_Key'); - sessionStorage.removeItem('RWebWindow_Key'); - if (new_key) console.log(`Use key ${new_key} from session storage`); - } + if (!arg.href) { + let href = (typeof document !== 'undefined') ? document.URL : ''; + const p = href.indexOf('#'); + if (p > 0) { + sessionKey = href.slice(p+1); + href = href.slice(0, p); + } + + const d = decodeUrl(href); + d_key = d.get('key'); + d_token = d.get('token'); + + if (typeof sessionStorage !== 'undefined') { + new_key = sessionStorage.getItem('RWebWindow_Key'); + sessionStorage.removeItem('RWebWindow_Key'); + if (new_key) console.log(`Use key ${new_key} from session storage`); + + if (sessionKey) + sessionStorage.setItem('RWebWindow_SessionKey', sessionKey); + else + sessionKey = sessionStorage.getItem('RWebWindow_SessionKey') || ''; + } - // special holder script, prevents headless chrome browser from too early exit - if (d.has('headless') && d.get('key') && (browser.isChromeHeadless || browser.isChrome) && !arg.ignore_chrome_batch_holder) - loadScript('root_batch_holder.js?key=' + (new_key || d.get('key'))); + // hide key and any following parameters from URL, chrome do not allows to close browser with changed URL + if (d_key && !d.has('headless') && isStr(href) && (typeof window !== 'undefined') && window?.history) { + const p = href.indexOf('?key='); + if (p > 0) window.history.replaceState(window.history.state, undefined, href.slice(0, p)); + } + + // special holder script, prevents headless chrome browser from too early exit + if (d.has('headless') && d_key && (browser.isChromeHeadless || browser.isChrome) && !arg.ignore_chrome_batch_holder) + loadScript('root_batch_holder.js?key=' + (new_key || d_key)); + + if (!arg.platform) + arg.platform = d.get('platform'); + + if (arg.platform === 'qt5') + browser.qt5 = true; + else if (arg.platform === 'cef3') + browser.cef3 = true; - if (!arg.platform) - arg.platform = d.get('platform'); + if (arg.batch === undefined) + arg.batch = d.has('headless'); - if (arg.platform === 'qt5') - browser.qt5 = true; - else if (arg.platform === 'cef3') - browser.cef3 = true; + if (arg.batch) setBatchMode(true); - if (arg.batch === undefined) - arg.batch = d.has('headless'); + if (!arg.socket_kind) + arg.socket_kind = d.get('ws'); - if (arg.batch) setBatchMode(true); + if (!new_key && arg.winW && arg.winH && !isBatchMode() && isFunc(window?.resizeTo)) + window.resizeTo(arg.winW, arg.winH); - if (!arg.socket_kind) - arg.socket_kind = d.get('ws'); + if (!new_key && arg.winX && arg.winY && !isBatchMode() && isFunc(window?.moveTo)) + window.moveTo(arg.winX, arg.winY); + } if (!arg.socket_kind) { if (browser.qt5) @@ -738,27 +862,24 @@ async function connectWebWindow(arg) { arg.socket_kind = 'websocket'; } - if (!new_key && arg.winW && arg.winH && !isBatchMode() && isFunc(window?.resizeTo)) - window.resizeTo(arg.winW, arg.winH); - - if (!new_key && arg.winX && arg.winY && !isBatchMode() && isFunc(window?.moveTo)) - window.moveTo(arg.winX, arg.winY); - // only for debug purposes // arg.socket_kind = 'longpoll'; const main = new Promise(resolveFunc => { const handle = new WebWindowHandle(arg.socket_kind, arg.credits); handle.setUserArgs(arg.user_args); - if (arg.href) handle.setHRef(arg.href); // apply href now while connect can be called from other place + if (arg.href) + handle.setHRef(arg.href); // apply href now while connect can be called from other place + else { + handle.key = new_key || d_key; + handle.token = d_token; + } if (window) { window.onbeforeunload = () => handle.close(true); if (browser.qt5) window.onqt5unload = window.onbeforeunload; } - handle.key = new_key || d.get('key'); - handle.token = d.get('token'); if (arg.receiver) { // when receiver exists, it handles itself callbacks diff --git a/js/scripts/jspdf.umd.min.js b/js/scripts/jspdf.umd.min.js index 297fa8ce1d991..54878cb3cb737 100644 --- a/js/scripts/jspdf.umd.min.js +++ b/js/scripts/jspdf.umd.min.js @@ -1,7 +1,7 @@ /** @license * * jsPDF - PDF Document creation from JavaScript - * Version 2.4.0 Built on 2021-09-14T10:30:30.228Z + * Version 2.5.1 Built on 2023-11-22T13:26:15.455Z * CommitID 00000000 * * Copyright (c) 2010-2021 James Hall , https://github.com/MrRio/jsPDF @@ -48,13 +48,13 @@ * kim3er, mfo, alnorth, Flamenco */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).jspdf={})}(this,(function(t){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var r=function(){return"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this}();function n(){r.console&&"function"==typeof r.console.log&&r.console.log.apply(r.console,arguments)}var i={log:n,warn:function(t){r.console&&("function"==typeof r.console.warn?r.console.warn.apply(r.console,arguments):n.call(null,arguments))},error:function(t){r.console&&("function"==typeof r.console.error?r.console.error.apply(r.console,arguments):n(t))}};function a(t,e,r){var n=new XMLHttpRequest;n.open("GET",t),n.responseType="blob",n.onload=function(){h(n.response,e,r)},n.onerror=function(){i.error("could not download file")},n.send()}function o(t){var e=new XMLHttpRequest;e.open("HEAD",t,!1);try{e.send()}catch(t){}return e.status>=200&&e.status<=299}function s(t){try{t.dispatchEvent(new MouseEvent("click"))}catch(r){var e=document.createEvent("MouseEvents");e.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),t.dispatchEvent(e)}}var c,u,h=r.saveAs||("object"!==("undefined"==typeof window?"undefined":e(window))||window!==r?function(){}:"undefined"!=typeof HTMLAnchorElement&&"download"in HTMLAnchorElement.prototype?function(t,e,n){var i=r.URL||r.webkitURL,c=document.createElement("a");e=e||t.name||"download",c.download=e,c.rel="noopener","string"==typeof t?(c.href=t,c.origin!==location.origin?o(c.href)?a(t,e,n):s(c,c.target="_blank"):s(c)):(c.href=i.createObjectURL(t),setTimeout((function(){i.revokeObjectURL(c.href)}),4e4),setTimeout((function(){s(c)}),0))}:"msSaveOrOpenBlob"in navigator?function(t,r,n){if(r=r||t.name||"download","string"==typeof t)if(o(t))a(t,r,n);else{var c=document.createElement("a");c.href=t,c.target="_blank",setTimeout((function(){s(c)}))}else navigator.msSaveOrOpenBlob(function(t,r){return void 0===r?r={autoBom:!1}:"object"!==e(r)&&(i.warn("Deprecated: Expected third argument to be a object"),r={autoBom:!r}),r.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)?new Blob([String.fromCharCode(65279),t],{type:t.type}):t}(t,n),r)}:function(t,n,i,o){if((o=o||open("","_blank"))&&(o.document.title=o.document.body.innerText="downloading..."),"string"==typeof t)return a(t,n,i);var s="application/octet-stream"===t.type,c=/constructor/i.test(r.HTMLElement)||r.safari,u=/CriOS\/[\d]+/.test(navigator.userAgent);if((u||s&&c)&&"object"===("undefined"==typeof FileReader?"undefined":e(FileReader))){var h=new FileReader;h.onloadend=function(){var t=h.result;t=u?t:t.replace(/^data:[^;]*;/,"data:attachment/file;"),o?o.location.href=t:location=t,o=null},h.readAsDataURL(t)}else{var l=r.URL||r.webkitURL,f=l.createObjectURL(t);o?o.location=f:location.href=f,o=null,setTimeout((function(){l.revokeObjectURL(f)}),4e4)}}); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).jspdf={})}(this,(function(t){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var r=function(){return"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this}();function n(){r.console&&"function"==typeof r.console.log&&r.console.log.apply(r.console,arguments)}var i={log:n,warn:function(t){r.console&&("function"==typeof r.console.warn?r.console.warn.apply(r.console,arguments):n.call(null,arguments))},error:function(t){r.console&&("function"==typeof r.console.error?r.console.error.apply(r.console,arguments):n(t))}};function a(t,e,r){var n=new XMLHttpRequest;n.open("GET",t),n.responseType="blob",n.onload=function(){l(n.response,e,r)},n.onerror=function(){i.error("could not download file")},n.send()}function o(t){var e=new XMLHttpRequest;e.open("HEAD",t,!1);try{e.send()}catch(t){}return e.status>=200&&e.status<=299}function s(t){try{t.dispatchEvent(new MouseEvent("click"))}catch(r){var e=document.createEvent("MouseEvents");e.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),t.dispatchEvent(e)}}var c,u,l=r.saveAs||("object"!==("undefined"==typeof window?"undefined":e(window))||window!==r?function(){}:"undefined"!=typeof HTMLAnchorElement&&"download"in HTMLAnchorElement.prototype?function(t,e,n){var i=r.URL||r.webkitURL,c=document.createElement("a");e=e||t.name||"download",c.download=e,c.rel="noopener","string"==typeof t?(c.href=t,c.origin!==location.origin?o(c.href)?a(t,e,n):s(c,c.target="_blank"):s(c)):(c.href=i.createObjectURL(t),setTimeout((function(){i.revokeObjectURL(c.href)}),4e4),setTimeout((function(){s(c)}),0))}:"msSaveOrOpenBlob"in navigator?function(t,r,n){if(r=r||t.name||"download","string"==typeof t)if(o(t))a(t,r,n);else{var c=document.createElement("a");c.href=t,c.target="_blank",setTimeout((function(){s(c)}))}else navigator.msSaveOrOpenBlob(function(t,r){return void 0===r?r={autoBom:!1}:"object"!==e(r)&&(i.warn("Deprecated: Expected third argument to be a object"),r={autoBom:!r}),r.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)?new Blob([String.fromCharCode(65279),t],{type:t.type}):t}(t,n),r)}:function(t,n,i,o){if((o=o||open("","_blank"))&&(o.document.title=o.document.body.innerText="downloading..."),"string"==typeof t)return a(t,n,i);var s="application/octet-stream"===t.type,c=/constructor/i.test(r.HTMLElement)||r.safari,u=/CriOS\/[\d]+/.test(navigator.userAgent);if((u||s&&c)&&"object"===("undefined"==typeof FileReader?"undefined":e(FileReader))){var l=new FileReader;l.onloadend=function(){var t=l.result;t=u?t:t.replace(/^data:[^;]*;/,"data:attachment/file;"),o?o.location.href=t:location=t,o=null},l.readAsDataURL(t)}else{var h=r.URL||r.webkitURL,f=h.createObjectURL(t);o?o.location=f:location.href=f,o=null,setTimeout((function(){h.revokeObjectURL(f)}),4e4)}}); /** * 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 l(t){var e;t=t||"",this.ok=!1,"#"==t.charAt(0)&&(t=t.substr(1,6));t={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"}[t=(t=t.replace(/ /g,"")).toLowerCase()]||t;for(var r=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(t){return[parseInt(t[1]),parseInt(t[2]),parseInt(t[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}}],n=0;n255?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,this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},this.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),r=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==r.length&&(r="0"+r),"#"+t+e+r}} + */function h(t){var e;t=t||"",this.ok=!1,"#"==t.charAt(0)&&(t=t.substr(1,6));t={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"}[t=(t=t.replace(/ /g,"")).toLowerCase()]||t;for(var r=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(t){return[parseInt(t[1]),parseInt(t[2]),parseInt(t[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,example:["#00ff00","336699"],process:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}}],n=0;n255?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,this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"},this.toHex=function(){var t=this.r.toString(16),e=this.g.toString(16),r=this.b.toString(16);return 1==t.length&&(t="0"+t),1==e.length&&(e="0"+e),1==r.length&&(r="0"+r),"#"+t+e+r}} /** * @license * Joseph Myers does not specify a particular license for his work. @@ -72,7 +72,7 @@ function f(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r=p(r,n,i,a,e[0],7,-680876936),a * without modifications. * * Reference: http://www.fpdf.org/en/script/script37.php - */function _(t,e){var r,n,i,a;if(t!==r){for(var o=(i=t,a=1+(256/t.length>>0),new Array(a+1).join(i)),s=[],c=0;c<256;c++)s[c]=c;var u=0;for(c=0;c<256;c++){var h=s[c];u=(u+h+o.charCodeAt(c))%256,s[c]=s[u],s[u]=h}r=t,n=s}else s=n;var l=e.length,f=0,d=0,p="";for(c=0;c>0),new Array(a+1).join(i)),s=[],c=0;c<256;c++)s[c]=c;var u=0;for(c=0;c<256;c++){var l=s[c];u=(u+l+o.charCodeAt(c))%256,s[c]=s[u],s[u]=l}r=t,n=s}else s=n;var h=e.length,f=0,d=0,p="";for(c=0;c€/\f©þdSiz";var a=(e+this.padding).substr(0,32),o=(r+this.padding).substr(0,32);this.O=this.processOwnerPassword(a,o),this.P=-(1+(255^i)),this.encryptionKey=A(a+this.O+this.lsbFirstWord(this.P)+this.hexToBytes(n)).substr(0,5),this.U=_(this.encryptionKey,this.padding)}function F(t){if(/[^\u0000-\u00ff]/.test(t))throw new Error("Invalid PDF Name Object: "+t+", Only accept ASCII characters.");for(var e="",r=t.length,n=0;n126)e+="#"+("0"+i.toString(16)).slice(-2);else e+=t[n]}return e}function I(t){if("object"!==e(t))throw new Error("Invalid Context passed to initialize PubSub (jsPDF-module)");var n={};this.subscribe=function(t,e,r){if(r=r||!1,"string"!=typeof t||"function"!=typeof e||"boolean"!=typeof r)throw new Error("Invalid arguments passed to PubSub.subscribe (jsPDF-module)");n.hasOwnProperty(t)||(n[t]={});var i=Math.random().toString(35);return n[t][i]=[e,!!r],i},this.unsubscribe=function(t){for(var e in n)if(n[e][t])return delete n[e][t],0===Object.keys(n[e]).length&&delete n[e],!0;return!1},this.publish=function(e){if(n.hasOwnProperty(e)){var a=Array.prototype.slice.call(arguments,1),o=[];for(var s in n[e]){var c=n[e][s];try{c[0].apply(t,a)}catch(t){r.console&&i.error("jsPDF PubSub Error",t.message,t)}c[1]&&o.push(s)}o.length&&o.forEach(this.unsubscribe)}},this.getTopics=function(){return n}}function C(t){if(!(this instanceof C))return new C(t);var e="opacity,stroke-opacity".split(",");for(var r in t)t.hasOwnProperty(r)&&e.indexOf(r)>=0&&(this[r]=t[r]);this.id="",this.objectNumber=-1}function j(t,e){this.gState=t,this.matrix=e,this.id="",this.objectNumber=-1}function O(t,e,r,n,i){if(!(this instanceof O))return new O(t,e,r,n,i);this.type="axial"===t?2:3,this.coords=e,this.colors=r,j.call(this,n,i)}function B(t,e,r,n,i){if(!(this instanceof B))return new B(t,e,r,n,i);this.boundingBox=t,this.xStep=e,this.yStep=r,this.stream="",this.cloneIndex=0,j.call(this,n,i)}function M(t){var n,a="string"==typeof arguments[0]?arguments[0]:"p",o=arguments[1],s=arguments[2],c=arguments[3],f=[],d=1,p=16,g="S",m=null;"object"===e(t=t||{})&&(a=t.orientation,o=t.unit||o,s=t.format||s,c=t.compress||t.compressPdf||c,null!==(m=t.encryption||null)&&(m.userPassword=m.userPassword||"",m.ownerPassword=m.ownerPassword||"",m.userPermissions=m.userPermissions||[]),d="number"==typeof t.userUnit?Math.abs(t.userUnit):1,void 0!==t.precision&&(n=t.precision),void 0!==t.floatPrecision&&(p=t.floatPrecision),g=t.defaultPathOperation||"S"),f=t.filters||(!0===c?["FlateEncode"]:f),o=o||"mm",a=(""+(a||"P")).toLowerCase();var v=t.putOnlyUsedFonts||!1,b={},y={internal:{},__private__:{}};y.__private__.PubSub=I;var w="1.3",N=y.__private__.getPdfVersion=function(){return w};y.__private__.setPdfVersion=function(t){w=t};var L={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]};y.__private__.getPageFormats=function(){return L};var A=y.__private__.getPageFormat=function(t){return L[t]};s=s||"a4";var x={COMPAT:"compat",ADVANCED:"advanced"},S=x.COMPAT;function _(){this.saveGraphicsState(),lt(new Vt(_t,0,0,-_t,0,Dr()*_t).toString()+" cm"),this.setFontSize(this.getFontSize()/_t),g="n",S=x.ADVANCED}function P(){this.restoreGraphicsState(),g="S",S=x.COMPAT}var j=y.__private__.combineFontStyleAndFontWeight=function(t,e){if("bold"==t&&"normal"==e||"bold"==t&&400==e||"normal"==t&&"italic"==e||"bold"==t&&"italic"==e)throw new Error("Invalid Combination of fontweight and fontstyle");return e&&(t=400==e||"normal"===e?"italic"===t?"italic":"normal":700!=e&&"bold"!==e||"normal"!==t?(700==e?"bold":e)+""+t:"bold"),t};y.advancedAPI=function(t){var e=S===x.COMPAT;return e&&_.call(this),"function"!=typeof t||(t(this),e&&P.call(this)),this},y.compatAPI=function(t){var e=S===x.ADVANCED;return e&&P.call(this),"function"!=typeof t||(t(this),e&&_.call(this)),this},y.isAdvancedAPI=function(){return S===x.ADVANCED};var E,q=function(t){if(S!==x.ADVANCED)throw new Error(t+" is only available in 'advanced' API mode. You need to call advancedAPI() first.")},D=y.roundToPrecision=y.__private__.roundToPrecision=function(t,e){var r=n||e;if(isNaN(t)||isNaN(r))throw new Error("Invalid argument passed to jsPDF.roundToPrecision");return t.toFixed(r).replace(/0+$/,"")};E=y.hpf=y.__private__.hpf="number"==typeof p?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,p)}:"smart"===p?function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,t>-1&&t<1?16:5)}:function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.hpf");return D(t,16)};var R=y.f2=y.__private__.f2=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f2");return D(t,2)},T=y.__private__.f3=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.f3");return D(t,3)},U=y.scale=y.__private__.scale=function(t){if(isNaN(t))throw new Error("Invalid argument passed to jsPDF.scale");return S===x.COMPAT?t*_t:S===x.ADVANCED?t:void 0},z=function(t){return S===x.COMPAT?Dr()-t:S===x.ADVANCED?t:void 0},H=function(t){return U(z(t))};y.__private__.setPrecision=y.setPrecision=function(t){"number"==typeof parseInt(t,10)&&(n=parseInt(t,10))};var W,V="00000000000000000000000000000000",G=y.__private__.getFileId=function(){return V},Y=y.__private__.setFileId=function(t){return V=void 0!==t&&/^[a-fA-F0-9]{32}$/.test(t)?t.toUpperCase():V.split("").map((function(){return"ABCDEF0123456789".charAt(Math.floor(16*Math.random()))})).join(""),null!==m&&(Ye=new k(m.userPermissions,m.userPassword,m.ownerPassword,V)),V};y.setFileId=function(t){return Y(t),this},y.getFileId=function(){return G()};var J=y.__private__.convertDateToPDFDate=function(t){var e=t.getTimezoneOffset(),r=e<0?"+":"-",n=Math.floor(Math.abs(e/60)),i=Math.abs(e%60),a=[r,Q(n),"'",Q(i),"'"].join("");return["D:",t.getFullYear(),Q(t.getMonth()+1),Q(t.getDate()),Q(t.getHours()),Q(t.getMinutes()),Q(t.getSeconds()),a].join("")},X=y.__private__.convertPDFDateToDate=function(t){var e=parseInt(t.substr(2,4),10),r=parseInt(t.substr(6,2),10)-1,n=parseInt(t.substr(8,2),10),i=parseInt(t.substr(10,2),10),a=parseInt(t.substr(12,2),10),o=parseInt(t.substr(14,2),10);return new Date(e,r,n,i,a,o,0)},K=y.__private__.setCreationDate=function(t){var e;if(void 0===t&&(t=new Date),t instanceof Date)e=J(t);else{if(!/^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])'?$/.test(t))throw new Error("Invalid argument passed to jsPDF.setCreationDate");e=t}return W=e},Z=y.__private__.getCreationDate=function(t){var e=W;return"jsDate"===t&&(e=X(W)),e};y.setCreationDate=function(t){return K(t),this},y.getCreationDate=function(t){return Z(t)};var $,Q=y.__private__.padd2=function(t){return("0"+parseInt(t)).slice(-2)},tt=y.__private__.padd2Hex=function(t){return("00"+(t=t.toString())).substr(t.length)},et=0,rt=[],nt=[],it=0,at=[],ot=[],st=!1,ct=nt,ut=function(){et=0,it=0,nt=[],rt=[],at=[],Qt=Kt(),te=Kt()};y.__private__.setCustomOutputDestination=function(t){st=!0,ct=t};var ht=function(t){st||(ct=t)};y.__private__.resetCustomOutputDestination=function(){st=!1,ct=nt};var lt=y.__private__.out=function(t){return t=t.toString(),it+=t.length+1,ct.push(t),ct},ft=y.__private__.write=function(t){return lt(1===arguments.length?t.toString():Array.prototype.join.call(arguments," "))},dt=y.__private__.getArrayBuffer=function(t){for(var e=t.length,r=new ArrayBuffer(e),n=new Uint8Array(r);e--;)n[e]=t.charCodeAt(e);return r},pt=[["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]];y.__private__.getStandardFonts=function(){return pt};var gt=t.fontSize||16;y.__private__.setFontSize=y.setFontSize=function(t){return gt=S===x.ADVANCED?t/_t:t,this};var mt,vt=y.__private__.getFontSize=y.getFontSize=function(){return S===x.COMPAT?gt:gt*_t},bt=t.R2L||!1;y.__private__.setR2L=y.setR2L=function(t){return bt=t,this},y.__private__.getR2L=y.getR2L=function(){return bt};var yt,wt=y.__private__.setZoomMode=function(t){var e=[void 0,null,"fullwidth","fullheight","fullpage","original"];if(/^\d*\.?\d*%$/.test(t))mt=t;else if(isNaN(t)){if(-1===e.indexOf(t))throw new Error('zoom must be Integer (e.g. 2), a percentage Value (e.g. 300%) or fullwidth, fullheight, fullpage, original. "'+t+'" is not recognized.');mt=t}else mt=parseInt(t,10)};y.__private__.getZoomMode=function(){return mt};var Nt,Lt=y.__private__.setPageMode=function(t){if(-1==[void 0,null,"UseNone","UseOutlines","UseThumbs","FullScreen"].indexOf(t))throw new Error('Page mode must be one of UseNone, UseOutlines, UseThumbs, or FullScreen. "'+t+'" is not recognized.');yt=t};y.__private__.getPageMode=function(){return yt};var At=y.__private__.setLayoutMode=function(t){if(-1==[void 0,null,"continuous","single","twoleft","tworight","two"].indexOf(t))throw new Error('Layout mode must be one of continuous, single, twoleft, tworight. "'+t+'" is not recognized.');Nt=t};y.__private__.getLayoutMode=function(){return Nt},y.__private__.setDisplayMode=y.setDisplayMode=function(t,e,r){return wt(t),At(e),Lt(r),this};var xt={title:"",subject:"",author:"",keywords:"",creator:""};y.__private__.getDocumentProperty=function(t){if(-1===Object.keys(xt).indexOf(t))throw new Error("Invalid argument passed to jsPDF.getDocumentProperty");return xt[t]},y.__private__.getDocumentProperties=function(){return xt},y.__private__.setDocumentProperties=y.setProperties=y.setDocumentProperties=function(t){for(var e in xt)xt.hasOwnProperty(e)&&t[e]&&(xt[e]=t[e]);return this},y.__private__.setDocumentProperty=function(t,e){if(-1===Object.keys(xt).indexOf(t))throw new Error("Invalid arguments passed to jsPDF.setDocumentProperty");return xt[t]=e};var St,_t,Pt,kt,Ft,It={},Ct={},jt=[],Ot={},Bt={},Mt={},Et={},qt=null,Dt=0,Rt=[],Tt=new I(y),Ut=t.hotfixes||[],zt={},Ht={},Wt=[],Vt=function t(e,r,n,i,a,o){if(!(this instanceof t))return new t(e,r,n,i,a,o);isNaN(e)&&(e=1),isNaN(r)&&(r=0),isNaN(n)&&(n=0),isNaN(i)&&(i=1),isNaN(a)&&(a=0),isNaN(o)&&(o=0),this._matrix=[e,r,n,i,a,o]};Object.defineProperty(Vt.prototype,"sx",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Vt.prototype,"shy",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Vt.prototype,"shx",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Vt.prototype,"sy",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Vt.prototype,"tx",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Vt.prototype,"ty",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Vt.prototype,"a",{get:function(){return this._matrix[0]},set:function(t){this._matrix[0]=t}}),Object.defineProperty(Vt.prototype,"b",{get:function(){return this._matrix[1]},set:function(t){this._matrix[1]=t}}),Object.defineProperty(Vt.prototype,"c",{get:function(){return this._matrix[2]},set:function(t){this._matrix[2]=t}}),Object.defineProperty(Vt.prototype,"d",{get:function(){return this._matrix[3]},set:function(t){this._matrix[3]=t}}),Object.defineProperty(Vt.prototype,"e",{get:function(){return this._matrix[4]},set:function(t){this._matrix[4]=t}}),Object.defineProperty(Vt.prototype,"f",{get:function(){return this._matrix[5]},set:function(t){this._matrix[5]=t}}),Object.defineProperty(Vt.prototype,"rotation",{get:function(){return Math.atan2(this.shx,this.sx)}}),Object.defineProperty(Vt.prototype,"scaleX",{get:function(){return this.decompose().scale.sx}}),Object.defineProperty(Vt.prototype,"scaleY",{get:function(){return this.decompose().scale.sy}}),Object.defineProperty(Vt.prototype,"isIdentity",{get:function(){return 1===this.sx&&(0===this.shy&&(0===this.shx&&(1===this.sy&&(0===this.tx&&0===this.ty))))}}),Vt.prototype.join=function(t){return[this.sx,this.shy,this.shx,this.sy,this.tx,this.ty].map(E).join(t)},Vt.prototype.multiply=function(t){var e=t.sx*this.sx+t.shy*this.shx,r=t.sx*this.shy+t.shy*this.sy,n=t.shx*this.sx+t.sy*this.shx,i=t.shx*this.shy+t.sy*this.sy,a=t.tx*this.sx+t.ty*this.shx+this.tx,o=t.tx*this.shy+t.ty*this.sy+this.ty;return new Vt(e,r,n,i,a,o)},Vt.prototype.decompose=function(){var t=this.sx,e=this.shy,r=this.shx,n=this.sy,i=this.tx,a=this.ty,o=Math.sqrt(t*t+e*e),s=(t/=o)*r+(e/=o)*n;r-=t*s,n-=e*s;var c=Math.sqrt(r*r+n*n);return s/=c,t*(n/=c)>16&255,i=u>>8&255,a=255&u}if(void 0===i||void 0===o&&n===i&&i===a)if("string"==typeof n)r=n+" "+s[0];else switch(t.precision){case 2:r=R(n/255)+" "+s[0];break;case 3:default:r=T(n/255)+" "+s[0]}else if(void 0===o||"object"===e(o)){if(o&&!isNaN(o.a)&&0===o.a)return r=["1.","1.","1.",s[1]].join(" ");if("string"==typeof n)r=[n,i,a,s[1]].join(" ");else switch(t.precision){case 2:r=[R(n/255),R(i/255),R(a/255),s[1]].join(" ");break;default:case 3:r=[T(n/255),T(i/255),T(a/255),s[1]].join(" ")}}else if("string"==typeof n)r=[n,i,a,o,s[2]].join(" ");else switch(t.precision){case 2:r=[R(n),R(i),R(a),R(o),s[2]].join(" ");break;case 3:default:r=[T(n),T(i),T(a),T(o),s[2]].join(" ")}return r},ne=y.__private__.getFilters=function(){return f},ie=y.__private__.putStream=function(t){var e=(t=t||{}).data||"",r=t.filters||ne(),n=t.alreadyAppliedFilters||[],i=t.addLength1||!1,a=e.length,o=t.objectId,s=function(t){return t};if(null!==m&&void 0===o)throw new Error("ObjectId must be passed to putStream for file encryption");null!==m&&(s=Ye.encryptor(o,0));var c={};!0===r&&(r=["FlateEncode"]);var u=t.additionalKeyValues||[],h=(c=void 0!==M.API.processDataByFilters?M.API.processDataByFilters(e,r):{data:e,reverseChain:[]}).reverseChain+(Array.isArray(n)?n.join(" "):n.toString());if(0!==c.data.length&&(u.push({key:"Length",value:c.data.length}),!0===i&&u.push({key:"Length1",value:a})),0!=h.length)if(h.split("/").length-1==1)u.push({key:"Filter",value:h});else{u.push({key:"Filter",value:"["+h+"]"});for(var l=0;l>"),0!==c.data.length&&(lt("stream"),lt(s(c.data)),lt("endstream"))},ae=y.__private__.putPage=function(t){var e=t.number,r=t.data,n=t.objId,i=t.contentsObjId;Zt(n,!0),lt("<>"),lt("endobj");var a=r.join("\n");return S===x.ADVANCED&&(a+="\nQ"),Zt(i,!0),ie({data:a,filters:ne(),objectId:i}),lt("endobj"),n},oe=y.__private__.putPages=function(){var t,e,r=[];for(t=1;t<=Dt;t++)Rt[t].objId=Kt(),Rt[t].contentsObjId=Kt();for(t=1;t<=Dt;t++)r.push(ae({number:t,data:ot[t],objId:Rt[t].objId,contentsObjId:Rt[t].contentsObjId,mediaBox:Rt[t].mediaBox,cropBox:Rt[t].cropBox,bleedBox:Rt[t].bleedBox,trimBox:Rt[t].trimBox,artBox:Rt[t].artBox,userUnit:Rt[t].userUnit,rootDictionaryObjId:Qt,resourceDictionaryObjId:te}));Zt(Qt,!0),lt("<>"),lt("endobj"),Tt.publish("postPutPages")},se=function(t){Tt.publish("putFont",{font:t,out:lt,newObject:Xt,putStream:ie}),!0!==t.isAlreadyPutted&&(t.objectNumber=Xt(),lt("<<"),lt("/Type /Font"),lt("/BaseFont /"+F(t.postScriptName)),lt("/Subtype /Type1"),"string"==typeof t.encoding&<("/Encoding /"+t.encoding),lt("/FirstChar 32"),lt("/LastChar 255"),lt(">>"),lt("endobj"))},ce=function(){for(var t in It)It.hasOwnProperty(t)&&(!1===v||!0===v&&b.hasOwnProperty(t))&&se(It[t])},ue=function(t){t.objectNumber=Xt();var e=[];e.push({key:"Type",value:"/XObject"}),e.push({key:"Subtype",value:"/Form"}),e.push({key:"BBox",value:"["+[E(t.x),E(t.y),E(t.x+t.width),E(t.y+t.height)].join(" ")+"]"}),e.push({key:"Matrix",value:"["+t.matrix.toString()+"]"});var r=t.pages[1].join("\n");ie({data:r,additionalKeyValues:e,objectId:t.objectNumber}),lt("endobj")},he=function(){for(var t in zt)zt.hasOwnProperty(t)&&ue(zt[t])},le=function(t,e){var r,n=[],i=1/(e-1);for(r=0;r<1;r+=i)n.push(r);if(n.push(1),0!=t[0].offset){var a={offset:0,color:t[0].color};t.unshift(a)}if(1!=t[t.length-1].offset){var o={offset:1,color:t[t.length-1].color};t.push(o)}for(var s="",c=0,u=0;ut[c+1].offset;)c++;var h=t[c].offset,l=(r-h)/(t[c+1].offset-h),f=t[c].color,d=t[c+1].color;s+=tt(Math.round((1-l)*f[0]+l*d[0]).toString(16))+tt(Math.round((1-l)*f[1]+l*d[1]).toString(16))+tt(Math.round((1-l)*f[2]+l*d[2]).toString(16))}return s.trim()},fe=function(t,e){e||(e=21);var r=Xt(),n=le(t.colors,e),i=[];i.push({key:"FunctionType",value:"0"}),i.push({key:"Domain",value:"[0.0 1.0]"}),i.push({key:"Size",value:"["+e+"]"}),i.push({key:"BitsPerSample",value:"8"}),i.push({key:"Range",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),i.push({key:"Decode",value:"[0.0 1.0 0.0 1.0 0.0 1.0]"}),ie({data:n,additionalKeyValues:i,alreadyAppliedFilters:["/ASCIIHexDecode"],objectId:r}),lt("endobj"),t.objectNumber=Xt(),lt("<< /ShadingType "+t.type),lt("/ColorSpace /DeviceRGB");var a="/Coords ["+E(parseFloat(t.coords[0]))+" "+E(parseFloat(t.coords[1]))+" ";2===t.type?a+=E(parseFloat(t.coords[2]))+" "+E(parseFloat(t.coords[3])):a+=E(parseFloat(t.coords[2]))+" "+E(parseFloat(t.coords[3]))+" "+E(parseFloat(t.coords[4]))+" "+E(parseFloat(t.coords[5])),lt(a+="]"),t.matrix&<("/Matrix ["+t.matrix.toString()+"]"),lt("/Function "+r+" 0 R"),lt("/Extend [true true]"),lt(">>"),lt("endobj")},de=function(t,e){var r=Kt(),n=Xt();e.push({resourcesOid:r,objectOid:n}),t.objectNumber=n;var i=[];i.push({key:"Type",value:"/Pattern"}),i.push({key:"PatternType",value:"1"}),i.push({key:"PaintType",value:"1"}),i.push({key:"TilingType",value:"1"}),i.push({key:"BBox",value:"["+t.boundingBox.map(E).join(" ")+"]"}),i.push({key:"XStep",value:E(t.xStep)}),i.push({key:"YStep",value:E(t.yStep)}),i.push({key:"Resources",value:r+" 0 R"}),t.matrix&&i.push({key:"Matrix",value:"["+t.matrix.toString()+"]"}),ie({data:t.stream,additionalKeyValues:i,objectId:t.objectNumber}),lt("endobj")},pe=function(t){var e;for(e in Ot)Ot.hasOwnProperty(e)&&(Ot[e]instanceof O?fe(Ot[e]):Ot[e]instanceof B&&de(Ot[e],t))},ge=function(t){for(var e in t.objectNumber=Xt(),lt("<<"),t)switch(e){case"opacity":lt("/ca "+R(t[e]));break;case"stroke-opacity":lt("/CA "+R(t[e]))}lt(">>"),lt("endobj")},me=function(){var t;for(t in Mt)Mt.hasOwnProperty(t)&&ge(Mt[t])},ve=function(){for(var t in lt("/XObject <<"),zt)zt.hasOwnProperty(t)&&zt[t].objectNumber>=0&<("/"+t+" "+zt[t].objectNumber+" 0 R");Tt.publish("putXobjectDict"),lt(">>")},be=function(){Ye.oid=Xt(),lt("<<"),lt("/Filter /Standard"),lt("/V "+Ye.v),lt("/R "+Ye.r),lt("/U <"+Ye.toHexString(Ye.U)+">"),lt("/O <"+Ye.toHexString(Ye.O)+">"),lt("/P "+Ye.P),lt(">>"),lt("endobj")},ye=function(){for(var t in lt("/Font <<"),It)It.hasOwnProperty(t)&&(!1===v||!0===v&&b.hasOwnProperty(t))&<("/"+t+" "+It[t].objectNumber+" 0 R");lt(">>")},we=function(){if(Object.keys(Ot).length>0){for(var t in lt("/Shading <<"),Ot)Ot.hasOwnProperty(t)&&Ot[t]instanceof O&&Ot[t].objectNumber>=0&<("/"+t+" "+Ot[t].objectNumber+" 0 R");Tt.publish("putShadingPatternDict"),lt(">>")}},Ne=function(t){if(Object.keys(Ot).length>0){for(var e in lt("/Pattern <<"),Ot)Ot.hasOwnProperty(e)&&Ot[e]instanceof y.TilingPattern&&Ot[e].objectNumber>=0&&Ot[e].objectNumber>")}},Le=function(){if(Object.keys(Mt).length>0){var t;for(t in lt("/ExtGState <<"),Mt)Mt.hasOwnProperty(t)&&Mt[t].objectNumber>=0&<("/"+t+" "+Mt[t].objectNumber+" 0 R");Tt.publish("putGStateDict"),lt(">>")}},Ae=function(t){Zt(t.resourcesOid,!0),lt("<<"),lt("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"),ye(),we(),Ne(t.objectOid),Le(),ve(),lt(">>"),lt("endobj")},xe=function(){var t=[];ce(),me(),he(),pe(t),Tt.publish("putResources"),t.forEach(Ae),Ae({resourcesOid:te,objectOid:Number.MAX_SAFE_INTEGER}),Tt.publish("postPutResources")},Se=function(){Tt.publish("putAdditionalObjects");for(var t=0;t>8&&(c=!0);t=s.join("")}for(r=t.length;void 0===c&&0!==r;)t.charCodeAt(r-1)>>8&&(c=!0),r--;if(!c)return t;for(s=e.noBOM?[]:[254,255],r=0,n=t.length;r>8)>>8)throw new Error("Character at position "+r+" of string '"+t+"' exceeds 16bits. Cannot be encoded into UCS-2 BE");s.push(h),s.push(u-(h<<8))}return String.fromCharCode.apply(void 0,s)},Ce=y.__private__.pdfEscape=y.pdfEscape=function(t,e){return Ie(t,e).replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)")},je=y.__private__.beginPage=function(t){ot[++Dt]=[],Rt[Dt]={objId:0,contentsObjId:0,userUnit:Number(d),artBox:null,bleedBox:null,cropBox:null,trimBox:null,mediaBox:{bottomLeftX:0,bottomLeftY:0,topRightX:Number(t[0]),topRightY:Number(t[1])}},Me(Dt),ht(ot[$])},Oe=function(t,e){var r,n,o;switch(a=e||a,"string"==typeof t&&(r=A(t.toLowerCase()),Array.isArray(r)&&(n=r[0],o=r[1])),Array.isArray(t)&&(n=t[0]*_t,o=t[1]*_t),isNaN(n)&&(n=s[0],o=s[1]),(n>14400||o>14400)&&(i.warn("A page in a PDF can not be wider or taller than 14400 userUnit. jsPDF limits the width/height to 14400"),n=Math.min(14400,n),o=Math.min(14400,o)),s=[n,o],a.substr(0,1)){case"l":o>n&&(s=[o,n]);break;case"p":n>o&&(s=[o,n])}je(s),dr(fr),lt(Nr),0!==Pr&<(Pr+" J"),0!==kr&<(kr+" j"),Tt.publish("addPage",{pageNumber:Dt})},Be=function(t){t>0&&t<=Dt&&(ot.splice(t,1),Rt.splice(t,1),Dt--,$>Dt&&($=Dt),this.setPage($))},Me=function(t){t>0&&t<=Dt&&($=t)},Ee=y.__private__.getNumberOfPages=y.getNumberOfPages=function(){return ot.length-1},qe=function(t,e,r){var n,a=void 0;return r=r||{},t=void 0!==t?t:It[St].fontName,e=void 0!==e?e:It[St].fontStyle,n=t.toLowerCase(),void 0!==Ct[n]&&void 0!==Ct[n][e]?a=Ct[n][e]:void 0!==Ct[t]&&void 0!==Ct[t][e]?a=Ct[t][e]:!1===r.disableWarning&&i.warn("Unable to look up font label for font '"+t+"', '"+e+"'. Refer to getFontList() for available fonts."),a||r.noFallback||null==(a=Ct.times[e])&&(a=Ct.times.normal),a},De=y.__private__.putInfo=function(){var t=Xt(),e=function(t){return t};for(var r in null!==m&&(e=Ye.encryptor(t,0)),lt("<<"),lt("/Producer ("+Ce(e("jsPDF "+M.version))+")"),xt)xt.hasOwnProperty(r)&&xt[r]&<("/"+r.substr(0,1).toUpperCase()+r.substr(1)+" ("+Ce(e(xt[r]))+")");lt("/CreationDate ("+Ce(e(W))+")"),lt(">>"),lt("endobj")},Re=y.__private__.putCatalog=function(t){var e=(t=t||{}).rootDictionaryObjId||Qt;switch(Xt(),lt("<<"),lt("/Type /Catalog"),lt("/Pages "+e+" 0 R"),mt||(mt="fullwidth"),mt){case"fullwidth":lt("/OpenAction [3 0 R /FitH null]");break;case"fullheight":lt("/OpenAction [3 0 R /FitV null]");break;case"fullpage":lt("/OpenAction [3 0 R /Fit]");break;case"original":lt("/OpenAction [3 0 R /XYZ null null 1]");break;default:var r=""+mt;"%"===r.substr(r.length-1)&&(mt=parseInt(mt)/100),"number"==typeof mt&<("/OpenAction [3 0 R /XYZ null null "+R(mt)+"]")}switch(Nt||(Nt="continuous"),Nt){case"continuous":lt("/PageLayout /OneColumn");break;case"single":lt("/PageLayout /SinglePage");break;case"two":case"twoleft":lt("/PageLayout /TwoColumnLeft");break;case"tworight":lt("/PageLayout /TwoColumnRight")}yt&<("/PageMode /"+yt),Tt.publish("putCatalog"),lt(">>"),lt("endobj")},Te=y.__private__.putTrailer=function(){lt("trailer"),lt("<<"),lt("/Size "+(et+1)),lt("/Root "+et+" 0 R"),lt("/Info "+(et-1)+" 0 R"),null!==m&<("/Encrypt "+Ye.oid+" 0 R"),lt("/ID [ <"+V+"> <"+V+"> ]"),lt(">>")},Ue=y.__private__.putHeader=function(){lt("%PDF-"+w),lt("%ºß¬à")},ze=y.__private__.putXRef=function(){var t="0000000000";lt("xref"),lt("0 "+(et+1)),lt("0000000000 65535 f ");for(var e=1;e<=et;e++){"function"==typeof rt[e]?lt((t+rt[e]()).slice(-10)+" 00000 n "):void 0!==rt[e]?lt((t+rt[e]).slice(-10)+" 00000 n "):lt("0000000000 00000 n ")}},He=y.__private__.buildDocument=function(){ut(),ht(nt),Tt.publish("buildDocument"),Ue(),oe(),Se(),xe(),null!==m&&be(),De(),Re();var t=it;return ze(),Te(),lt("startxref"),lt(""+t),lt("%%EOF"),ht(ot[$]),nt.join("\n")},We=y.__private__.getBlob=function(t){return new Blob([dt(t)],{type:"application/pdf"})},Ve=y.output=y.__private__.output=Fe((function(t,e){switch("string"==typeof(e=e||{})?e={filename:e}:e.filename=e.filename||"generated.pdf",t){case void 0:return He();case"save":y.save(e.filename);break;case"arraybuffer":return dt(He());case"blob":return We(He());case"bloburi":case"bloburl":if(void 0!==r.URL&&"function"==typeof r.URL.createObjectURL)return r.URL&&r.URL.createObjectURL(We(He()))||void 0;i.warn("bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser.");break;case"datauristring":case"dataurlstring":var n="",a=He();try{n=u(a)}catch(t){n=u(unescape(encodeURIComponent(a)))}return"data:application/pdf;filename="+e.filename+";base64,"+n;case"pdfobjectnewwindow":if("[object Window]"===Object.prototype.toString.call(r)){var o='